一、设备树
1、sound自定义设备节点
sound {
compatible = "fsl,imx6ul-evk-wm8960",
"fsl,imx-audio-wm8960";
model = "wm8960-audio";
cpu-dai = <&sai2>;
audio-codec = <&codec>;
asrc-controller = <&asrc>;
codec-master;
gpr = <&gpr 4 0x100000 0x100000>;
/*
* hp-det = <hp-det-pin hp-det-polarity>;
* hp-det-pin: JD1 JD2 or JD3
* hp-det-polarity = 0: hp detect high for headphone
* hp-det-polarity = 1: hp detect high for speaker
*/
hp-det = <3 0>;
hp-det-gpios = <&gpio5 4 0>;
mic-det-gpios = <&gpio5 4 0>;
audio-routing =
"Headphone Jack", "HP_L",
"Headphone Jack", "HP_R",
"Ext Spk", "SPK_LP",
"Ext Spk", "SPK_LN",
"Ext Spk", "SPK_RP",
"Ext Spk", "SPK_RN",
"LINPUT1", "Mic Jack",
"LINPUT3", "Mic Jack",
"RINPUT1", "Main MIC",
"RINPUT2", "Main MIC",
"Mic Jack", "MICB",
"Main MIC", "MICB",
"CPU-Playback", "ASRC-Playback",
"Playback", "CPU-Playback",
"ASRC-Capture", "CPU-Capture",
"CPU-Capture", "Capture";
};
2、I2C节点信息补充
&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6UL_CLK_SAI2>;
clock-names = "mclk";
wlf,shared-lrclk;
};
};
3、SAI节点补充
&sai2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai2
&pinctrl_sai2_hp_det_b>;
assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
<&clks IMX6UL_CLK_SAI2>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <0>, <12288000>;
status = "okay";
};
4、pinctrl引脚复用
pinctrl_sai2: sai2grp
{
fsl,pins = <
MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 0x17088
MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC 0x17088
MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA 0x11088
MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA 0x11088
MX6UL_PAD_JTAG_TMS__SAI2_MCLK 0x17088
>;
};
pinctrl_sai2_hp_det_b: sai2_hp_det_grp
{
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04 0x17059
>;
};
二、驱动代码
module
module_i2c_driver(wm8960_i2c_driver)
作用:简化了框架并且同时注册了I2C设备
/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
MODULE_DEVICE_TABLE
作用:声明变量
#ifdef MODULE
/* Creates an alias so file2alias.c can find device table. */
#define MODULE_DEVICE_TABLE(type, name) \
extern const typeof(name) __mod_##type##__##name##_device_table \
__attribute__ ((unused, alias(__stringify(name))))
#else /* !MODULE */
#define MODULE_DEVICE_TABLE(type, name)
#endif
devm
devm_kzalloc
调用的是devm_kmalloc,只是会申请的同时把内存清零
static inline void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)
{
return devm_kmalloc(dev, size, gfp | __GFP_ZERO);
}
devm_kmalloc
申请的内存跟设备进行绑定
当设备跟驱动分离时,跟设备绑定的内存会被自动释放,不需要我们手动释放
platform_driver_unregister执行的时候会释放这部分资源
/**
* devm_kmalloc - Resource-managed kmalloc
* @dev: Device to allocate memory for
* @size: Allocation size
* @gfp: Allocation gfp flags
*
* Managed kmalloc. Memory allocated with this function is
* automatically freed on driver detach. Like all other devres
* resources, guaranteed alignment is unsigned long long.
*
* RETURNS:
* Pointer to allocated memory on success, NULL on failure.
*/
void * devm_kmalloc(struct device *dev, size_t size, gfp_t gfp)
{
struct devres *dr;
/* use raw alloc_dr for kmalloc caller tracing */
dr = alloc_dr(devm_kmalloc_release, size, gfp);
if (unlikely(!dr))
return NULL;
/*
* This is named devm_kzalloc_release for historical reasons
* The initial implementation did not support kmalloc, only kzalloc
*/
set_node_dbginfo(&dr->node, "devm_kzalloc_release", size);
devres_add(dev, dr->data);
return dr->data;
}
/*
设备树节点(platform_device)
*/
sound {
compatible = "fsl,imx6ul-evk-wm8960",
"fsl,imx-audio-wm8960";
model = "wm8960-audio";
cpu-dai = <&sai2>;
audio-codec = <&codec>;
asrc-controller = <&asrc>;
codec-master;
gpr = <&gpr 4 0x100000 0x100000>;
/*
* hp-det = <hp-det-pin hp-det-polarity>;
* hp-det-pin: JD1 JD2 or JD3
* hp-det-polarity = 0: hp detect high for headphone
* hp-det-polarity = 1: hp detect high for speaker
*/
hp-det = <3 0>;
hp-det-gpios = <&gpio5 4 0>;
mic-det-gpios = <&gpio5 4 0>;
audio-routing =
"Headphone Jack", "HP_L",
"Headphone Jack", "HP_R",
"Ext Spk", "SPK_LP",
"Ext Spk", "SPK_LN",
"Ext Spk", "SPK_RP",
"Ext Spk", "SPK_RN",
"LINPUT1", "Mic Jack",
"LINPUT3", "Mic Jack",
"RINPUT1", "Main MIC",
"RINPUT2", "Main MIC",
"Mic Jack", "MICB",
"Main MIC", "MICB",
"CPU-Playback", "ASRC-Playback",
"Playback", "CPU-Playback",
"ASRC-Capture", "CPU-Capture",
"CPU-Capture", "Capture";
};
-----------------------------------------------------------------------------------------------------------
/*
platform_driver驱动代码
*/
static int imx_wm8960_probe(struct platform_device *pdev) <-传过来的参数类型是struct platform_device
{
.......
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
.......
}
static struct platform_driver imx_wm8960_driver = {
.driver = {
.name = "imx-wm8960",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
devm_kmalloc
和kmalloc
的区别:
devm_kmalloc
在platform_driver_unregister执行的时候自动释放内存资源
kmalloc
要手动释放内存资源
of
of_parse_phandle
获取设备节点属性指向的设备结点指针
获取np设备节点里面的phandle_name属性的值
static inline struct device_node *of_parse_phandle(const struct device_node *np,
const char *phandle_name,
int index)
of_find_device_by_node
/**
* of_find_device_by_node - Find the platform_device associated with a node
* @np: Pointer to device tree node
*
* Returns platform_device pointer, or NULL if not found
*/
struct platform_device *of_find_device_by_node(struct device_node *np)
of_property_read_bool
在设备节点中搜索属性,属性存在则返回true,否则返回false
of_node_put
减少节点引用次数
of_parse_phandle_with_fixed_args
获取设备节点对应属性值
cell_count属性有几个参数值
属性值里面有多个引用的时候,使用index确定获取相应的引用节点值
out_args,存储属性值
out_args[cell_count]
out_args = np->list_name[index]
/**
* of_parse_phandle_with_fixed_args() - Find a node pointed by phandle in a list
* @np: pointer to a device tree node containing a list
* @list_name: property name that contains a list
* @cell_count: number of argument cells following the phandle
* @index: index of a phandle to parse out
* @out_args: optional pointer to output arguments structure (will be filled)
*
* This function is useful to parse lists of phandles and their arguments.
* Returns 0 on success and fills out_args, on error returns appropriate
* errno value.
*
* Caller is responsible to call of_node_put() on the returned out_args->np
* pointer.
*
* Example:
*
* phandle1: node1 {
* }
*
* phandle2: node2 {
* }
*
* node3 {
* list = <&phandle1 0 2 &phandle2 2 3>;
* }
*
* To get a device_node of the `node2' node you may call this:
* of_parse_phandle_with_fixed_args(node3, "list", 2, 1, &args);
* */
int of_parse_phandle_with_fixed_args(const struct device_node *np,
const char *list_name, int cell_count,
int index, struct of_phandle_args *out_args)
of_property_read_bool
设备节点找属性,有就true,没就false
/*
Search for a property in a device node.
Returns true if the property exist false otherwise.
*/
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname)
of_parse_phandle
返回与phandle属性对应的节点的指针
设备树:
parent_node {
phandle_property = <&child_node>;
// 其他属性...
};
child_node {
// 子节点的属性...
};
函数:
struct device_node *node;
struct device_node *child_node;
// 假设parent_node是包含phandle属性的节点
node = of_find_node_by_name(NULL, "parent_node");
child_node = of_parse_phandle(node, "phandle_property", 0);
of_parse_phandle函数将返回与phandle属性对应的节点的指针,也就是"child_node"节点的指针。
这样,我们就可以通过child_node指针来访问和操作"child_node"节点的属性和信息。
regmap
syscon_node_to_regmap
通过np这个设备节点填写的寄存器地址来映射过去,然后使用regmap_update_bits就可以直接修改对应寄存器的值
struct regmap *syscon_node_to_regmap(struct device_node *np)
regmap_update_bits
map->reg &= ~mask
map->reg |= val & mask
先清除mask(掩码)对应位,再根据val值置位
/**
* regmap_update_bits: Perform a read/modify/write cycle on the register map
*
* @map: Register map to update
* @reg: Register to update
* @mask: Bitmask to change
* @val: New value for bitmask
*
* Returns zero for success, a negative number on error.
*/
int regmap_update_bits(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val)
{
int ret;
map->lock(map->lock_arg);
ret = _regmap_update_bits(map, reg, mask, val, NULL);
map->unlock(map->lock_arg);
return ret;
}
EXPORT_SYMBOL_GPL(regmap_update_bits);
static int _regmap_update_bits(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val,
bool *change)
{
int ret;
unsigned int tmp, orig;
ret = _regmap_read(map, reg, &orig);
if (ret != 0)
return ret;
tmp = orig & ~mask;
tmp |= val & mask;
if (tmp != orig) {
ret = _regmap_write(map, reg, tmp);
if (change)
*change = true;
} else {
if (change)
*change = false;
}
return ret;
}
clk
devm_clk_get
查找并获取时钟产生器的托管引用
返回一个struct clk,对应于时钟产生器,或者包含errno的有效IS_ERR()条件。
该实现使用@dev和@id来确定时钟消费者,从而确定时钟生产者。
(id可能是相同的字符串,但clk_get可能会根据dev返回不同的时钟生产者。)
devm_clk_get不应该在中断上下文中被调用
例如devm_clk_get(sai2设备指针,mclk0);得到的是clks IMX6ul_CLK_DUMMY
struct clk *devm_clk_get(struct device *dev, const char *id);
clk_get_rate
获取时钟源当前的时钟速率,单位为Hz
unsigned long clk_get_rate(struct clk *clk);
snd
struct snd_soc_ops
snd_soc_ops 结构体是 ALSA SoC 驱动中用于表示音频设备操作的结构体。
它包含了音频设备操作的函数指针,用于定义音频设备的行为。常用的函数指针和其作用如下:
- startup: 初始化音频设备并启动音频流。
- shutdown: 关闭音频设备并停止音频流。
- hw_params: 设置音频设备的硬件参数,例如采样率、通道数等。
- hw_free: 释放音频设备的硬件资源。
- digital_mute: 数字静音控制,用于控制数字音频信号的静音状态。
- trigger: 触发音频设备的操作,例如启动、停止、暂停等。
/* SoC audio ops */
struct snd_soc_ops
{
int (*startup)(struct snd_pcm_substream *);
void (*shutdown)(struct snd_pcm_substream *);
int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
int (*hw_free)(struct snd_pcm_substream *);
int (*prepare)(struct snd_pcm_substream *);
int (*trigger)(struct snd_pcm_substream *, int);
};
snd_soc_register_codec
/**
* snd_soc_register_codec - Register a codec with the ASoC core
*
* @codec: codec to register
*/
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
snd_soc_dai_set_fmt
/**
* snd_soc_dai_set_fmt - configure DAI hardware audio format.
* @dai: DAI
* @fmt: SND_SOC_DAIFMT_ format value.
*
* Configures the DAI hardware format and clocking.
*/
int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
snd_soc_dai_set_tdm_slot
/**
* snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation
* @dai: The DAI to configure
* @tx_mask: bitmask representing active TX slots.
* @rx_mask: bitmask representing active RX slots.
* @slots: Number of slots in use.
* @slot_width: Width in bits for each slot.
*
* This function configures the specified DAI for TDM operation. @slot contains
* the total number of slots of the TDM stream and @slot_with the width of each
* slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the
* active slots of the TDM stream for the specified DAI, i.e. which slots the
* DAI should write to or read from. If a bit is set the corresponding slot is
* active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to
* the first slot, bit 1 to the second slot and so on. The first active slot
* maps to the first channel of the DAI, the second active slot to the second
* channel and so on.
*
* TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask,
* @rx_mask and @slot_width will be ignored.
*
* Returns 0 on success, a negative error code otherwise.
*/
int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
snd_soc_dai_set_sysclk
/**
* snd_soc_dai_set_sysclk - configure DAI system or master clock.
* @dai: DAI
* @clk_id: DAI specific clock ID
* @freq: new clock frequency in Hz
* @dir: new clock direction - input/output.
*
* Configures the DAI master (MCLK) or system (SYSCLK) clocking.
*/
int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,unsigned int freq, int dir)
snd_soc_dai_set_pll
/**
* snd_soc_dai_set_pll - configure DAI PLL.
* @dai: DAI
* @pll_id: DAI specific PLL ID
* @source: DAI specific source for the PLL
* @freq_in: PLL input clock frequency in Hz
* @freq_out: requested PLL output clock frequency in Hz
*
* Configures and enables PLL to generate output clock based on input clock.
*/
int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out)
snd_soc_of_parse_card_name
通过card->dev->of_node来反向获取设备节点,然后在该设备节点中获取属性propname的字符串到card->name中储存
/* Retrieve a card's name from device tree */
int snd_soc_of_parse_card_name(struct snd_soc_card *card,const char *propname)
{
struct device_node *np;
int ret;
np = card->dev->of_node;
ret = of_property_read_string_index(np, propname, 0, &card->name);
}
snd_soc_of_parse_audio_routing
解析获取propname的值到card中
int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,const char *propname)
{
struct device_node *np = card->dev->of_node;
int num_routes,i, ret;
struct snd_soc_dapm_route *routes;
num_routes = of_property_count_strings(np, propname);
routes = devm_kzalloc(card->dev, num_routes * sizeof(*routes),GFP_KERNEL);
for (i = 0; i < num_routes; i++)
{
ret = of_property_read_string_index(np, propname,2 * i, &routes[i].sink);
ret = of_property_read_string_index(np, propname,
(2 * i) + 1, &routes[i].source);
}
card->num_of_dapm_routes = num_routes;
card->of_dapm_routes = routes;
}
三、函数逻辑解析
probe
1、获取设备树sound节点属性值 -> 设备节点(sai2 、codec)的指针
(cpu-dai -> &sai2 、 audio-codec -> &codec)
of_parse_phandle
2、根据设备节点(&sai2) 获取平台设备指针和I2C连接设备结构体指针
of_find_device_by_node、of_find_i2c_device_by_node
3、申请 WM8960声音信息自定义结构体 内存
devm_kzalloc
4、查看 设备节点是否设置了该属性
of_property_read_bool
5、获取时钟
devm_clk_get
6、解析属性的值到变量上
of_parse_phandle_with_fixed_args
7、通过节点映射到寄存器、根据寄存器偏移值更新相应位
syscon_node_to_regmap、regmap_update_bits
8、读取u32类型属性数组
of_property_read_u32_array
9、解析属性的设备节点指针(asrc-controller -> &asrc)
10、获取属性的值到变量中去
snd_soc_of_parse_card_name、snd_soc_of_parse_audio_routing
11、设置私有数据
platform_set_drvdata、snd_soc_card_set_drvdata
12、注册声卡
devm_snd_soc_register_card
hw_params
应用层配置参数的时候会回调这个函数
1、设置DAI(数字音频接口 digital audio interface)接口格式
snd_soc_dai_set_fmt
/*
* DAI hardware audio formats.
*
* Describes the physical PCM data formating and clocking. Add new formats
* to the end.
*/
接口支持的时序
DSP模式其实是PCM模式
#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */
#define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */
#define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */
#define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */
#define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */
#define SND_SOC_DAIFMT_AC97 6 /* AC97 */
#define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */
/*
* DAI hardware signal inversions.
*
* Specifies whether the DAI can also support inverted clocks for the specified
* format.
*/
sync和bclk极性配置
#define SND_SOC_DAIFMT_NB_NF (0 << 8) /* normal bit clock + frame */
#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */
#define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */
#define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */
/*
* DAI hardware clock masters.
*
* This is wrt the codec, the inverse is true for the interface
* i.e. if the codec is clk and FRM master then the interface is
* clk and frame slave.
*/
SND_SOC_DAIFMT_CBM_CFM是slave模式
SND_SOC_DAIFMT_CBS_CFS是master模式
#define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */
#define SND_SOC_DAIFMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */
2、master or slave
四、概念
gpr
通用控制寄存器
asrc
Asynchronous Sample Rate Converter(异步采样率转换器)
采样率转换,例如48K音频可以用asrc转换成44.1K