IMX6Q下tlv320aic3x音频驱动移植

原理图:

tlv320aic3x音频芯片

功率放大器

移植分为三步:

1.codec 驱动 sound/soc/codecs/tlv320aic3x.c
2.平台驱动 sound/soc/imx/imx-tlv320aic3x.c
3.添加板文件 

arch/arm/mach-mx6/board-mx6q_sabresd.c   和  arch/arm/mach-mx6/board-mx6q_sabresd.c


一、codec驱动是linux内核自带的,在sound/soc/codecs/下面有不同的音频芯片,根据开发的音频芯片选择或者修改。

在tlv320aic3x.c文件中有几个重要的结构体需要关注:

static struct snd_soc_dai_ops aic3x_dai_ops = {
    .hw_params    = aic3x_hw_params,  //硬件参数设置
    .digital_mute    = aic3x_mute,   //静音设置
    .set_sysclk    = aic3x_set_dai_sysclk,  //时钟设置
    .set_fmt    = aic3x_set_dai_fmt,  //格式设置
};

static struct snd_soc_dai_driver aic3x_dai = {
	.name = "tlv320aic3x-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = AIC3X_RATES,
		.formats = AIC3X_FORMATS,},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = AIC3X_RATES,
		.formats = AIC3X_FORMATS,},
	.ops = &aic3x_dai_ops,
	.symmetric_rates = 1,
};
需要注意的是这个.name,需要和sound/soc/imx/imx-tlv320aic3x.c中的  .codec_dai_name相同。

static struct snd_soc_dai_link imx_tlv320aic3x_dai[] = {
	{
		.name = "TLV320AIC3X",
		.stream_name = "AIC3X",
		.codec_dai_name = "tlv320aic3x-hifi",
		.codec_name = "tlv320aic3x-codec.0-0018",		//I2C-0
		.cpu_dai_name = "imx-ssi.1",			
		.platform_name = "imx-pcm-audio.1",
		
		.init =imx_3stack_tlv320aic3x_init,
		.ops = &imx_tlv320aic3x_hifi_ops,
	},
};
在sound/soc/codecs/tlv320aic3x.c文件中,我们需要关注 .name,这个名字要和sound/soc/imx/imx-tlv320aic3x.c的 .codec_name关联,这样才会调用aic_i2c_probe探测函数。如果名字不同的话会发现编译好的内核找不到声卡。
/* machine i2c codec control layer */
static struct i2c_driver aic3x_i2c_driver = {
	.driver = {
		.name = "tlv320aic3x-codec",
		.owner = THIS_MODULE,
	},
	.probe	= aic3x_i2c_probe,
	.remove = aic3x_i2c_remove,
	.id_table = aic3x_i2c_id,
};

static struct snd_soc_dai_link imx_tlv320aic3x_dai[] = {
    {
        .name = "TLV320AIC3X",
        .stream_name = "AIC3X",
        .codec_dai_name = "tlv320aic3x-hifi",
        .codec_name = "tlv320aic3x-codec.0-0018",        //I2C-0
        .cpu_dai_name = "imx-ssi.1",            
        .platform_name = "imx-pcm-audio.1",
        
        .init =imx_3stack_tlv320aic3x_init,
        .ops = &imx_tlv320aic3x_hifi_ops,
    },
};
在static struct snd_soc_dai_link imx_tlv320aic3x_dai[]中,0-0018的含义是这个芯片是挂载在i2c0上,设备号为0x18,根据芯片的参数进行修改。其他地方基本不用改动。


因为是平台设备驱动,所以就要将平台驱动和平台设备进行关联。

平台驱动在sound/soc/imx/imx-tlv320aic3x.c文件中:

static struct platform_driver imx_tlv320aic3x_audio_driver = {
	.probe = imx_tlv320aic3x_probe,
	.remove = imx_tlv320aic3x_remove,
	.driver = {
		   .name = "imx-tlv320",
	},
};
与之相关联的设备在板文件arch/arm/mach-mx6/board-mx6q_sabresd.c中:

static struct platform_device mx6_audio_tlv320_device = {
        .name = "imx-tlv320",
};
因为是通过名字进行匹配,所以二者名字一定要相同。
在板文件中,要进行i2c0的注册所和用到的引脚的配置。

 在arch/arm/mach-mx6/board-mx6q_sabresd.c中:

static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {
    {
		I2C_BOARD_INFO("ds1338", 0x68),
	},
	{
		I2C_BOARD_INFO("ov5640_mipi", 0x3c),
		.platform_data = (void *)&mipi_csi2_data,
	},
	{
		I2C_BOARD_INFO("tlv320aic3x", 0x18),
	},
};
将音频设备挂在i2c0上,tlv320aic3x是设备名,0x18是从设备号,可以参考数据手册查找。挂在好进行注册:

imx6q_add_imx_i2c(0, &mx6q_qy_imx6s_i2c_data);
i2c_register_board_info(0, mxc_i2c0_board_info,
            ARRAY_SIZE(mxc_i2c0_board_info));
在arch/arm/mach-mx6/board-mx6q_sabresd.h文件中配置引脚:
static iomux_v3_cfg_t mx6q_qy_imx6s_i2c_pads[] =
{
    MX6Q_PAD_CSI0_DAT8__I2C1_SDA,//i2c
    MX6Q_PAD_CSI0_DAT9__I2C1_SCL,,
};

static iomux_v3_cfg_t mx6q_qy_imx6s_audio_pads[] = {
	/*MX6Q_PAD_CSI0_DAT4__AUDMUX_AUD3_TXC,
	MX6Q_PAD_CSI0_DAT5__AUDMUX_AUD3_TXD,
	MX6Q_PAD_CSI0_DAT6__AUDMUX_AUD3_TXFS,
	MX6Q_PAD_CSI0_DAT7__AUDMUX_AUD3_RXD,*/

	MX6Q_PAD_GPIO_0__CCM_CLKO,   //CLK ouput
	MX6Q_PAD_SD2_CMD__GPIO_1_11, //AUDIO SD
	MX6Q_PAD_SD2_CLK__GPIO_1_10, //AUDIO REST
	
	MX6Q_PAD_SD2_DAT0__AUDMUX_AUD4_RXD,
	MX6Q_PAD_SD2_DAT1__AUDMUX_AUD4_TXFS,
	MX6Q_PAD_SD2_DAT2__AUDMUX_AUD4_TXD,
	MX6Q_PAD_SD2_DAT3__AUDMUX_AUD4_TXC,
};
因为我的开发板是外接喇叭,所以配置了功放:MX6Q_PAD_SD2_CMD__GPIO_1_11, //AUDIO SD

在arch/arm/mach-mx6/board-mx6q_sabresd.c写功能函数:

1.定义功放和复位

//audio 
#define AUDIO_REST		IMX_GPIO_NR(1 , 10)
#define AUDIO_SD		IMX_GPIO_NR(1 , 11)

static void tlv320_gpio_set(void)
{
    static void __iomem *audio_gpio_base = IO_ADDRESS(0x020e0740);//配置引脚寄存器第13位为 0=keeper
    writel(0x90B0,audio_gpio_base);

    gpio_request(AUDIO_SD, "audio_sd");  
    gpio_direction_output(AUDIO_SD, 0); //低电平有效 
    msleep(1);  
    gpio_set_value(AUDIO_SD, 0);
    
    gpio_request(AUDIO_REST, "audio_rest");  
    gpio_direction_output(AUDIO_REST, 1);  
    msleep(1);  
    gpio_set_value(AUDIO_REST, 1);
}
在函数static int mxc_tlv320_init(void){}中调用:

static int mxc_tlv320_init(void)
{
		//struct clk *clko;
		//struct clk *new_parent;
		int rate;

        clko = clk_get(NULL, "clko_clk");
        if (IS_ERR(clko)) {
                pr_err("can't get CLKO clock.\n");
                return PTR_ERR(clko);
        }
		
        /* both audio codec and comera use CLKO clk*/
        rate = clk_round_rate(clko, 24000000);
        clk_set_rate(clko, rate);
        tlv320_data.sysclk = rate;
		clk_enable(clko);
		
		tlv320_gpio_set();
        return 0;
}

下面放上板文件和imx-tlv320aic3x.c的代码片段

arch/arm/mach-mx6/board-mx6q_sabresd.c:

static struct regulator_consumer_supply qy_imx6s_vmmc_consumers[] = {
	REGULATOR_SUPPLY("vmmc", "sdhci-esdhc-imx.1"),
	REGULATOR_SUPPLY("vmmc", "sdhci-esdhc-imx.2"),
	REGULATOR_SUPPLY("vmmc", "sdhci-esdhc-imx.3"),
	REGULATOR_SUPPLY("vcc", "spi4.1"),
	REGULATOR_SUPPLY("IOVDD", "0-0018"),
	REGULATOR_SUPPLY("AVDD", "0-0018"),
	REGULATOR_SUPPLY("DRVDD", "0-0018"),
};

static struct regulator_init_data qy_imx6s_vmmc_init = {
	.num_consumer_supplies = ARRAY_SIZE(qy_imx6s_vmmc_consumers),
	.consumer_supplies = qy_imx6s_vmmc_consumers,
};

static struct fixed_voltage_config qy_imx6s_vmmc_reg_config = {
	.supply_name		= "vmmc",
	.microvolts		= 3300000,
	.gpio			= -1,
	.init_data		= &qy_imx6s_vmmc_init,
};

static struct platform_device qy_imx6s_vmmc_reg_devices = {
	.name	= "reg-fixed-voltage",
	.id	= 3,
	.dev	= {
		.platform_data = &qy_imx6s_vmmc_reg_config,
	},
};

static struct platform_device mx6_audio_tlv320_device = {
        .name = "imx-tlv320",
};

static int tlv320_clk_enable(int enable)
{
        if (enable)
                clk_enable(clko);
        else
                clk_disable(clko);

        return 0;
}
static struct mxc_audio_platform_data tlv320_data;

static void tlv320_gpio_set(void)
{
	static void __iomem *audio_gpio_base = IO_ADDRESS(0x020e0740);//13位  0=keeper
	writel(0x90B0,audio_gpio_base);

	gpio_request(AUDIO_SD, "audio_sd");  
	gpio_direction_output(AUDIO_SD, 0);  
	msleep(1);  
	gpio_set_value(AUDIO_SD, 0);
	
	gpio_request(AUDIO_REST, "audio_rest");  
	gpio_direction_output(AUDIO_REST, 1);  
	msleep(1);  
	gpio_set_value(AUDIO_REST, 1);
}
static int mxc_tlv320_init(void)
{
		//struct clk *clko;
		//struct clk *new_parent;
		int rate;

        clko = clk_get(NULL, "clko_clk");
        if (IS_ERR(clko)) {
                pr_err("can't get CLKO clock.\n");
                return PTR_ERR(clko);
        }
		
        /* both audio codec and comera use CLKO clk*/
        rate = clk_round_rate(clko, 24000000);
        clk_set_rate(clko, rate);
        tlv320_data.sysclk = rate;
		clk_enable(clko);
		
		tlv320_gpio_set();
        return 0;
}
static struct mxc_audio_platform_data tlv320_data = {
        .ssi_num = 1,
        .src_port = 2,
        .ext_port = 4,//control playing stop
        .init = mxc_tlv320_init,
        .clock_enable = tlv320_clk_enable,
};

static struct imx_ssi_platform_data mx6_ssi_pdata = {
        .flags = IMX_SSI_DMA | IMX_SSI_SYN,
};


static struct regulator_consumer_supply qy_imx6s_tlv320_consumers[] = {
	REGULATOR_SUPPLY("DVDD", "0-0018"),
};

static struct regulator_init_data qy_imx6s_tlv320_init = {
	.num_consumer_supplies = ARRAY_SIZE(qy_imx6s_tlv320_consumers),
	.consumer_supplies = qy_imx6s_tlv320_consumers,
};

static struct fixed_voltage_config qy_imx6s_tlv320_reg_config = {
	.supply_name	= "DVDD",
	.microvolts		= 1800000,
	.gpio			= -1,  //if changed no soundcard exist
	.init_data		= &qy_imx6s_tlv320_init,
};

static struct platform_device qy_imx6s_tlv320_reg_devices = {
	.name	= "reg-fixed-voltage",
	.id		= 4,
	.dev	= {
		.platform_data = &qy_imx6s_tlv320_reg_config,
	},
};

static int __init imx6q_init_audio(void)
{
	platform_device_register(&qy_imx6s_tlv320_reg_devices);
	mxc_register_device(&mx6_audio_tlv320_device,
                                    &tlv320_data);
    imx6q_add_imx_ssi(1, &mx6_ssi_pdata);
    mxc_tlv320_init();
	return 0;
}

static void __init mx6_qy_imx6s_board_init(void)
{
    ……………
    ……………
    imx6q_init_audio();
    ……………
    ……………
}
imx-tlv320aic3x.c

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/fsl_devices.h>
#include <linux/gpio.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/soc-dapm.h>
#include <asm/mach-types.h>
#include <mach/audmux.h>

#include "../codecs/tlv320aic23.h"
#include "imx-ssi.h"

#define CODEC_CLOCK 24000000

static int qiyang_tlv320_hw_params(struct snd_pcm_substream *substream,
			    struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int ret;

	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
				  SND_SOC_DAIFMT_NB_NF |
				  SND_SOC_DAIFMT_CBM_CFM);
	if (ret) {
		pr_err("%s: failed set cpu dai format\n", __func__);
		return ret;
	}

	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
				  SND_SOC_DAIFMT_NB_NF |
				  SND_SOC_DAIFMT_CBM_CFM);
	if (ret) {
		pr_err("%s: failed set codec dai format\n", __func__);
		return ret;
	}

	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
				     CODEC_CLOCK, SND_SOC_CLOCK_OUT);
	if (ret) {
		pr_err("%s: failed setting codec sysclk\n", __func__);
		return ret;
	}
	snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);

	ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
				SND_SOC_CLOCK_IN);
	if (ret) {
		pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
		return ret;
	}

	return 0;
}

static struct snd_soc_ops qiyang_tlv320_snd_ops = {
	.hw_params	= qiyang_tlv320_hw_params,
};

static struct snd_soc_dai_link qiyang_tlv320_dai = {
	.name		= "tlv320aic3x",
	.stream_name	= "TLV320AIC3X",
	.codec_dai_name	= "tlv320aic3x-hifi",
	.codec_name	= "tlv320aic3x-codec.0-0018",//i2c0
	.platform_name  = "imx-pcm-audio.1",
	.cpu_dai_name	= "imx-ssi.1",
	.ops		= &qiyang_tlv320_snd_ops,
};

static struct snd_soc_card qiyang_tlv320 = {
	.name		= "tlv320-audio",
	.dai_link	= &qiyang_tlv320_dai,
	.num_links	= 1,
};
static int imx_audmux_config(int slave, int master)
{
        unsigned int ptcr, pdcr;
        slave = slave - 1;
        master = master - 1;

        // SSI0 mastered by port 4 
        ptcr = MXC_AUDMUX_V2_PTCR_SYN |
                MXC_AUDMUX_V2_PTCR_TFSDIR |
                MXC_AUDMUX_V2_PTCR_TFSEL(master) |
                MXC_AUDMUX_V2_PTCR_TCLKDIR |
                MXC_AUDMUX_V2_PTCR_TCSEL(master);
        pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
        mxc_audmux_v2_configure_port(slave, ptcr, pdcr);

        ptcr = MXC_AUDMUX_V2_PTCR_SYN;
        pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
        mxc_audmux_v2_configure_port(master, ptcr, pdcr);

        return 0;
}
static int __devinit imx_tlv320_probe(struct platform_device *pdev)
{
	//printk("*****************888imx_tlv320_probe\n");
        struct mxc_audio_platform_data *plat = pdev->dev.platform_data;

        int ret = 0;

        imx_audmux_config(plat->src_port, plat->ext_port);

        ret = -EINVAL;
        if (plat->init && plat->init())
                return ret;
        return 0;
}
static int imx_tlv320_remove(struct platform_device *pdev)
{
        struct mxc_audio_platform_data *plat = pdev->dev.platform_data;

        if (plat->finit)
                plat->finit();

        return 0;
}

static struct platform_driver imx_tlv320_audio_driver = {
        .probe = imx_tlv320_probe,
        .remove = imx_tlv320_remove,
        .driver = {
                   .name = "imx-tlv320",
                   },
};

static struct platform_device *qiyang_tlv320_snd_device;

static int __init qiyang_tlv320_init(void)
{
	int ret;
	//printk("********************************************************11111!%d\n",machine_arch_type);
	ret = platform_driver_register(&imx_tlv320_audio_driver);
        if (ret)
                return -ENOMEM;
	qiyang_tlv320_snd_device = platform_device_alloc("soc-audio", 6);
	if (!qiyang_tlv320_snd_device)
		return -ENOMEM;
	
	platform_set_drvdata(qiyang_tlv320_snd_device, &qiyang_tlv320);
	ret = platform_device_add(qiyang_tlv320_snd_device);
	
	
	
	if (ret) {
		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
		platform_device_put(qiyang_tlv320_snd_device);
	}
	//printk("********************************************************2222222!\n");
	return ret;
}

static void __exit qiyang_tlv320_exit(void)
{
	platform_driver_unregister(&imx_tlv320_audio_driver);
	platform_device_unregister(qiyang_tlv320_snd_device);
}

module_init(qiyang_tlv320_init);
module_exit(qiyang_tlv320_exit);


MODULE_AUTHOR("allen young <yangcenhao@gmail.com>");
MODULE_DESCRIPTION("FUCK ALSA SoC driver");
ODULE_LICENSE("GPL");



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值