您的位置:首页 > 大数据 > 人工智能

IMX6Q下tlv320aic3x音频驱动移植

2017-07-25 11:21 507 查看
原理图:



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_
4000
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");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  imx6 cortexA linux 内核 IIC