开发平台:OrangePi
CPU:全志 H2+
虚拟机:Ubuntu 12.04 64位
说明:
tlv320adc3101声卡驱动主要使用了tlv320adc3101.c、sunxi-daudio0.csunxi-daudio0.c、sunxi-daudio0.c、sunxi-snddaudio0.c四个文件,其中tlv320adc3101.c是新加入的文件,其它文件为sunxi提供的linux-3.4内核自带的文件.
OrangePi-Kernel\linux-3.4\sound\soc\codecs中
tlv320adc3101.c为codec文件,编译后生成snd-soc-tlv320adc3101.ko;
OrangePi-Kernel\linux-3.4\sound\soc\sunxi\daudio0中
sunxi-daudio0.c为platform-dai(iis)文件,编译后生成sunxi-daudio0.ko
sunxi-daudiodma0.c为platform-dma文件,编译后生成sunxi-daudiodma0.ko
sunxi-snddaudio0.c为machine文件,编译后生成sunxi-snddaudio0.ko
snddaudio0.c是内核提供的codec文件,给未知的codec留了很多待补充的内容,这里不使用它,在makefile中将其注释
快速添加驱动方法:
一、给内核打补丁
1.前言
声卡驱动开发涉及到文件较多,使用补丁方法可简便快速为内核添加新驱动.下面章节再讲述驱动开发的具体过程.
2.打补丁
首先将补丁文件(h2_linux_adc3101.patch)拷贝到内核目录同文件夹OrangePi-Kernel下,然后进入linux-3.4内核目录,运行patch -p1 < ../h2_linux_adc3101.patch 即可.
ps:制作补丁方法
1)进入OrangePi-Kernel目录,执行sudo ./build_linux_kernel.sh clean命令,清除一下编译结果,以防止生成的补丁文件过大.
2)将linux-3.4文件夹重命名为linux-3.4-new
3)将原版内核源码linux-3.4解压到同一目录(OrangePi-Kernel)下,使用命令diff -rNu linux-3.4 linux-3.4-new > h2_linux_adc3101.patch就会得到h2_linux_adc3101.patch补丁文件.
二、其它文件更改
除了内核相关文件,linux源码文件夹OrangePi-Kernel中涉及到的其它文件有:OrangePi-Kernel/build_linux_kernel.sh(编译内核的批处理命令)、OrangePi-Kernel/build/orange_zero_h2_snd_adc3101_config(内核配置文件)、OrangePi-BuildLinux/params.sh(生成文件系统时传入的参数)、orangepi_h2_linux-master/README(内核编译步骤)、OrangePi-Kernel/chips/sun8iw7p1/bin/sys_config.fex(处理器各端口\外设资源配置)
声卡驱动开发具体过程:
一、在OrangePi-Kernel/linux-3.4/sound/soc/codecs中创建tlv320adc3101.c和tlv320adc3101.h文件,此两文件以tlv320aic32x4.c和tlv320aic32x4.h为模板,并进行相关修改:
1.在aic32x4_i2c_probe()函数中添加一行:
i2c->dev.init_name = "adc3101_iic_24";//codec会使用此名称,名称中不能有-和.,否则snd_soc_register_codec()->fmt_single_name()会不正确解析,在machine文件中与此名字进行匹配,而不是i2c_board_info或i2c_driver.id_table的名字。如果不加入此行代码,snd_soc_register_codec()->fmt_single_name()会解析得到adc3101_iic0.0-0018的字符串,即i2c_driver.id_table.name . i2c总线序号 - 4位16进制i2c设备地址。
2.在aic32x4_modinit(void)函数外添加:
static struct i2c_board_info i2c_snd_devs[] = {
{ I2C_BOARD_INFO("adc3101_iic0", 24), },
{ I2C_BOARD_INFO("adc3101-iic1", 25), },
};
static struct i2c_client *adc3101_client_24;
3.创建i2c设备,在aic32x4_modinit(void)函数中添加:(之前在sun8i中使用i2c_register_board_info()函数注册i2c设备后,加载此模块后无法自动调用aic32x4_i2c_probe()函数,使用此种方式可以。)
i2c_adap = i2c_get_adapter(0);
adc3101_client_24 = i2c_new_device(i2c_adap, &i2c_snd_devs[0]);
i2c_put_adapter(i2c_adap);
4.在aic32x4_exit(void)函数中添加:
i2c_unregister_device(adc3101_client_24);
5.在static const struct i2c_device_id aic32x4_i2c_id[]中添加一项
{ "adc3101_iic0", 0 },
6.在tlv320adc3101.h中添加如下代码(原linux-3.4\include\sound\tlv320aic32x4.h中的部分):
//ADC3101_PDATA
#define ADC3101_PWR_MICBIAS_2075_LDOIN 0x00000001
#define ADC3101_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002
#define ADC3101_PWR_ADC3101_LDO_ENABLE 0x00000004
#define ADC3101_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008
#define ADC3101_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010
#define ADC3101_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001
#define ADC3101_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002
7.在tlv320adc3101.c中添加如下代码(原linux-3.4\include\sound\tlv320aic32x4.h中的部分):
struct adc3101_pdata {
u32 power_cfg;
u32 micpga_routing;
bool swapdacs;
};
8.在adc3101_hw_params()函数最后添加如下代码:
/* DOUT control */
data = snd_soc_read(codec, ADC3101_DOUTCTL);
data &= ~(0x10); //DOUT bus keeper enabled
printk("adc3101 ADC3101_DOUTCTL data = %d\n", data);
snd_soc_write(codec, ADC3101_DOUTCTL, data); //DOUT bus keeper enabled
snd_soc_write(codec, ADC3101_ADCSPB, 0x01); // Program the processing block to be used : PRB_P1 ,应该有助于消除噪音
printk("set ADC3101_MICBIAS = 0x50.\n");
snd_soc_write(codec, ADC3101_MICBIAS, 0x50); //MICBIAS1 is powered to 2.5V,MICBIAS2 is powered to 2.5V.
//printk("set ADC3101_MICBIAS = 0x28.\n");
//snd_soc_write(codec, ADC3101_MICBIAS, 0x28); //MICBIAS1 is powered to 2V,MICBIAS2 is powered to 2V.
snd_soc_write(codec, ADC3101_LMICPGAVOL, 0x0); //Left Analog PGA not mute,gain= 0x0 for 0 dB,0x50 for 40 dB
snd_soc_write(codec, ADC3101_RMICPGAVOL, 0x0); //Right Analog PGA not mute,gain=0x0 for 0 dB,0x50 for 40 dB
snd_soc_write(codec, ADC3101_LADCVOL, 0x16); //Left Analog ADC volum 0x28:20 dB,max volum
snd_soc_write(codec, ADC3101_RADCVOL, 0x16); //Right Analog ADC volum 0x28:20 dB,max volum
snd_soc_write(codec, ADC3101_LAGC1, 0x80); //Left AGC enable,使能之后,mic灵敏度增加,噪音也同时加大
snd_soc_write(codec, ADC3101_RAGC1, 0x80); //Right AGC enable,使能之后,mic灵敏度增加,噪音也同时加大
snd_soc_write(codec, ADC3101_LMICPGAPIN, 0xF3); //左AD转换声道选择,0x3F:IN2L(P)与IN3L(M)作为差分信号输入
snd_soc_write(codec, ADC3101_RMICPGAPIN, 0xF3); //右AD转换声道选择,0x3F:IN2R(P)与IN3R(M)作为差分信号输入
snd_soc_write(codec, ADC3101_ADCSETUP, 0xC2);//0xC2-Left&Right,0x42-Right,0x82-Left //Left&Right-channel ADC is powered up , ADC channel volume control soft-stepping is disabled.
snd_soc_write(codec, ADC3101_ADCFGA, 0x0); //左右AD转换声道非静音,左右AD转换声道增益0dB.
9.在static const struct snd_soc_dai_ops adc3101_ops结构体中添加一项:
.set_clkdiv = adc3101_set_dai_clkdiv,
并在本文件中以空函数实现之:
static int adc3101_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div)
{
return 0;
}
如果不添加次函数,在编译时会报错:缺少这个函数.因为设置时钟的功能已经在adc3101_hw_params()函数中完成了,所以此函数为空即可.
10.分别修改此文件夹中的makefile和Kconfig.
二、修改OrangePi-Kernel\linux-3.4\sound\soc\sunxi\daudio0\sunxi-snddaudio0.c文件:
在static struct snd_soc_dai_link sunxi_snddaudio_dai_link中修改如下两行
.codec_dai_name = "adc3101_iis",//.codec_dai_name = "snddaudio",
.codec_name = "adc3101_iic_24",//.codec_name = "sunxi-daudio-codec.0",
三、配置内核
1.cd xxxxx/orangepi_h2_linux-master/OrangePi-Kernel/linux-3.4
make ARCH=arm menuconfig
选择处理器
System Type --->Select the wafer with arch sun8i->Allwinner Axx SOCs(sun8w7)
Select the chip with wafer sun8iw7->Allwinner Axx chip(sun8w7)
2.选中I2C 和 I2C_SUNXI
Device Drivers --->
<*> I2C support --->
<*> I2C device interface
I2C Hardware Bus support --->
<*> SUNXI I2C controller
3.选择声卡驱动
Device Drivers --->
<*> Sound card support --->
<*> Advanced Linux Sound Architecture --->
<*> ALSA for SoC audio support --->
<M> SoC daudio0 tdm interface for SUNXI chips
<M> Daudio0 Public Machine for SUNXI chips
<M> TLV320ADC3101 Codec support
4.保存退出。
5.将.config保存为orange_zero_h2_snd_adc3101_config,在build_linux_kernel.sh中会将其拷贝到arch/arm/configs/sun8iw7p1smp_linux_defconfig中作为默认的配置文件
使用命令:
cp .config ../build/orange_zero_h2_snd_adc3101_config
四、修改sys_config.fex
编译内核镜像时使用orangepi_h2_linux-master/OrangePi-Kernel/chips/sun8iw7p1/bin目录下的sys_config.fex.(注意:不要使用chips/sun8iw7p1/configs/dolphin-p2/sys_config.fex,经测试修改此文件无效)
原:
[pcm0]
daudio_used = 0
现: //使能i2s0端口.使能后,在声卡cpu-dai程序中将获得此参数配置,cpu-dai被正常加载;
[pcm0]
daudio_used = 1
原:
[twi1]
twi_used = 1
现: //禁用i2c1,解决i2c1与i2s0端口冲突的问题.禁用后,驱动程序将不为i2c1申请端口资源,设备文件中不会生成/dev/i2c-1
[twi1]
twi_used = 0
五、编译内核(编译内核最好使用64位linux操作系统,如遇缺少相关包支持,还需使用apt-get下载支持包)
1.修改OrangePi-Kernel/build_linux_kernel.sh,47行
原:
cp ../build/sun8iw7p1smp_android_defconfig arch/arm/configs/sun8iw7p1smp_linux_defconfig
修改为:
cp ../build/orange_zero_h2_snd_adc3101_config arch/arm/configs/sun8iw7p1smp_linux_defconfig
2在OrangePi-BuildLinux/params.sh中修改:(可解决编译过程中报内核版本低的错误,虚拟机ubuntu内核也为precise (lsb_release -a 命令查看),难道这两个需要一致?)
# === Ubuntu ===
distro="precise"
#distro="xenial"
3.编译内核
cd OrangePi-Kernel
sudo ./build_uboot.sh zero //compile uboot for OPI-zero
sudo ./build_linux_kernel.sh clean //cleans the kernel tree before build
sudo ./build_linux_kernel.sh zero //builds the uImage for OPI-zero
4.编译文件系统,打包,下载到tf卡
cd ../OrangePi-BuildLinux
//build file system
./create_image
sudo ./image_from_dir linux-precise orangepi ext4 zero //build zero image 此处要注意根据params.sh中distro参数的设置进行修改。
sudo dd bs=4M if=orangepi.img of=/dev/sd* //具体为sda或sdb或sdxx要通过dmesg | tail -10命令确认一下,不需要具体到sd*1或sd*2,否则会造成烧写不成功。
六、启动,加载驱动模块:
1.连接准备
tf卡接入开发板;
TTL转usb线连接电脑及开发板两端,打开超级终端,设置波特率:115200,数据位:8,奇偶校验:无,停止位:1,流控制:无;
2.启动
用户名:root
密码:orangepi
首次启动后要运行一下sudo fs_resize命令,否则会造成再次启动不成功。
3.加载驱动模块
板子启动后,可在/lib/modules/3.4.39_zero目录下找到编译好的内核模块,也可通过u盘或nfs方式加载模块,后者适用于驱动调试
ls /lib/modules/3.4.39_zero
insmod sunxi-daudio0.ko
insmod sunxi-daudiodma0.ko
insmod snd-soc-tlv320adc3101.ko
insmod sunxi-snddaudio0.ko
之后/dev/snd文件夹里会出现controlC0 \ pcmC0D0c节点
4.有时加载驱动模块会出现 error inserting 'xxxx.ko': -1 Invalid module format的错误,这种情况一般是更改内核配置文件将原驱动编译为模块,或修改内核其它部分配置项,再编译内核后发生。原因可能是由于内核模块不能比内核老,编辑一下内核模块源文件,但不改变其内容,再次编译得到的.ko文件拷贝进内核即可使用。
七、驱动调试
1.i2c接口调试
驱动中选择了<*> I2C device interface 以及<*> SUNXI I2C controller,系统启动后,/dev下会自动生成i2c-0设备节点。(更改sys_config.fex中[twi1] twi_used = 0后,系统将不会生成i2c-1设备节点,解决i2c1与i2s0端口冲突的问题.)
用i2cdetect检测有几组i2c总线在系统上,输入i2cdetect -l
用i2cdetect检测挂载在i2c总线上器件,输入i2cdetect -r -y 0(检测i2c-0总线上的挂载情况)
用i2cdump查看器件所有寄存器的值,输入i2cdump -f -y 0 0x18(查看i2c-0总线上设备地址为0x18的所有寄存器值)
设置单个寄存器值,输入 i2cset -f -y 0 0x18 0x77 0x3f (设置i2c-0上0x18器件的0x77寄存器值为0x3f)
读取单个寄存器值,输入 i2cget -f -y 0 0x18 0x77 (读取i2c-0上0x18器件的0x77寄存器值)
如果文件系统中没有集成i2cdetect等程序,则首先将gcc-linaro-arm-linux-gnueabihf-4.9-2014.07_linux交叉编译工具添加进环境变量,然后下载i2c-tools-3.1.0源码,进入i2c-tools源码目录,修改makefile文件,将CC ?= gcc 改为 CC := arm-linux-gnueabihf-gcc (:=是覆盖之前的值?= 是如果没有被赋值过就赋予等号后面的值+=是添加等号后面的值),然后i2c-tools源码目录输入make命令,编译完成后可在tools目录里得到i2cdetect等可执行文件,将其拷贝到板子上运行,在运行i2cdetect等程序前要加./。
2.i2s接口调试
i2s-0与i2c-1端口复用,会在运行sunxi-daudio0.c中devm_pinctrl_get_select_defaul()时报pin already requested的错误。
解决方法:在sys_config.fex文件中将[twi1] twi_used =1 改为 twi_used = 0,这样在加载i2c-sunxi.c和i2c-dev.c驱动时将不会申请i2c-1端口资源。
3.dma调试
cat /proc/interrupts
八、录音测试
注意:编译h2应用程序需使用gcc-linaro-arm-linux-gnueabihf-4.9-2014.07_linux交叉编译工具,而不能使用OrangePi-Kernel\brandy\gcc-linaro里的arm-linux-gnueabi交叉编译工具
1.编译alsa-lib
alsa编译安装路径默认为/usr,如果编译时指定路径--prefix = xxx,则安装到板子上也必须为此路径,则编译时可使用两种方法:
1) 编译时不指定路径,使用默认路径/usr,以免破坏板子的文件系统,但会改变ubuntu系统中/usr目录名称,如果操作不当会损坏ubuntu系统。
且本ubuntu系统更改环境变量后找不到arm-linux-gnueabihf-gcc编译器,所以这里不推荐使用此方法。
cd /
mv /usr /usr_bk
echo $PATH
得到环境变量为:
/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.07_linux/bin
修改环境变量:
export PATH=/usr_bk/lib/lightdm/lightdm:/usr_bk/local/sbin:/usr_bk/local/bin:/usr_bk/sbin:/usr_bk/bin:/sbin:/bin:/usr_bk/games:/opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.07_linux/bin
./configure --host=arm-linux-gnueabihf --enable-shared --disable-python
make
sudo mkdir /usr
sudo chown book:book /usr
make install
sudo cp -rf /usr /work/projects/alsa/
sudo rm -rf /usr
sudo mv /usr_bak /usr
恢复环境变量:
export PATH=/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.07_linux/bin
/*
把头文件和库复制进交叉工具链里
cd /work/projects/alsa/usr/include
sudo cp * -rfd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cd /work/projects/alsa/usr/lib
sudo cp * -rfd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
*/
把库复制到根文件系统的lib目录下
2)选定/home/alsa目录为存放alsa库编译结果的目录
首先将alsa-lib解压,将其目录里的所有文件拷贝到/home目录里。如果选择其它目录,在下面make时会抱错smixer-sbase.la libtool: link: only absolute run-paths are allowed,网络上没有找到好的解决办法,猜想是路径的问题,使用此方法得以解决。
./configure --host=arm-linux-gnueabihf --prefix=$PWD/alsa --enable-shared --disable-python --with-configdir=$PWD/alsa/alsa_lib/share --with-plugindir=$PWD/alsa/alsa_lib/lib
make
make install
2.编译alsa-utils
./configure --host=arm-linux-gnueabihf --prefix=$PWD/alsa CFLAGS="-I/home/alsa/include" LDFLAGS="-L/home/alsa/lib -lasound" --disable-alsamixer --disable-xmlto --disable-nls
这里,如果不指定--disable-nls,会报cannot stat 't-ja.gmo'的错。
make
make install
3.拷贝到板子上,运行
1)编译完成后,会在/home/alsa目录里得到alsa可执行文件、库文件、头文件。
2)将alsa目录拷贝到板子/home文件夹下
3)cp alsa/lib/* -rf /usr/lib
4)运行
cd /home/alsa/bin
./arecord -d 7 -f dat test.wav //录音
九、驱动模块调试命令集合(使用u盘作为驱动模块传递载体)
dmesg | tail -10
mount /dev/sda1 /mnt/
cd /mnt/
insmod snd-soc-tlv320adc3101.ko
insmod sunxi-daudio0.ko
insmod sunxi-daudiodma0.ko
insmod sunxi-snddaudio0.ko
ls /dev/snd/
cd /home/alsa/bin
./arecord -d 7 -f dat test.wav
cp test.wav /mnt
sync
umount /mnt/
录音:
./arecord -d 7 -f dat test.wav //-d 7 :录制7秒钟; -f dat :16 bit little endian, 48000 ,stereo.
./arecord -d 7 -f S16_LE -r 48000 test.wav //此种方式为单声道录音,这时只录声卡左声道的声音(MIC1和MIC3),右声道无论接不接,都不会录
./arecord -d 10 -f cd test.wav //以CD质量录制test.wav文件10秒钟.
调节音量:
./amixer controls
./amixer cget numid=4 //查看ADC Level Volume设定值
./amixer cset numid=4 40 //ADC Level Volume调至最大
./amixer cset numid=5 80 //PGA Level Volume调至最大