- 论坛徽章:
- 0
|
平台信息:Linux2.6.28+s3c2440
前言:由于最近需要在s3c2440上调试声卡驱动,达到左右声道可以同时录放音的功能。
我在google上搜索,网上像分析UDA1341驱动的真的很多,有人多高手都分析的相当到位,呵呵,小弟学习了他们的经验的基础上,终于调试出了可以做全双工使用的声卡驱动。同时编写了上层接口函数,测试完全通过。
static int __init s3c2410_uda1341_init ( void )
{
memzero ( &input_stream, sizeof ( audio_stream_t ) );
memzero ( &output_stream, sizeof ( audio_stream_t ) );
return platform_driver_register ( &s3c2410iis_driver );
想平台注册驱动设备结构体
}
static void __exit s3c2410_uda1341_exit ( void )
{
platform_driver_unregister ( &s3c2410iis_driver );
向平台注销驱动设备结构体
}
module_init ( s3c2410_uda1341_init );
module_exit ( s3c2410_uda1341_exit );
驱动的注册和初始化就不用一一介绍了,接着往下看。
static struct platform_driver s3c2410iis_driver =
{
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
.driver =
{
.name = "s3c2410-iis",
.owner = THIS_MODULE,
},
};
这个结构体里最重要的就是probe函数
static int s3c2410iis_probe ( struct platform_device *pdev )
{
unsigned long flags;
int ret;
dbg ( dbg_debug, "s3c2410iis_probe...\n" );
res = platform_get_resource ( pdev, IORESOURCE_MEM, 0 );
申请平台资源
if ( res == NULL )
{
dbg ( dbg_err, PFX "failed to get memory region resouce\n" );
ret = -ENOENT;
goto probe_out;
}
res = request_mem_region( res->start, RESSIZE( res ), pdev->name );
分配内存空间
if ( res == 0 )
{
dbg ( dbg_err, PFX "failed to request io memory region resouce\n" );
ret = -ENOENT;
goto probe_out;
}
iis_base = ioremap( res->start, RESSIZE( res ) );
物理地址到虚拟地址进行隐射
if ( iis_base == 0 )
{
dbg ( dbg_err, PFX "failed to ioremap() io memory region\n" );
ret = -ENOENT;
goto probe_free_mem_region;
}
iis_clock = clk_get ( &pdev->dev, "iis" );
取得IIS时钟信号
if ( iis_clock == NULL )
{
dbg ( dbg_err, PFX "failed to find clock source\n" );
ret = -EINVAL;
goto probe_iounmap;
}
clk_enable ( iis_clock );
使能时钟
local_irq_save ( flags );
/* GPB 4: L3CLOCK, OUTPUT */
s3c2410_gpio_cfgpin ( S3C2410_GPB4, S3C2410_GPB4_OUTP );
s3c2410_gpio_pullup ( S3C2410_GPB4, 1 );
/* GPB 3: L3DATA, OUTPUT */
s3c2410_gpio_cfgpin ( S3C2410_GPB3, S3C2410_GPB3_OUTP );
s3c2410_gpio_pullup ( S3C2410_GPB3, 1 );
/* GPB 2: L3MODE, OUTPUT */
s3c2410_gpio_cfgpin ( S3C2410_GPB2, S3C2410_GPB2_OUTP );
s3c2410_gpio_pullup ( S3C2410_GPB2, 1 );
/* GPE 3: I2SSDI */
s3c2410_gpio_cfgpin ( S3C2410_GPE3, S3C2410_GPE3_I2SSDI );
s3c2410_gpio_pullup ( S3C2410_GPE3, 1 );
/* GPE 0: I2SLRCK */
s3c2410_gpio_cfgpin ( S3C2410_GPE0, S3C2410_GPE0_I2SLRCK );
s3c2410_gpio_pullup ( S3C2410_GPE0, 1 );
/* GPE 1: I2SSCLK */
s3c2410_gpio_cfgpin ( S3C2410_GPE1, S3C2410_GPE1_I2SSCLK );
s3c2410_gpio_pullup ( S3C2410_GPE1, 1 );
/* GPE 2: CDCLK */
s3c2410_gpio_cfgpin ( S3C2410_GPE2, S3C2410_GPE2_CDCLK );
s3c2410_gpio_pullup ( S3C2410_GPE2, 1 );
/* GPE 4: I2SSDO */
s3c2410_gpio_cfgpin ( S3C2410_GPE4, S3C2410_GPE4_I2SSDO );
s3c2410_gpio_pullup ( S3C2410_GPE4, 1 );
local_irq_restore ( flags );
init_s3c2410_iis_bus();
初始化IIS总线
init_uda1341();
初始化UDA1341
output_stream.dma_ch = DMA_CH2;
/*
* Modified by Zhj EB
* the api s3c2410_dma_request has been changed since 2.6.25
* it returns the dma channel instead of -1 in the new kernel version
*/
if ( !audio_init_dma ( &output_stream, "UDA1341 out" ) )
{
audio_clear_dma ( &output_stream, &s3c2410iis_dma_out );
dbg ( dbg_err, AUDIO_NAME_VERBOSE
": unable to get DMA channels\n" );
ret = -EBUSY;
goto probe_clk_free;
}
input_stream.dma_ch = DMA_CH1;
if ( !audio_init_dma ( &input_stream, "UDA1341 in" ) )
{
audio_clear_dma ( &input_stream, &s3c2410iis_dma_in );
dbg ( dbg_err, AUDIO_NAME_VERBOSE
": unable to get DMA channels\n" );
ret = -EBUSY;
goto probe_clk_free;
}
audio_dev_dsp = register_sound_dsp ( &smdk2410_audio_fops, -1 );
audio_dev_mixer = register_sound_mixer ( &smdk2410_mixer_fops, -1 );
注册语音设备,,设备操作结构体:smdk2410_audio_fops
dbg ( dbg_info, AUDIO_NAME_VERBOSE " initialized\n" );
return 0;
probe_clk_free:
clk_disable( iis_clock );
clk_put( iis_clock );
probe_iounmap:
iounmap( iis_base );
probe_free_mem_region:
release_mem_region( res->start, RESSIZE( res ) );
probe_out:
return ret;
}
static void init_s3c2410_iis_bus ( void )
{
__raw_writel ( 0, iis_base + S3C2410_IISPSR );
初始化标志寄存器
__raw_writel ( 0, iis_base + S3C2410_IISCON );
初始化控制寄存器
__raw_writel ( 0, iis_base + S3C2410_IISMOD );
初始化模式寄存器
__raw_writel ( 0, iis_base + S3C2410_IISFCON );
初始化FIFO控制寄存器
clk_disable ( iis_clock );
}
static struct file_operations smdk2410_audio_fops =
{
llseek:
smdk2410_audio_llseek,
write:
smdk2410_audio_write,
read:
smdk2410_audio_read,
poll:
smdk2410_audio_poll,
ioctl:
smdk2410_audio_ioctl,
open:
smdk2410_audio_open,
release:
smdk2410_audio_release
};
static int smdk2410_audio_open ( struct inode *inode, struct file *file )
{
设备打开函数,控制设备打开的模式,计算设备打开的次数,同时初始化设备的基本状态
int cold = !audio_active;
DPRINTK ( "audio_open\n" );
if ( ( file->f_flags & O_ACCMODE ) == O_RDONLY )
只读模式打开[录音]
{
if ( audio_rd_refcount )
return -EBUSY;
audio_rd_refcount++;
}
else if ( ( file->f_flags & O_ACCMODE ) == O_WRONLY )
只写模式打开[放音]
{
if ( audio_wr_refcount )
return -EBUSY;
audio_wr_refcount++;
}
else if ( ( file->f_flags & O_ACCMODE ) == O_RDWR )
{
if ( audio_rd_refcount || audio_wr_refcount )
return -EBUSY;
audio_rd_refcount++;
audio_wr_refcount++;
}
else
return -EINVAL;
if ( cold )
{
audio_rate = AUDIO_RATE_DEFAULT;
audio_channels = AUDIO_CHANNELS_DEFAULT;
audio_fragsize = AUDIO_FRAGSIZE_DEFAULT;
audio_nbfrags = AUDIO_NBFRAGS_DEFAULT;
init_s3c2410_iis_bus_rxtx();
对IIS工作模式的初始化。。。。!!!!!!!!!!!
if ( ( file->f_mode & FMODE_WRITE ) )
{
// init_s3c2410_iis_bus_tx();
audio_clear_buf ( &output_stream );
/*
* Modified by Zhj EB
*/
if ( !output_stream.buffers && audio_setup_buf( &output_stream ) )
{
return -ENOMEM;
}
}
if ( ( file->f_mode & FMODE_READ ) )
{
// init_s3c2410_iis_bus_rx();
audio_clear_buf ( &input_stream );
}
}
return 0;
}
static void init_s3c2410_iis_bus_rxtx(void)
{
unsigned int iiscon,iismod,iisfcon;
__raw_writel ( 0, iis_base + S3C2410_IISPSR );
__raw_writel ( 0, iis_base + S3C2410_IISCON );
__raw_writel ( 0, iis_base + S3C2410_IISMOD );
__raw_writel ( 0, iis_base + S3C2410_IISFCON );
clk_enable ( iis_clock );
iiscon = iismod = iisfcon = 0;
iiscon |= S3C2410_IISCON_PSCEN;
iiscon |= S3C2410_IISCON_IISEN;
iismod |= S3C2410_IISMOD_MASTER;
iismod |= S3C2410_IISMOD_LR_LLOW;
iismod |= S3C2410_IISMOD_MSB;
iismod |= S3C2410_IISMOD_16BIT;
iismod |= S3C2410_IISMOD_384FS;
iismod |= S3C2410_IISMOD_32FS;
iismod |= S3C2410_IISMOD_TXRXMODE;
iisfcon |= S3C2410_IISFCON_RXDMA|S3C2410_IISFCON_RXENABLE;
iisfcon |= S3C2410_IISFCON_TXDMA|S3C2410_IISFCON_TXENABLE;
iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
iiscon &= ( ~S3C2410_IISCON_RXIDLE );
iiscon &= ( ~S3C2410_IISCON_TXIDLE );
audio_set_dsp_speed ( audio_rate );
__raw_writel ( iismod, iis_base + S3C2410_IISMOD );
__raw_writel ( iisfcon, iis_base + S3C2410_IISFCON );
__raw_writel ( iiscon, iis_base + S3C2410_IISCON );
设置寄存器,这个需要对照datasheet看,这样看起来更清晰。
主要就是设置工作在RXTX模式,设置数据传输位,使能DMA等
}
open做好了后,就是套路了,read和write函数。
static ssize_t smdk2410_audio_read ( struct file *file, char *buffer,
size_t count, loff_t * ppos )
{
const char *buffer0 = buffer;
audio_stream_t *s = &input_stream;
int chunksize, ret = 0;
DPRINTK ( "audio_read: count=%d\n", count );
if ( !s->buffers )
{
int i;
if ( audio_setup_buf ( s ) )
return -ENOMEM;
for ( i = 0; i nbfrags; i++ )
{
audio_buf_t *b = s->buf;
down ( &b->sem );
s3c2410_dma_enqueue ( s->dma_ch, ( void * ) b, b->dma_addr, s->fragsize );
申请DMA传输队列
NEXT_BUF ( s, buf );
}
}
while ( count > 0 )
{
audio_buf_t *b = s->buf;
/* Wait for a buffer to become full */
if ( file->f_flags & O_NONBLOCK )
{
ret = -EAGAIN;
if ( down_trylock ( &b->sem ) )
break;
}
else
{
ret = -ERESTARTSYS;
if ( down_interruptible ( &b->sem ) )
break;
}
chunksize = b->size;
if ( chunksize > count )
chunksize = count;
DPRINTK ( "read %d from %d\n", chunksize, s->buf_idx );
if ( copy_to_user ( buffer, b->start + s->fragsize - b->size,
chunksize ) )
{
up ( &b->sem );
return -EFAULT;
}
b->size -= chunksize;
buffer += chunksize;
count -= chunksize;
if ( b->size > 0 )
{
up ( &b->sem );
break;
}
/* Make current buffer available for DMA again */
s3c2410_dma_enqueue ( s->dma_ch, ( void * ) b, b->dma_addr, s->fragsize );
NEXT_BUF ( s, buf );
}
if ( ( buffer - buffer0 ) )
ret = buffer - buffer0;
return ret;
}
write函数和read基本查不多,以上只是一个过程,没有做太多的分析,主要是本人研究的不够透,但是我期待大家的讨论,这样可以共同提高。
本次驱动的调试和修改,大部分源码摘自于网络,经过我测试,基本没有问题。欢迎大家通过EMAIL向我索要源码。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/91378/showart_2050536.html |
|