再读声卡驱动(1)

声卡驱动体系极其复杂我虽然一读再读也只能理出一个大体脉络。
声卡设备模型建立流程:
 


 
 
在继续讨论之前先来看几个结构体:
文件s3c24xx_uda134x.c
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
 .name = "UDA134X",
 .stream_name = "UDA134X",
 .codec_dai = &uda134x_dai,
 .cpu_dai = &s3c24xx_i2s_dai,
 .ops = &s3c24xx_uda134x_ops,
};
从该结构体名可以看出该结构体是将几个结构体连接到一起,包装成一个结构体s3c24xx_uda134x_dai_link。
声卡uda134I有两个接口,(1)L3DATA,L3MODE,L3CLOCK这三根线用于数字音频处理参数,系统控制参数的
配置。(2)IIS接口,这个接口是录音和播放的数据流传输接口。
结构体uda134x_dai接描述了前者,结构体s3c24xx_i2s_dai描述了后者。
uda134x_dai:
                    该结构体在文件uda134x.c中实现,其中包含了声卡支持的波特率范围数据传输模式等参数,还
                   包含了一个硬件参数设置函数集。在文件uda134x.c中调用函数snd_soc_register_dai(uda134x_dai)
                   int snd_soc_register_dai(struct snd_soc_dai *dai)
                   {
                   。。。。。。
                     list_add(&dai->list, &dai_list); //将结构体uda134x_dai添加到链表dai_list上。
                   。。。。。。
                   }
s3c24xx_i2s_dai:
                   该结构体在文件s3c24xx-i2s.c中实现,其中包含了IIS支持的波特率数据传输模式等参数,还
                   包含了一个IIS接口寄存器参数设置函数集和几个初始化函数。在文件s3c24xx-i2s.c中
                  调用函数 snd_soc_register_dai(&s3c24xx_i2s_dai)
                   int snd_soc_register_dai(struct snd_soc_dai *dai)
                   {
                   。。。。。。
                    list_add(&dai->list, &dai_list);  //将结构体s3c24xx_i2s_dai添加到链表dai_list上。
                   。。。。。。
                   }
s3c24xx_uda134x_ops:
                   该结构在文件s3c24xx_uda134x.c中实现如下:
                  static struct snd_soc_ops s3c24xx_uda134x_ops = {
                   .startup = s3c24xx_uda134x_startup,   //获取时钟计算波特率
                   .shutdown = s3c24xx_uda134x_shutdown,
//调用结构体uda134x_dai和结构体s3c24xx_i2s_dai中的硬件操作函数对声卡uda1341,和CPU上IIS接口进行初始化设置。
                   .hw_params = s3c24xx_uda134x_hw_params, 
                  };
 
还是在文件s3c24xx_uda134x.c中:
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,
};
前面说了声卡uda1341有两个接口,一个用于数字音频处理参数,系统控制参数的 
配置。另一个由于录音和播放的数据流传输。将这些音频数据从内存中搬运到IIS接口的fifo中是通过
DMA实现的。对这DMA的各种操作函数就在结构体s3c24xx_soc_platform中。这样就包装出了一个
声卡底层操作函数集,snd_soc_s3c24xx_uda134x
s3c24xx_soc_platform:
                            该结构体在文件s3c24xx-pcm.c中实现如下:
                            struct snd_soc_platform s3c24xx_soc_platform = {
                             .name  = "s3c24xx-audio",
                             //上层的录音,播放,停止,开始等操作都是通过调用该操作函数集中的函数来实现的。
                             .pcm_ops  = &s3c24xx_pcm_ops, 
                             .pcm_new = s3c24xx_pcm_new,  //分配录音和播放的DMA缓存。
                             .pcm_free = s3c24xx_pcm_free_dma_buffers,
                            };
s3c24xx_uda134x_dai_link:
                           该结构体在上面已有描述。
num_links:
               表示PCM实例数,此处是1。PCM实例实现音频的播放录制等,最终都是对DMA操作函数的调用。
               每个声卡最多可有四个PCM实例。PCM实例由pcm播放流和录音流组成。每个流又由  多个
              子流组成。
以下要讲的结构体任然在文件s3c24xx_uda134x.c中:
static struct snd_soc_device s3c24xx_uda134x_snd_devdata = {
 .card = &snd_soc_s3c24xx_uda134x,
 .codec_dev = &soc_codec_dev_uda134x,
 .codec_data = &s3c24xx_uda134x,
};
声卡uda1341的三根引脚L3DATA,L3MODE,L3CLOCK在处理器上并无对应接口,因此要与处理器
上三根普通引脚来连接,对这三根普通引脚的拉高拉低操作函数包含在了结构体s3c24xx_uda134x中。
完成了所有底层的准备工作就该创建相应结构体和向内核注册了,完成这些工作的函数在结构体
soc_codec_dev_uda134x中。这样最终包装出了结构体s3c24xx_uda134x_snd_devdata 。
 
下面来看声卡驱动建立过程中调用的重要函数。
在文件s3c24xx_uda134x.c中注册了一平台设备驱动:
static struct platform_driver s3c24xx_uda134x_driver = {
 .probe  = s3c24xx_uda134x_probe,
 .remove = s3c24xx_uda134x_remove,
 .driver = {
  .name = "s3c24xx_uda134x",
  .owner = THIS_MODULE,
 },
};
该驱动与名为"s3c24xx_uda134x",的平台设备匹配后调用硬件探测函数s3c24xx_uda134x_probe()
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
//该结构体中包含了与声卡L3DATA,L3MODE,L3CLOCK连接的处理器的三个引脚。
 s3c24xx_uda134x_l3_pins = pdev->dev.platform_data  
 s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
 s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data, "data") < 0)  //申请并配置该引脚
  return -EBUSY;
 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,"clk") < 0) {
  return -EBUSY;
 }
 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode, "mode") < 0) {
  return -EBUSY;
 }
//为平台设备s3c24xx_uda134x_snd_device分配内存。
 s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
//将上面讲到的最终包装结构体设为平台设备的drvdata。
 platform_set_drvdata(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x_snd_devdata);
 s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;
//将平台设备添加到内核,并与文件soc-core.c中的平台设备驱动soc_driver相匹配后调用函数soc_probe()进一步初始化。
 ret = platform_device_add(s3c24xx_uda134x_snd_device);
 return ret;
}
/********************************************************************************************************/
在文件soc-core.c中有一平台设备驱动soc_driver ,与文件s3c24xx_uda134x.c中创建并添加到内核的
平台设备s3c24xx_uda134x_snd_device相匹配后调用探测函数soc_probe();驱动结构体如下:
static struct platform_driver soc_driver = {
 .driver  = {
  .name  = "soc-audio",
  .owner  = THIS_MODULE,
 },
 .probe  = soc_probe,
 .remove  = soc_remove,
 .suspend = soc_suspend,
 .resume  = soc_resume,
};
 
设备探测函数:
static int soc_probe(struct platform_device *pdev)
{
 int ret = 0;
//在函数s3c24xx_uda134x_probe中
//platform_set_drvdata(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x_snd_devdata);
//所以此处获取的socdev即是文件s3c24xx_uda134x.c中实现的结构体s3c24xx_uda134x_snd_devdata。
 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
 struct snd_soc_card *card = socdev->card;
  card->socdev = socdev;
 card->dev = &pdev->dev;  
 ret = snd_soc_register_card(card);  //讲解如下
。。。。。。
}
/********************************************************************************************************/
static int snd_soc_register_card(struct snd_soc_card *card)
{
。。。。。。
//此处的card即是文件s3c24xx_uda134x.c中实现的结构体snd_soc_s3c24xx_uda134x
//将card插入链表card_list中。
 list_add(&card->list, &card_list); 
 snd_soc_instantiate_cards(); //
。。。。。。
}
/********************************************************************************************************/
static void snd_soc_instantiate_cards(void)
{
 struct snd_soc_card *card;
 list_for_each_entry(card, &card_list, list)  //在链表card_list上找出所有的card并调用初始化函数。
  snd_soc_instantiate_card(card);
}
/********************************************************************************************************/
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
 struct platform_device *pdev = container_of(card->dev, struct platform_device, dev);
 struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;

。。。。。。

 if (card->probe) {   //这个初始化函数并没有被实现,为空。
  ret = card->probe(pdev);
  if (ret < 0)
   return;
 }
 for (i = 0; i < card->num_links; i++) {   //card->num_links为1,表示一个PCM实例。
  struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
//函数cpu_dai->probe在文件s3c24xx-i2s.c中实现原型为函数 s3c24xx_i2s_probe(),
//此函数完成对IIS接口平台时钟获取,管脚配置等一些设置。
  if (cpu_dai->probe) {  
   ret = cpu_dai->probe(pdev, cpu_dai);
   if (ret < 0)
    goto cpu_dai_err;
  }
 }
  //函数codec_dev->probe在文件uda134x.c中实现,此函数完成了声卡驱动的重要工作
//下面将详细讲解该函数。
 if (codec_dev->probe) {
  ret = codec_dev->probe(pdev);
  if (ret < 0)
   goto cpu_dai_err;
 }
//结构体platform在文件s3c24xx-pcm.c中初始化但并没有实现函数platform->probe(pdev);
 if (platform->probe) { 
  ret = platform->probe(pdev);
  if (ret < 0)
   goto platform_err;
 }
 。。。。。。
 card->instantiated = 1;
 。。。。。。
}
/********************************************************************************************************/
在文件uda134x.c中
static int uda134x_soc_probe(struct platform_device *pdev)
{
 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
 struct snd_soc_codec *codec;
 struct uda134x_priv *uda134x;
 void *codec_setup_data = socdev->codec_data;
 int ret = -ENOMEM;
 struct uda134x_platform_data *pd;
。。。。。。
 pd = codec_setup_data;
 。。。。。。
//问结构体socdev->card->codec分配内存,由下面初始化就可以看出该结构体很重要。
 socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
 codec = socdev->card->codec;
 uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
 codec->private_data = uda134x;
//缓存codec->reg_cache 中有声卡uda1341所有寄存器对应的缓存。每次配置寄存器时
//先将要写入寄存器的值写到缓存codec->reg_cache对应的位置,然后再写入声卡寄存器。
//读取声卡uda1341的寄存器的值即是从缓存codec->reg_cache中读取。
 codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg), GFP_KERNEL);
 mutex_init(&codec->mutex);
 codec->reg_cache_size = sizeof(uda134x_reg);
 codec->reg_cache_step = 1;
 codec->name = "UDA134X";
 codec->owner = THIS_MODULE;
 codec->dai = &uda134x_dai; 
 codec->num_dai = 1;
 codec->read = uda134x_read_reg_cache;  
 codec->write = uda134x_write;  //上层函数对声卡的控制最终都是通过调用该函数来完成的
#ifdef POWER_OFF_ON_STANDBY
 codec->set_bias_level = uda134x_set_bias_level;
#endif
 INIT_LIST_HEAD(&codec->dapm_widgets);
 INIT_LIST_HEAD(&codec->dapm_paths);
 codec->control_data = codec_setup_data;
。。。。。。
 uda134x_reset(codec);  //复位声卡
//重要函数将在下面讲解
 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);   【1】
 
 switch (pd->model) {
 case UDA134X_UDA1340:
 case UDA134X_UDA1344:
  ret = snd_soc_add_controls(codec, uda1340_snd_controls,
     ARRAY_SIZE(uda1340_snd_controls));
 break;
 case UDA134X_UDA1341:
  ret = snd_soc_add_controls(codec, uda1341_snd_controls,  ARRAY_SIZE(uda1341_snd_controls));   【2】
 break;
 default:
 return -EINVAL;
 }
 
 ret = snd_soc_init_card(socdev);    【3】
。。。。。。
 return ret;
}

【1】
在文件soc-core.c中
int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
{
 struct snd_soc_card *card = socdev->card;
 struct snd_soc_codec *codec = card->codec;
 int ret, i;
//创建一个card实例,idx是card的索引号,xid是标识字符串,第四一个参数为extra_size是
//要分配的额外数据大小。创建一个结构体snd_card由codec->card指向。
 ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);       (1)
。。。。。。
 codec->card->dev = socdev->dev;
 codec->card->private_data = codec; 
 strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver));
 for (i = 0; i < card->num_links; i++) {  //构造card->num_links个pcm实例
  ret = soc_new_pcm(socdev, &card->dai_link[i], i);      (2)
 。。。。。。
 }
 mutex_unlock(&codec->mutex);
 return ret;
}
(1)
int snd_card_create(int idx, const char *xid,  struct module *module, int extra_size, struct snd_card **card_ret)
{
 struct snd_card *card;
 int err, idx2;
。。。。。。
//创建一个结构体 struct snd_card *card; 该结构体极为重要,上面讲过的结构体都是
//活跃于声卡驱动的底层,这个结构体则贯穿整个声卡驱动始终。
 card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
。。。。。。
//对结构体 snd_card 初始化。
 mutex_unlock(&snd_card_mutex);
 card->number = idx;
 card->module = module;
 INIT_LIST_HEAD(&card->devices);
 init_rwsem(&card->controls_rwsem);
 rwlock_init(&card->ctl_files_rwlock);
 INIT_LIST_HEAD(&card->controls);
 INIT_LIST_HEAD(&card->ctl_files);
 spin_lock_init(&card->files_lock);
 INIT_LIST_HEAD(&card->files_list);
 init_waitqueue_head(&card->shutdown_sleep);
#ifdef CONFIG_PM
 mutex_init(&card->power_lock);
 init_waitqueue_head(&card->power_sleep);
#endif
 err = snd_ctl_create(card);    //该函数的讨论如下。
。。。。。。
}
int snd_ctl_create(struct snd_card *card)
{
 static struct snd_device_ops ops = {
  .dev_free = snd_ctl_dev_free,
  .dev_register = snd_ctl_dev_register,   //该函数的调用将创建一个设备节点。
  .dev_disconnect = snd_ctl_dev_disconnect,
 };
 if (snd_BUG_ON(!card))
  return -ENXIO;
 return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);   //讨论如下
}
 
nt snd_device_new(struct snd_card *card, snd_device_type_t type,
     void *device_data, struct snd_device_ops *ops)
{
 struct snd_device *dev;
 dev = kzalloc(sizeof(*dev), GFP_KERNEL);

 dev->card = card;
 dev->type = type;
 dev->state = SNDRV_DEV_BUILD;
 dev->device_data = device_data;    //device_data上面传入的参数是 card
 dev->ops = ops;     
 list_add(&dev->list, &card->devices);   //将struct snd_device *dev;挂到声卡结构体card->devices上。
 return 0;
}
 
(2)
static int soc_new_pcm(struct snd_soc_device *socdev,
 struct snd_soc_dai_link *dai_link, int num)
{
 struct snd_soc_card *card = socdev->card;
 struct snd_soc_codec *codec = card->codec;
 struct snd_soc_platform *platform = card->platform;
 struct snd_soc_dai *codec_dai = dai_link->codec_dai;
 struct snd_soc_dai *cpu_dai = dai_link->cpu_dai;
 struct snd_soc_pcm_runtime *rtd;
 struct snd_pcm *pcm;
 char new_name[64];
 int ret = 0, playback = 0, capture = 0;
 rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL);
 if (rtd == NULL)
  return -ENOMEM;
 rtd->dai = dai_link;
 rtd->socdev = socdev;
。。。。。。
 if (codec_dai->playback.channels_min)
  playback = 1;      
 if (codec_dai->capture.channels_min)
  capture = 1;
/*
函数snd_pcm_new构造pcm实例,第一个参数是card指针,第二个是标识字符串,第三个是
PCM设备索引(从0开始),第四和第五个参数分别为播放和录音设备的子流数。
*/
//该函数在后面讲解
 ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback, capture, &pcm); 
。。。。。。
 dai_link->pcm = pcm;
 pcm->private_data = rtd;
// soc_pcm_ops为PCM流操作函数集在文件soc-core.c中实现,最终都是调用
//函数集platform->pcm_ops中的函数。platform->pcm_ops在文件s3c24xx-pcm.c中定义
//这些函数都是实现录音播放等操作的底层函数,最终也都是对DMA缓存的操作。
 soc_pcm_ops.mmap = platform->pcm_ops->mmap;
 soc_pcm_ops.pointer = platform->pcm_ops->pointer;
 soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
 soc_pcm_ops.copy = platform->pcm_ops->copy;
 soc_pcm_ops.silence = platform->pcm_ops->silence;
 soc_pcm_ops.ack = platform->pcm_ops->ack;
 soc_pcm_ops.page = platform->pcm_ops->page;
/*
下面函数是将PAM实例中的录音流,播放流的所有子流的操作函数集设为soc_pcm_ops。
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
{
 struct snd_pcm_str *stream = &pcm->streams[direction];
 struct snd_pcm_substream *substream;
 for (substream = stream->substream; substream != NULL; substream = substream->next)
  substream->ops = ops; 
}
*/
 if (playback)
  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
 if (capture)
  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
 ret = platform->pcm_new(codec->card, codec_dai, pcm);
。。。。。。
}
 
 
int snd_pcm_new(struct snd_card *card, const char *id, int device,
  int playback_count, int capture_count,
         struct snd_pcm ** rpcm)
{
 struct snd_pcm *pcm;
 int err;
 static struct snd_device_ops ops = {
  .dev_free = snd_pcm_dev_free,
  .dev_register = snd_pcm_dev_register,   //该函数的调用会导致 个设备节点的创建。后面会讲解。
  .dev_disconnect = snd_pcm_dev_disconnect,
 };
。。。。。。
 pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);  //创建结构体snd_pcm。
。。。。。。
 pcm->card = card;
 pcm->device = device;
。。。。。。
//为pcm实例的播放流创建playback_count个子流。将这些子流结构体挂接到
//pcm->streams[stream]->substream上。
 if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
  snd_pcm_free(pcm);
  return err;
 }
//为pcm实例的录音流创建playback_count个子流。
 if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
  snd_pcm_free(pcm);
  return err;
 }
 。。。。。。
 if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {  //如下
  snd_pcm_free(pcm);
  return err;
 }
 if (rpcm)
  *rpcm = pcm;
 return 0;
}
 
 
int snd_device_new(struct snd_card *card, snd_device_type_t type,
 void *device_data, struct snd_device_ops *ops)
{
 struct snd_device *dev;

 dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 dev->card = card;
 dev->type = type;
 dev->state = SNDRV_DEV_BUILD;
 dev->device_data = device_data;   //device_data上面传入的参数是 pcm
 dev->ops = ops;
 list_add(&dev->list, &card->devices);   //将struct snd_device *dev;挂到声卡结构体card->devices上。
 return 0;
}
 
【2】
snd_soc_add_controls(codec, uda1341_snd_controls, ARRAY_SIZE(uda1341_snd_controls));
结构体数组uda1341_snd_controls[]在文件 uda134x.c中实现。
 
结构体snd_kcontrol_new原型如下
struct snd_kcontrol_new {
 snd_ctl_elem_iface_t iface; /* interface identifier */
 unsigned int device;  /* device/client number */
 unsigned int subdevice;  /* subdevice (substream) number */
 unsigned char *name;  /* ASCII name of item */
 unsigned int index;  /* index of item */
 unsigned int access;  /* access rights */
 unsigned int count;  /* count of same elements */
 snd_kcontrol_info_t *info;
 snd_kcontrol_get_t *get;
 snd_kcontrol_put_t *put;
 union {
  snd_kcontrol_tlv_rw_t *c;
  const unsigned int *p;
 } tlv;
 unsigned long private_value;
};
举一个例子来看一看在结构体数组uda1341_snd_controls[]中对结构体snd_kcontrol_new
的填充。
//各参数分别表示选项名,控制参数要写入的声卡寄存器地址,左移的位数
//(因为一个寄存器中可能存在多个参数的设置),该参数设置的最大值,最小值。
SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
如该寄存器由两项功能的设置,功能Tone Control - Bass既是由3~6位控制所以移位是2,最大值是0xF。

 将SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),展开如下:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
 .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
 .put = snd_soc_put_volsw, \
 .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }
 
 .info,.get,.put是三个声卡的操作函数。这几个函数最终都是调用
codec->write(codec, reg, value)将参数值写入缓存codec->reg_cache然后写入声卡寄存器,
或调用函数codec->read(codec, reg)从缓存codec->reg_cache中读取对应寄存器设置的值。
这两个函数在文件uda134x.c中实现。分别为uda134x_write(),uda134x_read_reg_cache()。
SOC_SINGLE_VALUE(reg, shift, max, invert)展开为
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \
 ((unsigned long)&(struct soc_mixer_control) \
 {.reg = xreg, .shift = xshift, .rshift = xshift, .max = xmax, \
 .invert = xinvert})
这为上面操作函数提供一个参数的设置范围和写入寄存器地址。
 
 
函数snd_soc_add_controls(codec, uda1341_snd_controls,  ARRAY_SIZE(uda1341_snd_controls));
的调用将把结构体数组uda1341_snd_controls[]中的各元素包装成结构体snd_kcontrol
挂到card->controls链表上。list_add_tail(&kcontrol->list, &card->controls);
【3】

int snd_soc_init_card(struct snd_soc_device *socdev)
{
 struct snd_soc_card *card = socdev->card;
 struct snd_soc_codec *codec = card->codec;
 int ret = 0, i, ac97 = 0, err = 0;

。。。。。。

 ret = snd_card_register(codec->card);

。。。。。。
}

 

int snd_card_register(struct snd_card *card)
{
。。。。。。

 if ((err = snd_device_register_all(card)) < 0)
  return err;
。。。。。。
 snd_cards[card->number] = card;
 
。。。。。。

}

 

int snd_device_register_all(struct snd_card *card)
{
 struct snd_device *dev;
 int err;
 
 if (snd_BUG_ON(!card))
  return -ENXIO;

//对挂在card->devices上的所有snd_device都调用dev->ops->dev_register(dev)来

//创建设备节点。
 list_for_each_entry(dev, &card->devices, list) {
  if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
   if ((err = dev->ops->dev_register(dev)) < 0)
    return err;
   dev->state = SNDRV_DEV_REGISTERED;
  }
 }
 return 0;
}

转自:http://chxxxyg.blog.163.com/blog/static/150281193201079927767/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值