s3c2440 uda1341声卡驱动分析(oos) (2011-03-10 12:56)
转载
1,驱动架构: 驱动分两个层次,上层是平台设备驱动,底层是audio驱动与mixer驱动。 (1)标准的平台设备驱动结构,probe与remove两个函数。 probe: 获得平台资源->申请内存区域-io内存重映射->获得并使能时钟->设置gpio口->初始化iis总线-> 初始化uda1341->audio dma初始化->注册dsp和mixer->释放内存区域。 代码及注释:
static int s3c2410iis_probe( struct platform_device * pdev) { struct resource * res; unsigned long flags; int ret; DPRINTK( "s3c2410iis_probe\n" ) ; //获得平台设备资源 res = platform_get_resource( pdev, IORESOURCE_MEM, 0) ; if ( res = = NULL ) { printk( KERN_INFO PFX "failed to get memory region resouce\n" ) ; return - ENOENT; } //申请可用内存 res = request_mem_region( res- > start, RESSIZE( res) , pdev- > name) ; if ( res = = 0) { printk( KERN_INFO PFX "failed to request io memory region.\n" ) ; return - ENOENT; } //io内存重映射 iis_base = ioremap( res- > start, RESSIZE( res) ) ; if ( iis_base = = 0) { printk( KERN_INFO PFX "failed to ioremap() io memory region.\n" ) ; ret = - EINVAL; goto free_mem_region; } //获得时钟资源 iis_clock = clk_get( & pdev- > dev, "iis" ) ; if ( iis_clock = = NULL ) { printk( KERN_INFO PFX "failed to find clock source\n" ) ; return - ENOENT; } /**************************modify by lfc*****************************/ clk_enable( iis_clock) ; //使能时钟 /*****************************end add********************************/ //禁用本地中断,gpio口设置,恢复中断 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) ; /* 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 //初始化dma ch1 ch2 output_stream. dma_ch = DMA_CH2; if ( ! audio_init_dma( & output_stream, "UDA1341 out" ) & DMACH_LOW_LEVEL) { audio_clear_dma( & output_stream, & s3c2410iis_dma_out) ; printk( KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n" ) ; return - EBUSY; } input_stream. dma_ch = DMA_CH1; if ( ! audio_init_dma( & input_stream, "UDA1341 in" ) & DMACH_LOW_LEVEL) { audio_clear_dma( & input_stream, & s3c2410iis_dma_in) ; printk( KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n" ) ; return - EBUSY; } //注册dsp及mixer audio_dev_dsp = register_sound_dsp( & smdk2410_audio_fops, - 1) ; audio_dev_mixer = register_sound_mixer( & smdk2410_mixer_fops, - 1) ; printk( AUDIO_NAME_VERBOSE " initialized\n" ) ; //释放内存区域 free_mem_region: release_mem_region( res- > start, RESSIZE( res) ) ; return 0; }
remove: 禁用时钟->取消dsp mixer注册->清除dma
static int s3c2410iis_remove( struct platform_device * dev) { DPRINTK( "s3c2410iis_remove\n" ) ; if ( iis_clock ! = NULL ) { clk_disable( iis_clock) ; clk_put( iis_clock) ; iis_clock = NULL ; } unregister_sound_dsp( audio_dev_dsp) ; unregister_sound_mixer( audio_dev_mixer) ; audio_clear_dma( & output_stream, & s3c2410iis_dma_out) ; audio_clear_dma( & input_stream, & s3c2410iis_dma_in) ; /* input */ printk( AUDIO_NAME_VERBOSE " unloaded\n" ) ; return 0; }
uda1341的初始化: 设置gpio口->uda1341复位->uda1341设置
static void init_uda1341( void ) { /* GPB 4: L3CLOCK */ /* GPB 3: L3DATA */ /* GPB 2: L3MODE */ unsigned long flags; DPRINTK( "init_uda1341\n" ) ; uda1341_volume = 62 - ( ( DEF_VOLUME * 61) / 100) ; uda1341_boost = 0; // uda_sampling = DATA2_DEEMP_NONE; // uda_sampling &= ~(DATA2_MUTE); local_irq_save( flags) ; s3c2410_gpio_setpin( S3C2410_GPB2, 1) ; //L3MODE=1 s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; //L3CLOCK=1 local_irq_restore( flags) ; uda1341_l3_address( UDA1341_REG_STATUS) ; uda1341_l3_data( 0x40 | STAT0_SC_384FS | STAT0_IF_MSB| STAT0_DC_FILTER) ; // reset uda1341 uda1341_l3_data( STAT1 | STAT1_ADC_ON | STAT1_DAC_ON) ; uda1341_l3_address( UDA1341_REG_DATA0) ; // uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volume uda1341_l3_data( DATA0 | DATA0_VOLUME( uda1341_volume) ) ; //lfc uda1341_l3_data( DATA1 | DATA1_BASS( uda1341_boost) | DATA1_TREBLE( 0) ) ; uda1341_l3_data( ( DATA2 | DATA2_DEEMP_NONE) & ~ ( DATA2_MUTE) ) ; uda1341_l3_data( EXTADDR( EXT2) ) ; uda1341_l3_data( EXTDATA( EXT2_MIC_GAIN( 0x6) ) | EXT2_MIXMODE_CH1) ; //input channel 1 select(input channel 2 off) }
(2)底层的audio和mixer其实就是字符设备,完成file_operations结构体后在上面说的probe函数中注册 audio驱动: fops结构体
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 } ;
write: 判断打开标志是否可写->判断BUFFER内存空间是否可用->判断阻塞方式还是非阻塞方式->循环写入内存块,并将写好的内存块加入dma队列->返回传输字节数
static ssize_t smdk2410_audio_write( struct file * file , const char * buffer, size_t count , loff_t * ppos) { const char * buffer0 = buffer; audio_stream_t * s = & output_stream; int chunksize, ret = 0; DPRINTK( "audio_write : start count=%d\n" , count ) ; switch ( file - > f_flags & O_ACCMODE) { case O_WRONLY: case O_RDWR: break ; default : DPRINTK( "EPERM\n" ) ; return - EPERM; } if ( ! s- > buffers & & audio_setup_buf( s) ) { DPRINTK( "ENOMEM\n" ) ; return - ENOMEM; } count & = ~ 0x03; while ( count > 0) { audio_buf_t * b = s- > buf; if ( file - > f_flags & O_NONBLOCK) { ret = - EAGAIN; if ( down_trylock( & b- > sem) ) { DPRINTK( "down_trylock error\n" ) ; break ; } } else { ret = - ERESTARTSYS; if ( down_interruptible( & b- > sem) ) { DPRINTK( "down_interruptible error\n" ) ; break ; } } if ( audio_channels = = 2) { //使用双频道 chunksize = s- > fragsize - b- > size; //当前内存块可供使用空间 if ( chunksize > count ) chunksize = count ; DPRINTK( "write %d to %d\n" , chunksize, s- > buf_idx) ; if ( copy_from_user( b- > start + b- > size, buffer, chunksize) ) { DPRINTK( "copy_from_user error\n" ) ; up( & b- > sem) ; return - EFAULT; } b- > size + = chunksize; //更新当前内存块的使用情况 } else { //单频道 chunksize = ( s- > fragsize - b- > size) > > 1; if ( chunksize > count ) chunksize = count ; DPRINTK( "write %d to %d\n" , chunksize* 2, s- > buf_idx) ; if ( copy_from_user_mono_stereo( b- > start + b- > size, buffer, chunksize) ) { DPRINTK( "copy_from_user_mono_stereo error\n" ) ; up( & b- > sem) ; return - EFAULT; } b- > size + = chunksize* 2; } buffer + = chunksize; count - = chunksize; if ( b- > size < s- > fragsize) { up( & b- > sem) ; break ; } /* 填满一块内存就交给dma去处理 */ if ( ( ret = s3c2410_dma_enqueue( s- > dma_ch, ( void * ) b, b- > dma_addr, b- > size) ) ) { printk( "dma enqueue failed.\n" ) ; return ret; } /* 把内存块加入dma队列后继续填写下一内存块 */ b- > size = 0; NEXT_BUF( s, buf) ; } if ( ( buffer - buffer0) ) ret = buffer - buffer0; //返回已传输的字节数 DPRINTK( "audio_write : end count=%d\n\n" , ret) ; return ret; }
read: 判断buffer内存空间是否为空,若未空则设置buffer空间,并将buffer内存块放入dma队列->循环将内存块数据读入用户空间->返回读出字节数
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 (ppos != &file->f_pos) return -ESPIPE; */ if ( ! s- > buffers) { int i; if ( audio_setup_buf( s) ) return - ENOMEM; for ( i = 0; i < s- > nbfrags; i+ + ) { audio_buf_t * b = s- > buf; down( & b- > sem) ; s3c2410_dma_enqueue( s- > dma_ch, ( void * ) b, b- > dma_addr, s- > fragsize) ; 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; // DPRINTK("audio_read: return=%d\n", ret); return ret; }
poll: 可读可写两部分判断是否可以无阻塞地读写(buffer信号量为大于0则可以)
static unsigned int smdk2410_audio_poll( struct file * file , struct poll_table_struct * wait) { unsigned int mask = 0; int i; DPRINTK( "audio_poll(): mode=%s\n" , ( file - > f_mode & FMODE_WRITE) ? "w" : "" ) ; if ( file - > f_mode & FMODE_READ) { if ( ! input_stream. buffers & & audio_setup_buf( & input_stream) ) return - ENOMEM; poll_wait( file , & input_stream. buf- > wait, wait) ; for ( i = 0; i < input_stream. nbfrags; i+ + ) { if ( input_stream. buffers[ i] . sem. count > 0) { mask | = POLLIN | POLLWRNORM; break ; } } } if ( file - > f_mode & FMODE_WRITE) { if ( ! output_stream. buffers & & audio_setup_buf( & output_stream) ) return - ENOMEM; poll_wait( file , & output_stream. buf- > wait, wait) ; for ( i = 0; i < output_stream. nbfrags; i+ + ) { if ( output_stream. buffers[ i] . sem. count > 0) { mask | = POLLOUT | POLLWRNORM; break ; } } } DPRINTK( "audio_poll() returned mask of %s\n" , ( mask & POLLOUT) ? "w" : "" ) ; return mask; }
ioctl: 根据oos audio programe guide完成相应功能
static int smdk2410_audio_ioctl( struct inode * inode, struct file * file , uint cmd, ulong arg ) { long val; DPRINTK( "smdk2410_audio_ioctl\n" ) ; switch ( cmd) { case SNDCTL_DSP_SETFMT: get_user( val, ( long * ) arg ) ; if ( val & AUDIO_FMT_MASK) { audio_fmt = val; break ; } else return - EINVAL; case SNDCTL_DSP_CHANNELS: case SNDCTL_DSP_STEREO: get_user( val, ( long * ) arg ) ; if ( cmd = = SNDCTL_DSP_STEREO) val = val ? 2 : 1; if ( val ! = 1 & & val ! = 2) return - EINVAL; DPRINTK( "audio_channels set to %d\n" , val) ; audio_channels = val; break ; case SOUND_PCM_READ_CHANNELS: DPRINTK( "audio_channels is %d\n" , audio_channels) ; put_user( audio_channels, ( long * ) arg ) ; break ; case SNDCTL_DSP_SPEED: get_user( val, ( long * ) arg ) ; val = audio_set_dsp_speed( val) ; if ( val < 0) return - EINVAL; put_user( val, ( long * ) arg ) ; break ; case SOUND_PCM_READ_RATE: put_user( audio_rate, ( long * ) arg ) ; break ; case SNDCTL_DSP_GETFMTS: put_user( AUDIO_FMT_MASK, ( long * ) arg ) ; break ; case SNDCTL_DSP_GETBLKSIZE: if ( file - > f_mode & FMODE_WRITE) return put_user( audio_fragsize, ( long * ) arg ) ; else return put_user( audio_fragsize, ( int * ) arg ) ; case SNDCTL_DSP_SETFRAGMENT: if ( file - > f_mode & FMODE_WRITE) { if ( output_stream. buffers) return - EBUSY; get_user( val, ( long * ) arg ) ; audio_fragsize = 1 < < ( val & 0xFFFF) ; if ( audio_fragsize < 16) audio_fragsize = 16; if ( audio_fragsize > 16384) audio_fragsize = 16384; audio_nbfrags = ( val > > 16) & 0x7FFF; if ( audio_nbfrags < 2) audio_nbfrags = 2; if ( audio_nbfrags * audio_fragsize > 128 * 1024) audio_nbfrags = 128 * 1024 / audio_fragsize; if ( audio_setup_buf( & output_stream) ) return - ENOMEM; } if ( file - > f_mode & FMODE_READ) { if ( input_stream. buffers) return - EBUSY; get_user( val, ( int * ) arg ) ; audio_fragsize = 1 < < ( val & 0xFFFF) ; if ( audio_fragsize < 16) audio_fragsize = 16; if ( audio_fragsize > 16384) audio_fragsize = 16384; audio_nbfrags = ( val > > 16) & 0x7FFF; if ( audio_nbfrags < 2) audio_nbfrags = 2; if ( audio_nbfrags * audio_fragsize > 128 * 1024) audio_nbfrags = 128 * 1024 / audio_fragsize; if ( audio_setup_buf( & input_stream) ) return - ENOMEM; } break ; case SNDCTL_DSP_SYNC: return audio_sync( file ) ; case SNDCTL_DSP_GETOSPACE: { audio_stream_t * s = & output_stream; audio_buf_info * inf = ( audio_buf_info * ) arg ; int err = access_ok( VERIFY_WRITE, inf, sizeof ( * inf) ) ; int i; int frags = 0, bytes = 0; if ( err) return err; for ( i = 0; i < s- > nbfrags; i+ + ) { if ( s- > buffers[ i] . sem. count > 0) { if ( s- > buffers[ i] . size = = 0) frags+ + ; bytes + = s- > fragsize - s- > buffers[ i] . size; } } put_user( frags, & inf- > fragments) ; put_user( s- > nbfrags, & inf- > fragstotal) ; put_user( s- > fragsize, & inf- > fragsize) ; put_user( bytes, & inf- > bytes) ; break ; } case SNDCTL_DSP_GETISPACE: { audio_stream_t * s = & input_stream; audio_buf_info * inf = ( audio_buf_info * ) arg ; int err = access_ok( VERIFY_WRITE, inf, sizeof ( * inf) ) ; int i; int frags = 0, bytes = 0; if ( ! ( file - > f_mode & FMODE_READ) ) return - EINVAL; if ( err) return err; for ( i = 0; i < s- > nbfrags; i+ + ) { if ( s- > buffers[ i] . sem. count > 0) { if ( s- > buffers[ i] . size = = s- > fragsize) frags+ + ; bytes + = s- > buffers[ i] . size; } } put_user( frags, & inf- > fragments) ; put_user( s- > nbfrags, & inf- > fragstotal) ; put_user( s- > fragsize, & inf- > fragsize) ; put_user( bytes, & inf- > bytes) ; break ; } case SNDCTL_DSP_RESET: if ( file - > f_mode & FMODE_READ) { audio_clear_buf( & input_stream) ; } if ( file - > f_mode & FMODE_WRITE) { audio_clear_buf( & output_stream) ; } return 0; case SNDCTL_DSP_NONBLOCK: file - > f_flags | = O_NONBLOCK; return 0; case SNDCTL_DSP_POST: case SNDCTL_DSP_SUBDIVIDE: case SNDCTL_DSP_GETCAPS: case SNDCTL_DSP_GETTRIGGER: case SNDCTL_DSP_SETTRIGGER: case SNDCTL_DSP_GETIPTR: case SNDCTL_DSP_GETOPTR: case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: case SNDCTL_DSP_SETDUPLEX: return - ENOSYS; default : return smdk2410_mixer_ioctl( inode, file , cmd, arg ) ; } return 0; }
open :
判断设备是否正忙->设置相关参数->初始化iis总线->清除缓冲区
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 | | audio_wr_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; if ( ( file - > f_mode & FMODE_WRITE) ) { init_s3c2410_iis_bus_tx( ) ; //可写则初始化iis发送 audio_clear_buf( & output_stream) ; } if ( ( file - > f_mode & FMODE_READ) ) { init_s3c2410_iis_bus_rx( ) ; //可读则初始化iis接收 audio_clear_buf( & input_stream) ; } } return 0; }
release :
清除缓冲区,读写计数归0
static int smdk2410_audio_release( struct inode * inode, struct file * file ) { DPRINTK( "audio_release\n" ) ; if ( file - > f_mode & FMODE_READ) { if ( audio_rd_refcount = = 1) audio_clear_buf( & input_stream) ; audio_rd_refcount = 0; } if ( file - > f_mode & FMODE_WRITE) { if ( audio_wr_refcount = = 1) { audio_sync( file ) ; audio_clear_buf( & output_stream) ; audio_wr_refcount = 0; } } return 0; }
mixer驱动 :
fops结构体
static struct file_operations smdk2410_mixer_fops = { ioctl: smdk2410_mixer_ioctl, open : smdk2410_mixer_open, release: smdk2410_mixer_release } ;
ioctl:
同样根据oos audio programe guide完成相应功能,通过audio的ioctl调用
static int smdk2410_mixer_ioctl( struct inode * inode, struct file * file , unsigned int cmd, unsigned long arg ) { int ret; long val = 0; DPRINTK( "smdk2410_mixer_ioctl\n" ) ; switch ( cmd) { case SOUND_MIXER_INFO: { mixer_info info; strncpy ( info. id, "UDA1341" , sizeof ( info. id) ) ; strncpy ( info. name, "Philips UDA1341" , sizeof ( info. name) ) ; info. modify_counter = audio_mix_modcnt; return copy_to_user( ( void * ) arg , & info, sizeof ( info) ) ; } case SOUND_OLD_MIXER_INFO: { _old_mixer_info info; strncpy ( info. id, "UDA1341" , sizeof ( info. id) ) ; strncpy ( info. name, "Philips UDA1341" , sizeof ( info. name) ) ; return copy_to_user( ( void * ) arg , & info, sizeof ( info) ) ; } case SOUND_MIXER_READ_STEREODEVS: return put_user( 0, ( long * ) arg ) ; case SOUND_MIXER_READ_CAPS: val = SOUND_CAP_EXCL_INPUT; return put_user( val, ( long * ) arg ) ; case SOUND_MIXER_WRITE_VOLUME: ret = get_user( val, ( long * ) arg ) ; if ( ret) return ret; uda1341_volume = 63 - ( ( ( val & 0xff) + 1) * 63) / 100; uda1341_l3_address( UDA1341_REG_DATA0) ; uda1341_l3_data( uda1341_volume) ; break ; case SOUND_MIXER_READ_VOLUME: val = ( ( 63 - uda1341_volume) * 100) / 63; val | = val < < 8; return put_user( val, ( long * ) arg ) ; case SOUND_MIXER_READ_IGAIN: val = ( ( 31- mixer_igain) * 100) / 31; return put_user( val, ( int * ) arg ) ; case SOUND_MIXER_WRITE_IGAIN: ret = get_user( val, ( int * ) arg ) ; if ( ret) return ret; mixer_igain = 31 - ( val * 31 / 100) ; /* use mixer gain channel 1*/ uda1341_l3_address( UDA1341_REG_DATA0) ; uda1341_l3_data( EXTADDR( EXT0) ) ; uda1341_l3_data( EXTDATA( EXT0_CH1_GAIN( mixer_igain) ) ) ; break ; default : DPRINTK( "mixer ioctl %u unknown\n" , cmd) ; return - ENOSYS; }
open,release: 空函数,略 以上ioctl参考资料: http://manuals.opensound.com/developer/ioctl.html
2,dma分析
(1)两个相关结构体: buffer
结构:
typedef struct { int size; /* buffer size */ char * start; /* point to actual buffer */ dma_addr_t dma_addr; /* physical buffer address */ struct semaphore sem; /* down before touching the buffer */ wait_queue_head_t wait; int master; /* owner for buffer allocation, contain size when true */ } audio_buf_t;
内存块结构:
typedef struct { audio_buf_t * buffers; /* pointer to audio buffer structures */ audio_buf_t * buf; /* current buffer used by read/write */ u_int buf_idx; /* index for the pointer above */ u_int fragsize; /* fragment i.e. buffer size */ u_int nbfrags; /* nbr of fragments */ dmach_t dma_ch; /* DMA channel (channel2 for audio) */ u_int dma_ok; } audio_stream_t;
(2)dma使用过程: 首先是在probe函数中调用audio_init_dma初始化dma:
static int __init audio_init_dma( audio_stream_t * s, char * desc) { int ret ; enum s3c2410_dmasrc source; int hwcfg; unsigned long devaddr; int dcon; unsigned int flags = 0; DPRINTK( "audio_init_dma\n" ) ; if ( s- > dma_ch = = DMA_CH2) { //dma通道2初始化 source = S3C2410_DMASRC_MEM; hwcfg = 3; devaddr = 0x55000010; dcon = ( 1< < 31) | ( 0< < 30) | ( 0< < 24) ; flags = S3C2410_DMAF_AUTOSTART; ret = s3c2410_dma_request( s- > dma_ch, & s3c2410iis_dma_out, NULL ) ; //为out申请dma通道 if ( ! ret & DMACH_LOW_LEVEL) { printk( KERN_ERR "failed to get dma channel\n" ) ; return ret; } //4个dma相关设置:device设置,传输设置,回调函数设置,标志设置 s3c2410_dma_devconfig( s- > dma_ch, source, hwcfg, devaddr) ; s3c2410_dma_config( s- > dma_ch, 2, dcon) ; s3c2410_dma_set_buffdone_fn( s- > dma_ch, audio_dmaout_done_callback) ; s3c2410_dma_setflags( s- > dma_ch, flags) ; s- > dma_ok = 1; return ret; } else if ( s- > dma_ch = = DMA_CH1) { source = S3C2410_DMASRC_HW; hwcfg = 3; devaddr = 0x55000010; dcon = ( 1< < 31) | ( 1< < 23) | ( 2< < 24) ; flags = S3C2410_DMAF_AUTOSTART; ret = s3c2410_dma_request( s- > dma_ch, & s3c2410iis_dma_in, NULL ) ; //为in申请dma通道 if ( ! ret & DMACH_LOW_LEVEL) { printk( KERN_ERR "failed to get dma channel\n" ) ; return ret; } //同上,4个dma相关设置:device设置,传输设置,回调函数设置,标志设置 s3c2410_dma_devconfig( s- > dma_ch, source, hwcfg, devaddr) ; s3c2410_dma_config( s- > dma_ch, 2, dcon) ; s3c2410_dma_set_buffdone_fn( s- > dma_ch, audio_dmain_done_callback) ; s3c2410_dma_setflags( s- > dma_ch, flags) ; s- > dma_ok = 1; return ret ; } else return 1; }
接着在读写函数中调用建audio_setup_buf立dma内存空间(读为in通道建立,写为out通道建立):
static int audio_setup_buf( audio_stream_t * s) { int frag; int dmasize = 0; char * dmabuf = 0; dma_addr_t dmaphys = 0; DPRINTK( "audio_setup_buf\n" ) ; if ( s- > buffers) return - EBUSY; s- > nbfrags = audio_nbfrags; s- > fragsize = audio_fragsize; s- > buffers = ( audio_buf_t * ) kmalloc( sizeof ( audio_buf_t) * s- > nbfrags, GFP_KERNEL) ; //动态获得内存块空间 if ( ! s- > buffers) goto err; memset ( s- > buffers, 0, sizeof ( audio_buf_t) * s- > nbfrags) ; for ( frag = 0; frag < s- > nbfrags; frag+ + ) { //为内存块每个buffer建立dma映射 audio_buf_t * b = & s- > buffers[ frag] ; if ( ! dmasize) { dmasize = ( s- > nbfrags - frag) * s- > fragsize; do { dmabuf = dma_alloc_coherent( NULL , dmasize, & dmaphys, GFP_KERNEL| GFP_DMA) ; //dma一致性映射建立,dmabuf指向开始处 if ( ! dmabuf) dmasize - = s- > fragsize; } while ( ! dmabuf & & dmasize) ; if ( ! dmabuf) goto err; b- > master = dmasize; } b- > start = dmabuf; b- > dma_addr = dmaphys; sema_init( & b- > sem, 1) ; //初始化buffer信号量 DPRINTK( "buf %d: start %p dma %d\n" , frag, b- > start, b- > dma_addr) ; init_waitqueue_head( & b- > wait) ; //初始化buffer等待队列头 dmabuf + = s- > fragsize; //改变dma虚地址 dmaphys + = s- > fragsize; //改变dma总线地址 dmasize - = s- > fragsize; //改变dma size } s- > buf_idx = 0; s- > buf = & s- > buffers[ 0] ; return 0; err: printk( AUDIO_NAME ": unable to allocate audio memory\n " ) ; audio_clear_buf( s) ; return - ENOMEM; }
然后再使用int s3c2410_dma_enqueue( unsigned int channel, void * id, dma_addr_t data, int size)
发起一次dma传输 最后传输结束后调用回调函数(内核调用)
static void audio_dmaout_done_callback( struct s3c2410_dma_chan * ch, void * buf, int size, enum s3c2410_dma_buffresult result) { audio_buf_t * b = ( audio_buf_t * ) buf; DPRINTK( "audio_dmaout_done_callback\n" ) ; up( & b- > sem) ; wake_up( & b- > wait) ; } static void audio_dmain_done_callback( struct s3c2410_dma_chan * ch, void * buf, int size, enum s3c2410_dma_buffresult result) { audio_buf_t * b = ( audio_buf_t * ) buf; DPRINTK( "audio_dmain_done_callback\n" ) ; b- > size = size; up( & b- > sem) ; wake_up( & b- > wait) ; }
3,L3总线分析 uda1341 datasheet上的时序图: 地址:
<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627173501.jpg" width=500 οnlοad="javascript:if(this.width>500)this.width=500;" border=0>
驱动中的实现代码:
static void uda1341_l3_address( u8 data) { int i; unsigned long flags; local_irq_save( flags) ; // write_gpio_bit(GPIO_L3MODE, 0); s3c2410_gpio_setpin( S3C2410_GPB2, 0) ; // write_gpio_bit(GPIO_L3CLOCK, 1); s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; udelay( 1) ; for ( i = 0; i < 8; i+ + ) { //从0位开始,按照时序图,逐位写入地址 if ( data & 0x1) { s3c2410_gpio_setpin( S3C2410_GPB4, 0) ; s3c2410_gpio_setpin( S3C2410_GPB3, 1) ; udelay( 1) ; s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; } else { s3c2410_gpio_setpin( S3C2410_GPB4, 0) ; s3c2410_gpio_setpin( S3C2410_GPB3, 0) ; udelay( 1) ; s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; } data > > = 1; } s3c2410_gpio_setpin( S3C2410_GPB2, 1) ; s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; local_irq_restore( flags) ; }
数据:
<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627173710.jpg" width=500 οnlοad="javascript:if(this.width>500)this.width=500;" border=0>
驱动中的实现代码:
static void uda1341_l3_data( u8 data) { int i; unsigned long flags; local_irq_save( flags) ; udelay( 1) ; for ( i = 0; i < 8; i+ + ) { if ( data & 0x1) { s3c2410_gpio_setpin( S3C2410_GPB4, 0) ; s3c2410_gpio_setpin( S3C2410_GPB3, 1) ; udelay( 1) ; s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; } else { s3c2410_gpio_setpin( S3C2410_GPB4, 0) ; s3c2410_gpio_setpin( S3C2410_GPB3, 0) ; udelay( 1) ; s3c2410_gpio_setpin( S3C2410_GPB4, 1) ; } data > > = 1; } local_irq_restore( flags) ; }
向指定地址写入数据,总时序图:
<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627180823.jpg" width=500 οnlοad="javascript:if(this.width>500)this.width=500;" border=0>
L3总线简述: 首先写入地址,时序为 L3MODE置电平->L3CLOCK置高电平->L3CLOCK置低电平->写一位地址->延时->L3CLOCK置高电平->L3CLOCK置低电平,开始写地址下一位->...8位地址写完->L3MODE,L3CLOCK置高电平 然后开始写入数据,时序为
L3CLOCK置低电平->写一位数据->延时->L3CLOCK置高电平->L3CLOCK置低电平,开始写下一位数据->...8位数据写完,向之前写入的地址地址,一次写数据完成 功能: 通过向DATA0和STATUS两个寄存器写入数据,来控制uda1341。
4,iis总线分析 L3总线是用来控制uda1341的,iis总线则用来收发音频数据。 首先在probe函数中初始化iis总线:
static void init_s3c2410_iis_bus( void ) { DPRINTK( "init_s3c2410_iis_bus\n" ) ; writel( 0, iis_base + S3C2410_IISPSR) ; writel( 0, iis_base + S3C2410_IISCON) ; writel( 0, iis_base + S3C2410_IISMOD) ; writel( 0, iis_base + S3C2410_IISFCON) ; clk_disable( iis_clock) ; }
然后在open函数中,视打开方式来初始化iis总线(收、发)
static void init_s3c2410_iis_bus_rx( void ) { unsigned int iiscon, iismod, iisfcon; char * dstr; DPRINTK( "init_s3c2410_iis_bus_rx\n" ) ; //Kill everything... writel( 0, iis_base + S3C2410_IISPSR) ; writel( 0, iis_base + S3C2410_IISCON) ; writel( 0, iis_base + S3C2410_IISMOD) ; writel( 0, iis_base + S3C2410_IISFCON) ; clk_enable( iis_clock) ; iiscon = iismod = iisfcon = 0; //Setup basic stuff iiscon | = S3C2410_IISCON_PSCEN; // Enable prescaler iismod | = S3C2410_IISMOD_MASTER; // Set interface to Master Mode iismod | = S3C2410_IISMOD_LR_LLOW; // Low for left channel iismod | = S3C2410_IISMOD_MSB; // IIS format iismod | = S3C2410_IISMOD_16BIT; // Serial data bit/channel is 16 bit iismod | = S3C2410_IISMOD_384FS; // Master clock freq = 384 fs iismod | = S3C2410_IISMOD_32FS; // 32 fs iisfcon| = S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; //Set RX FIFO acces mode to DMA //iisfcon|= S3C2410_IISFCON_TXDMA; //Set RX FIFO acces mode to DMA iiscon | = S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; //Enable RX DMA service request //iiscon |= S3C2410_IISCON_TXIDLE; //Set TX channel idle iiscon & = ( ~ S3C2410_IISCON_RXIDLE) ; iismod | = S3C2410_IISMOD_RXMODE; //Set RX Mode iismod | = S3C2410_IISMOD_TXMODE; dstr= "RX" ; //setup the prescaler audio_set_dsp_speed( audio_rate) ; //iiscon has to be set last - it enables the interface writel( iismod, iis_base + S3C2410_IISMOD) ; writel( iisfcon, iis_base + S3C2410_IISFCON) ; writel( iiscon, iis_base + S3C2410_IISCON) ; } static void init_s3c2410_iis_bus_tx( void ) { unsigned int iiscon, iismod, iisfcon; char * dstr; DPRINTK( "init_s3c2410_iis_bus_tx\n" ) ; //Kill everything... writel( 0, iis_base + S3C2410_IISPSR) ; writel( 0, iis_base + S3C2410_IISCON) ; writel( 0, iis_base + S3C2410_IISMOD) ; writel( 0, iis_base + S3C2410_IISFCON) ; clk_enable( iis_clock) ; iiscon = iismod = iisfcon = 0; //Setup basic stuff iiscon | = S3C2410_IISCON_PSCEN; // Enable prescaler iismod | = S3C2410_IISMOD_MASTER; // Set interface to Master Mode iismod | = S3C2410_IISMOD_LR_LLOW; // Low for left channel iismod | = S3C2410_IISMOD_MSB; // MSB format iismod | = S3C2410_IISMOD_16BIT; // Serial data bit/channel is 16 bit iismod | = S3C2410_IISMOD_384FS; // Master clock freq = 384 fs iismod | = S3C2410_IISMOD_32FS; // 32 fs iisfcon| = S3C2410_IISFCON_RXDMA; //Set RX FIFO acces mode to DMA iisfcon| = S3C2410_IISFCON_TXDMA; //Set TX FIFO acces mode to DMA iiscon | = S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; //Enable TX DMA service request //iiscon |= S3C2410_IISCON_RXIDLE; //Set RX channel idle iiscon & = ~ S3C2410_IISCON_TXIDLE; iismod | = S3C2410_IISMOD_TXMODE; //Set TX Mode iismod | = S3C2410_IISMOD_RXMODE; iisfcon| = S3C2410_IISFCON_TXENABLE; //Enable TX Fifo dstr= "TX" ; //setup the prescaler audio_set_dsp_speed( audio_rate) ; //iiscon has to be set last - it enables the interface writel( iismod, iis_base + S3C2410_IISMOD) ; writel( iisfcon, iis_base + S3C2410_IISFCON) ; writel( iiscon, iis_base + S3C2410_IISCON) ; }
文章来自
http://blogold.chinaunix.net/u3/97285/showart_1979063.html