海思hi3531d音频外接codec (tlv320aic32x4) (一)
前言
为了在海思平台上使用tlv320aic3254,花了大概2个星期研究海思音频部分的手册、3254的用户手册,最终参考tlv320aic31的代码,实现了3254的驱动,同时在mpp的sample中,增加了3254对应的宏,以及相应的初始化代码,这篇文章主要是做一个阶段性的梳理
参考资料
海思音频相关知识
海思的mpp(Media Process Platform)媒体处理平台针对音频提供了4种类型的API子模块,分别是AI(音频输入)、AO(音频输出)、ADEC(音频编码)、AENC(音频编码),本文的重点是AI和AO,编解码不做介绍
音频接口和 AI、AO 设备
音频输入输出接口简称为 AIO(Audio Input/Output)接口,用于和 Audio Codec 对接,完成声音的录制和播放。AIO 接口分为
两种类型:当为输入类型时,称为 AIP,当为输出类型时,称为 AOP
Hi3531DV100 内部集成 1 个 AIAO,包含 3 个 AIP(Audio Input Port)和 3 个 AOP(Audio Output Port)
AIAO 接口支持 I2S 和 PCM 两种模式,采用 DMA 方式存取数据,本文只针对I2S讲解
软件中负责抽象音频接口输入功能的单元,称之为 AI 设备;负责抽象音频接口输出功能的单元,称之为 AO 设备
录音和播放原理
原始音频信号以模拟信号的形式给出后,通过 Audio Codec,按一定采样率和采样精度转换为数字信号。Audio Codec 以 I2S 时序或 PCM 时序的方式,将数字信号传输给 AI设备。芯片利用 DMA 将 AI 设备中的音频数据搬移到内存中,完成录音操作。
播放和录音是基于同样的原理。芯片利用 DMA 将内存中的数据传输到 AO 设备。AO设备通过 I2S 时序或 PCM 时序向 Audio Codec 发送数据。Audio Codec 完成数字信号到模拟信号的转换过程,并输出模拟信号
AIAO I2S连接示意图
从这张图可以得到一个信息,内部的AIP和AOP和外部的I2S引脚有一个映射关系,tlv320aic32x4作为一个即能录制,又能播放的codec,应该选用上面的I2S2,内部对应的是AIP2和AOP0
打开《Hi3531DV100_PINOUT_CN.xlsx》,在3.管脚复用寄存器中搜I2S2,如图
- MCLK:主时钟,hi3531d会根据采样率的不同,生成一个主时钟,频率8M~15M不等
- BLCK:位时钟,每发送一个位的数据,都需要靠位时钟实现同步,计算公式为采样率 x 采样精度 x 声道数
- WS:声道选择,0和1表示传输不同的声道,频率等于采样频率
- SD_TX:海思发送引脚
- SD_RX:海思接收引脚
上面的5个引脚,默认功能就是I2S,可以用himm工具确认一下I2S引脚的复用情况
I2S主从模式
海思和3254均支持主模式或者从模式,当海思作为master时,将向外提供BCLK和WS,海思作为slave时,BLCK和WS由外部输入,codec也是一样。
两者的区别在于,海思不管是做master还是slave,一定会向外提供主时钟MCLK,因此codec不再需要单独的外部晶振,也不需要向外输出MCLK
硬件连接
tlv320aic3254驱动
驱动就三个文件:tlv320aic32x4.c、tlv320aic32x4.h、Makefile,下载链接
框架部分参考了tlv320aic31,内容部分做了比较大的改动
准备工作
在mpp/extdrv目录下,创建tlv320aic32x4目录,并将三个文件拷贝到其中,然后退回上层,在extdrv目录下,执行make,这时会生成对应的驱动程序
驱动代码的说明
为了防止文档较长,代码仅作节选
c文件
init函数主要是注册了一个misc设备,并进行了codec设备的初始化
static int __init tlv320aic32x4_init(void)
{
misc_register(&tlv320aic32x4_dev);
i2c_client_init();
tlv320aic32x4_device_init();
return 0;
}
static void __exit tlv320aic32x4_exit(void)
{
tlv320aic32x4_device_exit();
misc_deregister(&tlv320aic32x4_dev);
i2c_client_exit();
}
在注册misc设备的时候,给了一个结构体,这个驱动的主要内容,都在ioctl函数里面,open和close是什么都不做的
static struct file_operations tlv320aic32x4_fops =
{
.owner = THIS_MODULE,
.unlocked_ioctl = tlv320aic32x4_ioctl,
.open = tlv320aic32x4_open,
.release = tlv320aic32x4_close
};
static struct miscdevice tlv320aic32x4_dev =
{
MISC_DYNAMIC_MINOR,
I2C_DEV_NAME,
&tlv320aic32x4_fops,
};
在ioctl内部,将应用层传下来的参数copy_from_user到内核空间,然后根据cmd的类型,分别进行不同的操作
在文件的开头,也定义了一部分内容
/ define MICRO /
#define I2C_DEV (1) // tlv_aic32x4 use i2c-1
#define I2C_DEV_NAME "tlv320aic32x4" // I2C dev info
#define I2C_DEV_ADDR (0x30) // i2c dev address
hi3531d只有i2c-0和i2c-1,因此使用的是哪个,I2C_DEV就定义哪个
I2C_DEV_NAME是指定insmod驱动后,在dev目录下生成的节点名字
I2C_DEV_ADDR指定了codec的设备地址,7位地址原本是0x18,0x30是经过移位的,在海思平台下,无论是应用层还是驱动层,调用i2c都必须用移位后的地址,读写不区分,都是这个地址
#define DEBUG_LEVEL 3
#define DPRINTK(level,fmt,args...) do{ if(level < DEBUG_LEVEL)\
printk(KERN_INFO "%s [%s, line-%d]: " fmt "\n",I2C_DEV_NAME,__FUNCTION__,__LINE__,##args);\
}while(0)
这个宏定义主要是方便信息的打印,比如此处等级设成3,那么等级为0,1,2的,就一定会打印出来,如果不希望打印过多的调试信息,level最好改为1,只打印严重错误的信息
/* global variable */
static struct i2c_board_info hi_info =
{
I2C_BOARD_INFO(I2C_DEV_NAME, I2C_DEV_ADDR),
};
static struct i2c_client* tlv_client;
static unsigned int cur_page = 0;
static const struct aic32x4_rate_divs aic32x4_divs[] = {
/* hi3531d as master, aic32x4 as slave, it will be more easy to set parameters*/
/* channels less than 20 */
//mclk rate nadc madc aosr ndac mdac dosr
{12288000, AIC32x4_SAMPLE_RATE_48K, 1, 2, 128, 1, 2, 128 },
{12288000, AIC32x4_SAMPLE_RATE_24K, 1, 4, 128, 1, 2, 256 },
{12288000, AIC32x4_SAMPLE_RATE_12K, 1, 8, 128, 1, 2, 512 },
{8192000, AIC32x4_SAMPLE_RATE_32K, 1, 2, 128, 1, 2, 128 },
{8192000, AIC32x4_SAMPLE_RATE_16K, 1, 4, 128, 1, 2, 256 },
{8192000, AIC32x4_SAMPLE_RATE_8K, 1, 8, 128, 1, 2, 512 },
{11289600, AIC32x4_SAMPLE_RATE_44K, 1, 2, 128, 1, 2, 128 },
{11289600, AIC32x4_SAMPLE_RATE_22K, 1, 4, 128, 1, 2, 256 },
{11289600, AIC32x4_SAMPLE_RATE_11K, 1, 8, 128, 1, 2, 512 },
//mclk rate 0x12 0x13 0x14 0x0b 0x0c 0x0d-0x0e
/* channels equal 20 */
//mclk rate nadc madc aosr ndac mdac dosr
// {15360000, 48000, 1, 2, 128, 1, 2, 128 },
// {15360000, 24000, 1, 4, 128, 1, 2, 256 },
// {15360000, 12000, 1, 8, 128, 1, 2, 512 },
// {10240000, 32000, 1, 2, 128, 1, 2, 128 },
// {10240000, 16000, 1, 4, 128, 1, 2, 256 },
// {10240000, 8000, 1, 8, 128, 1, 2, 512 },
// {14112000, 44100, 1, 2, 128, 1, 2, 128 },
// {14112000, 22050, 1, 4, 128, 1, 2, 256 },
// {14112000, 11025, 1, 8, 128, 1, 2, 512 },
};
i2c_client* tlv_client实际上就是一个句柄,每次i2c读或写都要用到它。
cur_page主要是用来标记当前的读写的寄存器是多少页的,因为3254这个codec号称有128页,每一页最多有128个寄存器。
aic32x4_divs这个结构体,目的是为了给3254内部的dac、adc等设备时钟提供一个分频系数,不管是主还是从模式,这一步都是必须做的 。
海思应用层的通道数定义为20时的情况,目前没有做过测试,所以屏蔽掉了
static int i2c_client_init(void);
static void i2c_client_exit(void);
int tlv320aic32x4_read(unsigned char chip_addr, unsigned char reg_addr, unsigned char *reg_data);
int tlv320aic32x4_read_test(unsigned char chip_addr, unsigned char reg_addr, unsigned char *reg_data);
int tlv320aic32x4_write(unsigned char chip_addr, unsigned char reg_addr, unsigned char value);
以上这几个函数看名字就知道是干啥了,read读出来的值,放到形参指针中,read_test是read的一个简化,主要是调试阶段做测试用的
static int tlv320aic32x4_reg_list(void);
static int tlv320aic32x4_set_divs(int num);
static int tlv320aic32x4_get_divs(int rate);
static int tlv320aic32x4_soft_reset(void);
reg_list会打印页0和页1的所有寄存器,get_divs和set_divs是设置codec内部的分频系数,用的是上面的结构体aic32x4_divs[]
tlv320aic32x4_soft_reset会重置,并给所有的寄存器一个初始值,codec会设成i2s从模式,48k采样率,16bit位深,总之一般情况下,reset之后,是一个正常的配置,不是寄存器还原为默认值
static int tlv320aic32x4_device_init(void)
{
tlv320aic32x4_soft_reset();
tlv320aic32x4_set_divs(0);
return 0;
}
static int tlv320aic32x4_device_exit(void)
{
ret = tlv320aic32x4_write(I2C_DEV_ADDR, AIC32X4_PAGE_SEL, 0x0);
ret = tlv320aic32x4_write(I2C_DEV_ADDR, AIC32X4_SOFT_RST, 0x01);
msleep(10);
return 0;
}
init会按默认配置,做好所有的设置,codec能正常工作,exit会写reset寄存器,让codec不再工作
头文件
驱动的c文件从结构上来看,还是比较好懂的,没用用到很复杂的东西,下面看头文件
struct aic32x4_rate_divs {
u32 mclk;
u32 rate;
u8 nadc;
u8 madc;
u8 aosr;
u8 ndac;
u8 mdac;
u16 dosr;
};
未完待续
https://blog.csdn.net/whitefish520/article/details/108129844
- 分享
- 举报
-
浏览量:3663次2020-08-24 21:28:44
-
浏览量:4104次2022-04-01 17:01:16
-
浏览量:2997次2020-08-03 11:02:46
-
浏览量:4250次2018-12-25 20:34:34
-
浏览量:3075次2017-11-22 19:41:21
-
浏览量:1176次2023-07-13 14:05:43
-
浏览量:3482次2018-02-06 10:43:46
-
浏览量:2924次2020-07-27 15:26:51
-
浏览量:4257次2020-08-10 09:16:13
-
浏览量:3603次2022-01-04 09:00:18
-
浏览量:2352次2022-01-10 09:00:16
-
浏览量:3316次2020-03-26 15:59:03
-
浏览量:3713次2021-12-10 16:59:31
-
浏览量:3657次2020-08-25 18:10:00
-
浏览量:3815次2020-09-24 11:58:24
-
浏览量:4790次2020-08-11 10:32:41
-
浏览量:6425次2022-08-11 09:31:37
-
浏览量:820次2023-12-22 11:12:20
-
浏览量:14083次2019-09-21 19:14:57
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
在学了在学了!
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明