IMX6Q下tlv320aic3x音频驱动移植
2017-07-25 11:21
507 查看
原理图:
![](https://img-blog.csdn.net/20170725123427422?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXVtdXppXzE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
tlv320aic3x音频芯片
![](https://img-blog.csdn.net/20170725123438713?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXVtdXppXzE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
功率放大器
移植分为三步:
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文件中有几个重要的结构体需要关注:
因为是平台设备驱动,所以就要将平台驱动和平台设备进行关联。
平台驱动在sound/soc/imx/imx-tlv320aic3x.c文件中:
在板文件中,要进行i2c0的注册所和用到的引脚的配置。
在arch/arm/mach-mx6/board-mx6q_sabresd.c中:
在arch/arm/mach-mx6/board-mx6q_sabresd.c写功能函数:
1.定义功放和复位
下面放上板文件和imx-tlv320aic3x.c的代码片段
arch/arm/mach-mx6/board-mx6q_sabresd.c:
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[] = {imx-tlv320aic3x.c
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();
……………
……………
}
#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");
相关文章推荐
- IMX6ul下tlv320aic3x音频驱动调试
- linux2.6.32.2 mini2440平台移植-- UDA1341 音频驱动移植
- SylixOS音频驱动移植
- 2440 6.0BSP移植过程之音频驱动移植
- fl2440内核linux 3.0移植-----UDA1341音频驱动和mp3播放
- Linux-2.6.32.2内核在mini2440上的移植(十三)---移植UDA1341音频驱动
- DM8148音频驱动Tlv320aic3x换了 I2C 总线后无法发现设备驱动?
- linux-2.6.32在mini2440开发板上移植(13)之移植UDA1341 音频驱动
- linux2.6.32.2 mini2440平台移植-- UDA1341 音频驱动移植
- 2440 6.0BSP移植过程之音频驱动移植
- Linux-2.6.32.2内核在mini2440上的移植(十三)---移植UDA1341音频驱动
- linux-2.6.32在mini2440开发板上移植 ---移植UDA1341 音频驱动
- linux2.6.32.2 mini2440平台移植-- UDA1341 音频驱动移植
- 【引用】Linux-2.6.32.2内核在mini2440上的移植(十三)---移植UDA1341音频驱动
- 移植UDA1341音频芯片驱动
- 2440 6.0BSP移植过程之音频驱动移植
- Linux-2.6.32.2内核在mini2440上的移植(十三)---移植UDA1341音频驱动
- linux-2.6.32在mini2440开发板上移植 ---移植UDA1341 音频驱动
- 音频设备驱动移植
- 音频uda1341驱动移植