imx6q 音频芯片驱动开发--相关设备树以及代码中的对应设置
首先就不介绍asoc框架了,网上好的资料很多,推荐DroidPhone大神的,写的很全,知识面基本都介绍到了,不过由于写的比较早,没有使用设备树,最近也正好在调相关驱动,写点东西记录一下。https://blog.csdn.net/droidphone/category_1118446.htmlmpu:imx6qlinux-kernel:4.1.151、machine驱动使用的是设备树中的soun
首先就不介绍asoc框架了,网上好的资料很多,推荐DroidPhone大神的,写的很全,知识面基本都介绍到了,不过由于写的比较早,没有使用设备树,最近也正好在调相关驱动,写点东西记录一下。
https://blog.csdn.net/droidphone/category_1118446.html
源码我也上传了 有需要的可以下载
https://download.csdn.net/download/qq_17270067/13721398
mpu:imx6q
linux-kernel:4.1.15
1、machine驱动
使用的是设备树中的sound节点
sound {
compatible = "fsl,imx-audio-tas2505";
model = "tas2505-audio";
cpu-dai = <&ssi1>;
audio-codec = <&codec>;
audio-routing =
"Speaker Driver", "DAC Channel" ,
"Speaker", "Speaker Driver" ;
mux-int-port = <1>;
mux-ext-port = <3>;
};
cpu-dai:使用的platform节点
audio-codec:使用的codec节点
audio-routing:音频设备连接路径,每两个为一组,第一个为sink,第二个为source。
mux-int-port :使用了内部的哪个ssi。内部ssi接口ssi1,ssi2,ssi3, 分别对应 mux-int-port = <1>,mux-int-port = <2>,mux-int-port = <7>
mux-ext-port:使用了哪个外部AUD接口,有 AUD3,AUD4,AUD5,AUD6,分别对应mux-ext-port = <3>,mux-ext-port = <4>,mux-ext-port = <5>,mux-ext-port = <6>
在修改完了使用的AUDX接口之后,我们也需要将audmux的pinctrl修改成对应的接口
pinctrl_audmux: audmuxgrp {
fsl,pins = <
MX6QDL_PAD_CSI0_DAT4__AUD3_TXC 0x130b0
MX6QDL_PAD_CSI0_DAT5__AUD3_TXD 0x110b0
MX6QDL_PAD_CSI0_DAT6__AUD3_TXFS 0x130b0
MX6QDL_PAD_CSI0_DAT7__AUD3_RXD 0x130b0
>;
};
&audmux {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_audmux>;
status = "okay";
};
machine驱动文件中的probe函数如下:
static int imx_tas2505_probe ( struct platform_device* pdev )
{
struct device_node* cpu_np, *codec_np,*gpr_np;
struct device_node* np = pdev->dev.of_node;
struct platform_device* cpu_pdev;
struct imx_priv* priv = &card_priv;
struct i2c_client* codec_dev;
struct device_node* asrc_np;
struct imx_tas2505_data* data = NULL;
struct platform_device* asrc_pdev = NULL;
int int_port, ext_port;
int ret;
//读取设备树中的mux-int-port属性的值
ret = of_property_read_u32 ( np, "mux-int-port", &int_port );
if ( ret )
{
dev_err ( &pdev->dev, "mux-int-port missing or invalid\n" );
return ret;
}
//读取设备树中的mux-ext-port属性的值
ret = of_property_read_u32 ( np, "mux-ext-port", &ext_port );
if ( ret )
{
dev_err ( &pdev->dev, "mux-ext-port missing or invalid\n" );
return ret;
}
/*
* The port numbering in the hardware manual starts at 1, while
* the audmux API expects it starts at 0.
*/
int_port--;
ext_port--;
//根据codec和cpu的主从关系 配置ssi和aud的连接以及各条线的输入输出
//本质是设置寄存器的值 具体参照数据手册Chapter 15 Digital Audio Multiplexer (AUDMUX)
ret = imx_audmux_v2_configure_port ( ext_port,
IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL ( int_port ) |
IMX_AUDMUX_V2_PTCR_TCSEL ( int_port ) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
IMX_AUDMUX_V2_PDCR_RXDSEL ( int_port ) );
if ( ret )
{
dev_err ( &pdev->dev, "audmux internal port setup failed\n" );
return ret;
}
ret = imx_audmux_v2_configure_port ( int_port,
IMX_AUDMUX_V2_PTCR_SYN,
IMX_AUDMUX_V2_PDCR_RXDSEL ( ext_port ) );
if ( ret )
{
dev_err ( &pdev->dev, "audmux external port setup failed\n" );
return ret;
}
priv->pdev = pdev;
//获取platform和codec节点
cpu_np = of_parse_phandle ( pdev->dev.of_node, "cpu-dai", 0 );
codec_np = of_parse_phandle ( pdev->dev.of_node, "audio-codec", 0 );
if ( !cpu_np || !codec_np )
{
dev_err ( &pdev->dev, "phandle missing or invalid\n" );
ret = -EINVAL;
goto fail;
}
cpu_pdev = of_find_device_by_node ( cpu_np );
if ( !cpu_pdev )
{
dev_err ( &pdev->dev, "failed to find SSI platform device\n" );
ret = -EINVAL;
goto fail;
}
codec_dev = of_find_i2c_device_by_node ( codec_np );
if ( !codec_dev || !codec_dev->dev.driver )
{
dev_err ( &pdev->dev, "failed to find codec platform device\n" );
ret = -EINVAL;
goto fail;
}
data = devm_kzalloc ( &pdev->dev, sizeof ( *data ), GFP_KERNEL );
if ( !data )
{
ret = -ENOMEM;
goto fail;
}
//读取设备树codec-master 是否存在
if ( of_property_read_bool ( pdev->dev.of_node,"codec-master" ) )
{
data->is_codec_master = true;
}
//获取code时钟
data->codec_clk = devm_clk_get ( &codec_dev->dev, "mclk" );
if ( IS_ERR ( data->codec_clk ) )
{
ret = PTR_ERR ( data->codec_clk );
dev_err ( &pdev->dev, "failed to get codec clk: %d\n", ret );
goto fail;
}
//使能时钟
data->clk_frequency = clk_get_rate ( data->codec_clk );
ret = clk_prepare_enable ( data->codec_clk );
if ( ret )
{
dev_err ( &codec_dev->dev, "failed to enable codec clk: %d\n", ret );
goto fail;
}
data->dai.name = "HiFi";
data->dai.stream_name = "HiFi";
data->dai.codec_dai_name = "tas2505-hifi"; //此处名字需与codec中snd_soc_dai_driver结构中的name相同
data->dai.cpu_dai_name = dev_name ( &cpu_pdev->dev ); //得到设备的名字 该名字是在设备树转化成platform device被赋予
data->dai.codec_of_node = codec_np;
data->dai.platform_of_node = cpu_np;
printk ( "cpu_dai_name = %s\n",data->dai.cpu_dai_name );
data->dai.init = &imx_tas2505_dai_init; //
data->dai.ops = &imx_hifi_ops;
//设置与codec的通信格式为I2S cpu为主 codec为从
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
data->card.dev = &pdev->dev;
ret = snd_soc_of_parse_card_name ( &data->card, "model" );
if ( ret )
{
goto fail;
}
//找到设备树名字为audio-routing的项 并取出sink和source
ret = snd_soc_of_parse_audio_routing ( &data->card, "audio-routing" );
if ( ret )
{
goto fail;
}
data->card.num_links = 1;
data->card.owner = THIS_MODULE;
data->card.dai_link = &data->dai;
data->card.dapm_widgets = imx_tas2505_dapm_widgets; //注册不由codec寄存器控制的外部器件
data->card.num_dapm_widgets = ARRAY_SIZE ( imx_tas2505_dapm_widgets );
//设置私有数据
platform_set_drvdata ( pdev, &data->card );
snd_soc_card_set_drvdata ( &data->card, data );
//注册声卡
ret = devm_snd_soc_register_card ( &pdev->dev, &data->card );
if ( ret )
{
dev_err ( &pdev->dev, "snd_soc_register_card failed (%d)\n", ret );
goto fail;
}
of_node_put ( cpu_np );
of_node_put ( codec_np );
return 0;
fail:
if ( data && !IS_ERR ( data->codec_clk ) )
{
clk_put ( data->codec_clk );
}
if ( cpu_np )
{
of_node_put ( cpu_np );
}
if ( codec_np )
{
of_node_put ( codec_np );
}
return ret;
}
2、codec驱动
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6QDL_PAD_KEY_COL3__I2C2_SCL 0x4001b8b1
MX6QDL_PAD_KEY_ROW3__I2C2_SDA 0x4001b8b1
MX6QDL_PAD_NANDF_D2__GPIO2_IO02 0x80000000 //reset pin
>;
};
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog>;
......
pinctrl_hog: hoggrp {
fsl,pins = <
......
/*CCM_CLKO1 为codec的时钟提供脚*/
MX6QDL_PAD_GPIO_0__CCM_CLKO1 0x130b0
......
>;
};
......
};
&i2c2 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
codec: tas2505@18{
compatible = "ti,tas2505";
reg = <0x18>;
clocks = <&clks IMX6QDL_CLK_CKO>;
reset-gpio = <&gpio2 2 1>;
clock-names = "mclk";
};
};
reg:i2c设备地址,注意设备树中需要的是高7位地址。
clocks:时钟源,IMX6QDL_CLK_CKO定义的时钟源是MCLK,默认频率为24MHz。此时钟实际上也是可以修改的,参照How to change audio clock from 24M to 24.576M。
IMX6QDL_CLK_CKO 定义在include/dt-bindings/clock/imx6qdl-clock.h
设备树中也需要定义codec的时钟提供脚。
reset-gpio:设备的复位引脚
codec驱动中的probe函数如下:
static int tas2505_i2c_probe ( struct i2c_client* client,
const struct i2c_device_id* id )
{
struct device* dev = &client->dev;
struct device_node* np = dev->of_node;
struct tas2505_data* tas2505;
int ret;
tas2505 = devm_kzalloc ( &client->dev, sizeof ( *tas2505 ), GFP_KERNEL );
if ( tas2505 == NULL )
{
return -ENOMEM;
}
//获取设备树的中reset-gpio脚
tas2505->reset_gpio = of_get_named_gpio ( np, "reset-gpio", 0 );
if ( gpio_is_valid ( tas2505->reset_gpio ) )
{
//设置成输出高
ret = devm_gpio_request_one ( dev, tas2505->reset_gpio,
GPIOF_OUT_INIT_HIGH,
"reset" );
if ( ret )
{
dev_err ( dev, "unable to get reset pin \n" );
return ret;
}
}
tas2505->regmap = devm_regmap_init_i2c ( client, &tas2505_regmap_config ); //注册i2c regmap
if ( IS_ERR ( tas2505->regmap ) )
{
ret = PTR_ERR ( tas2505->regmap );
dev_err ( dev, "Failed to allocate register map: %d\n",
ret );
return ret;
}
tas2505->dev = dev;
//设置私有数据 sound核心层需要读取regmap 之后可以使用snd_soc_write等相关函数直接操作寄存器
dev_set_drvdata ( tas2505->dev, tas2505 );
//注册codec
ret = snd_soc_register_codec ( dev,
&soc_codec_dev_tas2505,
tas2505_dai, ARRAY_SIZE ( tas2505_dai ) );
if ( ret < 0 )
{
dev_err ( dev, "Failed to register codec: %d\n", ret );
}
return ret;
}
platform驱动基本不需要更改,我也没细看,所以就不分析了。
更多推荐
所有评论(0)