WM8960官网代码解析


一、设备树

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_kmallockmalloc的区别:

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值