星期六, 5月 12, 2007

MP3 Player

V1.0

copyright@2006

email: mesmerli@hotmail.com

Makefile (playmp3)

CROSS_COMPILE = arm-linux-

LINUXDIR = /usr/src/creator/s3c2410/linux
export LINUXDIR

#
# Include the make variables (CC, etc...)
#

AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar

export AS LD CC CPP AR

MP3LIBNAME = mad

CFLAGS= -O0 -gdwarf-2 -DHAVE_CONFIG_H -DFPM_DEFAULT -Dlinux -Dunix -DNDEBUG -Ilibmad -I.
OBJS= demo.o bstdfile.o madlld.o
EXEC = playmp3_Cross

all: $(EXEC).exe $(EXEC)_Static.exe

$(EXEC).exe: mp3lib $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS) -L./libmad -l$(MP3LIBNAME) $(LDLIBS)

$(EXEC)_Static.exe: mp3lib $(OBJS)
$(CC) $(LDFLAGS) -static -o $@ $(OBJS) -L./libmad -l$(MP3LIBNAME) $(LDLIBS)

mp3lib:
$(MAKE) -C libmad

.PHONY : clean

clean:
$(MAKE) -C libmad clean
rm -f $(EXEC) *.bin *.elf *.o *.s *.gdb *.bak *.*~ *.exe

Makefile (libmad)

CROSS_COMPILE = arm-linux-

LINUXDIR = /usr/src/creator/s3c2410/linux
INCLUDE = /usr/src/creator/s3c2410/linux/include
export LINUXDIR

#
# Include the make variables (CC, etc...)
#

AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar

export AS LD CC CPP AR
LIBNAME = mad
SONAME = libmad.so.1.0.0
SOVERSION = libmad.so.1.0
CFLAGS = -Wall


CC += -I$(LINUXDIR)/include

#CFLAGS= -nostartfiles -g -I. -trigraphs -DHAVE_CONFIG_H -DFPM_DEFAULT -DNDEBUG -O2 -Wa,-a=$@.l
CFLAGS= -gdwarf-2 -I. -DHAVE_CONFIG_H -DFPM_DEFAULT -DNDEBUG -Wa,-a=$@.l
AFLAGS=-mapcs-32 -msoft-float -mno-fpu $(CFLAGS)
LFLAGS= -Wl,-M,-Map=$@.map


OBJS= bit.o decoder.o fixed.o frame.o huffman.o layer12.o layer3.o stream.o synth.o timer.o

.c.o:
$(CC) -fPIC -DPIC $(CFLAGS) -c -o $@ $<

all : lib$(LIBNAME).a lib$(LIBNAME).so

lib$(LIBNAME).so: $(OBJS)
$(CC) -shared $(CFLAGS) $(OBJS) -lc -Wl,-soname -Wl,$(SOVERSION)
-o $(SONAME) &&
ln -sf $(SONAME) $(SOVERSION) &&
ln -sf $(SONAME) lib$(LIBNAME).so


lib$(LIBNAME).a: $(OBJS)
$(AR) rs libmad.a bit.o decoder.o fixed.o frame.o huffman.o layer12.o layer3.o stream.o synth.o timer.o

.PHONY : clean
clean:
rm -f *.a *.o *.bak *.*~ *.o.l *.so

Audio Driver

s3c2410-uda1341.c

/*
* Glue audio driver for the S3C2410 & Philips UDA1341 codec.
*
* Copyright (c) 2000 John Dorsey
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License.
*
* History:
*
* 2000-09-04 John Dorsey SA-1111 Serial Audio Controller support
* was initially added to the sa1100-uda1341.c
* driver.
*
* 2001-06-03 Nicolas Pitre Made this file a separate module, based on
* the former sa1100-uda1341.c driver.
*
* 2001-09-23 Russell King Remove old L3 bus driver.
* 2002-02-26 Sangwook Lee Modifed for S3C2400 <hitchcar@samsung.co.kr>
*
* 2002-06-10 Sangwook Lee Modified for S3C2410 <hitchcar@sec.samsung.com>
*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/pm.h>
#include <linux/l3/l3.h>
#include <linux/l3/uda1341.h>

#include <asm/semaphore.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/dma.h>

#include "s3c2410-audio.h"

#undef DEBUG
#ifdef DEBUG
#define DPRINTK( x... ) printk( ##x )
#define swldebug( x... ) printk( ##x)
#else
#define DPRINTK( x... )
#define swldebug( x...)
#endif


static struct l3_client uda1341;


#ifdef YOU_WANT_CHANGE_MPLL

static unsigned long SAVE_MPLLCON,SAVE_CLKDIVN;
/************************** MPLL *******************************/
static void ChangeMPllValue(int mdiv,int pdiv,int sdiv)
{
rMPLLCON=(mdiv<<12)|(pdiv<<4)|sdiv;
}

/************************ HCLK, PCLK ***************************/

static void ChangeClockDivider(int hdivn,int pdivn)
// hdivn:pdivn FCLK:HCLK:PCLK
// 0:0 1:1:1
// 0:1 1:1:2
// 1:0 1:2:2
// 1:1 1:2:4
{
rCLKDIVN=(hdivn<<1)|pdivn;
}
#endif


static int
mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
/*
* We only accept mixer (type 'M') ioctls.
*/
if (_IOC_TYPE(cmd) != 'M')
return -EINVAL;
// printk("%s[%d] %s : before l3_command n", __FILE__,__LINE__,__FUNCTION__);
return l3_command(&uda1341, cmd, (void *)arg);
}

static struct file_operations uda1341_mixer_fops = {
ioctl: mixer_ioctl,
owner: THIS_MODULE
};

/*
* Audio interface
*/

static void s3c2410_audio_init(void *dummy)
{
/* Initialize the UDA1341 internal state */
l3_open(&uda1341);
}


static void s3c2410_audio_shutdown(void *dummy)
{
#ifdef SWL_CHANGE_CLOCK
rCLKDIVN = SAVE_CLKDIVN;
rMPLLCON = SAVE_MPLLCON;
#endif
l3_close(&uda1341);
}

static int s3c2410_audio_ioctl( struct inode *inode, struct file *file,
uint cmd, ulong arg)
{
long val = 0;
int ret = 0;

switch (cmd) {
case SNDCTL_DSP_STEREO:
ret = get_user(val, (int *) arg);
if (ret)
return ret;
/* the UDA1341 is stereo only */
ret = (val == 0) ? -EINVAL : 1;
return put_user(ret, (int *) arg);

case SNDCTL_DSP_CHANNELS:
case SOUND_PCM_READ_CHANNELS:
/* the UDA1341 is stereo only */
return put_user(2, (long *) arg);

case SNDCTL_DSP_SPEED:
ret = get_user(val, (long *) arg);
if (ret) break;
if (val < 8000) val = 8000;
if (val > 44100) val = 44100;

/* For example
* my wav file 8.0 khz sampling rate.
* codeclk is 384fs = 8.0*384 = 3.0720Mhz
* IISPSR :
* Prescaler control A : PCLK / ( N +1) = 3.072 Mhz
* N = 50.75/3.072 -1 = 15.67 = 0x10
* Prescaler control B : A=B could be
*
*/
switch(val) {
case 8000:
#if (AUDIO_CODEC_CLOCK == 256)
rIISPSR=(24<<5)+24;
#else
rIISPSR=(15<<5)+15;
#endif
break;
case 11025:
#if (AUDIO_CODEC_CLOCK == 256)
rIISPSR=(17<<5)+17;
#else
rIISPSR=(11<<5)+11;
#endif
break;
case 22050:
#if (AUDIO_CODEC_CLOCK == 256)
rIISPSR=(16<<5)+16;
#else
#if (LCD_TYPE == TFT640_480 )
rIISPSR=(10<<5)+10;

#else
rIISPSR=(5<<5)+5;
//Prescaler_A/B= for 8.4672MHz, 1:2:4, Fclk = 203MHz
#endif
#endif
break;
case 44100:
#if (AUDIO_CODEC_CLOCK == 256)
rIISPSR=(7<<5)+7;
//Prescaler_A/B= for 11.2896MHz, 1:2:4, Fclk = 203MHz, 640*480
#else
rIISPSR=(2<<5)+2;
#endif
break;
default:
#if (AUDIO_CODEC_CLOCK == 256)
rIISPSR=(16<<5)+16;
#else
rIISPSR=(2<<5)+2;
#endif
break;
}

case SOUND_PCM_READ_RATE: /* I am n't sure what it is */
return put_user(val , (long *) arg);

case SNDCTL_DSP_SETFMT:
case SNDCTL_DSP_GETFMTS:
/*printk("%s[%d] %s : DSP_SAMPLESIZE n", __FILE__,__LINE__,__FUNCTION__); */
/* we can do 16-bit only */
return put_user(AFMT_S16_LE, (long *) arg);

default:
/* Maybe this is meant for the mixer (as per OSS Docs) */
return mixer_ioctl(inode, file, cmd, arg);
}

return ret;
}

static audio_stream_t output_stream;

static audio_state_t audio_state = {
output_stream: &output_stream,
skip_dma_init: 0, /* done locally */
hw_init: s3c2410_audio_init,
hw_shutdown: s3c2410_audio_shutdown,
client_ioctl: s3c2410_audio_ioctl,
sem: __MUTEX_INITIALIZER(audio_state.sem),
};

static int s3c2410_audio_open(struct inode *inode, struct file *file)
{
/* Acquire and initialize DMA for transfer */
output_stream.dma_ch = 2;/* used by s3c2410_audio_attach function */
audio_state.output_id = "S3C2410 Audio Out";
audio_state.output_dma = DMA2_SOURCE0;

/******* !!! VERY IMPORTANT !!!!! *************************/
/*
* THIS FUNCTION ENTRY POINT TO drivers/sound/s3c2410-audio.c
*/
return s3c2410_audio_attach(inode, file, &audio_state);
}

/*
* Missing fields of this structure will be patched with the call
* to sa1100_audio_attach().
*/
static struct file_operations s3c2410_audio_fops = {
open: s3c2410_audio_open,
owner: THIS_MODULE
};


static int audio_dev_id, mixer_dev_id;


static int __init s3c2410_uda1341_init(void)
{
struct uda1341_cfg cfg;
int ret;

DPRINTK(" s3c2410_uda1341_init n");
ret = l3_attach_client(&uda1341, "l3-s3c2410", "uda1341");
if (ret) goto out;

#if (AUDIO_CODEC_CLOCK == 256)
cfg.fs = 256;
#else
cfg.fs = 384;
#endif
cfg.format = FMT_I2S;

/*---------------!!! VERY IMPORTANT !!!!---------------
*
* THIS FUNCTION ENTRY POINT TO drivers/l3/l3-s3c2410.c
* l3_command defined in include/linux/l3/l3.h
*/

/*
*
l3_command(&uda1341, L3_UDA1341_CONFIGURE, &cfg);
------> drivers/l3/l3-s3c2410.c init1341();
*
*/

l3_command(&uda1341, L3_UDA1341_CONFIGURE, &cfg);

/* register devices */
audio_dev_id = register_sound_dsp(&s3c2410_audio_fops, -1);
mixer_dev_id = register_sound_mixer(&uda1341_mixer_fops, -1);

printk(KERN_INFO "S3C2410 UDA1341 / IIS initializedn");

return 0;

out:
return ret;
}

static void __exit s3c2410_uda1341_exit(void)
{
unregister_sound_dsp(audio_dev_id);
unregister_sound_mixer(mixer_dev_id);
s3c2410_free_dma(output_stream.dma_ch);
/* s3c2410_free_dma(input_stream.dma_ch); */
l3_detach_client(&uda1341);
}

module_init(s3c2410_uda1341_init);
module_exit(s3c2410_uda1341_exit);

MODULE_AUTHOR("Sangwook Lee ");
MODULE_DESCRIPTION("Audio driver for the S3C2410 & Philips UDA1341 codec.");

EXPORT_NO_SYMBOLS;

s3c2410-audio.c

/*
* Common audio handling for the S3C2410 processor
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License.
*
*
* This module handles the generic buffering/DMA/mmap audio interface for
* codecs connected to the S3C2410 chip. All features depending on specific
* hardware implementations like supported audio formats or samplerates are
* relegated to separate specific modules.
*
*
* 2001-10-19 Nicolas Pitre - brought DMA registration processing
* into this module for better ressource
* management. This also fixes a bug
* with the suspend/resume logic.
* 2002-03-4 Sangwook Lee <hitchcar@samsung.co.kr>
* modified for s3c2410
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#include <asm/dma.h>
#include "s3c2410-audio.h"

#ifdef DEBUG
#define DPRINTK( x... ) printk( ##x )
#define swldebug(x...) printk( ##x )
#else
#define DPRINTK( x... )
#define swldebug( x... )
#endif


#define AUDIO_NAME "s3c2410-audio"
#define AUDIO_NBFRAGS_DEFAULT 16


/* 8192 works on 22.05Khz but not 44.1Khz */

#define AUDIO_FRAGSIZE_DEFAULT 16384


#define NEXT_BUF(_s_,_b_) {
(_s_)->_b_##_idx++;
(_s_)->_b_##_idx %= (_s_)->nbfrags;
(_s_)->_b_ = (_s_)->buffers + (_s_)->_b_##_idx; }

#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref)

/*
* This function frees all buffers
*/

static void audio_clear_buf(audio_stream_t * s)
{
DPRINTK("audio_clear_bufn");

/* ensure DMA won't run anymore */
s->active = 0;
s->stopped = 0;
s3c2410_dma_flush_all(s->dma_ch);

if (s->buffers) {
int frag;
for (frag = 0; frag < s->nbfrags; frag++) {
if (!s->buffers[frag].master)
continue;
consistent_free(s->buffers[frag].start,
s->buffers[frag].master,
s->buffers[frag].dma_addr);
}
kfree(s->buffers);
s->buffers = NULL;
}

s->buf_idx = 0;
s->buf = NULL;
}


/*
* This function allocates the buffer structure array and buffer data space
* according to the current number of fragments and fragment size.
*/

static int audio_setup_buf(audio_stream_t * s)
{
int frag;
int dmasize = 0;
char *dmabuf = NULL;
dma_addr_t dmaphys = 0;

if (s->buffers) return -EBUSY;

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++) {
audio_buf_t *b = &s->buffers[frag];

/*
* Let's allocate non-cached memory for DMA buffers.
* We try to allocate all memory at once.
* If this fails (a common reason is memory fragmentation),
* then we allocate more smaller buffers.
*/
if (!dmasize) {
dmasize = (s->nbfrags - frag) * s->fragsize;

do {
dmabuf =
consistent_alloc(GFP_KERNEL|GFP_DMA, dmasize, &dmaphys);
if (!dmabuf) dmasize -= s->fragsize;
} while (!dmabuf && dmasize);
if (!dmabuf) goto err;
b->master = dmasize;
memzero(dmabuf, dmasize);
}

b->start = dmabuf;
b->dma_addr = dmaphys;
b->stream = s;
sema_init(&b->sem, 1);
DPRINTK("buf %d: virt_start %p dma_addr %pn", frag, b->start,
b->dma_addr);

dmabuf += s->fragsize;
dmaphys += s->fragsize;
dmasize -= s->fragsize;
}

s->buf_idx = 0;
s->buf = &s->buffers[0];
s->bytecount = 0;
s->fragcount = 0;
return 0;
err:
printk(AUDIO_NAME ": unable to allocate audio memoryn ");
audio_clear_buf(s);
return -ENOMEM;
}

/*
* This function yanks all buffers from the DMA code's control and
* resets them ready to be used again.
*/

static void audio_reset_buf(audio_stream_t * s)
{
int frag;

s->active = 0;
s->stopped = 0;
s3c2410_dma_flush_all(s->dma_ch);
if (s->buffers) {
for (frag = 0; frag < s->nbfrags; frag++) {
audio_buf_t *b = &s->buffers[frag];
b->size = 0;
sema_init(&b->sem, 1);
}
}
s->bytecount = 0;
s->fragcount = 0;
}


/*
* DMA callback functions
*/

static void audio_dmaout_done_callback(void *buf_id, int size)
{
audio_buf_t *b = (audio_buf_t *) buf_id;
audio_stream_t *s = b->stream;

/* Accounting */
s->bytecount += size;
s->fragcount++;

/* Recycle buffer */
if (s->mapped) {
s3c2410_dma_queue_buffer(s->dma_ch, buf_id,
b->dma_addr, s->fragsize);
}
else{
up(&b->sem);
}
/* And any process polling on write. */
wake_up(&s->wq);

}

static void audio_dmain_done_callback(void *buf_id, int size)
{
audio_buf_t *b = (audio_buf_t *) buf_id;
audio_stream_t *s = b->stream;

/* Accounting */
s->bytecount += size;
s->fragcount++;

/* Recycle buffer */
if (s->mapped) {
s3c2410_dma_queue_buffer(s->dma_ch, buf_id,
b->dma_addr, s->fragsize);
} else {
b->size = size;
up(&b->sem);
}

/* And any process polling on write. */
wake_up(&s->wq);
}

static int audio_sync(struct file *file)
{
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *s = state->output_stream;
audio_buf_t *b;

DPRINTK("audio_syncn");

if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped)
return 0;

/*
* Send current buffer if it contains data. Be sure to send
* a full sample count.
*/
b = s->buf;
if (b->size &= ~3) {
down(&b->sem);
s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, b->size);
b->size = 0;
NEXT_BUF(s, buf);
}

/*
* Let's wait for the last buffer we sent i.e. the one before the
* current buf_idx. When we acquire the semaphore, this means either:
* - DMA on the buffer completed or
* - the buffer was already free thus nothing else to sync.
*/
b = s->buffers + ((s->nbfrags + s->buf_idx - 1) % s->nbfrags);
if (down_interruptible(&b->sem))
return -EINTR;
up(&b->sem);
return 0;
}


static int audio_write(struct file *file, const char *buffer,
size_t count, loff_t * ppos)
{
const char *buffer0 = buffer;
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *s = state->output_stream;
int chunksize, ret = 0;

// printk("n 1~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ n");
// printk(__FUNCTION__ " continue write fucntion here n");

if (ppos != &file->f_pos)return -ESPIPE;
if (s->mapped) return -ENXIO;
if (!s->buffers && audio_setup_buf(s)) return -ENOMEM;

while (count > 0) {
audio_buf_t *b = s->buf;

/* Wait for a buffer to become free */
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
if (down_trylock(&b->sem)) break;
} else {
ret = -ERESTARTSYS;
if (down_interruptible(&b->sem)) break;
}

/* Feed the current buffer */
chunksize = s->fragsize - b->size;
// printk(__FUNCTION__ " s->fragsize 0x%08X b->size 0x%08X count 0x%08X n", s->fragsize,b->size,count );

if (chunksize > count) chunksize = count;

DPRINTK("write %d to buf_idx %dn", chunksize, s->buf_idx);
if (copy_from_user(b->start + b->size, buffer, chunksize)) {
up(&b->sem);
return -EFAULT;
}

b->size += chunksize;
buffer += chunksize;
count -= chunksize;
if (b->size < s->fragsize) {
// printk(" b->size 0x%08X s->fragsize 0x%08X n",b->size,s->fragsize);
up(&b->sem);
break;
}

/*
* Send current buffer to dma
* b->dma_addr is physical address
*/
s->active = 1;
s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, b->size);
b->size = 0; /* indicate that the buffer has been sent */
NEXT_BUF(s, buf);
}

if ((buffer - buffer0)) ret = buffer - buffer0;

DPRINTK("audio_write: return=%dn", ret);
return ret;
}


static inline void audio_check_tx_spin(audio_state_t *state)
{
/*
* With some codecs like the Philips UDA1341 we must ensure
* there is an output stream at any time while recording since
* this is how the UDA1341 gets its clock from the S3C2410.
* So while there is no playback data to send, the output DMA
* will spin with all zeroes. We use the cache flush special
* area for that.
*/
#ifdef S3C2410_AUDIO_REC
if (state->need_tx_for_rx && !state->tx_spinning) {
s3c2410_dma_set_spin(state->output_stream->dma_ch,
(dma_addr_t)FLUSH_BASE_PHYS, 2048);
state->tx_spinning = 1;
}
#endif
}


static void audio_prime_dma(audio_stream_t *s)
{
int i;

s->active = 1;
for (i = 0; i < s->nbfrags; i++) {
audio_buf_t *b = s->buf;
down(&b->sem);
s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, s->fragsize);
NEXT_BUF(s, buf);
}
}


static int audio_read(struct file *file, char *buffer,
size_t count, loff_t * ppos)
{
char *buffer0 = buffer;
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *s = state->input_stream;
int chunksize, ret = 0;

DPRINTK("audio_read: count=%dn", count);

if (ppos != &file->f_pos)
return -ESPIPE;
if (s->mapped)
return -ENXIO;

if (!s->active) {
if (!s->buffers && audio_setup_buf(s))
return -ENOMEM;
audio_check_tx_spin(state);
audio_prime_dma(s);
}

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

/* Grab data from the current buffer */
chunksize = b->size;
if (chunksize > count)
chunksize = count;
DPRINTK("read %d from %dn", 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_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, s->fragsize);
NEXT_BUF(s, buf);
}

if ((buffer - buffer0))
ret = buffer - buffer0;
DPRINTK("audio_read: return=%dn", ret);
return ret;
}


static int audio_mmap(struct file *file, struct vm_area_struct *vma)
{
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *s;
unsigned long size, vma_addr;
int i, ret;

if (vma->vm_pgoff != 0)
return -EINVAL;

if (vma->vm_flags & VM_WRITE) {
if (!state->wr_ref)
return -EINVAL;;
s = state->output_stream;
} else if (vma->vm_flags & VM_READ) {
if (!state->rd_ref)
return -EINVAL;
s = state->input_stream;
} else return -EINVAL;

if (s->mapped)
return -EINVAL;
size = vma->vm_end - vma->vm_start;
if (size != s->fragsize * s->nbfrags)
return -EINVAL;
if (!s->buffers && audio_setup_buf(s))
return -ENOMEM;
vma_addr = vma->vm_start;
for (i = 0; i < s->nbfrags; i++) {
audio_buf_t *buf = &s->buffers[i];
if (!buf->master)
continue;
ret = remap_page_range(vma_addr, buf->dma_addr, buf->master, vma->vm_page_prot);
if (ret)
return ret;
vma_addr += buf->master;
}
s->mapped = 1;

return 0;
}


static unsigned int audio_poll(struct file *file,
struct poll_table_struct *wait)
{
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *is = state->input_stream;
audio_stream_t *os = state->output_stream;
unsigned int mask = 0;
int i;

DPRINTK("audio_poll(): mode=%s%sn",
(file->f_mode & FMODE_READ) ? "r" : "",
(file->f_mode & FMODE_WRITE) ? "w" : "");

if (file->f_mode & FMODE_READ) {
/* Start audio input if not already active */
if (!is->active) {
if (!is->buffers && audio_setup_buf(is))
return -ENOMEM;
audio_check_tx_spin(state);
audio_prime_dma(is);
}
poll_wait(file, &is->wq, wait);
}

if (file->f_mode & FMODE_WRITE) {
if (!os->buffers && audio_setup_buf(os))
return -ENOMEM;
poll_wait(file, &os->wq, wait);
}

if (file->f_mode & FMODE_READ) {
if (is->mapped) {
if (is->bytecount > 0)
mask |= POLLIN | POLLRDNORM;
} else {
for (i = 0; i < is->nbfrags; i++) {
if (atomic_read(&is->buffers[i].sem.count) > 0) {
mask |= POLLIN | POLLRDNORM;
break;
}
}
}
}
if (file->f_mode & FMODE_WRITE) {
if (os->mapped) {
if (os->bytecount > 0)
mask |= POLLOUT | POLLWRNORM;
} else {
for (i = 0; i < os->nbfrags; i++) {
if (atomic_read(&os->buffers[i].sem.count) > 0) {
mask |= POLLOUT | POLLWRNORM;
break;
}
}
}
}

DPRINTK("audio_poll() returned mask of %s%sn",
(mask & POLLIN) ? "r" : "",
(mask & POLLOUT) ? "w" : "");

return mask;
}


static loff_t audio_llseek(struct file *file, loff_t offset, int origin)
{
return -ESPIPE;
}


static int audio_set_fragments(audio_stream_t *s, int val)
{
if (s->active)
return -EBUSY;
if (s->buffers)
audio_clear_buf(s);
s->nbfrags = (val >> 16) & 0x7FFF;
val &= 0xffff;
if (val < 4)
val = 4;
if (val > 15)
val = 15;
s->fragsize = 1 << val;
if (s->nbfrags < 2)
s->nbfrags = 2;
if (s->nbfrags * s->fragsize > 128 * 1024)
s->nbfrags = 128 * 1024 / s->fragsize;
if (audio_setup_buf(s))
return -ENOMEM;
return val|(s->nbfrags << 16);
}

static int audio_ioctl(struct inode *inode, struct file *file,
uint cmd, ulong arg)
{
audio_state_t *state = (audio_state_t *)file->private_data;
audio_stream_t *os = state->output_stream;
audio_stream_t *is = state->input_stream;
long val;

/* dispatch based on command */
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, (int *)arg);

case SNDCTL_DSP_GETBLKSIZE:
if (file->f_mode & FMODE_WRITE)
return put_user(os->fragsize, (int *)arg);
else
return put_user(is->fragsize, (int *)arg);

case SNDCTL_DSP_GETCAPS:
val = DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP;
if (is && os)
val |= DSP_CAP_DUPLEX;
return put_user(val, (int *)arg);

case SNDCTL_DSP_SETFRAGMENT:
if (get_user(val, (long *) arg))
return -EFAULT;
if (file->f_mode & FMODE_READ) {
int ret = audio_set_fragments(is, val);
if (ret < 0)
return ret;
ret = put_user(ret, (int *)arg);
if (ret)
return ret;
}
if (file->f_mode & FMODE_WRITE) {
int ret = audio_set_fragments(os, val);
if (ret < 0)
return ret;
ret = put_user(ret, (int *)arg);
if (ret)
return ret;
}
return 0;

case SNDCTL_DSP_SYNC:
return audio_sync(file);

case SNDCTL_DSP_SETDUPLEX:
return 0;

case SNDCTL_DSP_POST:
return 0;

case SNDCTL_DSP_GETTRIGGER:
val = 0;
if (file->f_mode & FMODE_READ && is->active && !is->stopped)
val |= PCM_ENABLE_INPUT;
if (file->f_mode & FMODE_WRITE && os->active && !os->stopped)
val |= PCM_ENABLE_OUTPUT;
return put_user(val, (int *)arg);

case SNDCTL_DSP_SETTRIGGER:
if (get_user(val, (int *)arg))
return -EFAULT;
if (file->f_mode & FMODE_READ) {
if (val & PCM_ENABLE_INPUT) {
if (!is->active) {
if (!is->buffers && audio_setup_buf(is))
return -ENOMEM;
audio_prime_dma(is);
}
audio_check_tx_spin(state);
if (is->stopped) {
is->stopped = 0;
s3c2410_dma_resume(is->dma_ch);
}
} else {
s3c2410_dma_stop(is->dma_ch);
is->stopped = 1;
}
}
if (file->f_mode & FMODE_WRITE) {
if (val & PCM_ENABLE_OUTPUT) {
if (!os->active) {
if (!os->buffers && audio_setup_buf(os))
return -ENOMEM;
if (os->mapped)
audio_prime_dma(os);
}
if (os->stopped) {
os->stopped = 0;
s3c2410_dma_resume(os->dma_ch);
}
} else {
s3c2410_dma_stop(os->dma_ch);
os->stopped = 1;
}
}
return 0;

case SNDCTL_DSP_GETOPTR:
case SNDCTL_DSP_GETIPTR:
{
count_info inf = { 0, };
audio_stream_t *s = (cmd == SNDCTL_DSP_GETOPTR) ? os : is;
audio_buf_t *b;
dma_addr_t ptr;
int bytecount, offset, flags;

if ((s == is && !(file->f_mode & FMODE_READ)) ||
(s == os && !(file->f_mode & FMODE_WRITE)))
return -EINVAL;
if (s->active) {
save_flags_cli(flags);
offset = 0;
bytecount = s->bytecount + offset;
s->bytecount = -offset;
inf.blocks = s->fragcount;
s->fragcount = 0;
restore_flags(flags);
if (bytecount < 0)
bytecount = 0;
inf.bytes = bytecount;
}
return copy_to_user((void *)arg, &inf, sizeof(inf));
}

case SNDCTL_DSP_GETOSPACE:
{
audio_buf_info inf = { 0, };
int i;

if (!(file->f_mode & FMODE_WRITE))
return -EINVAL;
if (!os->buffers && audio_setup_buf(os))
return -ENOMEM;
for (i = 0; i < os->nbfrags; i++) {
if (atomic_read(&os->buffers[i].sem.count) > 0) {
if (os->buffers[i].size == 0)
inf.fragments++;
inf.bytes += os->fragsize - os->buffers[i].size;
}
}
inf.fragstotal = os->nbfrags;
inf.fragsize = os->fragsize;
return copy_to_user((void *)arg, &inf, sizeof(inf));
}

case SNDCTL_DSP_GETISPACE:
{
audio_buf_info inf = { 0, };
int i;

if (!(file->f_mode & FMODE_READ))
return -EINVAL;
if (!is->buffers && audio_setup_buf(is))
return -ENOMEM;
for (i = 0; i < is->nbfrags; i++) {
if (atomic_read(&is->buffers[i].sem.count) > 0) {
if (is->buffers[i].size == is->fragsize)
inf.fragments++;
inf.bytes += is->buffers[i].size;
}
}
inf.fragstotal = is->nbfrags;
inf.fragsize = is->fragsize;
return copy_to_user((void *)arg, &inf, sizeof(inf));
}

case SNDCTL_DSP_NONBLOCK:
file->f_flags |= O_NONBLOCK;
return 0;

case SNDCTL_DSP_RESET:
if (file->f_mode & FMODE_READ) {
audio_reset_buf(is);
}
if (file->f_mode & FMODE_WRITE) {
audio_reset_buf(os);
}
return 0;

default:
/*
* Let the client of this module handle the
* non generic ioctls
* your can find other functions in s3c2410-uda1341.c
*/
return state->client_ioctl(inode, file, cmd, arg);
}

return 0;
}


static int audio_release(struct inode *inode, struct file *file)
{
audio_state_t *state = (audio_state_t *)file->private_data;
DPRINTK("audio_releasen");

down(&state->sem);

if (file->f_mode & FMODE_READ) {
audio_clear_buf(state->input_stream);
if (!state->skip_dma_init) {
s3c2410_free_dma(state->input_stream->dma_ch);
if (state->need_tx_for_rx && !state->wr_ref)
s3c2410_free_dma(state->output_stream->dma_ch);
}
state->rd_ref = 0;
}

if (file->f_mode & FMODE_WRITE) {
audio_sync(file);
audio_clear_buf(state->output_stream);
if (!state->skip_dma_init)
if (!state->need_tx_for_rx || !state->rd_ref) {
s3c2410_free_dma(state->output_stream->dma_ch);
}
state->wr_ref = 0;
}

if (!AUDIO_ACTIVE(state)) {
if (state->hw_shutdown)
state->hw_shutdown(state->data);
}

up(&state->sem);
return 0;
}

extern int s3c2410_audio_attach(struct inode *inode, struct file *file,
audio_state_t *state)
{
int err, need_tx_dma;

down(&state->sem);
/* access control */
err = -ENODEV;
if ((file->f_mode & FMODE_WRITE) && !state->output_stream)
goto out;
err = -EBUSY;
if ((file->f_mode & FMODE_WRITE) && state->wr_ref)
goto out;

/* request DMA channels */

need_tx_dma = (file->f_mode & FMODE_WRITE);
if (state->wr_ref) need_tx_dma = 0;
if (need_tx_dma) {
err = s3c2410_request_dma(state->output_stream->dma_ch,
state->output_id,
state->output_dma);
if (err) goto out;
}

/* now complete initialisation */
if (!AUDIO_ACTIVE(state)) {
if (state->hw_init)state->hw_init(state->data);
}

if ((file->f_mode & FMODE_WRITE)) {
state->wr_ref = 1;
audio_clear_buf(state->output_stream);
state->output_stream->fragsize = AUDIO_FRAGSIZE_DEFAULT;
state->output_stream->nbfrags = AUDIO_NBFRAGS_DEFAULT;
state->output_stream->mapped = 0;
s3c2410_dma_set_callback(state->output_stream->dma_ch,
audio_dmaout_done_callback);
init_waitqueue_head(&state->output_stream->wq);
}

file->private_data = state;
file->f_op->release = audio_release;
file->f_op->write = audio_write;
file->f_op->read = audio_read;
file->f_op->mmap = audio_mmap;
file->f_op->poll = audio_poll;
file->f_op->ioctl = audio_ioctl;
file->f_op->llseek = audio_llseek;
err = 0;

out:
up(&state->sem);
return err;
}

//EXPORT_SYMBOL(s3c2410_audio_attach);

MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("Common audio handling for the SA11x0 processor");
MODULE_LICENSE("GPL");

s3c2410-audio.h

/*
* Common audio handling for the S3C2400
*
* Copyright (c) 2000 Nicolas Pitre <nico@cam.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License.
*/


/*
* Buffer Management
*/

typedef struct {
int size; /* buffer size */
char *start; /* points to actual buffer */
dma_addr_t dma_addr; /* physical buffer address */
struct semaphore sem; /* down before touching the buffer */
int master; /* owner for buffer allocation, contain size when true */
struct audio_stream_s *stream; /* owning stream */
struct semaphore *wsem; /* down before touching the buffer */
} audio_buf_t;

typedef struct audio_stream_s {
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 i.e. buffers */
int bytecount; /* nbr of processed bytes */
int fragcount; /* nbr of fragment transitions */
dmach_t dma_ch; /* DMA channel ID */
wait_queue_head_t wq; /* for poll */
int mapped:1; /* mmap()'ed buffers */
int active:1; /* actually in progress */
int stopped:1; /* might be active but stopped */
} audio_stream_t;

/*
* State structure for one instance
*/

typedef struct {
audio_stream_t *output_stream;
audio_stream_t *input_stream;
dma_device_t output_dma;
dma_device_t input_dma;
char *output_id;
char *input_id;
int rd_ref:1; /* open reference for recording */
int wr_ref:1; /* open reference for playback */
int need_tx_for_rx:1; /* if data must be sent while receiving */
int tx_spinning:1; /* tx spinning active */
int skip_dma_init:1; /* hack for the S3C2400 */
void *data;
void (*hw_init)(void *);
void (*hw_shutdown)(void *);
int (*client_ioctl)(struct inode *, struct file *, uint, ulong);
struct pm_dev *pm_dev;
struct semaphore sem; /* to protect against races in attach() */
} audio_state_t;

/*
* Functions exported by this module
*/
extern int s3c2410_audio_attach( struct inode *inode, struct file *file,
audio_state_t *state);

0 意見: