Linux ALSA驱动之五:Linux ALSA驱动之Platform源码分析(基于Linux 5.18)

Posted

tags:

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

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 DAIss 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;
;

2.4、snd_soc_dai_ops结构体

置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为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.
*/

/* Dont 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 prob

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

Linux audio驱动模型

Linux ALSA声卡驱动之一:ALSA架构简介

arm linux利用alsa驱动并使用usb音频设备

Linux ALSA源码分析(基于Linux 5.18)

Linux ALSA源码分析(基于Linux 5.18)

Linux ALSA源码分析(基于Linux 5.18)