韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之参数设置(基于优龙FS2410开发板,UDA1341声卡)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之参数设置(基于优龙FS2410开发板,UDA1341声卡)相关的知识,希望对你有一定的参考价值。
一、实验环境
1.1 虚拟机环境
a) Vmware版本:Vmware Workstation 12.5.7
b) Ubuntu版本:9.10
c) 内核版本:2.6.31.14
d) toolchain版本:arm-linux-gcc 4.3.2
1.2 开发板
优龙FS2410开发板,UDA1341声卡
内核版本:3.4.2
二、概述
2.1 目标
之前在写裸板程序时,已经了解了怎样写代码来操作硬件,设置参数,传输数据。现在的任务就是按照ASOC的框架,来重新写这些代码(难点在于理解软件框架)
2.2 哪些地方的代码会涉及到参数设置
根据之前“分析调用过程”那节视频中,用strace跟踪的结果,总结出以下几个地方的代码会涉及到参数设置:
1. soc_pcm_open依次调用cpu_dai, platform(dma), codec_dai, machine的open或startup函数,经查:
- cpu_dai
s3c24xx_i2s_dai.ops(即s3c24xx_i2s_dai_ops),无startup函数
- platform(dma)
samsung_asoc_platform.ops(即dma_ops).dma_open里:snd_pcm_hw_constraint_integer,snd_soc_set_runtime_hwparams(&dma_hardware)
- codec_dai
uda134x_dai_ops.startup即uda134x_startup 里:
snd_pcm_hw_constraint_minmax(SNDRV_PCM_HW_PARAM_RATE), snd_pcm_hw_constraint_minmax(SNDRV_PCM_HW_PARAM_SAMPLE_BITS)
- machine
s3c24xx_uda134x_dai_link.ops(即s3c24xx_uda134x_ops).startup即s3c24xx_uda134x_startup里:
主要工作是设置rate[]的值(算法原理可参考:在uda1341的i2s中256fs,384fs和512fs表示的实际意义和如何auto智能选择)
通过分析这些函数可知,它们并没有涉及到硬件操作,所以我们把它们都归并到dma_open里实现
2. soc_pcm_hw_params依次调用machine,codec_dai,cpu_dai,platform(dma)的hw_params函数
- machine
s3c24xx_uda134x .hw_params 即s3c24xx_uda134x_hw_params
- codec_dai
uda134x_dai_ops.hw_params 即uda134x_hw_params
- cpu_dai
s3c24xx_i2s_dai_ops.hw_params 即s3c24xx_i2s_hw_params
- platform(dma)
samsung_asoc_platform.ops(即dma_ops). hw_params 即dma_hw_params
通过分析这些函数可知,它们涉及到了硬件操作,所以我们需要分别在这些hw_params函数所属的驱动中实现
三、具体实现
3.1 实现dma_open
(参考:soundsocsamsungdma.c)
static const struct snd_pcm_hardware s3c2440_dma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128*1024, .period_bytes_min = PAGE_SIZE, .period_bytes_max = PAGE_SIZE*2, .periods_min = 2, .periods_max = 128, .fifo_size = 32, }; static int s3c2440_dma_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; int ret; //去掉了和prtd相关的代码,因为这是samsung自己定义的用于dma操作的数据结构,我们不使用,而是自己来实现dma操作 /* 设置属性 */ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); //约束:采样周期必须是整数 snd_soc_set_runtime_hwparams(substream, &s3c2440_dma_hardware); //后续的snd_pcm_hw_constraints_complete会使用runtime->hw里的信息来调用一系列的snd_pcm_hw_constraint_xxx return 0; }
3.2 实现machine、codec_dai、cpu_dai、platform(dma)的hw_params函数
注:由于codec_dai(uda1341_hw_params)涉及代码最复杂,所以放在最后实现
3.2.1 实现s3c2440_uda1341.hw_params 即s3c2440_uda1341_hw_params
分析内核的s3c24xx_uda134x_hw_params()可知:它主要是调用了snd_soc_dai_set_fmt、snd_soc_dai_set_sysclk、snd_soc_dai_set_clkdiv来设置cpu_dai和codec_dai的数据格式以及时钟,而这些工作可以拆开后分别放在cpu_dai(见下文s3c2440_i2s_hw_params)和codec_dai(见下文uda1341_hw_params ())中。所以s3c2440_uda1341_hw_params不用实现。
注:关于怎样根据app提供的rate来计算clk、clk_source、fs_mod、div,可参考:在uda1341的i2s中256fs,384fs和512fs表示的实际意义和如何auto智能选择
3.2.2 实现s3c2440_i2s_dai_ops.hw_params 即s3c2440_i2s_hw_params
struct s3c2440_iis_regs { unsigned int iiscon ; unsigned int iismod ; unsigned int iispsr ; unsigned int iisfcon; unsigned int iisfifo; }; static volatile unsigned int *gpecon; static volatile struct s3c2440_iis_regs *iis_regs; static int s3c2440_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { /* 根据params设置IIS控制器 */ /* 参考裸板程序soc/iis.c的iis_init */ int tmp_fs; int i; int min = 0xffff; int pre = 0; unsigned int fs; struct clk *clk = clk_get(NULL, "pclk"); /* 配置GPIO用于IIS */ *gpecon &= ~((3<<0) | (3<<2) | (3<<4) | (3<<6) | (3<<8)); *gpecon |= ((2<<0) | (2<<2) | (2<<4) | (2<<6) | (2<<8)); /* bit[9] : Master clock select, 0-PCLK * bit[8] : 0 = Master mode * bit[7:6] : 10 = Transmit mode * bit[4] : 0-IIS compatible format * bit[3] : serial bit clock frequency select,1: 32fs,这样可以兼容8bit 和16bit的serial data bit per channel * bit[2] : 384fs, 确定了MASTER CLOCK之后, fs = MASTER CLOCK/384 * bit[1:0] : Serial bit clock frequency select, 32fs */ if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) iis_regs->iismod = (2<<6) | (0<<4) | (1<<3) | (1<<2) | (1); else if (params_format(params) == SNDRV_PCM_FORMAT_S8) iis_regs->iismod = (2<<6) | (0<<4) | (0<<3) | (1<<2) | (1); else return -EINVAL; /* 参考: s3c2440 datasheet: IIS prescaler(IISPSR) register * Master clock = PCLK/(n+1) * fs = Master clock / 384 * fs = PCLK / (n+1) / 384 */ //根据以上公式,对于app传入的fs采样率,计算出最接近的tmp_fs,以及对应的iispsr fs = params_rate(params); for (i = 0; i <= 31; i++) //IISPSR controlA, data value:[0~31] { tmp_fs = clk_get_rate(clk)/384/(i+1); if (ABS(tmp_fs, fs) < min) { min = ABS(tmp_fs, fs); pre = i; } } iis_regs->iispsr = (pre << 5) | (pre); /* * bit15 : Transmit FIFO access mode select, 1-DMA * bit13 : Transmit FIFO, 1-enable */ iis_regs->iisfcon = (1<<15) | (1<<13); /* * bit[5] : Transmit DMA service request, 1-enable * bit[1] : IIS prescaler, 1-enable */ iis_regs->iiscon = (1<<5) | (1<<1) ; clk_put(clk); return 0; } static int s3c2440_iis_init(void) { gpecon = ioremap(0x56000040, 4); iis_regs = ioremap(0x55000000, sizeof(struct s3c2440_iis_regs));
platform_device_register(&s3c2440_iis_dev); platform_driver_register(&s3c2440_iis_drv); return 0; } static void s3c2440_iis_exit(void) { platform_device_unregister(&s3c2440_iis_dev); platform_driver_unregister(&s3c2440_iis_drv); iounmap(gpecon); iounmap(iis_regs); }
3.2.3 实现s3c2440_dma_ops. hw_params 即s3c2440_dma_hw_params
分析内核的dma_hw_params()可知:主要工作涉及到数据的传输,所以我们把s3c2440_dma_hw_params留到下一节“数据传输”再实现。
3.2.4 实现uda1341_dai_ops.hw_params 即uda1341_hw_params
(为了简单, 在uda1341_init_regs里就设置好固定的参数(比如时钟、格式),并且由uda1341_soc_probe一次性调用)
#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000 #define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE) /* status control */ #define STAT0 (0x00) #define STAT0_RST (1 << 6) #define STAT0_SC_MASK (3 << 4) #define STAT0_SC_512FS (0 << 4) #define STAT0_SC_384FS (1 << 4) #define STAT0_SC_256FS (2 << 4) #define STAT0_IF_MASK (7 << 1) #define STAT0_IF_I2S (0 << 1) #define STAT0_IF_LSB16 (1 << 1) #define STAT0_IF_LSB18 (2 << 1) #define STAT0_IF_LSB20 (3 << 1) #define STAT0_IF_MSB (4 << 1) #define STAT0_IF_LSB16MSB (5 << 1) #define STAT0_IF_LSB18MSB (6 << 1) #define STAT0_IF_LSB20MSB (7 << 1) #define STAT0_DC_FILTER (1 << 0) #define STAT0_DC_NO_FILTER (0 << 0) #define STAT1 (0x80) #define STAT1_DAC_GAIN (1 << 6) /* gain of DAC */ #define STAT1_ADC_GAIN (1 << 5) /* gain of ADC */ #define STAT1_ADC_POL (1 << 4) /* polarity of ADC */ #define STAT1_DAC_POL (1 << 3) /* polarity of DAC */ #define STAT1_DBL_SPD (1 << 2) /* double speed playback */ #define STAT1_ADC_ON (1 << 1) /* ADC powered */ #define STAT1_DAC_ON (1 << 0) /* DAC powered */ /* data0 direct control */ #define DATA0 (0x00) #define DATA0_VOLUME_MASK (0x3f) #define DATA0_VOLUME(x) (x) #define DATA1 (0x40) #define DATA1_BASS(x) ((x) << 2) #define DATA1_BASS_MASK (15 << 2) #define DATA1_TREBLE(x) ((x)) #define DATA1_TREBLE_MASK (3) #define DATA2 (0x80) #define DATA2_PEAKAFTER (0x1 << 5) #define DATA2_DEEMP_NONE (0x0 << 3) #define DATA2_DEEMP_32KHz (0x1 << 3) #define DATA2_DEEMP_44KHz (0x2 << 3) #define DATA2_DEEMP_48KHz (0x3 << 3) #define DATA2_MUTE (0x1 << 2) #define DATA2_FILTER_FLAT (0x0 << 0) #define DATA2_FILTER_MIN (0x1 << 0) #define DATA2_FILTER_MAX (0x3 << 0) /* data0 extend control */ #define EXTADDR(n) (0xc0 | (n)) #define EXTDATA(d) (0xe0 | (d)) #define EXT0 0 #define EXT0_CH1_GAIN(x) (x) #define EXT1 1 #define EXT1_CH2_GAIN(x) (x) #define EXT2 2 #define EXT2_MIC_GAIN_MASK (7 << 2) #define EXT2_MIC_GAIN(x) ((x) << 2) #define EXT2_MIXMODE_DOUBLEDIFF (0) #define EXT2_MIXMODE_CH1 (1) #define EXT2_MIXMODE_CH2 (2) #define EXT2_MIXMODE_MIX (3) #define EXT4 4 #define EXT4_AGC_ENABLE (1 << 4) #define EXT4_INPUT_GAIN_MASK (3) #define EXT4_INPUT_GAIN(x) ((x) & 3) #define EXT5 5 #define EXT5_INPUT_GAIN(x) ((x) >> 2) #define EXT6 6 #define EXT6_AGC_CONSTANT_MASK (7 << 2) #define EXT6_AGC_CONSTANT(x) ((x) << 2) #define EXT6_AGC_LEVEL_MASK (3) #define EXT6_AGC_LEVEL(x) (x) #define UDA1341_L3ADDR 5 //即UDA1341的设备地址,见 uda1341 datasheet p.11. #define UDA1341_DATA0_ADDR ((UDA1341_L3ADDR << 2) | 0) #define UDA1341_DATA1_ADDR ((UDA1341_L3ADDR << 2) | 1) #define UDA1341_STATUS_ADDR ((UDA1341_L3ADDR << 2) | 2) /* UDA1341 registers */ #define UDA1341_DATA00 0 #define UDA1341_DATA01 1 #define UDA1341_DATA10 2 #define UDA1341_EA000 3 #define UDA1341_EA001 4 #define UDA1341_EA010 5 #define UDA1341_EA100 6 #define UDA1341_EA101 7 #define UDA1341_EA110 8 #define UDA1341_DATA1 9 #define UDA1341_STATUS0 10 #define UDA1341_STATUS1 11 #define UDA1341_REG_NUM 12 #define UDA1341_EXTADDR_PREFIX 0xC0 #define UDA1341_EXTDATA_PREFIX 0xE0 /* 所有寄存器的默认值 */ static const char uda1341_reg[UDA1341_REG_NUM] = { /* DATA0 */ 0x00, 0x40, 0x80, /* Extended address registers */ 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, /* data1 */ 0x00, /* status regs */ 0x00, 0x83, }; static const char uda1341_reg_addr[UDA1341_REG_NUM] = { UDA1341_DATA0_ADDR, UDA1341_DATA0_ADDR, UDA1341_DATA0_ADDR, 0, 1, 2, 4, 5, 6, //扩展寄存器的地址EA2,EA1,EA0,见:UDA1341 datasheet “7.21 programming the sound processing and other features”. UDA1341_DATA1_ADDR, UDA1341_STATUS_ADDR, UDA1341_STATUS_ADDR }; static const char uda1341_data_bit[UDA1341_REG_NUM] = { 0 /* VC */, (1<<6)/* BB,TR */, (1<<7)/* PP,DE,MT,M */, 0, 0, 0, 0, 0, 0,//6个扩展寄存器不使用uda1341_data_bit,而是在发送数据时,直接| UDA1341_EXTDATA_PREFIX 0, //data1 用于读数据,用不着 0/* status0 */, (1<<7)/* status1 */, }; static volatile unsigned int *gpbdat; static volatile unsigned int *gpbcon; static int uda1341_soc_probe(struct snd_soc_codec *codec) { int ret; uda1341_init_regs(codec); return ret; } /* * The codec has no support for reading its registers except for peak level... */ static inline unsigned int uda1341_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u8 *cache = codec->reg_cache; if (reg >= UDA1341_REG_NUM) return -1; return cache[reg]; } /*以下有关用GPIO模拟L3接口的函数,参考裸板程序的set_mod、set_clk、set_dat、sendbyte、l3_write */ static void set_mod(int val) { if (val) { *gpbdat |= (1<<2); } else { *gpbdat &= ~(1<<2); } } static void set_clk(int val) { if (val) { *gpbdat |= (1<<4); } else { *gpbdat &= ~(1<<4); } } static void set_dat(int val) { if (val) { *gpbdat |= (1<<3); } else { *gpbdat &= ~(1<<3); } } static void sendbyte(unsigned int byte) { int i; for (i = 0; i < 8; i++) { set_clk(0); udelay(1); set_dat(byte & 1); udelay(1); set_clk(1); udelay(1); byte >>= 1; } } static void l3_write(u8 addr, u8 data) { set_clk(1); set_dat(1); set_mod(1); udelay(1); set_mod(0); udelay(1); sendbyte(addr); udelay(1); set_mod(1); sendbyte(data); set_clk(1); set_dat(1); set_mod(1); } static int uda1341_write_reg(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { u8 *cache = codec->reg_cache; /* 先保存 */ if (reg >= UDA1341_REG_NUM) return -1; cache[reg] = value; /* 再写入硬件 */ /* 对于EA,需要调用2次l3_write */ if ((reg >= UDA1341_EA000) && (reg <= UDA1341_EA110)) { l3_write(UDA1341_DATA0_ADDR, uda1341_reg_addr[reg] | UDA1341_EXTADDR_PREFIX); l3_write(UDA1341_DATA0_ADDR, value | UDA1341_EXTDATA_PREFIX); } else { l3_write(uda1341_reg_addr[reg], value | uda1341_data_bit[reg]); } return 0; } static struct snd_soc_codec_driver soc_codec_dev_uda1341 = { .probe = uda134x_soc_probe, // .remove = uda134x_soc_remove, // .suspend = uda134x_soc_suspend, // .resume = uda134x_soc_resume, /* UDA1341的寄存器不支持读操作 * 要知道某个寄存器的当前值, * 只能在写入时保存起来 */ .reg_cache_size = sizeof(uda134x_reg), .reg_word_size = sizeof(u8), .reg_cache_default = uda134x_reg, .reg_cache_step = 1, .read = uda134x_read_reg_cache, .write = uda134x_write, // .set_bias_level = uda134x_set_bias_level, }; //参考: 裸板程序uda1341_init static void uda1341_init_regs(struct snd_soc_codec *codec) { /* GPB 4: L3CLOCK */ /* GPB 3: L3DATA */ /* GPB 2: L3MODE */ *gpbcon &= ~((3<<4) | (3<<6) | (3<<8)); *gpbcon |= ((1<<4) | (1<<6) | (1<<8)); /* bit[6] : reset bit[5:4] : system clk frequency, 1:384fs bit[3~1] : input data format, 0:iis bit[0] : DC filter, 1:yes */ uda1341_write_reg(codec, UDA1341_STATUS0, 0x40 | STAT0_SC_384FS | STAT0_DC_FILTER); // reset uda1341 uda1341_write_reg(codec, UDA1341_STATUS1, STAT1_ADC_ON | STAT1_DAC_ON); //打开ADC和DAC通道 uda1341_write_reg(codec, UDA1341_DATA00, DATA0_VOLUME(0x0)); // maximum volume uda1341_write_reg(codec, UDA1341_DATA01, DATA1_BASS(0)| DATA1_TREBLE(0)); uda1341_write_reg(codec, UDA1341_DATA10, 0); // not mute } static int uda1341_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { /* 根据params的值,设置UDA1341的寄存器 * 比如时钟设置,格式 */ /* 为了简单, 在uda1341_init_regs里就设置好时钟、格式等参数 */ return 0; }
四、参考资料
1. 韦东山 嵌入式Linux视频教程_3期项目实战之ALSA声卡:第2课第1.1_17节_ALSA声卡08_从零编写之框架
2. DroidPhone 《Linux ALSA 声卡驱动》
3. gliethttp 在uda1341的i2s中256fs,384fs和512fs表示的实际意义和如何auto智能选择
以上是关于韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之参数设置(基于优龙FS2410开发板,UDA1341声卡)的主要内容,如果未能解决你的问题,请参考以下文章
韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之数据传输(基于优龙FS2410开发板,UDA1341声卡)
学员源鑫笔记韦东山物联网流媒体实战项目-智慧家居视频监控系统(值得收藏)...
学员源鑫笔记韦东山物联网流媒体实战项目-智慧家居视频监控系统(值得收藏)...
韦东山freeRTOS系列教程:入门文档+视频教程+进阶视频教程(全部免费的freeRTOS系列教程freeRTOS学习路线)
韦东山freeRTOS系列教程:入门文档+视频教程+进阶视频教程(全部免费的freeRTOS系列教程freeRTOS学习路线)