Linux ALSA驱动之Platform源码分析(wm8350.c)

Posted Coder个人博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux ALSA驱动之Platform源码分析(wm8350.c)相关的知识,希望对你有一定的参考价值。

1、Platform概述

        ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DA〉把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音频信号。在具体实现上,ASoC又把Platform驱动分为两个部分: platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpudai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与platform_driver进行交互。

cpu_dai_driver 部分:
        在嵌入式系统里面通常指SoC的I2S、PCM总线控制器,负责把音频数据从I2S tx FIFO搬运到CODEC(这是音频播放的情形,录制则方向相反)。cpu_dai通过snd_soc_register_dai()/devm_snd_soc_register_component()来注册。

        注:DAI是 Digital Audio Interface的简称,分为cpu_dai和codec_dai,这两者通过 I2S/PCM 总线连接,AIF 是 Audio Interface 的简称,嵌入式系统中一般是I2S和PCM接口。

platform_driver部分:
        负责把dma buffer中的音频数据搬运到I2S tx FIFO。音频DMA驱动通过 platform_driver_register()/module_platform_driver() 来注册,故也常用platform来指代音频DMA驱动(这里的 platform 需要与 SoC Platform 区分开)。

2、snd_soc_dai_driver

2.1、snd_soc_dai_driver注册流程

        DAI驱动通常对应cpu的一个或几个I2S/PCM接口,实现一个DAI驱动大致可以分为以下几个步骤:

                1、定义一个snd_soc_dai_driver结构的实例;
                2、在对应的platform_driver中的probe回调中通过API: snd_soc_register_dai或者snd_soc_register_dais注册snd_soc_dai实例;
                3、实现snd_soc_dai_driver结构中的probe、suspend等回调;
                4、实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

        具体代码流程如下(sound/soc/codecs/wm8350.c)

/* snd_soc_dai_ops 结构体实例 */
static const struct snd_soc_dai_ops wm8350_dai_ops = 
	 .hw_params	= wm8350_pcm_hw_params,
	 .mute_stream	= wm8350_mute,
	 .set_fmt	= wm8350_set_dai_fmt,
	 .set_sysclk	= wm8350_set_dai_sysclk,
	 .set_pll	= wm8350_set_fll,
	 .set_clkdiv	= wm8350_set_clkdiv,
	 .no_capture_mute = 1,
;

/* snd_soc_dai_driver结构体实例 */
static struct snd_soc_dai_driver wm8350_dai = 
	.name = "wm8350-hifi",
	.playback = 
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8350_RATES,
		.formats = WM8350_FORMATS,
	,
	.capture = 
		 .stream_name = "Capture",
		 .channels_min = 1,
		 .channels_max = 2,
		 .rates = WM8350_RATES,
		 .formats = WM8350_FORMATS,
	 ,
	.ops = &wm8350_dai_ops,
;

/* platform平台probe函数 */
static int wm8350_probe(struct platform_device *pdev)

    /* 注册component组件参数为soc_component_dev_wm8350 wm8350_dai*/
	return devm_snd_soc_register_component(&pdev->dev,
			&soc_component_dev_wm8350,
			&wm8350_dai, 1);


/**
 * devm_snd_soc_register_component - resource managed component registration
 * @dev: Device used to manage component
 * @cmpnt_drv: Component driver
 * @dai_drv: DAI driver
 * @num_dai: Number of DAIs to register
 *
 * Register a component with automatic unregistration when the device is
 * unregistered.
 */
/* 进入devm_snd_soc_register_component函数 */
int devm_snd_soc_register_component(struct device *dev,
			 const struct snd_soc_component_driver *cmpnt_drv,
			 struct snd_soc_dai_driver *dai_drv, int num_dai)

	const struct snd_soc_component_driver **ptr;
	int ret;

    /* 申请devm_component_release空间 */
	ptr = devres_alloc(devm_component_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return -ENOMEM;

    /*调用snd_soc_register_component注册cmpnt_drv、 dai_drv */
	ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
	if (ret == 0) 
		*ptr = cmpnt_drv;
		devres_add(dev, ptr);
	 else 
		devres_free(ptr);
	

	return ret;

EXPORT_SYMBOL_GPL(devm_snd_soc_register_component);

/* 进入snd_soc_register_component函数 */
int snd_soc_register_component(struct device *dev,
			const struct snd_soc_component_driver *component_driver,
			struct snd_soc_dai_driver *dai_drv,
			int num_dai)

	struct snd_soc_component *component;
	int ret;

    /* 申请component空间 */
	component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL);
	if (!component)
		return -ENOMEM;
    
    /* 调用snd_soc_component_initialize函数注册component_driver */
	ret = snd_soc_component_initialize(component, component_driver, dev);
	if (ret < 0)
		return ret;

    /* 调用snd_soc_add_component注册 dai_drv */
	return snd_soc_add_component(component, dai_drv, num_dai);

EXPORT_SYMBOL_GPL(snd_soc_register_component);


/* 进入snd_soc_add_component函数 */
int snd_soc_add_component(struct snd_soc_component *component,
			  struct snd_soc_dai_driver *dai_drv,
			  int num_dai)

	int ret;
	int i;

	mutex_lock(&client_mutex);

	if (component->driver->endianness) 
		for (i = 0; i < num_dai; i++) 
			convert_endianness_formats(&dai_drv[i].playback);
			convert_endianness_formats(&dai_drv[i].capture);
		
	
    
    /* 调用snd_soc_register_dais函数注册dai_drv */
	ret = snd_soc_register_dais(component, dai_drv, num_dai);
	if (ret < 0) 
		dev_err(component->dev, "ASoC: Failed to register DAIs: %d\\n",
			ret);
		goto err_cleanup;
	

	if (!component->driver->write && !component->driver->read) 
		if (!component->regmap)
			component->regmap = dev_get_regmap(component->dev,
							   NULL);
		if (component->regmap)
			snd_soc_component_setup_regmap(component);
	

	/* see for_each_component */
	list_add(&component->list, &component_list);

err_cleanup:
	if (ret < 0)
		snd_soc_del_component_unlocked(component);

	mutex_unlock(&client_mutex);

	if (ret == 0)
		snd_soc_try_rebind_card();

	return ret;

EXPORT_SYMBOL_GPL(snd_soc_add_component);

/**
 * snd_soc_register_dais - Register a DAI with the ASoC core
 *
 * @component: The component the DAIs are registered for
 * @dai_drv: DAI driver to use for the DAIs
 * @count: Number of DAIs
 */
/* 进入snd_soc_register_dais函数 */
static int snd_soc_register_dais(struct snd_soc_component *component,
				 struct snd_soc_dai_driver *dai_drv,
				 size_t count)

	struct snd_soc_dai *dai;
	unsigned int i;
	int ret;

	for (i = 0; i < count; i++) 
        /* 最终调用snd_soc_register_dai函数注册dai_drv */
		dai = snd_soc_register_dai(component, dai_drv + i, count == 1 &&
					   !component->driver->non_legacy_dai_naming);
		if (dai == NULL) 
			ret = -ENOMEM;
			goto err;
		
	

	return 0;

err:
	snd_soc_unregister_dais(component);

	return ret;


/**
 * snd_soc_register_dai - Register a DAI dynamically & create its widgets
 *
 * @component: The component the DAIs are registered for
 * @dai_drv: DAI driver to use for the DAI
 * @legacy_dai_naming: if %true, use legacy single-name format;
 * 	if %false, use multiple-name format;
 *
 * Topology can use this API to register DAIs when probing a component.
 * These DAIs's widgets will be freed in the card cleanup and the DAIs
 * will be freed in the component cleanup.
 */
/* 进入到snd_soc_register_dai函数 */
struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
					 struct snd_soc_dai_driver *dai_drv,
					 bool legacy_dai_naming)

	struct device *dev = component->dev;
	struct snd_soc_dai *dai;

	dev_dbg(dev, "ASoC: dynamically register DAI %s\\n", dev_name(dev));

	lockdep_assert_held(&client_mutex);

    /* 申请dai空间 */
	dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);
	if (dai == NULL)
		return NULL;

	/*
	 * Back in the old days when we still had component-less DAIs,
	 * instead of having a static name, component-less DAIs would
	 * inherit the name of the parent device so it is possible to
	 * register multiple instances of the DAI. We still need to keep
	 * the same naming style even though those DAIs are not
	 * component-less anymore.
	 */
	if (legacy_dai_naming &&
	    (dai_drv->id == 0 || dai_drv->name == NULL)) 
		dai->name = fmt_single_name(dev, &dai->id);
	 else 
		dai->name = fmt_multiple_name(dev, dai_drv);
		if (dai_drv->id)
			dai->id = dai_drv->id;
		else
			dai->id = component->num_dai;
	
	if (!dai->name)
		return NULL;

	dai->component = component;
	dai->dev = dev;
	dai->driver = dai_drv;

	/* see for_each_component_dais */
    /* 将dai->list添加到component->dai_list中 */
	list_add_tail(&dai->list, &component->dai_list);
	component->num_dai++;

	dev_dbg(dev, "ASoC: Registered DAI '%s'\\n", dai->name);
	return dai;

EXPORT_SYMBOL_GPL(snd_soc_register_dai);

/* 至此cpu_dai添加完成 */

2.2、snd_soc_dai结构体

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */
struct snd_soc_dai 
	const char *name;
	int id;
	struct device *dev;

	/* driver ops */
	struct snd_soc_dai_driver *driver;

	/* DAI runtime info */
	unsigned int stream_active[SNDRV_PCM_STREAM_LAST + 1]; /* usage count */

	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_component *component;

	/* CODEC TDM slot masks and params (for fixup) */
	unsigned int tx_mask;
	unsigned int rx_mask;

	struct list_head list;

	/* function mark */
	struct snd_pcm_substream *mark_startup;
	struct snd_pcm_substream *mark_hw_params;
	struct snd_pcm_substream *mark_trigger;
	struct snd_compr_stream  *mark_compr_startup;

	/* bit field */
	unsigned int probed:1;
;

        snd_soc_dai该结构在snd_soc_register_dai函数中通过动态内存申请获得.简要介绍一下几个重要字段:

                1、driver指向关联的snd_soc_dai_driver结构,由注册时通过参数传入。
                2、playback_dma_data 用于保存该dai播放stream的dma信息目标地址,dma传送单元大小和通道号等。
                3、capture_dma_data 同上,用于录音stream。
                4、component指向关联的snd_soc_component结构体中。

2.3、snd_soc_dai_driver结构体

/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver 
	/* DAI description */
	const char *name;
	unsigned int id;
	unsigned int base;
	struct snd_soc_dobj dobj;

	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	/* compress dai */
	int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
	/* Optional Callback used at pcm creation*/
	int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
		       struct snd_soc_dai *dai);

	/* ops */
	const struct snd_soc_dai_ops *ops;
	const struct snd_soc_cdai_ops *cops;

	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;
	struct snd_soc_pcm_stream playback;
	unsigned int symmetric_rate:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_sample_bits:1;

	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;
;

        snd_soc_dai_driver结构体需要自己根据不同的soc芯片进行定义,这里只介绍几个关键字段:

                1、probe、remove回调函数,分别在声卡加载和卸载时被调用。
                2、ops指向snd_soc_dai_ops结构,用于配置和控制该dai,后面细讲。
                3、playback snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力。
                4、capture snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力。

2.4、snd_soc_dai_ops结构体

        snd_soc_dai_driver结构体中的ops字段指向一个snd_soc_dai_ops结构体,该结构体实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:

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 (*get_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 (*set_stream)(struct snd_soc_dai *dai,
			  void *stream, int direction);
	void *(*get_stream)(struct snd_soc_dai *dai, int direction);

	/*
	 * DAI digital mute - optional.
	 * Called by soc-core to minimise any pops.
	 */
	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 *);

	/*
	 * Format list for auto selection.
	 * Format will be increased if priority format was
	 * not selected.
	 * see
	 *	snd_soc_dai_get_fmt()
	 */
	u64 *auto_selectable_formats;
	int num_auto_selectable_formats;

	/* bit field */
	unsigned int no_capture_mute:1;
;

        工作时钟配置函数通常由machine驱动调用:

                1、set_sysclk设置dai的主时钟。
                2、set_pll设置PLL参数。
                3、set_clkdiv设置分频系数。

        dai的格式配置参数,通常也由machine驱动调用:

                1、set_fmt设置dai的格式。

                2、set_tdm_slot如果dai支持时分复用,用于设置时分复用的slot、set_channel_map声道的时分复用映射设置。
                3、set_tristate设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调。

        标准的snd_soc_ops回调通常由soc-core在进行PCM操作时调用:

                1、startup:打开设备,设备开始工作的时候回调
                2、shutdown:关闭设备前调用
                3、hw_params:设置硬件的相关参数
                4、trigger:DAM开始时传输,结束传输,暂停传世,恢复传输的时候被回调。

3、platform_driver

3.1、platform_driver注册流程

/* snd_soc_component_driver结构体实例化 */
static const struct snd_soc_component_driver soc_component_dev_wm8350 = 
	.probe			= wm8350_component_probe,
	.remove			= wm8350_component_remove,
	.set_bias_level		= wm8350_set_bias_level,
	.controls		= wm8350_snd_controls,
	.num_controls		= ARRAY_SIZE(wm8350_snd_controls),
	.dapm_widgets		= wm8350_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(wm8350_dapm_widgets),
	.dapm_routes		= wm8350_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(wm8350_dapm_routes),
	.suspend_bias_off	= 1,
	.idle_bias_on		= 1,
	.use_pmdown_time	= 1,
	.endianness		= 1,
	.non_legacy_dai_naming	= 1,
;

/* 进入到wm8350_probe函数 */
static int wm8350_probe(struct platform_device *pdev)

    /* 通过devm_snd_soc_register_component函数注册soc_component_dev_wm8350 */
	return devm_snd_soc_register_component(&pdev->dev,
			&soc_component_dev_wm8350,
			&wm8350_dai, 1);


/* 进入platform_driver函数 */
static struct platform_driver wm8350_codec_driver = 
	.driver = 
		   .name = "wm8350-codec",
		   ,
	.probe = wm8350_probe,
;

/* 通过module_platform_driver宏来注册platform_driver */
module_platform_driver(wm8350_codec_driver);

3.2、platform_driver结构体

         在编写 platform 驱动的时候,首先定义一个platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后 probe函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。

struct platform_driver 
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
	/*
	 * For most device drivers, no need to care about this flag as long as
	 * all DMAs are handled through the kernel DMA API. For some special
	 * ones, for example VFIO drivers, they know how to manage the DMA
	 * themselves and set this flag so that the IOMMU layer will allow them
	 * to setup and manage their own I/O address space.
	 */
	bool driver_managed_dma;
;

        platform_driver结构体用于注册驱动到Platform总线,此处只讲几个重点字段:

                1、probe:当驱动与设备匹配成功以后probe函数就会执行。一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。
                2、driver:device_driver 结构体变量,Linux 内核里面大量使用到了面向对象的思维, device_driver相当于基类,提供了最基础的驱动框架。 plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量。

3.3、snd_soc_component结构体

struct snd_soc_component 
    /* device_driver->name 和snd_soc_component_driver->id有关, */
	const char *name;
	int id;
	const char *name_prefix;
	struct device *dev;
	struct snd_soc_card *card;

	unsigned int active;

	unsigned int suspended:1; /* is in suspend PM state */
    /* 用于把自己挂载到全局链表component_list下, component_list在soc-core中保持的全局变量 */
	struct list_head list;
	struct list_head card_aux_list; /* for auxiliary bound components */
	struct list_head card_list;

    /* 指向下属的snd_soc_component_driver, 该结构体一般由底层平台驱动实现 */
	const struct snd_soc_component_driver *driver;

    /* 链表头, 挂接snd_soc_dai->list   list_add(&dai->list, &component->dai_list) */
	struct list_head dai_list;
	int num_dai;

	struct regmap *regmap;
	int val_bytes;

	struct mutex io_mutex;

	/* attached dynamic objects */
	struct list_head dobj_list;

	/*
	 * DO NOT use any of the fields below in drivers, they are temporary and
	 * are going to be removed again soon. If you use them in driver code
	 * the driver will be marked as BROKEN when these fields are removed.
	 */

	/* Don't use these, use snd_soc_component_get_dapm() */
	struct snd_soc_dapm_context dapm;

	/* machine specific init */
	int (*init)(struct snd_soc_component *component);

	/* function mark */
	void *mark_module;
	struct snd_pcm_substream *mark_open;
	struct snd_pcm_substream *mark_hw_params;
	struct snd_pcm_substream *mark_trigger;
	struct snd_compr_stream  *mark_compr_open;
	void *mark_pm;

	struct dentry *debugfs_root;
	const char *debugfs_prefix;
;

3.4、snd_soc_component_driver结构体

struct snd_soc_component_driver 
	const char *name;

	/* Default control and setup, added after probe() is run */
	const struct snd_kcontrol_new *controls;
	unsigned int num_controls;
	const struct snd_soc_dapm_widget *dapm_widgets;
	unsigned int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	unsigned int num_dapm_routes;

	int (*probe)(struct snd_soc_component *component);
	void (*remove)(struct snd_soc_component *component);
	int (*suspend)(struct snd_soc_component *component);
	int (*resume)(struct snd_soc_component *component);

	unsigned int (*read)(struct snd_soc_component *component,
			     unsigned int reg);
	int (*write)(struct snd_soc_component *component,
		     unsigned int reg, unsigned int val);

	/* pcm creation and destruction */
	int (*pcm_construct)(struct snd_soc_component *component,
			     struct snd_soc_pcm_runtime *rtd);
	void (*pcm_destruct)(struct snd_soc_component *component,
			     struct snd_pcm *pcm);

	/* component wide operations */
	int (*set_sysclk)(struct snd_soc_component *component,
			  int clk_id, int source, unsigned int freq, int dir);
	int (*set_pll)(struct snd_soc_component *component, int pll_id,
		       int source, unsigned int freq_in, unsigned int freq_out);
	int (*set_jack)(struct snd_soc_component *component,
			struct snd_soc_jack *jack,  void *data);

	/* DT */
	int (*of_xlate_dai_name)(struct snd_soc_component *component,
				 const struct of_phandle_args *args,
				 const char **dai_name);
	int (*of_xlate_dai_id)(struct snd_soc_component *comment,
			       struct device_node *endpoint);
	void (*seq_notifier)(struct snd_soc_component *component,
			     enum snd_soc_dapm_type type, int subseq);
	int (*stream_event)(struct snd_soc_component *component, int event);
	int (*set_bias_level)(struct snd_soc_component *component,
			      enum snd_soc_bias_level level);

	int (*open)(struct snd_soc_component *component,
		    struct snd_pcm_substream *substream);
	int (*close)(struct snd_soc_component *component,
		     struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_soc_component *component,
		     struct snd_pcm_substream *substream,
		     unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_soc_component *component,
			 struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_soc_component *component,
		       struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_soc_component *component,
		       struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_soc_component *component,
		       struct snd_pcm_substream *substream, int cmd);
	int (*sync_stop)(struct snd_soc_component *component,
			 struct snd_pcm_substream *substream);
	snd_pcm_uframes_t (*pointer)(struct snd_soc_component *component,
				     struct snd_pcm_substream *substream);
	int (*get_time_info)(struct snd_soc_component *component,
		struct snd_pcm_substream *substream, struct timespec64 *system_ts,
		struct timespec64 *audio_ts,
		struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
		struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
	int (*copy_user)(struct snd_soc_component *component,
			 struct snd_pcm_substream *substream, int channel,
			 unsigned long pos, void __user *buf,
			 unsigned long bytes);
	struct page *(*page)(struct snd_soc_component *component,
			     struct snd_pcm_substream *substream,
			     unsigned long offset);
	int (*mmap)(struct snd_soc_component *component,
		    struct snd_pcm_substream *substream,
		    struct vm_area_struct *vma);
	int (*ack)(struct snd_soc_component *component,
		   struct snd_pcm_substream *substream);
	snd_pcm_sframes_t (*delay)(struct snd_soc_component *component,
				   struct snd_pcm_substream *substream);

	const struct snd_compress_ops *compress_ops;

	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;

	/*
	 * signal if the module handling the component should not be removed
	 * if a pcm is open. Setting this would prevent the module
	 * refcount being incremented in probe() but allow it be incremented
	 * when a pcm is opened and decremented when it is closed.
	 */
	unsigned int module_get_upon_open:1;

	/* bits */
	unsigned int idle_bias_on:1;
	unsigned int suspend_bias_off:1;
	unsigned int use_pmdown_time:1; /* care pmdown_time at stop */
	/*
	 * Indicates that the component does not care about the endianness of
	 * PCM audio data and the core will ensure that both LE and BE variants
	 * of each used format are present. Typically this is because the
	 * component sits behind a bus that abstracts away the endian of the
	 * original data, ie. one for which the transmission endian is defined
	 * (I2S/SLIMbus/SoundWire), or the concept of endian doesn't exist (PDM,
	 * analogue).
	 */
	unsigned int endianness:1;
	unsigned int non_legacy_dai_naming:1;

	/* this component uses topology and ignore machine driver FEs */
	const char *ignore_machine;
	const char *topology_name_prefix;
	int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
				  struct snd_pcm_hw_params *params);
	bool use_dai_pcm_id;	/* use DAI link PCM ID as PCM device number */
	int be_pcm_base;	/* base device ID for all BE PCMs */

#ifdef CONFIG_DEBUG_FS
	const char *debugfs_prefix;
#endif
;

        module_platform_driver函数的详解请参考《module_platform_driver源码分析
       

以上是关于Linux ALSA驱动之Platform源码分析(wm8350.c)的主要内容,如果未能解决你的问题,请参考以下文章

Linux ALSA驱动之Platform源码分析(wm8350.c)

Linux ALSA驱动之Platform源码分析(wm8350.c)

Linux ALSA驱动之Platform源码分析(wm8350.c)

Linux ALSA驱动之Platform源码分析(wm8350.c)

Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)

Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)