首先就不介绍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驱动基本不需要更改,我也没细看,所以就不分析了。

Logo

更多推荐