Linux音频驱动之一:音频驱动注册流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38696651/article/details/89431706

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记

一.uda134x平台设备的注册
static struct s3c24xx_uda134x_platform_data s3c24xx_uda134x_data = {
	.l3_clk = S3C2410_GPB(4),
	.l3_data = S3C2410_GPB(3),
	.l3_mode = S3C2410_GPB(2),
	.model = UDA134X_UDA1341,
};

static struct platform_device s3c24xx_uda134x = {
	.name = "s3c24xx_uda134x",
	.dev = {
		.platform_data    = &s3c24xx_uda134x_data,
	}
};
  • 设备名称:s3c24xx_uda134x
  • 设备的platform_data的:s3c24xx_uda134x_data
    L3控制引脚:
    L3CLOCK: GPB4
    L3DATA: GPB3
    L3MODE: GPB2

uda134x设备在mini2440_machine_init函数中调用,mini2440_machine_init函数在内核起来时调用。
mini2440_machine_init函数中初始化大部分硬件设备,包括uda134x设备。

static void __init mini2440_machine_init(void)
{
#if defined (LCD_WIDTH)
	s3c24xx_fb_set_platdata(&mini2440_fb_info);
#endif
	s3c_i2c0_set_platdata(NULL);

	s3c2410_gpio_cfgpin(S3C2410_GPC(0), S3C2410_GPC0_LEND);

	s3c_device_nand.dev.platform_data = &friendly_arm_nand_info;
	s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;
	platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
	s3c_pm_init();
}
  • 设置地址:/sys/devices/platform/s3c24xx_uda134x.0
二. uda134x平台driver的注册
  • 搜索"s3c24xx_uda134x"字符串,对应的平台driver在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,
	},
};

static int __init s3c24xx_uda134x_init(void)
{
	return platform_driver_register(&s3c24xx_uda134x_driver);
}
module_init(s3c24xx_uda134x_init);
  • 注册时间:内核起来时注册该platform driver。
  • probe函数为s3c24xx_uda134x_probe,注册platform driver或者platform device时自动匹配,匹配上了调用probe函数。
三. uda134x platform driver的probe函数
  • 申请 L3CLOCK,L3DATA,L3MODE三个引脚,设置输出模式,并拉低。
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) 
{
    gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
    return -EBUSY;
}
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode, "mode") < 0) 
{
    gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
    gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
    return -EBUSY;
}
  • 申请注册了一个soc-audio的平台设备,设备的注册的地址是:/sys/devices/platform/soc-audio
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
ret = platform_device_add(s3c24xx_uda134x_snd_device);
  • 设置drvdata,把s3c24xx_uda134x_snd_devdata这个全集变量设置给它。
platform_set_drvdata(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x_snd_devdata);
四. soc-audio设备的drvdata

看一下s3c24xx_uda134x_snd_devdata这个全局变量,这个变量十分复杂。

static struct snd_soc_device s3c24xx_uda134x_snd_devdata = 
{
	.card = &snd_soc_s3c24xx_uda134x,
	.codec_dev = &soc_codec_dev_uda134x,
	.codec_data = &s3c24xx_uda134x,
};

把里面的展开,得到下面的结构体:

static struct snd_soc_device s3c24xx_uda134x_snd_devdata = 
{
	.card =
	{
		.name = "S3C24XX_UDA134X",
		.platform =
		 {
			.name		= "s3c24xx-audio",
			.pcm_ops 	= &s3c24xx_pcm_ops,
			.pcm_new	= s3c24xx_pcm_new,
			.pcm_free	= s3c24xx_pcm_free_dma_buffers,
		};
		
		.dai_link =
		{
			.name = "UDA134X",
			.stream_name = "UDA134X",
			
			.codec_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,
			},
			
			.cpu_dai =
			{
				.name = "s3c24xx-i2s",
				.id = 0,
				.probe = s3c24xx_i2s_probe,
				.suspend = s3c24xx_i2s_suspend,
				.resume = s3c24xx_i2s_resume,
				.playback = {
					.channels_min = 2,
					.channels_max = 2,
					.rates = S3C24XX_I2S_RATES,
					.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
				.capture = {
					.channels_min = 2,
					.channels_max = 2,
					.rates = S3C24XX_I2S_RATES,
					.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
				.ops = &s3c24xx_i2s_dai_ops,
			},
			.ops = &s3c24xx_uda134x_ops,
		};
		.num_links = 1,
	};
	.codec_dev =
	{
		.probe =        uda134x_soc_probe,
		.remove =       uda134x_soc_remove,
		.suspend =      uda134x_soc_suspend,
		.resume =       uda134x_soc_resume,
	},
	
	.codec_data =
	 {
		.l3 = {
		.setdat = setdat,
		.setclk = setclk,
		.setmode = setmode,
		.data_hold = 1,
		.data_setup = 1,
		.clock_high = 1,
		.mode_hold = 1,
		.mode = 1,
		.mode_setup = 1,
		},
	},
};

https://blog.csdn.net/gqb_driver/article/details/8551551
参考其他人的博客

codec_dai:
name:“UDA134X”
作用:对声卡芯片设置波特率,数据传输模式等硬件设置。
playback(播放):
stream_name = “Playback”,
channels_min = 1,
channels_max = 2,
rates = UDA134X_RATES,采样频率为8KHz ~ 48KHz
formats = UDA134X_FORMATS =SNDRV_PCM_FMTBIT_S8,SNDRV_PCM_FMTBIT_S16_LE,SNDRV_PCM_FMTBIT_S18_3LE,SNDRV_PCM_FMTBIT_S20_3LE
数据可以是8位,16位,18位,20位,但是都是先发高位数据。
录音:
录音上面的参数设置和播放是一样的。

cpu_dai:
name = “s3c24xx-i2s”,
作用:I2S的硬件设置
playback(播放):
channels_min = 2,
channels_max = 2,
rates = S3C24XX_I2S_RATES, 采用频率是8KHz ~ 96KHz
formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,只支持8位和16位的数据。
capture(录制):
录制的设置也是和上面是一样的。

可见,采样频率CPU支持的种类比UDA134X芯片多,数据格式UDA134X芯片支持的多。

s3c24xx_uda134x_ops操作集:

调用结构体uda134x_dai和结构体s3c24xx_i2s_dai中的硬件操作函数对声卡uda1341,和CPU上IIS接口进行初始化设置。

platform:
name = “s3c24xx-audio”
作用:使用DMA在内存和I2S的缓存中搬运数据。

五. 匹配soc-audio设备的driver

搜索"soc-audio",在soc-core.c文件中注册了这个driver。也是在内核起来的时候注册的。

static int __init snd_soc_init(void)
{
	return platform_driver_register(&soc_driver);
}
六. soc-audio driver的probe函数soc_probe
ret = snd_soc_register_card(card);

注册soc card,也就是snd_soc_s3c24xx_uda134x。

将snd_soc_s3c24xx_uda134x添加到card_list这个链表。

list_add(&card->list, &card_list);

初始化所有的cards。取出card_list的每一次card,调用snd_soc_instantiate_card函数。

snd_soc_instantiate_cards();
    list_for_each_entry(card, &card_list, list)
        snd_soc_instantiate_card(card);
七. snd_soc_instantiate_card函数分析
  • 取出platform_list的每一个platform,看有没有注册过s3c24xx-audio这个platform,没有的话直接返回。
list_for_each_entry(platform, &platform_list, list)
    if (card->platform == platform) 
    {
        found = 1;
        break;
    }
    if (!found) 
    {
        dev_dbg(card->dev, "Platform %s not registered\n", card->platform->name);
        return;
    }

  • 判断有没有注册上面的s3c24xx_i2s_dai这个cpu_dai,没有的话直接返回。
ac97 = 0;
for (i = 0; i < card->num_links; i++) 
{
    found = 0;
    list_for_each_entry(dai, &dai_list, list)
        if (card->dai_link[i].cpu_dai == dai) 
        {
            found = 1;
            break;
        }
        
    if (!found) 
    {
        dev_dbg(card->dev, "DAI %s not registered\n",
            card->dai_link[i].cpu_dai->name);
        return;
    }

    if (card->dai_link[i].cpu_dai->ac97_control)
        ac97 = 1;
}

  • 判断有没有注册上面的uda134x_dai这个codec_dai,没有的话直接返回。
for (i = 0; i < card->num_links; i++) 
{
    found = 0;
    list_for_each_entry(dai, &dai_list, list)
        if (card->dai_link[i].codec_dai == dai) 
        {
            found = 1;
            break;
        }
        
    if (!found) 
    {
        dev_dbg(card->dev, "DAI %s not registered\n",
            card->dai_link[i].codec_dai->name);
        return;
    }
}
  • 通过上面的几步检查,我们要的platform, cpu_dai,codec_dai都已经准备好了。
  • 执行card->probe,但是我们这里card->probe = NULL,就不执行了。
if (card->probe) {
    ret = card->probe(pdev);
    if (ret < 0)
        return;
	}
  • 执行s3c24xx_i2s_dai的probe函数。
for (i = 0; i < card->num_links; i++) 
{
    struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
    if (cpu_dai->probe) 
    {
        ret = cpu_dai->probe(pdev, cpu_dai);
        if (ret < 0)
            goto cpu_dai_err;
    }
}

这个函数里面做的操作:
获取iis时钟,并使能iis时钟;
配置IIS引脚;
I2S enable;

  • 执行soc_codec_dev_uda134x的probe函数,就是uda134x_soc_probe函数。

  • 执行s3c24xx_soc_platform的probe函数,但是没有probe函数。

if (platform->probe) {
    ret = platform->probe(pdev);
    if (ret < 0)
        goto platform_err;
}
八. uda134x_soc_probe函数分析
  • 申请snd_soc_s3c24xx_uda134x的codec内存,snd_soc_codec类型,初始化赋值。
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (socdev->card->codec == NULL)
    return ret;

codec = socdev->card->codec;

uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
if (uda134x == NULL)
    goto priv_err;
codec->private_data = uda134x;

codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg),
               GFP_KERNEL);
if (codec->reg_cache == NULL)
    goto reg_err;

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;

codec.name = “UDA134X”
codec.dai = &uda134x_dai
codec.read = uda134x_read_reg_cache
codec.write = uda134x_write
codec.control_data = &s3c24xx_uda134x
codec.private_data = uda134x_priv
codec.socdev =s3c24xx_uda134x_snd_devdata

现在整理一下各个数据结构之间的关系,如下图所示:
在这里插入图片描述

  • 调用snd_soc_new_pcms函数
    调用snd_card_create函数创建一个声卡变量struct snd_card *card。
    card->number赋值
    card->module赋值
    card->devices链表初始化
    card->controls链表初始化
    card->files_list链表初始化
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);

调用snd_ctl_create函数创建一个conctrol组件,并初始化,然后插入card->devices链表

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;

	if (snd_BUG_ON(!card || !device_data || !ops))
		return -ENXIO;
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (dev == NULL) {
		snd_printk(KERN_ERR "Cannot allocate device\n");
		return -ENOMEM;
	}
	dev->card = card;
	dev->type = type;
	dev->state = SNDRV_DEV_BUILD;
	dev->device_data = device_data;
	dev->ops = ops;
	list_add(&dev->list, &card->devices);	/* add to the head of list */
	return 0;
}

将创建的声卡赋值给codec->card,如下图所示:
在这里插入图片描述

调用soc_new_pcm函数创建pcm部件

for (i = 0; i < card->num_links; i++) 
{
    ret = soc_new_pcm(socdev, &card->dai_link[i], i);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't create pcm %s\n",
            card->dai_link[i].stream_name);
        mutex_unlock(&codec->mutex);
        return ret;
    }
}

card->num_links = 1,只创建了1个pcm部件

申请结构体snd_soc_pcm_runtime *rtd,并赋值
rtd->dai = s3c24xx_uda134x_dai_link
rtd->socdev = s3c24xx_uda134x_snd_devdata

调用snd_pcm_new函数创建一个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,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rpcm)
		*rpcm = NULL;
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (pcm == NULL) {
		snd_printk(KERN_ERR "Cannot allocate PCM\n");
		return -ENOMEM;
	}
	pcm->card = card;
	pcm->device = device;
	if (id)
		strlcpy(pcm->id, id, sizeof(pcm->id));
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}

一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
分配一个snd_pcm结构体

pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);

pcm实例和声卡关联

pcm->card = card;

然后分别创建一个playback stream和capture stream。

if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) 
{
    snd_pcm_free(pcm);
    return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) 
{
    snd_pcm_free(pcm);
    return err;
}

分配一个pcm设备snd_device,放入声卡的devices链表中。

snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)

pcm指针保存在s3c24xx_uda134x_dai_link.pcm中。

dai_link->pcm = pcm;

上面申请的rtd指针保存在pcm->private_data中。

pcm->private_data = rtd;

给snd_pcm.streams[i].substream[i].ops赋值,mmap,pointer,ioctl等几个函数使用s3c24xx_pcm_ops的,其他的使用soc_pcm_ops自己的。soc_pcm_ops如下:

soc_pcm_ops.open		= soc_pcm_open,
soc_pcm_ops.close		= soc_codec_close,
soc_pcm_ops.hw_params	= soc_pcm_hw_params,
soc_pcm_ops.hw_free	= soc_pcm_hw_free,
soc_pcm_ops.prepare	= soc_pcm_prepare,
soc_pcm_ops.trigger	= soc_pcm_trigger,
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;
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);

给snd_pcm.streams[i].substream[i]申请DMA内存

ret = platform->pcm_new(codec->card, codec_dai, pcm);

就是调用的是s3c24xx_pcm_new函数,申请的DMA内存的物理地址保存在snd_pcm.streams[i].substream[i].dma_buffer.addr中,虚拟地址保存在snd_pcm.streams[i].substream[i].dma_buffer.area中,内存大小是128Kb。

把DMA内存释放的接口告诉pcm。

pcm->private_free = platform->pcm_free;

platform->pcm_free = s3c24xx_pcm_free_dma_buffers,这个接口释放DMA内存。

上面几个数据结构之间的关联如下图所示:
在这里插入图片描述

  • 调用snd_soc_add_controls接口增加Control接口
    Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。
    本文使用的芯片是uda1341,所有的Control接口都定义在了uda1341_snd_controls数组中。
    数据中的每个元素SOC_SINGLE和SOC_ENUM这种宏定义。
    我们选取一个音量控制的Control接口,把他展开,得到:
uda1341_snd_controls[0] = 
{
    .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
    .name = "Master Playback Volume",
    .info = snd_soc_info_volsw,
    .get = snd_soc_get_volsw,
    .put = snd_soc_put_volsw,
    .private_value =
    {
        soc_mixer_control.reg = UDA134X_DATA000,
        soc_mixer_control.shift = 0,
        soc_mixer_control.rshift = 0,
        soc_mixer_control.max = 0x3F,
        soc_mixer_control.invert = 1,
    },
}

uda1341_snd_controls这个数组中一共定义了20个Control接口,调用snd_soc_add_controls注册,实际上是注册到snd_card的controls链表中。
进入snd_soc_add_controls函数分析。

int snd_soc_add_controls(struct snd_soc_codec *codec,const struct snd_kcontrol_new *controls, int num_controls)
{
	struct snd_card *card = codec->card;
	int err, i;

	for (i = 0; i < num_controls; i++) {
		const struct snd_kcontrol_new *control = &controls[i];
		err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
		if (err < 0) {
			dev_err(codec->dev, "%s: Failed to add %s\n",
				codec->name, control->name);
			return err;
		}
	}
	return 0;
}

调用snd_ctl_add函数使用for循环全部添加。snd_ctl_add函数参数2传入snd_kcontrol结构体数据。
看一下snd_soc_cnew函数。

snd_soc_cnew
    memcpy(&template, _template, sizeof(template));
    snd_ctl_new1(&template, data);
        snd_ctl_new(&kctl, access)

主要是把上面的uda1341_snd_controls[0]拷贝,然后把里面的数据保存到snd_kcontrol这个结构体,然后申请内存,保存snd_kcontrol这个结构体,同时返回申请到的内存的指针。返回给snd_ctl_add函数的参数2。

进入snd_ctl_add函数

snd_ctl_add
    snd_ctl_find_id(card, &id)
    snd_ctl_find_hole(card, kcontrol->count)
    list_add_tail(&kcontrol->list, &card->controls);

这个函数里面首先检查这个Control接口是否已经存在了,然后看看还能不能注册,最后是把Control接口添加到snd_card的controls链表中。
数据结构如下所示:
在这里插入图片描述

  • 调用snd_soc_init_card函数注册声卡
    主要是调用了snd_card_register函数注册声卡。
snd_card_register(codec->card);
    snd_device_register_all(card)
        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;
            }
        }

取出card->devices的每一个设备,调用它的dev_register函数,传入的参数是snd_device类型。
前面主要注册了两个设备,一个是SNDRV_DEV_PCM类型的snd_device, 一个是SNDRV_DEV_CONTROL
类型的的snd_device。
它们的dev_register函数分别是:snd_pcm_dev_register和 snd_ctl_dev_register。

进入snd_pcm_dev_register函数分析。

snd_pcm_dev_register
err = snd_pcm_add(pcm);
    for (cidx = 0; cidx < 2; cidx++)
    {
        switch (cidx) 
        {
            case SNDRV_PCM_STREAM_PLAYBACK:
            sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
            devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
            break;
            case SNDRV_PCM_STREAM_CAPTURE:
            sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
            devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
            break;
        }
        snd_register_device_for_dev(devtype, pcm->card, pcm->device, &snd_pcm_f_ops[cidx], pcm, str, dev);
            device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name)
    }

将pcm放入链表snd_pcm_devices中。
调用device_create创建pcmC0D0p,pcmC0D0c两个pcm设备。
分别创建snd_minor类型的变量preg,初始化赋值后存入snd_minors[256]中。

进入snd_ctl_dev_register函数分析。

static int snd_ctl_dev_register(struct snd_device *device)
    snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name)
        snd_register_device_for_dev(type, card, dev, f_ops, private_data, name,snd_card_get_device_link(card)
            device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name)

最终调用device_create函数注册的Ctrl设备。
同时分配了一个snd_minor类型的变量preg。
preg->type = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
preg->card = 0;
preg->device = 1;
preg->f_ops = &snd_pcm_f_ops;
preg->private_data = g_snd_pcm;
把preg保存在snd_minors[256]中。

九. 总结

看到这里,在梳理一下前面的数据结构之间的关系,如下图所示:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_38696651/article/details/89431706
今日推荐