Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2132298
  • 博文数量: 288
  • 博客积分: 10594
  • 博客等级: 上将
  • 技术积分: 3469
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-27 19:27
文章分类

全部博文(288)

文章存档

2012年(4)

2011年(30)

2010年(40)

2009年(32)

2008年(71)

2007年(79)

2006年(32)

分类: LINUX

2011-10-13 09:54:40

君正4760B的audio(OSS) 驱动分析

    君正的audio驱动使用了传统的OSS模式, 真是BT,大家都在用ALSA,他
还用OSS, 在网上详细资料甚少,在这里记录一下自己的过程,希望有人能够用
到。 

文章作者: dyronchina@gmail.com, 文章不断完善中....

系统环境: 
CPU Jz4760b
Dai I2s
Codec 内部
简介:

    君正的codec有两种接法,一种使用内部的codec,另外一种使用外部的
codec, 我们使用的是内部codec, 连接codec的接口有三种, PCM, I2S, 
AC-LINK, 我们使用的是I2S.

    按照惯例,先统一一下术语:
       
       AIC:
            君正的AC’97 and I2S Controller

       采样频率:

           是指每秒采样的次数。 8k, 44.1k, 48k等等

       量化精度:

           量化精度是指对采样数据分析的精度,量化精度越高,声音越逼真。

连接方式

 

 

 

 

OSS的驱动结构

         

      关于OSS架构,不用太多介绍了吧,OSS与ALSA不同,ALSA提供一堆接口与库来供上
层用户访问, OSS主要提供两个基本设备,dsp, mixer, mixer主要用于对codec的控制, dsp
主要用于播放和录音。

         下边是OSS的注册流程图:

注册设备:

    首先注册设备,  arch/mips/mach-jz4760b/boards/m7r_3/m7r_3-audio.c中注册了audio用
到的switch_gpio设备和系统操作的接口, mixer设备等。

  1. #if CONFIG_GPIO_HP_DETECT  
  2. struct gpio_switch_platform_data jz_hp_switch_data = {  
  3.     .name       = "h2w",  
  4.     .gpio       = GPIO_HEAD_DET,  
  5.     .state_on   = "1",  
  6.     .state_off  = "0",  
  7.     .valid_level = 0,  
  8. };  
  9.   
  10. struct platform_device jz_hp_switch_device = {  
  11.     .name = "switch-gpio",  
  12.     .id   = -1,  
  13.     .dev  = {  
  14.         .platform_data = &jz_hp_switch_data,  
  15.     },  
  16. };  
  17. #endif  

注册audio的switch-gpio设备,会在switch-gpio.c中调用,用于耳机插拔的检测,其中
GPIO_HEAD_DET宏定义了用于耳机检测的GPIO引脚。

  1. static struct platform_device jz_dlv_device = {  
  2.     .name = "jz_dlv",  
  3.     .id   = -1,  
  4.     .dev  = {  
  5.         .platform_data = &jz_dlv_platform_data,  
  6.     },  
  7. };  
  8.   
  9.   
  10. /*-----------------------*/  
  11.   
  12.   
  13. static int __init m7r_3_dlv_board_init(void)  
  14. {  
  15.     int ret = 0;  
  16.   
  17.   
  18.     ret = platform_device_register(&jz_dlv_device);  
  19.   
  20.   
  21.     return ret;  
  22. }  
  23.   
  24. static jz_dlv_platform_data_t jz_dlv_platform_data = {  
  25.     .dlv_replay_volume_base = M7R_3_REPLAY_VOLUME_BASE,  
  26.     .dlv_record_volume_base = M7R_3_RECORD_VOLUME_BASE,  
  27.     /*set vaule ROUTE_COUNT will use default route*/  
  28.     .default_replay_route = ROUTE_COUNT,  
  29.     .default_record_route = ROUTE_COUNT,  
  30.     .default_call_record_route = ROUTE_COUNT,  
  31.   
  32.   
  33.     .dlv_set_device = m7r_3_dlv_set_device,  
  34.     .dlv_set_gpio_before_set_route = m7r_3_dlv_set_gpio_before_set_route,  
  35.     .dlv_set_gpio_after_set_route = m7r_3_dlv_set_gpio_after_set_route,  
  36.     .dlv_init_part = m7r_3_dlv_init_part,  
  37.     .dlv_reset_part = m7r_3_dlv_reset_part,  
  38.     .dlv_turn_off_part = m7r_3_dlv_turn_off_part,  
  39.     .dlv_shutdown_part = m7r_3_dlv_shutdown_part,  
  40.     .dlv_suspend_part = m7r_3_dlv_suspend_part,  
  41.     .dlv_resume_part = m7r_3_dlv_resume_part,  
  42.     .dlv_anti_pop_part = m7r_3_dlv_anti_pop_part,  
  43. };  

    注册jz_dlv设备,主要是为了将jz_dlv_platform_data传给jz_dlv中使用。 在jz_dlv.c中
赋值,如下图所示:

  1. static int jz_dlv_probe(struct platform_device *pdev)  
  2. {  
  3.     dlv_platform_data = pdev->dev.platform_data;  
  4.   
  5.   
  6.     return 0;  
  7. }  
  8.   
  9.   
  10. static struct platform_device jz_snd_device = {  
  11.     .name = "mixer",  
  12.     .id = -1,  
  13.     .dev = {  
  14.         .platform_data = &jz_snd_endpoints,  
  15.     },  
  16. };  
  17. /* - Sound device */  

    注册mixer设备, 在arch/mips/mach-jz4760b/common/platform.c中,主要用于调用到
jz47XX中的probe函数。 Endpoints结构如下:

  1. static struct msm_snd_endpoints jz_snd_endpoints = {  
  2.     .endpoints = snd_endpoints_list,  
  3.     .num = ARRAY_SIZE(snd_endpoints_list),  
  4. };  


 


注册dsp及mixer。

Audio首先是注册上jzdlv_ioctl, 然后再进入mixer的probe, 注册mixer及dsp设备。

  1. static int __init init_dlv(void)  
  2. {  
  3.     int retval;  
  4.   
  5.   
  6.     cpm_start_clock(CGM_AIC);  
  7.   
  8.   
  9.     spin_lock_init(&dlv_irq_lock);  
  10.   
  11.   
  12.     INIT_WORK(&dlv_irq_work, dlv_irq_work_handler);  
  13.   
  14.   
  15.     dlv_work_queue = create_singlethread_workqueue("dlv_irq_wq");  
  16.   
  17.   
  18.     if (!dlv_work_queue) {  
  19.         // this can not happen, if happen, we die!  
  20.         BUG();  
  21.     }  
  22.   
  23.   
  24.     register_jz_codecs((void *)jzdlv_ioctl);  

    首先在module_init中通过register_jz_codecs注册jzdlv_ioctl方法,jzdlv_ioctl中全是
对dsp操作的函数。

  1. void register_jz_codecs(void *func)  
  2. {  
  3.     int i;  
  4.   
  5.   
  6.     ENTER();  
  7.   
  8.   
  9.     for (i = 0; i < NR_I2S; i++) {  
  10.         if (the_codecs[i].codecs_ioctrl == 0) {   
  11.             printk("register codec %x\n",(unsigned int)func);  
  12.             the_codecs[i].id = i;  
  13.             the_codecs[i].codecs_ioctrl = func;  
  14.             init_MUTEX(&(the_codecs[i].i2s_sem));  
  15.             break;  
  16.         }  
  17.     }  
  18. f  
  19.   
  20.     LEAVE();  
  21. }  

    看上边可知,我们系统中只有一个codec,其实就是将jzdlv_ioctl赋值给the_codec[0], 
并将the_codecs[0]的信号量进行初始化。 

  1. "white-space:pre">   dlv_reset_part();  
  2.     retval = request_irq(IRQ_AIC, dlv_codec_irq, IRQF_DISABLED, "dlv_codec_irq", NULL);  
  3.     if (retval) {  
  4.         printk("JZ DLV: Could not get AIC CODEC irq %d\n", IRQ_AIC);  
  5.         return retval;  
  6.     }  
  7.   
  8.   
  9. #ifdef CONFIG_HP_SENSE_DETECT  
  10.     retval = platform_driver_register(&jz_hp_switch_driver);  
  11.     if (retval) {  
  12.         printk("JZ HP Switch: Could net register headphone sense switch\n");  
  13.         return retval;  
  14.     }  
  15. #endif  
  16.   
  17.   
  18.     retval = platform_driver_register(&jz_dlv_driver);  
  19.     if (retval) {  
  20.         printk("JZ CODEC: Could net register jz_dlv_driver\n");  
  21.         return retval;  
  22.     }  
  23.   
  24.   
  25.     return 0;  
  26. }  

    后边申请了AIC的中断,用于处理中断及短路保护处理。 Platform_driver_register注册
了jz_dlv_driver驱动,就将前面所讲的jz_dlv_platform_data 赋值给了dlv_platform_data 。 

  1. static struct platform_driver snd_plat_driver = {  
  2.     .probe      =  init_jz_i2s,  
  3.     .driver     = {  
  4.         .name   = "mixer",  
  5.         .owner  = THIS_MODULE,  
  6.     },  
  7.     .suspend    = jz_i2s_suspend,  
  8.     .resume     = jz_i2s_resume,  
  9.     .shutdown       = jz_i2s_shutdown,  
  10. };  
  11.   
  12.   
  13. static int __init snd_init(void)  
  14. {  
  15.     return platform_driver_register(&snd_plat_driver);  
  16. }  

    注册名为“mixer”的snd_plat_driver,与上文讲到的jz_snd_device匹配, 进入
init_jz_i2s函数。 

  1. static int __init init_jz_i2s(struct platform_device *pdev)  
  2. {  
  3.     struct i2s_codec *default_codec = &(the_codecs[0]);  
  4.     int errno;  
  5.     int fragsize;  
  6.     int fragstotal;  
  7.   
  8.   
  9.     cpm_start_clock(CGM_AIC);  
  10.   
  11.   
  12.     REG_AIC_I2SCR |= AIC_I2SCR_ESCLK;  
  13.   
  14.   
  15.     i2s_controller_init();  
  16.     if (default_codec->codecs_ioctrl == NULL) {    
  17.         printk("default_codec: not ready!");  
  18.         return -1;  
  19.     }  
  20.   
  21.   
  22.     default_codec->codecs_ioctrl(default_codec, CODEC_INIT, 0);  
  23.   
  24.   
  25.     if ((errno = probe_jz_i2s(&the_i2s_controller)) < 0) {  
  26.         return errno;  
  27.     }  
  28.   
  29.   
  30.     /* May be external CODEC need it ... 
  31.      * default_codec->codecs_ioctrl(default_codec, CODEC_SET_GPIO_PIN, 0);    
  32.      */  
  33.   
  34.   
  35.     attach_jz_i2s(the_i2s_controller);  

         首先将the_codec[0]赋值给了default_codec,此时the_codec[0]就是刚才注册的dsp设
备操作方法。 由于register_jz_codecs是在module_init中调用的,比probe要早,所以这个
时候一定注册成功了。 

         接着对AIC 的寄存器进行初始化,设置为I2S模式,内部codec模式等等。

         此时调用DSP中的codec_init初始化codec设备。 初始完成codec后,进入probe_jz_i2s
对the_i2s_controller进行内存分配与基本的信息初始化。

         接下来进入attach_jz_i2s,进行mixer与dsp设备注册。 

  1. static void __init attach_jz_i2s(struct jz_i2s_controller_info *controller)  
  2. {  
  3.     char    *name = NULL;  
  4.     int adev = 0; /* No of Audio device. */  
  5.   
  6.   
  7.     ENTER();  
  8.   
  9.   
  10.     name = controller->name;  
  11.   
  12.   
  13.     /* Initialize I2S CODEC and register /dev/mixer. */  
  14.     if (jz_i2s_codec_init(controller) <= 0) {  
  15.         goto mixer_failed;  
  16.     }  
  17.   
  18.   
  19.     /* Initialize AIC controller and reset it. */  
  20.     jz_i2s_reinit_hw(controller->i2s_codec,1);  
  21.     adev = register_sound_dsp(&jz_i2s_audio_fops, -1);  
  22.     if (adev < 0) {  
  23.         goto audio_failed;  
  24.     }  
  25.   
  26.   
  27.     controller->dev_audio = adev;  
  28.   
  29.   
  30.     LEAVE();  
  31. }  
  32.   
  33. /* I2S codec initialisation. */  
  34. static int __init jz_i2s_codec_init(struct jz_i2s_controller_info *controller)  
  35. {  
  36.     int i;  
  37.   
  38.   
  39.     ENTER();  
  40.   
  41.   
  42.     for (i = 0; i < NR_I2S; i++) {  
  43.         the_codecs[i].private_data = controller;  
  44.         if (i2s_probe_codec(&the_codecs[i]) == 0) {  
  45.             break;  
  46.         }  
  47.         if ((the_codecs[i].dev_mixer = register_sound_mixer(&jz_i2s_mixer_fops,   
  48.                       the_codecs[i].id)) < 0) {  
  49.             printk(KERN_ERR "JZ I2S: couldn't register mixer!\n");  
  50.             break;  
  51.         }  
  52.           
  53.     }  
  54.     controller->i2s_codec = &the_codecs[0];  
  55.   
  56.   
  57.     LEAVE();  
  58.     return i;  
  59. }  

         jz_i2s_codec_init中通过register_sound_mixer将jz_i2s_mixer_fops与the_codecs[0]设备

注册在一起。 最后将the_codecs[0]赋值给刚才分配内存的controller->i2s_codec。 这就完成
了mixer设备的注册, 下面看看jz_i2s_mixer_fops的函数。 

  1. static struct file_operations jz_i2s_mixer_fops =   
  2. {  
  3.     owner:      THIS_MODULE,  
  4.     ioctl:      jz_i2s_ioctl_mixdev,  
  5.     open:       jz_i2s_open_mixdev,  
  6.     write:      jz_i2s_write_mixdev,  
  7. };  

      这里open与write并没有做太多实质的内容, 主要通过ioctl控制mixer设备的声音大

小,route与standby等。 

  1. /* Initialize AIC controller and reset it. */  
  2. jz_i2s_reinit_hw(controller->i2s_codec,1);  
  3. adev = register_sound_dsp(&jz_i2s_audio_fops, -1);  
  4. if (adev < 0) {  
  5.     goto audio_failed;  
  6. }  
  7.   
  8.   
  9. controller->dev_audio = adev;  

     回到attach_jz_i2s函数, 通过register_sound_dsp注册dsp设备, dsp的操作接口多一

点, 看下面结构体。 

  1. /* static struct file_operations jz_i2s_audio_fops */  
  2. static struct file_operations jz_i2s_audio_fops = {  
  3.     owner:      THIS_MODULE,  
  4.     open:       jz_audio_open,  
  5.     release:    jz_audio_release,  
  6.     write:      jz_audio_write,  
  7.     read:       jz_audio_read,  
  8.     ioctl:      jz_audio_ioctl,  
  9.     mmap:       jz_audio_mmap  
  10. };  

    这里介绍一下君正dsp的读写操作方式, 有两种, 一种是众所周知的通过read&write
实现的, 另一种是通过mmap接口,mmap出去一段内存,应用程序直接写mmap的内存,
然后通过写入direct_info info 来触发驱动来更新,后者相对操作更快一些。 

          现在回到init_jz_i2s函数完成播放与录音的DMA内存初始化。 

  1. /* Now the command is not supported by DLV CODEC ... 
  2.  * default_codec->codecs_ioctrl(default_codec, CODEC_SET_VOLUME_TABLE, 0); 
  3.  */  
  4. fragsize = JZCODEC_RW_BUFFER_SIZE * PAGE_SIZE;  
  5. fragstotal = JZCODEC_RW_BUFFER_TOTAL;  
  6.   
  7.   
  8. audio_init_endpoint(&out_endpoint, fragsize, fragstotal);  
  9. audio_init_endpoint(&in_endpoint, fragsize, fragstotal);  
  10.   
  11.   
  12. printk("JZ I2S OSS audio driver initialized\n");  
  13.   
  14.   
  15. LEAVE();  

 

至此OSS系统的mixer与dsp都注册完成了,对于各种播放和录音的通路流程在下将再分析 。 

 

阅读(3009) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~