ASOC registration process

1. What is ASOC

The sound card driver in the embedded system is ASOC (ALSA System on Chip), which is a layer encapsulated on the ALSA driver. It is divided into 3 parts, Machine, Platform and Codec. The relationship between the three parts is shown in the following figure: Machine refers to our development board, Platform refers to Soc, and Codec refers to codec (such as uda1341)

Second, the embedded system sound card registration process

Here we take the mini2440 development board as an example 

安装新驱动

insmod alsa/driver/myalsa/platform/s3c2440_iis.ko 
insmod alsa/driver/myalsa/platform/s3c2440_dma.ko 
insmod alsa/driver/myalsa/codec/uda1341.ko 
insmod alsa/driver/myalsa/machine/s3c2440_uda1341.ko 

First, we install the sound card driver we wrote into the kernel (the Machine part of the ko file must be placed at the end, Platform and Codec have no special requirements, the following will explain why)

1.platform part

1.1 cpu_dai

 When installing s3c2440_iis.ko, the platform driver s3c24xx_iis_driver will be registered. Because there is a platform device with the same name as "s3c24xx-iis" in the kernel, the corresponding probe function s3c24xx_iis_dev_probe is called. This function will eventually put s3c24xx_i2s_dai (cpu_dai hardware operation) Enter the linked list dai_list and name it "s3c24xx-iis" (the Machine driver later instantiates the sound card based on this name)

1. platform:
1.1 s3c24xx-i2s.c : 把s3c24xx_i2s_dai放入链表dai_list, .name = "s3c24xx-iis",
s3c24xx_iis_dev_probe
  snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
    list_add(&dai->list, &dai_list);
平台驱动
static struct platform_driver s3c24xx_iis_driver = {
	.probe  = s3c24xx_iis_dev_probe,
	.remove = s3c24xx_iis_dev_remove,
	.driver = {
		.name = "s3c24xx-iis",
		.owner = THIS_MODULE,
	},
};

平台设备
struct platform_device s3c_device_iis = {
	.name		  = "s3c24xx-iis",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_iis_resource),
	.resource	  = s3c_iis_resource,
	.dev              = {
		.dma_mask = &s3c_device_iis_dmamask,
		.coherent_dma_mask = 0xffffffffUL
	}
};
static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};

//cpu_dai相关的硬件操作
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
	.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,
};

1.2 platform_dma

When installing s3c2440_dma.ko, put Samsung_asoc_platform in the linked list platform_list and name it "samsung-audio" (the Machineko driver later instantiates the sound card based on this name)

1.2 sound/soc/samsung/dma.c : 把samsung_asoc_platform放入了链表platform_list, .name = "samsung-audio",
samsung_asoc_platform_probe
  snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
    list_add(&platform->list, &platform_list);
static struct snd_pcm_ops dma_ops = {
	.open		= dma_open,
	.close		= dma_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= dma_hw_params,
	.hw_free	= dma_hw_free,
	.prepare	= dma_prepare,
	.trigger	= dma_trigger,
	.pointer	= dma_pointer,
	.mmap		= dma_mmap,
};
//cpu_dam相关的硬件操作
static struct snd_soc_platform_driver samsung_asoc_platform = {
	.ops		= &dma_ops,
	.pcm_new	= dma_new,
	.pcm_free	= dma_free_dma_buffers,
};

2. Codec registration part

When installing uda1341.ko, put soc_codec_dev_uda134x and uda134x_dai into the dai_list and codec_list linked lists (used to later instantiate the sound card with the Machineko driver)

2. codec: uda134x.c
uda134x_codec_probe
  snd_soc_register_codec(&pdev->dev,&soc_codec_dev_uda134x, &uda134x_dai, 1);
      struct snd_soc_codec *codec;
      codec->driver = codec_drv; = &soc_codec_dev_uda134x
      
      snd_soc_register_dais(dev, dai_drv, num_dai); // uda134x_dai
        list_add(&dai->list, &dai_list); : 把uda134x_dai放入了链表dai_list
      list_add(&codec->list, &codec_list); 把soc_codec_dev_uda134x放入了链表codec_list
//设置codec芯片的硬件操作
static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
	.probe =        uda134x_soc_probe,
	.remove =       uda134x_soc_remove,
	.suspend =      uda134x_soc_suspend,
	.resume =       uda134x_soc_resume,
	.reg_cache_size = sizeof(uda134x_reg),
	.reg_word_size = sizeof(u8),
	.reg_cache_default = uda134x_reg,
	.reg_cache_step = 1,
	.read = uda134x_read_reg_cache,
	.write = uda134x_write,
	.set_bias_level = uda134x_set_bias_level,
};

 

static struct snd_soc_dai_ops uda134x_dai_ops = {
	.startup	= uda134x_startup,
	.shutdown	= uda134x_shutdown,
	.hw_params	= uda134x_hw_params,
	.digital_mute	= uda134x_mute,
	.set_sysclk	= uda134x_set_dai_sysclk,
	.set_fmt	= uda134x_set_dai_fmt,
};

//codec_dai相关的硬件操作
static struct snd_soc_dai_driver uda134x_dai = {
	.name = "uda134x-hifi",
	/* 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,
};

3. Machine part

The machine part has a lot of content, and the overall calling process is as follows:

s3c24xx_uda134x_probe
  s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
  platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x);     
  platform_device_add(s3c24xx_uda134x_snd_device);
  
  .....
  soc_probe
    snd_soc_register_card(card);  // card = &snd_soc_s3c24xx_uda134x

      card->rtd = devm_kzalloc(card->dev,...
      card->rtd[i].dai_link = &card->dai_link[i];  // &s3c24xx_uda134x_dai_link
      
      list_add(&card->list, &card_list);
      
      snd_soc_instantiate_cards();  // 实例化声卡
          snd_soc_instantiate_card(card);
            3.1   /* bind DAIs */
                  for (i = 0; i < card->num_links; i++)
                    soc_bind_dai_link(card, i);
                        3.1.1 /* find CPU DAI */
                              rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai
                        3.1.2 /* find_codec */
                              rtd->codec = codec;  = // codec, codec->driver=&soc_codec_dev_uda134x
                        3.1.3 /* find CODEC DAI */      
                              rtd->codec_dai = codec_dai; // = &uda134x_dai
                        3.1.4 /* find_platform */
                              rtd->platform = platform; // = &samsung_asoc_platform
            3.2 /* initialize the register cache for each available codec */
                ret = snd_soc_init_codec_cache(codec, compress_type);
                
            3.3 snd_card_create
			err = snd_ctl_create(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,
				};
				snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
					dev->ops = ops;
            
            3.4 /* early DAI link probe */
                soc_probe_dai_link    
                		/* probe the cpu_dai */
                		/* probe the CODEC */
                		/* probe the platform */
                		/* probe the CODEC DAI */
                		/* create the pcm */
                		ret = soc_new_pcm(rtd, num);
                			struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
							soc_pcm_ops->open	= soc_pcm_open;
							soc_pcm_ops->close	= soc_pcm_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->pointer	= soc_pcm_pointer;
                					
                			snd_pcm_new
								static struct snd_device_ops ops = {
									.dev_free = snd_pcm_dev_free,
									.dev_register =	snd_pcm_dev_register,
									.dev_disconnect = snd_pcm_dev_disconnect,
								};
								err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
									dev->ops = ops;

								pcm->private_data = rtd;
            3.5 snd_card_register
			snd_device_register_all
				err = dev->ops->dev_register(dev)调用前面snd_pcm_new的snd_pcm_dev_register
                              		snd_pcm_dev_register
                           			err = snd_register_device_for_dev(devtype, pcm->card,
						  	pcm->device,
						  	&snd_pcm_f_ops[cidx],
						  	pcm, str, dev);

When installing s3c2440_uda1341.ko, register the platform driver s3c24xx_uda134x_driver. Because there is a platform device mini2440_audio with the same name in the kernel, the s3c24xx_uda134x_probe function is called

//平台驱动
static struct platform_driver s3c24xx_uda134x_driver = {
	.probe  = s3c24xx_uda134x_probe,
	.remove = s3c24xx_uda134x_remove,
	.driver = {
		.name = "s3c24xx_uda134x",
		.owner = THIS_MODULE,
	},
};

//平台设备
static struct platform_device mini2440_audio = {
	.name		= "s3c24xx_uda134x",
	.id		= 0,
	.dev		= {
		.platform_data	= &mini2440_audio_pins,
	},
};

① The s3c24xx_uda134x_probe function calls platform_set_drvdata to save the snd_soc_card structure snd_soc_s3c24xx_uda134x (including dai_link: used to connect Platform
and Codec, which will be used during instantiation) and save it in pdev (platform_device)-> dev-> p->
driver_data_add_devices_add_x3 ), Because there is a platform driver soc_driver with the same name in the kernel, so call the probe function of the driver (soc_probe function)

    s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
    platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x); 
        dev_set_drvdata(&pdev->dev, data);
            dev->p->driver_data = data;    
    platform_device_add(s3c24xx_uda134x_snd_device);
//指定了platform和codec
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
	.name = "UDA134X",
	.stream_name = "UDA134X",
	.codec_name = "uda134x-codec",
	.codec_dai_name = "uda134x-hifi",
	.cpu_dai_name = "s3c24xx-iis",
	.ops = &s3c24xx_uda134x_ops,
	.platform_name	= "samsung-audio",
};

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
	.name = "S3C24XX_UDA134X",
	.dai_link = &s3c24xx_uda134x_dai_link,
	.num_links = 1,        //只有1个dai_link
};
/* ASoC platform driver */
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

② The soc_probe function calls the platform_get_drvdata function to take out the snd_soc_card structure snd_soc_s3c24xx_uda134x (containing dai_link: used to connect Platform and Codec, which will be used during instantiation) of step ① , and then call snd_soc_register_card function to register it.

soc_probe
    struct snd_soc_card *card = platform_get_drvdata(pdev);
        return dev->p->driver_data;
    snd_soc_register_card(card);  // card = &snd_soc_s3c24xx_uda134x

③ The snd_soc_register_card function takes out the dai_link of the snd_soc_card structure (the snd_soc_s3c24xx_uda134x structure has only one dai_link, where the for is only executed once), and then calls the snd_soc_instantiate_cards function to instantiate the sound card . In fact, it is based on the names of the snd_soc_s3c24xx_uda134x, dailist_list_structure_list_link_dai134x_dai134x structure The codec_list linked list finds the four structures that our platform and codec registration parts put in the queue . (The soc_bind_dai_link function scans the three linked lists one by one, and matches according to the names in card-> dai_link []. After matching, the corresponding codec, dai, and platform instances are assigned to (snd_soc_card) card-> rtd [] (snd_soc_pcm_runtime). After this process, snd_soc_pcm_runtime: (card-> rtd) saved Codec, DAI and Platform driver information used in this Machine)

snd_soc_register_card
    card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
			    (card->num_links + card->num_aux_devs),
			    GFP_KERNEL);
    for (i = 0; i < card->num_links; i++)
		card->rtd[i].dai_link = &card->dai_link[i];
    list_add(&card->list, &card_list);
    snd_soc_instantiate_cards();
        snd_soc_instantiate_card(card);
            soc_bind_dai_link(card, i);
                /* find CPU DAI */
                rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai
                /* find_codec */
                rtd->codec = codec;  = // codec, codec->driver=&soc_codec_dev_uda134x
                /* find CODEC DAI */      
                rtd->codec_dai = codec_dai; // = &uda134x_dai
                /* find_platform */
                rtd->platform = platform; // = &samsung_asoc_platform

④Call the API of traditional ALSA sound card driver, snd_card_create function, this function then calls snd_ctl_create, snd_ctl_create function calls snd_device_new to fill the snd_device_ops structure ops to the ops member of snd_device (dev-> ops = ops;), and then hang snd_device to (snd_card ) Card-> devices list. The dev_register member of the snd_device_ops structure will be used in step ⑧ snd_card_register later. At present, we just hang it in a linked list , as shown below:

            3.3 snd_card_create
			err = snd_ctl_create(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,
				};
				snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
					dev->ops = ops;

⑤ The soc_probe_dai_link function first calls the probe function such as cpu_dai found in the previous step (for example: s3c24xx_i2s_dai structure s3c24xx_iis_dev_probe), which is to say hardware initialization . Then call soc_new_pcm.

soc_probe_dai_link    
   /* probe the cpu_dai */
    ret = cpu_dai->driver->probe(cpu_dai);
   /* probe the CODEC */
    ret = soc_probe_codec(card, codec);
   /* probe the platform */
    ret = platform->driver->probe(platform);
   /* probe the CODEC DAI */
    ret = codec_dai->driver->probe(codec_dai);
   /* create the pcm */
    ret = soc_new_pcm(rtd, num);

⑥soc_new_pcm first function to function card-> rtd-> ops structure in pointer assignment function ( procedure APP invoked program will again invoke these functions by the hardware-related function (e.g., the front platform s3c24xx_i2s_dai) ), then the The address of card-> rtd-> ops is assigned to substream-> ops . Finally call snd_pcm_new.

ret = soc_new_pcm(rtd, num);
    struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;

    soc_pcm_ops->open	= soc_pcm_open;
    soc_pcm_ops->close	= soc_pcm_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->pointer	= soc_pcm_pointer;
                					
    snd_pcm_new

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
        substream->ops = ops;
或
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
        substream->ops = ops;

⑦snd_pcm_new is the traditional ALSA sound card driver API, this step is similar to step ④, the difference is that step ④ corresponds to snd_control, here is snd_pcm . After snd_pcm_new is executed, it returns to the soc_new_pcm function. Here is a (snd_pcm) pcm-> private_data = rtd; when our app calls the program, we will take out rtd from pcm-> private_data (rtd has cpu_dai, codec_dai corresponding to our development board) ...)

snd_pcm_new
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
	err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
			dev->ops = ops;

	pcm->private_data = rtd;

⑧snd_card_register, this function will call snd_ctl_dev_register and snd_pcm_dev_register in steps ④ and ⑦ (register all logical devices hanging under the sound card through snd_device_register_all (), snd_device_register_all () is actually through snd_card ’s devices list, and traverse all snd_device, Call ops-> dev_register () of snd_device to register each device), these two functions are also the API of traditional ALSA sound card, they will eventually call snd_register_device_for_dev function, snd_ctl_f_ops, snd_pcm_f_ops as snd_register_device_for_dev parameters are passed in, And is recorded in the field f_ops in snd_minors [minor], and create a device node (device_create (class_create is in sound_core.c)). When the user program needs to open the control and pcm devices, the driver can obtain the various callback functions in the snd_ctl_f_ops and snd_pcm_f_ops structures through the snd_minors [] global array and this device number (introduced in another article)

3.5 snd_card_register
		snd_device_register_all
			err = dev->ops->dev_register(dev)调用前面snd_ctl_create的snd_ctl_dev_register和snd_pcm_new的snd_pcm_dev_register
				snd_ctl_dev_register
					snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, name)
                                            snd_register_device_for_dev
	                                            preg->type = type;
	                                            preg->card = card ? card->number : -1;
	                                            preg->device = dev;
	                                            preg->f_ops = f_ops;
	                                            preg->private_data = private_data;

	                                            snd_minors[minor] = preg;
	                                            preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);

				和snd_pcm_dev_register
				 	err = snd_register_device_for_dev(devtype, pcm->card,
						  	pcm->device,
						  	&snd_pcm_f_ops[cidx],
						  	pcm, str, dev);
static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.llseek =	no_llseek,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};


const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};

 At this point, the task of the machine part has been completed.

 

4. Character device registration

There is alsa_sound_init () function in sound / core / sound.c. The parameter major in register_chrdev is the same as the major when creating the pcm device is device_create. This result is that when the application opens the device file / dev / snd / When pcmCxDxp, it will enter the open callback function of snd_fops. The open function is indexed by the minor device number, and the snd_minor structure that was filled when the conrol and pcm device was originally registered is taken from the snd_minors global array, and then the control and pcm device are taken out of the snd_minor structure. f_ops, and replace file-> f_op with f_ops of pcm device, then directly call f_ops-> open () of pcm device ...

#define CONFIG_SND_MAJOR	116	/* standard configuration */
static int major = CONFIG_SND_MAJOR;

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

alsa_sound_init
    register_chrdev(major, "alsa", &snd_fops)

We will introduce the calling process in detail in the next article.

 

 

Published 42 original articles · Like 10 · Visitors 10,000+

Guess you like

Origin blog.csdn.net/qq_37659294/article/details/104748747