君正的audio驱动使用了传统的OSS模式, 真是BT,大家都在用ALSA,他
还用OSS, 在网上详细资料甚少,在这里记录一下自己的过程,希望有人能够用
到。
文章作者: http://blog.csdn.net/dyron, 文章不断完善中....
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与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,
/*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),
};
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;
}
}
f
LEAVE();
}
看上边可知,我们系统中只有一个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_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;
}
/* 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;
int adev = 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 分析