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

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

    君正的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架构,不用太多介绍了吧,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),
};


注册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;
		}
	}
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 分析


你可能感兴趣的:(audio,linux,codec,struct,module,hp)