历史上的今天
今天是:2025年07月21日(星期一)
2021年07月21日 | 23.声卡驱动(移植+测试)
2021-07-21 来源:eefocus
linux-2.6.22.6soundSound_core.c
1.声音三要素
1.1采样频率
音频采样率是指录音设备在一秒钟内对声音信号的采样次数, 常用的采样率有:
8KHz - 电话所用采样率, 对于人的说话已经足够清除
22.05KHz - 无线电广播所用采样率
32KHz - miniDV 数码视频、DAT所用采样率
44.1KHz - 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率
48KHz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
50KHz - 商用数字录音机所用采样率
96K - BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨等所用采样率
而2440开发板的采样频率IISRCK最高可以达到96KHz,满足了很多常用的采样场合,如下图所示:

其中CODECLK便是MCLK
1.2量化位数
指每个采样点里传输的数字信号次数,如下图所示, 其中蓝线表示模拟信号,红线表示数字信号,量化位越高,数字信号就越可能接近原始信号,音质越好

一般的量化位数为:
8位: 分成 256 次;
16位: 分为65536次, 已到 CD 标准;
32位: 分为 4294967296次,很少用到
2440的开发板只支持8位,16位,如下图所示:
其中LRCK就是采样频率,当LRCK为低时,表示传输的采样数据是左声道,当LRCK为高时,表示传输的采样数据是右声道,每个采样点,SD(serial data)都可以传输8位,或16位数字信号(从低位到高位传输)

1.3声道数
常有单声道和立体声之分,(有的也处理成两个喇叭输出同一个声道的声音),而立体声更能感受到空间效果,但数据量翻倍
所以,声音的每秒数据量(字节/s)= (采样频率 × 量化位数 × 声道数) / 8;
2. WM9876声卡硬件分析
声卡是负责录音、播音、调节音量和声音合成等的一种多媒体板卡
本节使用的声卡是2440板上自带的WM9876声卡

当我们播放声音时 ,将数字信号传入I2SDO脚,声卡便通过解码,产生模拟信号到喇叭/耳机
录音时,声卡便获取麦克风的模拟信号,编码出数字信号到I2SDI引脚上
WM8976接口分为两种:I2S接口(提供音频接收和发送)、控制接口(控制音量大小,使能各个输出通道等)
IIS接口相关的引脚如下
MCLK : 主机为编解码芯片提供的系统同步时钟 ( Master/system clock input ) ,在2440中,一般设置为256fs,或者384fs
BCLK: 编解码芯片提供的串行时钟信号 ( Audio bit clock output ) ,也就是量化位深,比如I2SIRCK=44.1khz,量化位为32位,则BCLK=44.1khz322
I2SLRCK: 采样频率信号,当为低电平时是采样的是左声道信号,为高电平是右声道信号,常见有44.1Khz(1fs)
I2SDI : ADC数据输入
I2SDO :DAC数据输出
如下图所示:

控制接口相关的引脚如下
CSB/GPIO1: 3线 控制数据使能引脚
SCLK: 3线/2线 时钟引脚
SDIN: 3线/2线 数据输入输出引脚
MODE: 3线/2线 控制选择,当MODE为高,表示为3线控制,MODE位低,表示2线控制,如下图所示:

其它引脚如下:
R/LOUT1:音频左/右输出通道1,外接耳机插孔
R/LOUT2:音频左/右输出通道2,未接
OUT3:单声道输出通道3,未接
OUT4:单声道输出通道4,未接
LIP/LIN:音频输入通道,外接麦克风
那么3线和2线的控制引脚又有什么区别?
3线控制:
如下图所示,3线控制,每周期都要传输16位数据(7位寄存器地址+9位寄存器数据),传输完成后,给CSB一个上升沿便完成一次数据的传输

2线控制:
如下图所示,2线控制就是I2C通信方式

本节的WM8976的MODE脚接的高电平,所以是3线控制
3.接下来便来分析linux内核的声卡系统
在linux声卡中存在两种声卡系统,一种是OSS(开放声音系统),一种是ALSA(先进Linux声音架构)。本节系统以OSS(Open Sound System)为例 ,
内核以linux-2.6.22.6版本为例,位于:linux-2.6.22.6soundSound_core.c
3.1首先进入入口函数
3.1.1init函数开始看起 注册平台
static int __init s3c2410_uda1341_init(void) {
memzero(&input_stream, sizeof(audio_stream_t));
memzero(&output_stream, sizeof(audio_stream_t));
return driver_register(&s3c2410iis_driver);
}
3.1.2probe调用
static struct device_driver s3c2410iis_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type,
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
};
3.1.3probe函数
static int s3c2410iis_probe(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct resource *res;
unsigned long flags;
printk ("s3c2410iis_probe...n");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
printk(KERN_INFO PFX "failed to get memory region resoucen");
return -ENOENT;
}
iis_base = (void *)S3C24XX_VA_IIS ;
if (iis_base == 0) {
printk(KERN_INFO PFX "failed to ioremap() regionn");
return -EINVAL;
}
iis_clock = clk_get(dev, "iis");
if (iis_clock == NULL) {
printk(KERN_INFO PFX "failed to find clock sourcen");
return -ENOENT;
}
/*使能时钟*/
clk_enable(iis_clock);
local_irq_save(flags);
/*配置GPIO管脚*/
/* GPB 4: L3CLOCK, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB4,1);
/* GPB 3: L3DATA, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB3,S3C2410_GPB3_OUTP);
/* GPB 2: L3MODE, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB2,S3C2410_GPB2_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB2,1);
/* GPE 3: I2SSDI */
s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI);
s3c2410_gpio_pullup(S3C2410_GPE3,0);
/* GPE 0: I2SLRCK */
s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_pullup(S3C2410_GPE0,0);
/* GPE 1: I2SSCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_pullup(S3C2410_GPE1,0);
/* GPE 2: CDCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK);
s3c2410_gpio_pullup(S3C2410_GPE2,0);
/* GPE 4: I2SSDO */
s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO);
s3c2410_gpio_pullup(S3C2410_GPE4,0);
local_irq_restore(flags);
/*iis总线初始化*/
init_s3c2410_iis_bus();
/*使用L3接口初始化芯片*/
init_uda1341();
/* 设置两个DMA通道:一个用于播放,另一个用于录音 */
output_stream.dma_ch = DMACH_I2S_OUT;
if (audio_init_dma(&output_stream, "UDA1341 out")) {
audio_clear_dma(&output_stream,&s3c2410iis_dma_out);
printk( KERN_WARNING AUDIO_NAME_VERBOSE
": unable to get DMA channelsn" );
return -EBUSY;
}
input_stream.dma_ch = DMACH_I2S_IN;
if (audio_init_dma(&input_stream, "UDA1341 in")) {
audio_clear_dma(&input_stream,&s3c2410iis_dma_in);
printk( KERN_WARNING AUDIO_NAME_VERBOSE
": unable to get DMA channelsn" );
return -EBUSY;
}
audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);
printk(AUDIO_NAME_VERBOSE " initializedn");
return 0;
}
3.1.4看下init_uda1341
static void init_uda1341(void)
{
/* GPB 4: L3CLOCK */
/* GPB 3: L3DATA */
/* GPB 2: L3MODE */
unsigned long flags;
uda1341_volume = 62 - ((DEF_VOLUME * 61) / 100);
uda1341_boost = 0;
// uda_sampling = DATA2_DEEMP_NONE;
// uda_sampling &= ~(DATA2_MUTE);
local_irq_save(flags);
s3c2410_gpio_setpin(S3C2410_GPB2,1);//L3MODE=1
s3c2410_gpio_setpin(S3C2410_GPB4,1);//L3CLOCK=1
local_irq_restore(flags);
/*地址模式和数据模式*/
uda1341_l3_address(UDA1341_REG_STATUS);
uda1341_l3_data(0x40 | STAT0_SC_384FS | STAT0_IF_MSB|STAT0_DC_FILTER); // reset uda1341
uda1341_l3_data(STAT1 | STAT1_ADC_ON | STAT1_DAC_ON);
uda1341_l3_address(UDA1341_REG_DATA0);
uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volume
uda1341_l3_data(DATA1 |DATA1_BASS(uda1341_boost)| DATA1_TREBLE(0));
uda1341_l3_data((DATA2 |DATA2_DEEMP_NONE) &~(DATA2_MUTE));
uda1341_l3_data(EXTADDR(EXT2));
#if 1
/* 对于MINI2440, */
uda1341_l3_data(EXTDATA(EXT2_MIC_GAIN(0x6)) | EXT2_MIXMODE_CH2);//input channel 2 select
#else
/* 对于TQ2440 */
uda1341_l3_data(EXTDATA(EXT2_MIC_GAIN(0x6)) | EXT2_MIXMODE_CH1);//input channel 1 select(input channel 2 off)
#endif
}
3.1.5uda1341_l3_address
static void uda1341_l3_address(u8 data)
{
int i;
unsigned long flags;
local_irq_save(flags);
// write_gpio_bit(GPIO_L3MODE, 0);
s3c2410_gpio_setpin(S3C2410_GPB2,0);
// write_gpio_bit(GPIO_L3CLOCK, 1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);
udelay(1);
/*发送地址的过程,参考时序图*/
for (i = 0; i < 8; i++) {
if (data & 0x1) {
s3c2410_gpio_setpin(S3C2410_GPB4,0);
s3c2410_gpio_setpin(S3C2410_GPB3,1);
udelay(1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);
} else {
s3c2410_gpio_setpin(S3C2410_GPB4,0);
s3c2410_gpio_setpin(S3C2410_GPB3,0);
udelay(1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);
}
data >>= 1;
}
s3c2410_gpio_setpin(S3C2410_GPB2,1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);
local_irq_restore(flags);
}
如下图所示:

入口函数里,只注册了一个主设备号为(SOUND_MAJOR)14的“sound”字符设备和class类,这里为什么没有创建设备节点?
是因为, 当注册声卡系统的驱动后,才会有设备节点,此时这里的代码是没有驱动的,后面会分析到
3.2 再来看看“sound”字符设备的file_perations:

这里只有个.open,为什么没有.read,.write函数?
显然在.open函数里做了某些处理,我们进入soundcore_open()来看看
3.3 soundcore_open()代码如下:
int soundcore_open(struct inode *inode, struct file *file)
{
int chain;
int unit = iminor(inode); //获取次设备号,通过次设备号来找声卡驱动
struct sound_unit *s;
const struct file_operations *new_fops = NULL; //定义一个新的file_operations
chain=unit&0x0F;
if(chain==4 || chain==5) /* dsp/audio/dsp16 */
{
unit&=0xF0;
unit|=3;
chain=3;
}
spin_lock(&sound_loader_lock);
s = __look_for_unit(chain, unit); //里面通过chains[chain]数组里找到sound_unit结构体
//一个sound_unit对应一个声卡驱动
if (s)
new_fops = fops_get(s->unit_fops); //通过sound_unit,获取对应的file_operations
... ...
if (new_fops) { //当找到file_operations
int err = 0;
const struct file_operations *old_fops = file->f_op;//设上次的file_operations等于当前的
file->f_op = new_fops; //设置系统的file_operations等于s-> unit_fops
spin_unlock(&sound_loader_lock);
if(file->f_op->open)
err = file->f_op->open(inode,file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
spin_unlock(&sound_loader_lock);
return -ENODEV;
}
通过上面的代码和注释分析到,系统声卡之所以只有一个open(),里面是通过次设备号来调用__look_for_unit()函数,找到chains[chain]数组里的驱动声卡sound_unit结构体,然后来替换系统声卡的file_operations,实现偷天换日的效果。
史海拾趣
|
刚做完按键切换输入法 又发现问题: 当系统启动后,我不点击屏幕直接按键切换输入法,当切换到汉王的手写软件时,弹出来个对话框:提示需要重起机器 当我按完对话框上的OK后(无需重起),再切换输入法,就不会弹出该对话框 后来多次实验发现, ...… 查看全部问答> |
|
我在调一个Windows CE 5 的串口驱动程序. 该程序第一个字节常常出错. 发送的0x41, 收到的却是0. 不懂. 我在调试过程中, 想在发送程序HW_XSC1_TxIntrEx中用一个字节变量读取发送的内容, 然后在接收程序HW_XSC1_RxIntr中用这个字节变量和串口接收的 ...… 查看全部问答> |
|
我想在evc下编写一个磁盘操作的小程序,需要使用到OpenStore, OpenPartition等函数。SDK的头文件里面可以看到这些函数的定义,编译也能通过,可是连接的时候就出错了。不知道需要连接哪些库文件?需要下载额外的SDK吗? 请高手赐教了:) 谢谢!… 查看全部问答> |
|
#define READADDRESS 0x10000000 DataAddr = (volatile BYTE *) VirtualAlloc(0,sizeof(BYTE),MEM_RESERVE, PAGE_NOACCESS); if(DataAddr == NULL) ...… 查看全部问答> |
|
我使用的QQ2440V3板配800*480 7寸屏,自己通过调整显示驱动参数改为竖屏显示成功,观察到笔针触摸校准程序与横屏显示时不太一样:横屏显示时笔针校准顺序为中心、左上角、左下角、右下角、右上角;改成竖屏显示后笔针校准顺序为中心、右上角、左上 ...… 查看全部问答> |
|
学校要做一个设计,利用ARM7和GSM模块实现对写字楼或教学楼用电情况的监控和报警装置。主要要实现的功能有两个:1、监控用电量,如果用电量超过一定功率,则用短信报告给控电房,以合理安排用电,做到安全用电;2、如果监测到房间没有人,也用 ...… 查看全部问答> |
|
我以前在PB4.2下用CEC加载PCI的驱动DLL到内核没问题,可是生机到PB5下CEC不一样了怎么添加那?我的平台是威盛芯片组的PC104,请高手告诉我怎么在PB5下把写好的PCI驱动DLL加载到内核那。… 查看全部问答> |




