您的位置:首页 > 其它

s3c2440 uda1341声卡驱动分析(oos) (2011-03-10 12:56)转载

2012-01-29 00:59 344 查看
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上的时序图:

地址:

[align=center]<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627173501.jpg" width=500 onload="javascript:if(this.width>500)this.width=500;" border=0>[/align]
驱动中的实现代码:

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);

}
数据:

[align=center]<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627173710.jpg" width=500 onload="javascript:if(this.width>500)this.width=500;" border=0>[/align]

驱动中的实现代码:
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);

}
向指定地址写入数据,总时序图:

[align=center]<IMG src="http://blogimg.chinaunix.net/blog/upfile2/090627180823.jpg" width=500 onload="javascript:if(this.width>500)this.width=500;" border=0>[/align]

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: