imx6ul linux实现MQS播放音乐

武, 军/ 九月 23, 2017/ Linux, MPU

1.配置内核
wujun@wj-vBox:~/freescale/linux-imx$ make menuconfig
输入/查找关键字MQS,显示如下:
x Symbol: SND_SOC_IMX_MQS [=y] x
x Type : tristate x
x Prompt: SoC Audio support for i.MX boards with MQS x
x Location: x
x -> Device Drivers x
x -> Sound card support (SOUND [=y]) x
x -> Advanced Linux Sound Architecture (SND [=y]) x
x -> ALSA for SoC audio support (SND_SOC [=y]) x
x (1) -> SoC Audio for Freescale CPUs x
x Defined at sound/soc/fsl/Kconfig:333 x
x Depends on: SOUND [=y] && !M68K && !UML && SND [=y] && SND_SOC [=y] && SND_IMX_SOC [=y] && OF [=y]x
x Selects: SND_SOC_IMX_PCM_DMA [=y] && SND_SOC_FSL_SAI [=y] && SND_SOC_FSL_MQS [=y] && SND_SOC_FSL_UTILS [=y]x
去相关目录下查看,已经开启了.查看.config内的配置。
wujun@wj-vBox:~/freescale/linux-imx$ vi .config
CONFIG_SND_SOC_IMX_MQS=y
默认是已经开启了mqs,不需要重新配置内核.

2.修改dts
可以参考下面两个文件关于mqs的配置.
http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git/tree/arch/arm/boot/dts/imx6ul-14×14-ddr3-arm2-mqs.dts?h=imx_4.1.15_1.0.0_ga
http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git/tree/arch/arm/boot/dts/imx6ul-14×14-ddr3-arm2.dts?h=imx_4.1.15_1.0.0_ga

imx6ul-14×14-ddr3-arm2.dts
456 pinctrl_mqs: mqsgrp {
457 fsl,pins = <
458 MX6UL_PAD_JTAG_TDI__MQS_LEFT 0x11088
459 MX6UL_PAD_JTAG_TDO__MQS_RIGHT 0x11088
460 >;
461 };

imx6ul-14×14-ddr3-arm2-mqs.dts
***********************************************************************************
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

#include “imx6ul-14×14-ddr3-arm2.dts”

/ {
sound-mqs {
compatible = “fsl,imx6ul-ddr3-arm2-mqs”,
“fsl,imx-audio-mqs”;
model = “mqs-audio”;
cpu-dai = <&sai1>;
asrc-controller = <&asrc>;
audio-codec = <&mqs>;
};
};

&clks {
assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <786432000>;
};

&sai1 {
assigned-clocks = <&clks IMX6UL_CLK_SAI1_SEL>,
<&clks IMX6UL_CLK_SAI1>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <0>, <24576000>;
status = “okay”;
};

&mqs {
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_mqs>;
clocks = <&clks IMX6UL_CLK_SAI1>;
clock-names = “mclk”;
status = “okay”;
};
***********************************************************************************
可见imx6ul-14×14-ddr3-arm2.dts里面只是定义了相应的GPIO而已。
imx6ul-14×14-ddr3-arm2-mqs.dts里引用了imx6ul-14×14-ddr3-arm2.dts并且增加了mqs的驱动参数。
也就是说imx6ul-14×14-ddr3-arm2.dts实际上没有使用mqs,imx6ul-14×14-ddr3-arm2-mqs.dts使用了mqs。
在编译生成的.dtb的时候可以看到imx6ul-14×14-ddr3-arm2-mqs.dtb要比imx6ul-14×14-ddr3-arm2.dts大一些。

通过查看驱动,了解驱动是如何找到设备参数的。驱动代码不是在driver下,而是sound/soc/目录下。
可以看到驱动是查找fsl,”fsl,imx-audio-mqs”节点
wujun@wj-vBox:~/freescale/linux-imx$ grep -rn “compatible” sound/soc/ | grep “mqs”
sound/soc/codecs/fsl_mqs.c:208: { .compatible = “fsl,imx6sx-mqs”, },
sound/soc/fsl/imx-mqs.c:257: { .compatible = “fsl,imx-audio-mqs”, },

我们使用的是imx6ul-evk不能直接使用imx6ul-14×14-ddr3-arm2-mqs.dtb.每一个dts都代表着不同的硬件设计.
修改imx6ul-14×14-evk.dts,删掉sound相关设定,evk原本事使用wm8960编解码芯片的。
增加MQS GPIO设定
pinctrl_mqs: mqsgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__MQS_LEFT 0x11088
MX6UL_PAD_GPIO1_IO00__MQS_RIGHT 0x11088
>;
};
这里我们更换了两个pin,因为上面两个pin已经接出来了,可以方便连线。

关于GPIO的定义可以在imx6ul-pinfunc.h文件中搜索。
./imx6ul-pinfunc.h:86:#define MX6UL_PAD_GPIO1_IO01__MQS_LEFT 0x0060 0x02EC 0x0000 0x4 0x0
./imx6ul-pinfunc.h:77:#define MX6UL_PAD_GPIO1_IO00__MQS_RIGHT 0x005C 0x02E8 0x0000 0x4 0x0

重新拷贝一份mqs.dts
cp imx6ul-14×14-ddr3-arm2-mqs.dts imx6ul-14×14-evk-mqs.dts
修改 imx6ul-14×14-evk-mqs.dts这样编译出来的才是我们evk能用的dtb.
#include “imx6ul-14×14-evk.dts”

3.编译
我们应该是要编译imx6ul-14×14-evk-mqs.dts
需要增加编译项,在arch/arm/boot/dts/Makefile里面
imx6ul-14×14-evk.dtb \
+ imx6ul-14×14-evk-mqs.dtb \
只需要编译设备树
wujun@wj-vBox:~/freescale/linux-imx$ make dtbs
DTC arch/arm/boot/dts/imx6ul-14×14-evk-mqs.dtb

因为没有修改内核,所以可以不需要编译,如果有修改就要编译一下make -j8

将内核和dtb拷贝出来放到tftp的共享目录下。
wujun@wj-vBox:~/freescale/linux-imx$ cp arch/arm/boot/zImage ~/share-doc/
wujun@wj-vBox:~/freescale/linux-imx$ cp arch/arm/boot/dts/imx6ul-14×14-ddr3-arm2.dtb ~/share-doc/

4.更新系统
重启板卡进入uboot网口接到PC.
开启tftp设定文件目录(内核和dtb的存放目录)
uboot 设定IP,并且选择使用tf卡里面的文件系统
=> setenv ipaddr 192.168.56.6;setenv serverip 192.168.56.5;
=> mmc dev 1;setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw;
switch to partitions #0, OK
mmc1 is current device
=> tftp 0x81000000 zImage;tftp 0x81A00000 imx6ul-14×14-evk-mqs.dtb;bootz 0x81000000 – 0x81A00000;

内核启动后查看内核log
root@imx6ulevk:~# dmesg | grep mqs
[ 2.691214] imx-mqs sound-mqs: fsl-mqs-dai <-> 2028000.sai mapping ok
[ 2.698021] imx-mqs sound-mqs: snd-soc-dummy-dai <-> 2034000.asrc mapping ok
[ 2.704218] imx-mqs sound-mqs: fsl-mqs-dai <-> 2028000.sai mapping ok
[ 2.858716] #0: mqs-audio

设备已经有了实际上是在/dev/snd下,下面就是需要测试设备是否正常.

5.安装播放器
源码参考
http://www.cnblogs.com/CZM-/p/6265225.html
http://blog.csdn.net/u013286409/article/details/47414273
http://blog.csdn.net/azloong/article/details/6140824

EVK上编译
wujun@wj-vBox:~/wavplay$ gcc wavplay.c -o wav-player /usr/lib/libasound.so
wujun@wj-vBox:~/wavplay$ ./wav-player wav-test.wav
也可以写个简单的Makefile

在PC上编译
wujun@wj-vBox:~/wavplay$ gcc wavplay.c -o wav-player /usr/lib/x86_64-linux-gnu/libasound.so
wujun@wj-vBox:~/wavplay$ ./wav-player wav-test.wav

wav-test.wav是随便找的wav文件。

如果正常,在GPIO1_IO01和GPIO1_IO00就可以有音频播放出来,加上一个喇叭或者耳机就能听到声音。
声音不大,没有找到方法调节音量,在产品上是要加功放的。
至此整个过程完毕。在整个过程中实际上并不是一帆风顺,有几个小坑。
(1)修改GPIO的时候出现了GPIO冲突,这个可以再dts文件里搜一下GPIO1_IO01就能看到了。
root@imx6ulevk:~# dmesg | grep mqs
[ 2.671735] imx6ul-pinctrl 20e0000.iomuxc: pin MX6UL_PAD_GPIO1_IO01 already requested by 2040000.tsc; cannot claim for 2000000.aips-bus:mqs
[ 2.683154] imx6ul-pinctrl 20e0000.iomuxc: pin-24 (2000000.aips-bus:mqs) status -22
[ 2.689550] imx6ul-pinctrl 20e0000.iomuxc: could not request pin 24 (MX6UL_PAD_GPIO1_IO01) from group mqsgrp on device 20e0000.iomuxc
(2)一开始找到的播放器是wavplay,下载安装好后不能使用.
wavplay默认使用/dev/dsp播放。可以通过命令修改设备,可是alsa下实际上使用了多个设备很难指定一个设备去播放.
当然不一定对,只是没有再深挖。

最后附上播放器源码。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>

/* RIFF WAVE file struct.
* For details see WAVE file format documentation
* (for example at http://www.wotsit.org).
*/
typedef struct WAV_HEADER_S
{
char riffType[4]; //4byte,资源交换文件标志:RIFF
unsigned int riffSize; //4byte,从下个地址到文件结尾的总字节数
char waveType[4]; //4byte,wav文件标志:WAVE
char formatType[4]; //4byte,波形文件标志:FMT(最后一位空格符)
unsigned int formatSize; //4byte,音频属性(compressionCode,numChannels,sampleRate,bytesPerSecond,blockAlign,bitsPerSample)所占字节数
unsigned short compressionCode;//2byte,格式种类(1-线性pcm-WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM)
unsigned short numChannels; //2byte,通道数
unsigned int sampleRate; //4byte,采样率
unsigned int bytesPerSecond; //4byte,传输速率
unsigned short blockAlign; //2byte,数据块的对齐,即DATA数据块长度
unsigned short bitsPerSample; //2byte,采样精度-PCM位宽
char dataType[4]; //4byte,数据标志:data
unsigned int dataSize; //4byte,从下个地址到文件结尾的总字节数,即除了wav header以外的pcm data length
} WAV_HEADER;

WAV_HEADER wav_header;

int set_pcm_play(FILE *fp);

int main(int argc, char *argv[])
{
int nread;
FILE *fp;
char riff[5];

if (argc != 2) {
printf(“Usage: wav-player file_name\n”);
exit(1);
}

fp = fopen(argv[1], “rb”);
if (fp == NULL) {
perror(“open file failed:\n”);
exit(1);
}

nread = fread(&wav_header, 1, sizeof(wav_header), fp);
printf(“nread=%d\n”, nread);

memcpy(riff, wav_header.riffType, 4);
riff[4] = ‘\0’;
printf(“riffType: %4s\n”, riff);
printf(“riffSize: %d\n”, wav_header.riffSize);
printf(“waveType: %s\n”, wav_header.waveType);
printf(“formatType: %s\n”, wav_header.formatType);
printf(“formatSize: %d\n”, wav_header.formatSize);
printf(“compressionCode: %d\n”, wav_header.compressionCode);
printf(“numChannels: %d\n”, wav_header.numChannels);
printf(“sampleRate: %d\n”, wav_header.sampleRate);
printf(“bytesPerSecond: %d\n”, wav_header.bytesPerSecond);
printf(“blockAlign: %d\n”, wav_header.blockAlign);
printf(“bitsPerSample: %d\n”, wav_header.bitsPerSample);
printf(“dataType: %s\n”, wav_header.dataType);
printf(“dataSize: %d\n”, wav_header.dataSize);

set_pcm_play(fp);

return 0;
}

int set_pcm_play(FILE *fp)
{
int rc;
int ret;
int size;
snd_pcm_t* handle; //PCM设备句柄
snd_pcm_hw_params_t* params;//硬件信息和PCM流配置
unsigned int val;
int dir = 0;
snd_pcm_uframes_t frames;
char *buffer;
unsigned char ch[100]; //用来存储wav文件的头信息
int channels = wav_header.numChannels;
int frequency = wav_header.sampleRate;
int bit = wav_header.bitsPerSample;
int datablock = wav_header.blockAlign;

rc = snd_pcm_open(&handle, “default”, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
perror(“\nOpen PCM device failed:”);
exit(1);
}

snd_pcm_hw_params_alloca(&params); //分配params结构体
if (rc < 0) {
perror(“\nsnd_pcm_hw_params_alloca:”);
exit(1);
}

rc = snd_pcm_hw_params_any(handle, params);//初始化params
if (rc < 0) {
perror(“\nsnd_pcm_hw_params_any:”);
exit(1);
}

rc = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); //初始化访问权限
if (rc < 0) {
perror(“\nsed_pcm_hw_set_access:”);
exit(1);
}

//采样位数
switch (bit / 8) {
case 1:
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8); break;
case 2:
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); break;
case 3:
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24_LE); break ;
}

rc = snd_pcm_hw_params_set_channels(handle, params, channels); //设置声道,1表示单声>道,2表示立体声
if (rc < 0) {
perror(“\nsnd_pcm_hw_params_set_channels:”);
exit(1);
}

val = frequency;
rc = snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); //设置>频率
if (rc < 0) {
perror(“\nsnd_pcm_hw_params_set_rate_near:”);
exit(1);
}

rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
perror(“\nsnd_pcm_hw_params:”);
exit(1);
}

rc = snd_pcm_hw_params_get_period_size(params, &frames, &dir); /*获取周期长度*/
if (rc < 0) {
perror(“\nsnd_pcm_hw_params_get_period_size:”);
exit(1);
}

size = frames * datablock; /*4 代表数据快长度*/

buffer = (char*)malloc(size);
fseek(fp, 58, SEEK_SET); //定位歌曲到数据区

while (1) {
memset(buffer, 0, sizeof(buffer));
ret = fread(buffer, 1, size, fp);
if (ret == 0) {
printf(“Play finished!\n”);
break;
} else if (ret != size) {
}
// 写音频数据到PCM设备
while (ret = snd_pcm_writei(handle, buffer, frames) < 0) {
usleep(2000);
if (ret == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, “underrun occurred\n”);
//完成硬件参数设置,使设备准备好
snd_pcm_prepare(handle);
} else if (ret < 0) {
fprintf(stderr, “error from writei: %s\n”, snd_strerror(ret));
}
}
}

snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);

return 0;
}