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

来源:互联网 发布:java垃圾回收机制详解 编辑:程序博客网 时间:2024/04/28 01:44

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

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

文章作者: http://blog.csdn.net/dyron, 文章不断完善中....

系统环境: 

CPUJz4760bDaiI2sCodec内部

简介:

    君正的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设备等。

#if CONFIG_GPIO_HP_DETECTstruct gpio_switch_platform_data jz_hp_switch_data = {.name= "h2w",.gpio= GPIO_HEAD_DET,.state_on= "1",.state_off= "0",.valid_level = 0,};struct platform_device jz_hp_switch_device = {.name = "switch-gpio",.id   = -1,.dev  = {.platform_data = &jz_hp_switch_data,},};#endif

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

static struct platform_device jz_dlv_device = {.name = "jz_dlv",.id   = -1,.dev  = {.platform_data = &jz_dlv_platform_data,},};/*-----------------------*/static int __init m7r_3_dlv_board_init(void){int ret = 0;ret = platform_device_register(&jz_dlv_device);return ret;}static jz_dlv_platform_data_t jz_dlv_platform_data = {.dlv_replay_volume_base = M7R_3_REPLAY_VOLUME_BASE,.dlv_record_volume_base = M7R_3_RECORD_VOLUME_BASE,/*set vaule ROUTE_COUNT will use default route*/.default_replay_route = ROUTE_COUNT,.default_record_route = ROUTE_COUNT,.default_call_record_route = ROUTE_COUNT,.dlv_set_device = m7r_3_dlv_set_device,.dlv_set_gpio_before_set_route = m7r_3_dlv_set_gpio_before_set_route,.dlv_set_gpio_after_set_route = m7r_3_dlv_set_gpio_after_set_route,.dlv_init_part = m7r_3_dlv_init_part,.dlv_reset_part = m7r_3_dlv_reset_part,.dlv_turn_off_part = m7r_3_dlv_turn_off_part,.dlv_shutdown_part = m7r_3_dlv_shutdown_part,.dlv_suspend_part = m7r_3_dlv_suspend_part,.dlv_resume_part = m7r_3_dlv_resume_part,.dlv_anti_pop_part = m7r_3_dlv_anti_pop_part,};

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

static int jz_dlv_probe(struct platform_device *pdev){dlv_platform_data = pdev->dev.platform_data;return 0;}static struct platform_device jz_snd_device = {.name = "mixer",.id = -1,.dev = {.platform_data = &jz_snd_endpoints,},};/* - Sound device */

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

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


注册dsp及mixer。

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

static int __init init_dlv(void){int retval;cpm_start_clock(CGM_AIC);spin_lock_init(&dlv_irq_lock);INIT_WORK(&dlv_irq_work, dlv_irq_work_handler);dlv_work_queue = create_singlethread_workqueue("dlv_irq_wq");if (!dlv_work_queue) {// this can not happen, if happen, we die!BUG();}register_jz_codecs((void *)jzdlv_ioctl);

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

void register_jz_codecs(void *func){int i;ENTER();for (i = 0; i < NR_I2S; i++) {if (the_codecs[i].codecs_ioctrl == 0) {printk("register codec %x\n",(unsigned int)func);the_codecs[i].id = i;the_codecs[i].codecs_ioctrl = func;init_MUTEX(&(the_codecs[i].i2s_sem));break;}}fLEAVE();}

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

dlv_reset_part();retval = request_irq(IRQ_AIC, dlv_codec_irq, IRQF_DISABLED, "dlv_codec_irq", NULL);if (retval) {printk("JZ DLV: Could not get AIC CODEC irq %d\n", IRQ_AIC);return retval;}#ifdef CONFIG_HP_SENSE_DETECTretval = platform_driver_register(&jz_hp_switch_driver);if (retval) {printk("JZ HP Switch: Could net register headphone sense switch\n");return retval;}#endifretval = platform_driver_register(&jz_dlv_driver);if (retval) {printk("JZ CODEC: Could net register jz_dlv_driver\n");return retval;}return 0;}

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

static struct platform_driver snd_plat_driver = {.probe=  init_jz_i2s,.driver= {.name= "mixer",.owner= THIS_MODULE,},.suspend= jz_i2s_suspend,.resume= jz_i2s_resume,.shutdown       = jz_i2s_shutdown,};static int __init snd_init(void){return platform_driver_register(&snd_plat_driver);}

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

static int __init init_jz_i2s(struct platform_device *pdev){struct i2s_codec *default_codec = &(the_codecs[0]);int errno;int fragsize;int fragstotal;cpm_start_clock(CGM_AIC);REG_AIC_I2SCR |= AIC_I2SCR_ESCLK;i2s_controller_init();if (default_codec->codecs_ioctrl == NULL) {printk("default_codec: not ready!");return -1;}default_codec->codecs_ioctrl(default_codec, CODEC_INIT, 0);if ((errno = probe_jz_i2s(&the_i2s_controller)) < 0) {return errno;}/* May be external CODEC need it ... * default_codec->codecs_ioctrl(default_codec, CODEC_SET_GPIO_PIN, 0); */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设备注册。 

static void __init attach_jz_i2s(struct jz_i2s_controller_info *controller){char*name = NULL;intadev = 0; /* No of Audio device. */ENTER();name = controller->name;/* Initialize I2S CODEC and register /dev/mixer. */if (jz_i2s_codec_init(controller) <= 0) {goto mixer_failed;}/* Initialize AIC controller and reset it. */jz_i2s_reinit_hw(controller->i2s_codec,1);adev = register_sound_dsp(&jz_i2s_audio_fops, -1);if (adev < 0) {goto audio_failed;}controller->dev_audio = adev;LEAVE();}/* I2S codec initialisation. */static int __init jz_i2s_codec_init(struct jz_i2s_controller_info *controller){int i;ENTER();for (i = 0; i < NR_I2S; i++) {the_codecs[i].private_data = controller;if (i2s_probe_codec(&the_codecs[i]) == 0) {break;}if ((the_codecs[i].dev_mixer = register_sound_mixer(&jz_i2s_mixer_fops,                       the_codecs[i].id)) < 0) {printk(KERN_ERR "JZ I2S: couldn't register mixer!\n");break;}}controller->i2s_codec = &the_codecs[0];LEAVE();return i;}

         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的函数。 

static struct file_operations jz_i2s_mixer_fops = {owner:THIS_MODULE,ioctl:jz_i2s_ioctl_mixdev,open:jz_i2s_open_mixdev,write:jz_i2s_write_mixdev,};

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

小,route与standby等。 

/* Initialize AIC controller and reset it. */jz_i2s_reinit_hw(controller->i2s_codec,1);adev = register_sound_dsp(&jz_i2s_audio_fops, -1);if (adev < 0) {goto audio_failed;}controller->dev_audio = adev;

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

点, 看下面结构体。 

/* static struct file_operations jz_i2s_audio_fops */static struct file_operations jz_i2s_audio_fops = {owner:THIS_MODULE,open:jz_audio_open,release:jz_audio_release,write:jz_audio_write,read:jz_audio_read,ioctl:jz_audio_ioctl,mmap:       jz_audio_mmap};

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

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

/* Now the command is not supported by DLV CODEC ... * default_codec->codecs_ioctrl(default_codec, CODEC_SET_VOLUME_TABLE, 0); */fragsize = JZCODEC_RW_BUFFER_SIZE * PAGE_SIZE;fragstotal = JZCODEC_RW_BUFFER_TOTAL;audio_init_endpoint(&out_endpoint, fragsize, fragstotal);audio_init_endpoint(&in_endpoint, fragsize, fragstotal);printk("JZ I2S OSS audio driver initialized\n");LEAVE();

 

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

 
ADUIO OSS 的读写buff 分析