Chinaunix首页 | 论坛 | 博客
  • 博客访问: 514793
  • 博文数量: 92
  • 博客积分: 3146
  • 博客等级: 中校
  • 技术积分: 2314
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-27 10:20
文章分类

全部博文(92)

文章存档

2014年(3)

2013年(17)

2012年(16)

2011年(22)

2010年(34)

分类: 嵌入式

2011-07-27 19:28:13

ALSA: Advanced Linux Sound Architecture,它包括内核驱动集合、API库和工具。用户层程序直接调用libsound的API库,不需要打开设备等操作,因此编程者不需要了解底层细节。

这 里不分析ALSA的核心代码core,也不阐述如何在用户层进行声卡编程,仅仅简要介绍在ALSA的架构上添加一个声卡驱动,即上图中的Sound Driver。其实文档《wirte an alsa driver》很详尽的介绍如何写一个ALSA驱动,但是那是以PCI声卡为例的。在嵌入式中,音频数据传输一般用I2S接口,控制一般用I2c或SPI 接口。如下仅以嵌入式声卡为例,其驱动代码一般放在sound/soc下面。

以数据结构为线索,简要解析其过程。每个重要结构体旁边有个类似的标号[XX],[xx]为[0]时,表明这个结构体是一个大类,包含标号为[1]的结构体……;[xx]为[EXT]时,表明该结构体不在本模块使用。


CODEC

驱动代码位于sound/soc/codec下,如uda134x.c。

struct snd_soc_dai [0]

  1. /*
  2.  * Digital Audio Interface runtime data.
  3.  *
  4.  * Holds runtime data for a DAI.
  5.  */
  6. struct snd_soc_dai {
  7.     /* DAI description */
  8.     char *name;
  9.     unsigned int id;
  10.     int ac97_control;

  11.     struct device *dev;
  12.     void *ac97_pdata;    /* platform_data for the ac97 codec */

  13.     /* DAI callbacks */
  14.     int (*probe)(struct platform_device *pdev,
  15.          struct snd_soc_dai *dai);
  16.     void (*remove)(struct platform_device *pdev,
  17.          struct snd_soc_dai *dai);
  18.     int (*suspend)(struct snd_soc_dai *dai);
  19.     int (*resume)(struct snd_soc_dai *dai);

  20.     /* ops */
  21.     struct snd_soc_dai_ops *ops;

  22.     /* DAI capabilities */
  23.     struct snd_soc_pcm_stream capture;
  24.     struct snd_soc_pcm_stream playback;
  25.     unsigned int symmetric_rates:1;

  26.     /* DAI runtime info */
  27.     struct snd_pcm_runtime *runtime;
  28.     struct snd_soc_codec *codec;
  29.     unsigned int active;
  30.     unsigned char pop_wait:1;
  31.     void *dma_data;

  32.     /* DAI private data */
  33.     void *private_data;

  34.     /* parent platform */
  35.     struct snd_soc_platform *platform;

  36.     struct list_head list;
  37. };
/* * Digital Audio Interface runtime data. * * Holds runtime data for a DAI. */ struct snd_soc_dai { /* DAI description */ char *name; unsigned int id; int ac97_control; struct device *dev; void *ac97_pdata; /* platform_data for the ac97 codec */ /* DAI callbacks */ int (*probe)(struct platform_device *pdev, struct snd_soc_dai *dai); void (*remove)(struct platform_device *pdev, struct snd_soc_dai *dai); int (*suspend)(struct snd_soc_dai *dai); int (*resume)(struct snd_soc_dai *dai); /* ops */ struct snd_soc_dai_ops *ops; /* DAI capabilities */ struct snd_soc_pcm_stream capture; struct snd_soc_pcm_stream playback; unsigned int symmetric_rates:1; /* DAI runtime info */ struct snd_pcm_runtime *runtime; struct snd_soc_codec *codec; unsigned int active; unsigned char pop_wait:1; void *dma_data; /* DAI private data */ void *private_data; /* parent platform */ struct snd_soc_platform *platform; struct list_head list; };模块初始化函数中都需要调用snd_soc_register_dai()将定义好的结构体snd_soc_dai注册到ALSA中。而对于结构体snd_soc_dai有几个成员是非常重要的,如name、capture、playback、ops。

name 指定模块声卡名称;capture是录音参数设定,playback是播放参数设定,均包含channel数目、PCM_RATE和PCM_FMTBIT 等信息;ops是声卡操作函数集合指针,这个操作函数集合详见struct snd_soc_dai_ops定义,主要实现的有hw_params(硬件参数设定)、digital_mute(静音操作)、set_fmt(格式配 置)等,这些函数的实现均与硬件相关,根据硬件的数据手册来实现。

以下是struct snd_soc_dai的定义范例:

  1. struct snd_soc_dai uda134x_dai = {
  2.     .name = "UDA134X",
  3.     /* playback capabilities */
  4.     .playback = {
  5.         .stream_name = "Playback",
  6.         .channels_min = 1,
  7.         .channels_max = 2,
  8.         .rates = UDA134X_RATES,
  9.         .formats = UDA134X_FORMATS,
  10.     },
  11.     /* capture capabilities */
  12.     .capture = {
  13.         .stream_name = "Capture",
  14.         .channels_min = 1,
  15.         .channels_max = 2,
  16.         .rates = UDA134X_RATES,
  17.         .formats = UDA134X_FORMATS,
  18.     },
  19.     /* pcm operations */
  20.     .ops = &uda134x_dai_ops,
  21. };
struct snd_soc_dai uda134x_dai = { .name = "UDA134X", /* playback capabilities */ .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* capture capabilities */ .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* pcm operations */ .ops = &uda134x_dai_ops, };小结:以上的结构体看起来复杂,实现上基本结构是非常简单易懂的。所要做的工作有:1/定义snd_soc_dai和 snd_soc_dai_ops这两个结构体,前者设置好capture & playback的参数和声卡函数操作集合指针,该指针指向snd_soc_dai_ops结构体;2/根据硬件数据手册编写相关操作函数如 hw_params、set_fmt和digital_mute等;3/编写模块初始化函数uda134x_init(),调用 snd_soc_register_dai()注册之前定义好的snd_soc_dai。

注:关于2的相关操作函数,之前也提过控制一般用 I2C或SPI接口的。但是在操作函数里,我们可以使用codec->hw_write()来操作。当然在probe函数中,hw_write是在probe初始化好的,如 codec->hw_write = (hw_write_t)i2c_master_send;,这就使得控制接口抽象起来。

struct snd_soc_codec_device [EXT]

接 下来有一个结构体snd_soc_codec_device要留意的,一般来说,这个结构体是在codec下定义,但是注册操作是在另外一个文件中进行 的,以2410的UDA134X为例是在sound/soc/s3c24xx/s3c24xx_uda134x.c。这些留到以后分析,这里只是需要将这 个结构体EXPORT出来就行了如:EXPORT_SYMBOL_GPL(soc_codec_dev_uda134x);。

先看snd_soc_codec_device结构体定义:

  1. /* codec device */
  2. struct snd_soc_codec_device {
  3.     int (*probe)(struct platform_device *pdev);
  4.     int (*remove)(struct platform_device *pdev);
  5.     int (*suspend)(struct platform_device *pdev, pm_message_t state);
  6.     int (*resume)(struct platform_device *pdev);
  7. };
/* codec device */ struct snd_soc_codec_device { int (*probe)(struct platform_device *pdev); int (*remove)(struct platform_device *pdev); int (*suspend)(struct platform_device *pdev, pm_message_t state); int (*resume)(struct platform_device *pdev); };根据结构体定义,我们可以按部就班编写uda134x_probe、uda134x_remove、 uda134x_suspend和uda134x_resume函数,然后将这个结构体EXPORT出来,使其在之后的模块中注册。Probe指声卡的探 测与初始化,remove指声卡的卸载,suspend指声卡的休眠,resume指声卡从休眠状态下恢复。详细介绍probe函数。

先看一下probe的代码脉络:

  1. static int uda134x_soc_probe(struct platform_device *pdev)
  2. {
  3.     //获得snd_soc_device结构体
  4.     struct snd_soc_device *socdev = platform_get_drvdata(pdev);
  5.     struct snd_soc_codec *codec;
  6.     …
  7.     //为codec分配内存
  8.     socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
  9.     if (socdev->card->codec == NULL)
  10.         return ret;

  11.     codec = socdev->card->codec;

  12.     …

  13.     //初始化codec
  14.     codec->name = "uda134x";
  15.     codec->owner = THIS_MODULE;
  16.     codec->dai = &uda134x_dai; //指向上面定义好的dai
  17.     codec->num_dai = 1;
  18.     codec->read = uda134x_read_reg_cache; //控制接口—读
  19.     codec->write = uda134x_write; //控制接口—写

  20.     …

  21.     mutex_init(&codec->mutex);
  22.     INIT_LIST_HEAD(&codec->dapm_widgets);
  23.     INIT_LIST_HEAD(&codec->dapm_paths);
  24.     
  25.     …

  26.     /* register pcms */
  27.     ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);

  28.     …

  29.     ret = snd_soc_add_controls(codec, uda134x_snd_controls,
  30.                     ARRAY_SIZE(uda134x_snd_controls));

  31.     …

  32.     /* register card */
  33.     ret = snd_soc_init_card(socdev);
  34. }
static int uda134x_soc_probe(struct platform_device *pdev) { //获得snd_soc_device结构体 struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; … //为codec分配内存 socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (socdev->card->codec == NULL) return ret; codec = socdev->card->codec; … //初始化codec codec->name = "uda134x"; codec->owner = THIS_MODULE; codec->dai = &uda134x_dai; //指向上面定义好的dai codec->num_dai = 1; codec->read = uda134x_read_reg_cache; //控制接口—读 codec->write = uda134x_write; //控制接口—写 … mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); … /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); … ret = snd_soc_add_controls(codec, uda134x_snd_controls, ARRAY_SIZE(uda134x_snd_controls)); … /* register card */ ret = snd_soc_init_card(socdev); }开始看到socdev = platform_get_drvdata(pdev)这句不免有点疑惑,到底pdev是在哪里初始化好了?答案是sound/soc /目录下的文件中,如sound/soc/s3c24xx/s3c24xx_uda134x.c。在声卡的初始化过程中,其实首先 是调用sound/soc/下的相关驱动的probe函数,在probe有platform_set_drvdata()的操作, 这里有个将指针类型的转换:(struct snd_soc_device *s) à (struct platform_device *),Linux驱动抽象模型无处不在。

snd_soc_add_controls()将操作集合挂到card->control链 表上来,这个集合实现了音频播放时各个参数的设置,主要有.info、.get和.set。如playback volume control:SOC_DOUBLE_R_TLV("Playback Volume", SNDCARD_REG_L_GAIN, SNDCARD_REG_R_GAIN, 0, 192, 0, digital_tlv),其中SNDCARD_REG_L_GAIN和SNDCARD_REG_R_GAIN分别是左右声道音量增益寄存器偏移。最终要 调用的函数都是在soc-core.c里面的,这里只是提供一些跟硬件相关的参数,大为增加了代码的复用性。

对于函数 snd_soc_new_pcms()是这样描述的:Create a new sound card based upon the codec and interface pcms.这个函数非常重要,用于创建一个PCM实例以便播放数据流。函数里重要的是如下两句:

  1. ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);
  2. ret = soc_new_pcm(socdev, &card->dai_link[i], i);
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card); ret = soc_new_pcm(socdev, &card->dai_link[i], i);

前 者create and initialize a soundcard structure,然后这个codec->card会接下的snd_soc_init_card()进行初始化。后者创建播放流/录音流的子流, 将所有播放流/录音流的子流操作函数设为soc_pcm_ops。

小结:这一部分有点复杂,要实现probe、remove、 suspend和resume,还有一系列的snd_kcontrol_new参数设置函数数组。另外有个地方要清楚的:初始化的过程是 SOC_soc_init()->platform_device_add()->soc_codec_dev_uda134x.probe 即uda134x_probe()。【对于s3c24xx_uda134x 是:s3c24xx_uda134x_probe()->platform_device_add()->soc_codec_dev_uda134x.probe 即uda134x_soc_probe()】


SOC

驱动代码位于sound/soc/下,如s3c24xx_uda134x.c。这部分的probe是先于CODEC中的probe调用的。

struct snd_soc_device [0]

  1. /* SoC Device - the audio subsystem */
  2. struct snd_soc_device {
  3.     struct device *dev;
  4.     struct snd_soc_card *card;
  5.     struct snd_soc_codec_device *codec_dev;
  6.     void *codec_data;
  7. };
/* SoC Device - the audio subsystem */ struct snd_soc_device { struct device *dev; struct snd_soc_card *card; struct snd_soc_codec_device *codec_dev; void *codec_data; };这个结构体用于向内核注册一个device。初始化一般如下:

static struct snd_soc_device SOC_SNDCARD_snd_devdata = { .card = &snd_soc_s3c24xx_uda134x, .codec_dev = &soc_codec_dev_uda134x,//就是CODEC定义的snd_soc_codec_device结构体 .codec_data = &s3c24xx_uda134x, //私有数据,一般存放SNDCARD控制接口信息,如I2C从设备地址等 };

对于module_init,其实用platform_driver_register注册一个platform_driver结构体的方式也好,还是直接写一个init也好,都问题不大。前者更贴近Linux的驱动模型。Probe的一般过程如下:

  1. static struct snd_soc_device SOC_SNDCARD_snd_devdata = {
  2.     .card = &snd_soc_s3c24xx_uda134x,
  3.     .codec_dev = &soc_codec_dev_uda134x,//就是CODEC定义的snd_soc_codec_device结构体
  4.     .codec_data = &s3c24xx_uda134x, //私有数据,一般存放SNDCARD控制接口信息,如I2C从设备地址等
  5. };
static int s3c24xx_uda134x_probe(struct platform_device *pdev) { s3c24xx_uda134x_snd_devdata.codec_dev = &soc_codec_dev_uda134x; s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1); platform_set_drvdata(s3c24xx_uda134x_snd_device, & s3c24xx_uda134x_snd_devdata); s3c24xx_uda134x_snd_devdata.dev = & s3c24xx_uda134x_snd_device->dev; platform_device_add(s3c24xx_uda134x_snd_device); }可以看到CODEC定义的struct snd_soc_codec_device soc_codec_dev_uda134x在这里进行platform_device_add的,随后便执行 soc_codec_dev_uda134x.probe,其过程可以复习CODEC中的uda134x_soc_probe()。

接下来是s3c24xx_uda134x_snd_devdata.card的来历。

struct snd_soc_card [1]


  1. /* SoC card */
  2. struct snd_soc_card {
  3.     char *name;
  4.     struct device *dev;

  5.     struct list_head list;

  6.     int instantiated;

  7.     int (*probe)(struct platform_device *pdev);
  8.     int (*remove)(struct platform_device *pdev);

  9.     /* the pre and post PM functions are used to do any PM work before and
  10.      * after the codec and DAI's do any PM work. */
  11.     int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
  12.     int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
  13.     int (*resume_pre)(struct platform_device *pdev);
  14.     int (*resume_post)(struct platform_device *pdev);

  15.     /* callbacks */
  16.     int (*set_bias_level)(struct snd_soc_card *,
  17.              enum snd_soc_bias_level level);

  18.     /* CPU <--> Codec DAI links */
  19.     struct snd_soc_dai_link *dai_link;
  20.     int num_links;

  21.     struct snd_soc_device *socdev;

  22.     struct snd_soc_codec *codec;

  23.     struct snd_soc_platform *platform;
  24.     struct delayed_work delayed_work;
  25.     struct work_struct deferred_resume_work;
  26. };
/* SoC card */ struct snd_soc_card { char *name; struct device *dev; struct list_head list; int instantiated; int (*probe)(struct platform_device *pdev); int (*remove)(struct platform_device *pdev); /* the pre and post PM functions are used to do any PM work before and * after the codec and DAI's do any PM work. */ int (*suspend_pre)(struct platform_device *pdev, pm_message_t state); int (*suspend_post)(struct platform_device *pdev, pm_message_t state); int (*resume_pre)(struct platform_device *pdev); int (*resume_post)(struct platform_device *pdev); /* callbacks */ int (*set_bias_level)(struct snd_soc_card *, enum snd_soc_bias_level level); /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; int num_links; struct snd_soc_device *socdev; struct snd_soc_codec *codec; struct snd_soc_platform *platform; struct delayed_work delayed_work; struct work_struct deferred_resume_work; };定义这个结构体是让snd_soc_register_card()注册一个card的。初始化范例:
  1. static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
  2.     .name = "S3C24XX_UDA134X",
  3.     .platform = &s3c24xx_soc_platform,
  4.     .dai_link = &s3c24xx_uda134x_dai_link,
  5.     .num_links = 1,
  6. };
static struct snd_soc_card snd_soc_s3c24xx_uda134x = { .name = "S3C24XX_UDA134X", .platform = &s3c24xx_soc_platform, .dai_link = &s3c24xx_uda134x_dai_link, .num_links = 1, };s3c24xx_soc_platform是定义在PCM中的,包含一系列pcm_ops操作函数集合等信息,这里不谈。成员.dai_link是本模块中的另外一个主角。
  1. /* SoC machine DAI configuration, glues a codec and cpu DAI together */
  2. struct snd_soc_dai_link {
  3.     char *name;            /* Codec name */
  4.     char *stream_name;        /* Stream name */

  5.     /* DAI */
  6.     struct snd_soc_dai *codec_dai;
  7.     struct snd_soc_dai *cpu_dai;

  8.     /* machine stream operations */
  9.     struct snd_soc_ops *ops;

  10.     /* codec/machine specific init - e.g. add machine controls */
  11.     int (*init)(struct snd_soc_codec *codec);

  12.     /* Symmetry requirements */
  13.     unsigned int symmetric_rates:1;

  14.     /* Symmetry data - only valid if symmetry is being enforced */
  15.     unsigned int rate;

  16.     /* DAI pcm */
  17.     struct snd_pcm *pcm;
  18. };

/* SoC machine DAI configuration, glues a codec and cpu DAI together */ 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; /* machine stream operations */ struct snd_soc_ops *ops; /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec); /* Symmetry requirements */ unsigned int symmetric_rates:1; /* Symmetry data - only valid if symmetry is being enforced */ unsigned int rate; /* DAI pcm */ struct snd_pcm *pcm; };因为一个平台可以运行多个音频设备,snd_soc_dai_link的作用也在这,将CODEC定义的 snd_soc_dai挂到一个链表上。我这里没往下深究。.name指定codec名称;.codec_dai指向CODEC定义的 snd_soc_dai结构体;.cpu_dai指向I2S定义的snd_soc_dai结构体;.ops接下来分析。一般来说,初始化以上几个成员就行 了。

struct snd_soc_ops [3]

  1. /* SoC audio ops */
  2. struct snd_soc_ops {
  3.     int (*startup)(struct snd_pcm_substream *);
  4.     void (*shutdown)(struct snd_pcm_substream *);
  5.     int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
  6.     int (*hw_free)(struct snd_pcm_substream *);
  7.     int (*prepare)(struct snd_pcm_substream *);
  8.     int (*trigger)(struct snd_pcm_substream *, int);
  9. };
/* 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); };在这里,一般需要实现.hw_paras、.startup和.shutdown,这些均是对pcm substream进行操作的。例如在s3c24xx_soc_hw_params()中,有:
  1. snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S);//设置为I2S mode
  2. snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, SND_SOC_CLOCK_IN);//设置主时钟MCLK频率
snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S);//设置为I2S mode snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, SND_SOC_CLOCK_IN);//设置主时钟MCLK频率

注: 如果留意到CODEC小结中的2/,那么就会明白,在这里定义的操作最终会调用到CODEC(或许还有PCM、I2S中的)里定义好 的.ops.set_fmt、.ops.set_sysclk 等。

小结:这一层的重要的结构体是一脉相承的,并没有复杂的分支,除了.codec_dev 是从CODEC Export过来,.platform从PCM Export过来,.cpu_dai从I2S Export过来。函数主要是module_init()和一个snd_soc_ops操作函数集合。这一层负责将音频设备的几部分模块与CPU平台挂起 来。

注:底层硬件操作—

Codec--控制接口及芯片基本初始化

Pcm  --pcm dma操作

I2s  --i2s配置操作

之 后i2s和pcm其实都跟codec差不多了,只需要理解alsa-core、三层 的关系。其中codec、pcm、i2s可以看做同层的,分别对于音频设备的control、dma、i2s接口;会分别export相关结构体给层,层将音频设备三部分与CPU Spec联结起来,其probe顺序是.probe->.probe;另外在各自的module_init中将自身注册到alsa-core中。

有空再写一些pcm dma方面的。

阅读(1446) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~