海思hi3531d音频外接codec (tlv320aic32x4) (一)

在学了在学了! 2020-08-24 21:24:41 5687

前言

为了在海思平台上使用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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
在学了在学了!
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区