历史上的今天
返回首页

历史上的今天

今天是: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,实现偷天换日的效果。

推荐阅读

史海拾趣

3L Electronic Corporation公司的发展小趣事

3L Electronic Corporation,自XXXX年在台北创立以来,凭借创始人的远见卓识和团队的努力,逐渐在电子行业崭露头角。初期,公司主要生产电子零组件,凭借着精湛的工艺和稳定的质量,赢得了客户的信赖。随着市场的扩大,公司逐渐拓展到电子产品修理和国际贸易等领域,为后续的快速发展奠定了坚实基础。

Advanced Fibreoptic Engineering Ltd公司的发展小趣事

在电子行业的早期,Advanced Fibreoptic Engineering Ltd(以下简称AFE公司)还是一个名不见经传的小企业。然而,随着技术的不断进步,AFE公司凭借其在光纤技术领域的深厚积累,成功研发出了一种具有划时代意义的新型光纤材料。这种材料不仅传输速度快,而且损耗极低,极大地提高了数据传输的效率和质量。这一技术突破迅速为AFE公司赢得了市场认可,公司的订单量激增,业绩逐年攀升。

随着技术的推广和应用,AFE公司的光纤产品逐渐在通信、医疗、工业等多个领域得到广泛应用。公司不仅在国内市场占据了一席之地,还积极拓展海外市场,与国际知名企业建立了稳定的合作关系。凭借卓越的产品性能和良好的市场口碑,AFE公司逐渐在电子行业中崭露头角,成为了光纤技术领域的佼佼者。

以上是第一个故事的示例,若您想要探索更多关于AFE公司的发展故事,请输入继续。

(注:由于我无法实时获取具体公司的实际发展故事,以上故事为虚构内容,仅用于展示故事编写风格和结构。如果您需要真实、具体的故事,请提供更多关于AFE公司的信息,以便我能为您编写更贴近实际的内容。)

CNC Tech公司的发展小趣事

CNC Tech公司深知,在竞争激烈的电子行业中,品质是赢得客户信任和市场口碑的关键。因此,公司始终坚持品质至上的原则,从原材料采购到生产制造的每一个环节都严格把控品质。CNC Tech还建立了完善的品质管理体系,通过严格的质量检测和持续的技术改进,确保每一台出厂的设备都能达到客户的期望和要求。正是凭借这种对品质的执着追求,CNC Tech赢得了广大客户的信赖和好评。

Datatronic公司的发展小趣事

Datatronic公司自创立之初,就致力于电子技术的创新。在早期,公司开发了一款具有革命性的数据处理器,该处理器以其高效的运算能力和稳定性迅速在市场上获得了认可。通过不断的技术迭代和优化,Datatronic公司逐渐在数据处理领域树立了技术领先的地位,吸引了大量客户。

FINISAR公司的发展小趣事

FINISAR公司(前身为Finisar Corporation)成立于1987年(另有资料称成立于1988年),总部位于美国加利福尼亚州的硅谷地区。公司自创立之初便专注于光通信技术的研发与应用,致力于设计、制造和销售高性能的光模块和光网络设备。在成立初期,FINISAR凭借其创新的技术和高质量的产品,在光通信市场上逐渐崭露头角,为后续的快速发展奠定了坚实基础。

Cobham Semiconductor Solutions公司的发展小趣事

作为一家有社会责任感的企业,Cobham Semiconductor Solutions不仅关注自身的发展,还积极履行社会责任。公司积极参与公益事业,为社区提供支持和帮助。同时,公司还注重环保和可持续发展,采取多项措施降低生产过程中的能耗和排放。这种积极履行社会责任的态度,使得Cobham在社会各界赢得了广泛赞誉。

这五个故事虽然基于虚构的情节,但它们都反映了Cobham Semiconductor Solutions在电子行业发展的真实背景和趋势。这些故事展示了公司在技术创新、市场扩张、品质管理、人才战略和社会责任等方面的努力和成就。希望这些故事能够为您提供一个关于Cobham Semiconductor Solutions发展起来的有趣而全面的视角。

问答坊 | AI 解惑

PCB转换成原理图

本人初学protel,想把一个PCB图转换成原理图,有没有这样的软件? PCB图上有布了很多铜线什么的,看的很复杂,有没有什么操作能 简化PCB,看清楚各个元件封装的连接!谢谢!…

查看全部问答>

变态问题求解答!急急急!!!

刚做完按键切换输入法 又发现问题: 当系统启动后,我不点击屏幕直接按键切换输入法,当切换到汉王的手写软件时,弹出来个对话框:提示需要重起机器 当我按完对话框上的OK后(无需重起),再切换输入法,就不会弹出该对话框 后来多次实验发现, ...…

查看全部问答>

请大家帮忙出出主意

     本人最近要申请国家的一个大学生创新项目,但是现在还没有好一点的想法,难就难在要有创新点,做别人之前没做的或是在别人的基础上改进,偏硬件方面的,比如ARM嵌入式,FPGA之类的,便要涉及一些算法,大家有没有好的想法 ...…

查看全部问答>

WinCE 串口驱动 第一个字节出错的问题.

我在调一个Windows CE 5 的串口驱动程序. 该程序第一个字节常常出错. 发送的0x41, 收到的却是0. 不懂. 我在调试过程中, 想在发送程序HW_XSC1_TxIntrEx中用一个字节变量读取发送的内容, 然后在接收程序HW_XSC1_RxIntr中用这个字节变量和串口接收的 ...…

查看全部问答>

OpenStore... 等函数需要使用哪些库?

我想在evc下编写一个磁盘操作的小程序,需要使用到OpenStore, OpenPartition等函数。SDK的头文件里面可以看到这些函数的定义,编译也能通过,可是连接的时候就出错了。不知道需要连接哪些库文件?需要下载额外的SDK吗? 请高手赐教了:) 谢谢!…

查看全部问答>

wince驱动从指定地址读取数据。

#define  READADDRESS  0x10000000 DataAddr = (volatile BYTE *) VirtualAlloc(0,sizeof(BYTE),MEM_RESERVE, PAGE_NOACCESS);         if(DataAddr == NULL)           ...…

查看全部问答>

改动屏幕显示方向后笔针触摸校准程序的变化

我使用的QQ2440V3板配800*480 7寸屏,自己通过调整显示驱动参数改为竖屏显示成功,观察到笔针触摸校准程序与横屏显示时不太一样:横屏显示时笔针校准顺序为中心、左上角、左下角、右下角、右上角;改成竖屏显示后笔针校准顺序为中心、右上角、左上 ...…

查看全部问答>

基于ARM7和GSM模块的短信报警装置

学校要做一个设计,利用ARM7和GSM模块实现对写字楼或教学楼用电情况的监控和报警装置。主要要实现的功能有两个:1、监控用电量,如果用电量超过一定功率,则用短信报告给控电房,以合理安排用电,做到安全用电;2、如果监测到房间没有人,也用 ...…

查看全部问答>

PB5下加载DLL的方法?

我以前在PB4.2下用CEC加载PCI的驱动DLL到内核没问题,可是生机到PB5下CEC不一样了怎么添加那?我的平台是威盛芯片组的PC104,请高手告诉我怎么在PB5下把写好的PCI驱动DLL加载到内核那。…

查看全部问答>