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

IMX6ul下tlv320aic3x音频驱动调试

2017-10-11 16:29 381 查看
原理图:



tlv320aic3x音频芯片



功率放大器

移植分为三步:

1.codec 驱动 sound/soc/codecs/tlv320aic3x.c

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

a
4000
rch/arm/mach-mx6/board-mx6q_sabresd.c 和 arch/arm/mach-mx6/board-mx6q_sabresd.c

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

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

[cpp] view
plain copy

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相同。

[cpp] view
plain copy

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探测函数。如果名字不同的话会发现编译好的内核找不到声卡。

[cpp] view
plain copy

/* 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文件中:

[cpp] view
plain copy

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中:

[cpp] view
plain copy

static struct platform_device mx6_audio_tlv320_device = {

.name = "imx-tlv320",

};

因为是通过名字进行匹配,所以二者名字一定要相同。

在板文件中,要进行i2c0的注册所和用到的引脚的配置。

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

[cpp] view
plain copy

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是从设备号,可以参考数据手册查找。挂在好进行注册:

[cpp] view
plain copy

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文件中配置引脚:

[cpp] view
plain copy

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.定义功放和复位

[cpp] view
plain copy

//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){}中调用:

[cpp] view
plain copy

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:

[cpp] view
plain copy

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

[cpp] view
plain copy

#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");

<p>ODULE_LICENSE("GPL");</p><p>调试</p><p>/usr/test/test.wav</p><p>1、录音arecord -f dat test.wav</p><p>2、播放录音aplay test.wav</p>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: