君正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设备等。
- #if CONFIG_GPIO_HP_DETECT
- struct 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,
-
- .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,
- },
- };
-
注册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) {
-
- 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;
- }
- }
- f
-
- LEAVE();
- }
看上边可知,我们系统中只有一个codec,其实就是将jzdlv_ioctl赋值给the_codec[0],
并将the_codecs[0]的信号量进行初始化。
- "white-space:pre"> 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_DETECT
- retval = platform_driver_register(&jz_hp_switch_driver);
- if (retval) {
- printk("JZ HP Switch: Could net register headphone sense switch\n");
- return retval;
- }
- #endif
-
-
- retval = 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;
- }
-
-
-
-
-
-
-
- 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;
- int adev = 0;
-
-
- ENTER();
-
-
- name = controller->name;
-
-
-
- if (jz_i2s_codec_init(controller) <= 0) {
- goto mixer_failed;
- }
-
-
-
- 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();
- }
-
-
- 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等。
-
- 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 = {
- 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内存初始化。
-
-
-
- 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都注册完成了,对于各种播放和录音的通路流程在下将再分析 。