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源码分析》