www.pudn.com > uda1380-s3c2410-linux-rev.zip.zip > uda1380.c


/******
 *
 * FileName: uda1380.c
 *
 * Version: 0.10
 *
 * Date: 2005-03-20
 *
 * Copyright (C) 2005 Shenzhen Newenjoy Technology Co,.LTD.
 *
 * Designed by Yinshenk < Email: yinshenk@21cn.com >
 *
 * Philips UDA1380 Audio drive device for Samsuang S3C2410 Linux 4.8.XX
 * configure.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License.
 * 
 *****************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

#include 
#include 
#include 
#include 
#include 
#include 

#include "2410addr.h"
#include "2410lib.h"
#include "def.h"
#include "2410iic.h"
#include "uda1380.h"

/*
 * Samsung S3C2410 core and Philips UDA1380 Audio Hardware circuit
 * connecter description (From S3C2410 to UDA1380 ).
 *
 * I2C Bus circuit connect part:
 * 	IIC_SDA/GPE15	---> L3Data
 * 	IIC_SCL/GPE14	---> L3Clock
 *
 * I2S Bus circuirt connect part:
 * 	IIS_SDO/IIS_SDI/GPE4	---> DATA1
 * 	IIS_SDI/nSS0/GPE3	---> DATA0
 * 	CDCLK/GPE2		---> SYSCLK
 * 	IIS_SCLK/GPE1		---> BCKI
 * 	IIS_LRCK/GPE0		---> WSI
 *
 * other:
 * 	L3MODE		---> (GND)
 * 	SEL_L3_IIC	---> (VCC)
 */

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

/*
 * This drive device program value and paremeter description.
 *****************************************************************************/
#define UDA1380_ADDR		0x30	// Philips UDA1380 Audio device address.

#define MUTE		        (0x1 << 2)

#define NO_DE_EMPHASIS		(0x10 << 3)
#define DE_EMPHASIS_32		(0x11 << 3)
#define DE_EMPHASIS_441		(0x12 << 3)
#define DE_EMPHASIS_48		(0x13 << 3)

#define GPIO_L3CLOCK		(GPIO_MODE_OUT | GPIO_PULLUP_DIS | GPIO_B4)
#define GPIO_L3DATA			(GPIO_MODE_OUT | GPIO_PULLUP_DIS | GPIO_B3)
#define GPIO_L3MODE			(GPIO_MODE_OUT | GPIO_PULLUP_DIS | GPIO_B2)

#define AUDIO_NAME			"UDA1380"
#define AUDIO_NAME_VERBOSE	"UDA1380 audio driver"

#define AUDIO_FMT_MASK		(AFMT_S16_LE)
#define AUDIO_FMT_DEFAULT	(AFMT_S16_LE)

#define AUDIO_CHANNELS_DEFAULT	2
#define AUDIO_RATE_DEFAULT		22050

#define AUDIO_NBFRAGS_DEFAULT	8
#define AUDIO_FRAGSIZE_DEFAULT	8192

#define S_CLOCK_FREQ	384									// frequency 
#define PCM_ABS(a) (a < 0 ? -a : a)

/* define audio managment struction */
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
	int master;												// owner for buffer allocation, contain size when true
} audio_buf_t;

/* define audio data stream managment struction */
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)
} audio_stream_t;

static audio_stream_t output_stream;

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


static u_int audio_rate;									//
static int audio_channels;									//
static int audio_fmt;										//
static u_int audio_fragsize;								//
static u_int audio_nbfrags;									//


static int audio_rd_refcount;								//
static int audio_wr_refcount;								//
#define audio_active		(audio_rd_refcount | audio_wr_refcount)

static int audio_dev_dsp;									//
static int audio_dev_mixer;									//
static int audio_mix_modcnt;								//

static int uda1380_volume;									//
static u8 uda_sampling;										//

/***** S.Yin insert program begin 2005-3-20 **********/

static char _iicData[IICBUFSIZE];
static volatile int _iicDataCount;
static volatile int _iicStatus;
static volatile int _iicControl;
static int _iicPt;

U32 uda1380_data;

static int delayLoopCount = FCLK/10000/10;

/* digital master volume control register */
#define DEF_VOLUME	20

unsigned long rtc_r_GPECON;									// Port E control register.
unsigned long rtc_r_GPEUP;									// Port E pull-up disable register.
unsigned long r_IICCON;										// I2C Bus control register.
unsigned long r_IICSTAT;									// I2C Bus status register.
unsigned long r_IICADD;										// I2C Bus address register.
unsigned long r_IICDS;										// I2C Bus Tx/Rx data shift register.

#define rGPECON		(*(volatile unsigned long *)rtc_r_GPECON)	//Port E control
#define rGPEUP		(*(volatile unsigned long *)rtc_r_GPEUP) 	//Pull-up control E

#define rIICCON  	(*(volatile unsigned char *)r_IICCON) 	//IIC control
#define rIICSTAT	(*(volatile unsigned char *)r_IICSTAT) 	//IIC status
#define rIICADD		(*(volatile unsigned char *)r_IICADD) 	//IIC address
#define rIICDS		(*(volatile unsigned char *)r_IICDS) 	//IIC data shift
	
/* Philips UDA1380 synchronization or Initialization infomation */
static struct uda1380_reg_info {
	unsigned short num;										// Reigisters.
	unsigned short default_value;							// parameter.
} uda1380_reg_info[] = {
	{ REG0, REG0_EN_ADC|REG0_EN_DAC|REG0_EN_INT|REG0_EN_DEC|REG0_SC_384FS|REG0_ADC_USE_SYSCLK|REG0_DAC_USE_SYSCLK },
	{ I2S_REG, (FMT_I2S << I2S_REG_SFORO_SHIFT)|(FMT_I2S << I2S_REG_SFORI_SHIFT | (1<< 6)) },
	{ PWR_REG, PWR_REG_PON_DAC|PWR_REG_PON_BIAS },
	{ MASTER_VOL_REG, 0x4040 },
	{ MIXER_VOL_REG, 0xaa },
	{ MBT_REG, 0xaa},
	{ MMCDM_REG, 0x0 },
	{ MIXER_CTL_REG, 0x70 },
	{ DEC_VOL_REG, 0xaa },
	{ DPM_REG, 0xaa },
	{ DEC_ADC_REG, 0xaa },
	{ DEC_AGC_REG, 0xaa },
}; 

/** S.Yin insert program end 2005-03-20 ****/

/*
 * function program part of the Philips UDA1380 audio.
 *****************************************************************************/

/***** S.Yin insert program begin 2005-03-20 **********/

/**
 * I2C Bus control register address set.
 */
int rtc_address_map(void)
{
    rtc_r_GPECON=(unsigned long)__ioremap(0x56000040,4,0);	//
    rtc_r_GPEUP	=(unsigned long)__ioremap(0x56000048,4,0);	//
    r_IICCON	=(unsigned long)__ioremap(0x54000000,4,0);	//
    r_IICSTAT	=(unsigned long)__ioremap(0x54000004,4,0);	//
    r_IICADD	=(unsigned long)__ioremap(0x54000008,4,0);	//
    r_IICDS		=(unsigned long)__ioremap(0x5400000c,4,0);	//

    return 0;
}

/**
 * Delay control.
 * time=0: adjust the Delay function by WatchDog timer.
 * time>0: the number of loop time resolution of time is 100us.
 */
void Delay(int time)
{
    int i, adjust = 0;										// define value.

    if ( time == 0 ) {										//
        time = 200;											// reset paremeter.
        adjust = 1;											//
        delayLoopCount = 400;								//

        //PCLK/1M,Watch-dog disable,1/64,interrupt disable,reset disable
        rWTCON = ((PCLK/1000000-1)<<8)|(2<<3); 				//
        rWTDAT = 0xffff;									//for first update
        rWTCNT = 0xffff;									//resolution=64us @any PCLK 
        rWTCON = ((PCLK/1000000-1)<<8)|(2<<3)|(1<<5);	 	//Watch-dog timer start
    }
    for ( ; time > 0; time-- )								// Delay.
        for ( i = 0; i < delayLoopCount; i++ );				//
    if ( adjust == 1 ) {									// loop counter.
        rWTCON = ((PCLK/1000000-1)<<8)|(2<<3);				//
															// Watch-dog timer stop
        i = 0xffff - rWTCNT;    							// 1count->64us, 200*400 cycle runtime = 64*i us
        delayLoopCount = 8000000/(i*64);					// 200*400:64*i=1*x:100 -> x=80000*100/(64*i)   
    }
}

/**
 * Check ACK and Pending bit.
 */
int AckPoll( U8 slvAddr )
{
    int i;													// define value.

    while ( 1 ) {											//
        rIICDS	= slvAddr;									// Write slave address to rIICDS.
        rIICSTAT	= 0xf0;									// Write 0xf0 (M/T start) to rIICSTAT.
        rIICCON	= 0xe0;										// Resumes IIC operation. 
        Delay(2);											// Wait until stop condtion is in effect.

        for ( i = 0; i < 1000; i ++ ) {						// 
            _iicStatus = rIICSTAT;							// To check if _iicStatus is changed 
            if ( !(_iicStatus & 0x1 )) {					// CHeck ACK flag received.
//                printk("ACK is received................\n");
                return 0;									// Normal return when ACK is received.
            }
            if ( i > 256 )									// Check time over.
                return 5;									// Error return.
        }
    }
    return 2;												// Error return.
}

/**
 * I2C bus write an slave address to client device.
 */
int WriteInitByte( U8 slvAddr )
{
    int i;													// define value.

    rIICDS	= slvAddr;	  			 				 		// Write slave address to rIICDS.
    for ( i = 0; i < 20; i ++ );							//
    rIICSTAT	= 0xf0;										// Write 0xf0 (M/T start) to rIICSTAT.
    Delay(2);												// Delay. The data of the rIICDS is transmitted.

    for ( i = 0; i < 2000; i ++ ) {							// The data of the rIICDS is shifted to SDA, and error check.
        _iicControl = rIICCON;								//
        if (( _iicControl & 0x10 ) > 0 )					// ACK period and then interrupt is pending.
            return 0;										// Normal out.
        if ( i > 1000 )										// Timeout.
            return 1;										// Stop or Error return.
    }
    return 2;												// Error return.
}

/**
 * I2C Bus write an data to client device ( Master Transmitted Mode Operation ).
 */
int _Wr2410Iic( U8 slvAddr, U8 addr, U32 wdata )
{
    int i;													// define value.

    /* Configured. Initialize. */
    _iicData[0]	= addr;										// Set register address.
    _iicData[1]	= wdata / 256;								// Set data high tybe.
    _iicData[2]	= wdata & 0xff;								// Set data low tybe.

    _iicPt	= 0;											// Define data pointer.
    _iicDataCount	= 3;									// Set received data counter.
   
    /* IIC-Bus send initialize tybe (device address) */
    if ( WriteInitByte( slvAddr ) > 0 )						//
        return 1;											//

    /* IIC-Bus send register address and data */
    while ( _iicDataCount ) {								// 
        rIICDS = _iicData[_iicPt];							// Write slave address to rIICDS.
        for ( i = 0; i < 20; i ++ );						// 
        rIICCON  = 0xe0;									// CLear pending bit to resumes IIC operation.

        _iicPt += 1;										// Counter pointer.
        _iicDataCount -= 1;									// Data counter.
        Delay(2);											// Delay. The data of the rIICDS is transmitted.

        for ( i = 0; i < 1000; i ++ ) {						// Error status process.
            _iicControl = rIICCON;							//
            if (( _iicControl & 0x10 ) > 0 )				// ACK period and then interrupt is pending.
                break;										// normal out.
            if ( i > 256 )									// Timeout.
                return 2;									// Stop or Error return.
        }
    }
    rIICSTAT	= 0xd0;										// Write 0xd0 (M/T STOP) to rIICSTAT. 
    rIICCON 	= 0xe0;										// Clear pending to resumes IIC operation. 
    Delay(50);												// Wait until stop condtion is in effect.

    /* Write completed oparation check. */
    i = AckPoll( slvAddr );									// Check ACK flag bit.

    rIICSTAT = 0xd0;								 		// Master Tx condition, Stop(Write), Output Enable
    rIICCON  = 0xe0;         								// Resumes IIC operation. 
    Delay(2);              									// Wait until stop condtion is in effect.

    return i;												// return.
}

/**
 * I2C Bus read an data from client device( Master receive mode ).
 * (program is not normal or have error)
 */
int _Rd2410Iic( U8 slvAddr, U8 addr )
{
    int i, readControl;										// define value.

    /* Read oparation initialize */
    _iicPt     	= 0;										// define pointer.
    _iicDataCount	= 1 * 2;								// Data counter (word).
    if ( _iicDataCount > IICBUFSIZE )						// Check data counter. 
        return 1;											// Error return.

    /* IIC-Bus send initialize tybe */
    if ( WriteInitByte( slvAddr ) > 0 )						//
        return 2;											//

    /* IIC-Bus send register address */
    rIICDS 	= addr;											// Write slave address to rIICDS.
    for ( i = 0; i < 10; i ++ );							//
    rIICCON	= 0xe0;											// Clear pending bit.
    Delay(10);												// The data of the IICDS (slave address) is transmitted, Delay.

    for ( i = 0; i < 2000; i ++ ) {							// Error status process.
        _iicControl = rIICCON;								//
        if (( _iicControl & 0x10 ) > 0 )					// ACK period and then interrupt is pending.
            break;											// Normal out.
        if ( i > 1000 )										// Timeout.
            return 3;										// Error return, or STOP.
    }

    /* IIC-Bus send slave address */
    rIICDS	= slvAddr | 0x01;					 			// Write register address to rIICDS..
    for ( i = 0; i < 20; i ++ );							//
    rIICSTAT	= 0xb0;      								// Writ 0xb0 (M/R, START) to rIICSTAT.
    Delay(2);												// The data of the IICDS (slave address) is transmitted, Delay.

    for ( i = 0; i < 2000; i ++ ) {							// Error status process.
        _iicControl = rIICCON;								//
        if (( _iicControl & 0x10 ) > 0 )					// ACK period and then interrupt is pending.
            break;											// Normal out.
        if ( i > 1000 )										// Timeout.
            return 4;										// Error return, or STOP.
    }

    /* IIC-Bus read data */
    readControl	= 1000; 									// Timeout contrl counter.
    while ( _iicDataCount ) {								// Data number completed..
        _iicControl = rIICCON;								//
        if (( _iicControl & 0x10 ) > 0 ) {					// ACK period and then interrupt is pending.
            _iicData[_iicPt] = rIICDS;						// Read a new data from rIICDS.
            Delay(2);										// The data of the IICDS (slave address) is transmitted, Delay.
            rIICCON = 0xe0;									//Clear pending bit. Resumes IIC operation with ACK

            _iicPt += 1;									// Data pointer.
            _iicDataCount -= 1;								// Data counter.
            readControl = 1000;								// Timeout control register reset.
        }
        if ( (readControl--) <= 0 )							// Check timeout.
            return 5;										// Error return, orSTOP.
    }
    rIICSTAT	= 0xd0;     				 				// Writ 0xb0 (M/R, STOP) to rIICSTAT.
    rIICCON	= 0xe0;											// Clear pending bit.
    Delay(2);												// Wait until stop condtion is in effect.

    /* Read completed oparation check. */
    i = AckPoll( slvAddr );									// Check ACK flag bit.

    rIICSTAT = 0xd0;		 								// Master Tx condition, Stop(Write), Output Enable
    rIICCON  = 0xe0;        			 					// Resumes IIC operation. 
    Delay(2);              									// Wait until stop condtion is in effect.

    return i;												// return.
}

/***** S.Yin insrt program end 2005-03-20 *****/

/***** S.Yin close this part program begin 2005-03-20 *****

static void uda1341_l3_address(u8 data)
{
	int i;
	int flags;

	local_irq_save(flags);

	write_gpio_bit(GPIO_L3MODE, 0);
	write_gpio_bit(GPIO_L3DATA, 0);
	write_gpio_bit(GPIO_L3CLOCK, 1);
	udelay(1);
	
	for (i = 0; i < 8; i++) {
		if (data & 0x1) {
			write_gpio_bit(GPIO_L3CLOCK, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3DATA, 1);
			udelay(1);
			write_gpio_bit(GPIO_L3CLOCK, 1);
		udelay(1);
		} else {
			write_gpio_bit(GPIO_L3CLOCK, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3DATA, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3CLOCK, 1);
			udelay(1);
		}
		data >>= 1;
	}

	write_gpio_bit(GPIO_L3MODE, 1);
	udelay(1);
	local_irq_restore(flags);
}

static void uda1341_l3_data(u8 data)
{
	int i;
	int flags;

	local_irq_save(flags);

	write_gpio_bit(GPIO_L3MODE, 1);
	udelay(1);
	write_gpio_bit(GPIO_L3MODE, 0);
	udelay(1);
	write_gpio_bit(GPIO_L3MODE, 1);

	for (i = 0; i < 8; i++) {
		if (data & 0x1) {
			write_gpio_bit(GPIO_L3CLOCK, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3DATA, 1);
			udelay(1);
			write_gpio_bit(GPIO_L3CLOCK, 1);
			udelay(1);
		} else {
			write_gpio_bit(GPIO_L3CLOCK, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3DATA, 0);
			udelay(1);
			write_gpio_bit(GPIO_L3CLOCK, 1);
			udelay(1);
		}

		data >>= 1;
	}

	write_gpio_bit(GPIO_L3MODE, 1);
	write_gpio_bit(GPIO_L3MODE, 0);
	udelay(1);
	write_gpio_bit(GPIO_L3MODE, 1);
	local_irq_restore(flags);
}

***** S.Yin close this part program end 2005-03-20 *****/

/**
 * Clear Audio data stream buffer.
 */
static void audio_clear_buf(audio_stream_t * s)
{
    int frag;												// define value.

    DPRINTK( "audio_clear_buf\n" );							// pinter.
    s3c2410_dma_flush_all( s->dma_ch );						// DMA 

    if ( s->buffers ) {										// If buffer have data... 
        for ( frag = 0; frag < s->nbfrags; frag++ ) {		//
            if ( !s->buffers[frag].master )					//
                continue;									// If Buffer have data, then
            consistent_free(s->buffers[frag].start,
                s->buffers[frag].master,
                s->buffers[frag].dma_addr);					// Free DMA buffer.
        }
        kfree(s->buffers);									// Free Buffer.
        s->buffers = NULL;									// Buffer clear.
    }

    s->buf_idx = 0;											// Buffer pointer.
    s->buf = NULL;											// Buffer clear.
}

/**
 * Set audio data stream buffer. 
 */
static int audio_setup_buf(audio_stream_t * s)
{
    int frag;												// define value.
    int dmasize = 0;										//
    char *dmabuf = 0;										//
    dma_addr_t dmaphys = 0;									//

    /* Set buffer point and size.*/
    if (s->buffers)											// If buffer have data, then.
    return -EBUSY;											// return busy status.

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

    /* Move data to buffer. */
    for ( frag = 0; frag < s->nbfrags; frag++ ) {			//
        audio_buf_t *b = &s->buffers[frag];					//

        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;							//
        }
        b->start = dmabuf;									//
        b->dma_addr = dmaphys;								//
        sema_init(&b->sem, 1);								//

        DPRINTK("buf %d: start %p dma %p\n", frag, b->start,
            b->dma_addr);									//

        dmabuf += s->fragsize;								// counter.
        dmaphys += s->fragsize;								// 
        dmasize -= s->fragsize;								//
    }
    s->buf_idx = 0;											//
    s->buf = &s->buffers[0];								//

    return 0;												// normal return.

    /* Error process */
err:
    printk(AUDIO_NAME ": unable to allocate audio memory\n ");
    audio_clear_buf(s);										//
    return -ENOMEM;											//
}

/**
 * DMA request server.
 */
static void audio_dmaout_done_callback(void *buf_id, int size)
{
    audio_buf_t *b = (audio_buf_t *) buf_id;				//

    up(&b->sem);											//
    wake_up(&b->sem.wait);									//
}

static int audio_sync(struct file *file)
{
	audio_stream_t *s = &output_stream;						//
	audio_buf_t *b = s->buf;								//

	DPRINTK("audio_sync\n");								//
	if (!s->buffers)										//
		return 0;											//

	if (b->size != 0) {										//
		down(&b->sem);										//
        s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
          b->dma_addr, b->size, DMA_BUF_WR);				//
		b->size = 0;										//
		NEXT_BUF(s, buf);									//
	}

	b = s->buffers + ((s->nbfrags + s->buf_idx - 1) % s->nbfrags);
	if (down_interruptible(&b->sem))						//
		return -EINTR;										//
	up(&b->sem);											//

	return 0;												//
}

static inline int copy_from_user_mono_stereo(char *to, const char *from, int count)
{
	u_int *dst = (u_int *)to;								//
	const char *end = from + count;							//

	if (verify_area(VERIFY_READ, from, count))				//
		return -EFAULT;										//

	if ((int)from & 0x2) {									//
		u_int v;											//
		__get_user(v, (const u_short *)from); from += 2;	//
		*dst++ = v | (v << 16);								//
	}

	while (from < end-2) {									//
		u_int v, x, y;										//
		__get_user(v, (const u_int *)from); from += 4;		//
		x = v << 16;										//
		x |= x >> 16;										//
		y = v >> 16;										//
		y |= y << 16;										//
		*dst++ = x;											//
		*dst++ = y;											//
	}

	if (from < end) {										//
		u_int v;											//
		__get_user(v, (const u_short *)from);				//
		*dst = v | (v << 16);								//
	}

	return 0;												//
}

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:											//
		  	return -EPERM;									//
	}

	if (!s->buffers && audio_setup_buf(s))					//
		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))						//
				break;										//
		} else {											//
			ret = -ERESTARTSYS;								//
			if (down_interruptible(&b->sem))				//
				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)) {
				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)) {	//
				up(&b->sem);								//
				return -EFAULT;								//
			}

			b->size += chunksize*2;							//
		}

		buffer += chunksize;								//
		count -= chunksize;									//
		if (b->size < s->fragsize) {						//
			up(&b->sem);									//
			break;											//
		}

		s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
					   b->dma_addr, b->size, DMA_BUF_WR);	//
		b->size = 0;										//
		NEXT_BUF(s, buf);									//
	}

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

	DPRINTK("audio_write : end count=%d\n\n", ret);			//

	return ret;												//
}

static ssize_t smdk2410_audio_read(struct file *file, char *buffer, 
                                        size_t count, loff_t * ppos)
{
	return 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_WRITE) {						//
		if (!output_stream.buffers && audio_setup_buf(&output_stream))
			return -ENOMEM;									//
		poll_wait(file, &output_stream.buf->sem.wait, wait);//

		for (i = 0; i < output_stream.nbfrags; i++) {		//
			if (atomic_read(&output_stream.buffers[i].sem.count) > 0)
				mask |= POLLOUT | POLLWRNORM;				//
		}
	}

	DPRINTK("audio_poll() returned mask of %s\n",
		(mask & POLLOUT) ? "w" : "");						//

	return mask;											//
}

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

static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file, 
                                unsigned int cmd, unsigned long arg)
{
        int ret;											//
        long val = 0;										//

	switch (cmd) {											//
		case SOUND_MIXER_INFO:								//
		{
			mixer_info info;								//
			strncpy(info.id, "UDA1380", sizeof(info.id));	//
			strncpy(info.name,"Philips UDA1380", 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, "UDA1380", sizeof(info.id));
			strncpy(info.name,"Philips UDA1380", 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;									//
/***** S.Yin insert program begin 2005-03-20 *****/

			uda1380_volume = 63 - (((val & 0xff) + 1) * 63) / 100;
            _Wr2410Iic( 0x30, MASTER_VOL_REG, uda1380_volume );

/***** S.Yin insert program end 2005-03-20 *****/

/***** S.Yin close this part program begin 2005-03-20

			uda1341_l3_address(UDA1380_ADDR + UDA1341_REG_DATA);
			uda1341_l3_data(uda1380_volume);

***** S.Yin close this part program end 2005-03-20 *****/
			break;											//
		
		case SOUND_MIXER_READ_VOLUME:						//
			val = ((63 - uda1380_volume) * 100) / 63;		//
			val |= val << 8;								//
			return put_user(val, (long *) arg);				//
		default:											//
			DPRINTK("mixer ioctl %u unknown\n", cmd);		//
			return -ENOSYS;									//
	}			

	audio_mix_modcnt++;										//
	return 0;												//
}

static int iispsr_value(int s_bit_clock, int sample_rate)
{
        int i, prescaler = 0;								//
        unsigned long tmpval;								//
        unsigned long tmpval384;							//
        unsigned long tmpval384min = 0xffff;				//

 	tmpval384 = s3c2410_get_bus_clk(GET_PCLK) / s_bit_clock;

        for (i = 0; i < 32; i++) {							//
                tmpval = tmpval384/(i+1);					//
                if (PCM_ABS((sample_rate - tmpval)) < tmpval384min) {
                        tmpval384min = PCM_ABS((sample_rate - tmpval));
                        prescaler = i;						//
                }
        }

        DPRINTK("prescaler addr= %d\n", prescaler);			//

        return prescaler;									//
}

static long audio_set_dsp_speed(long val)
{
	switch (val) {											//
		case 48000:											//
		case 44100:											//
			IISPSR = (IISPSR_A(iispsr_value(S_CLOCK_FREQ, 44100)) 
				| IISPSR_B(iispsr_value(S_CLOCK_FREQ, 44100)));
			break;											//
		case 22050:											//
			IISPSR = (IISPSR_A(iispsr_value(S_CLOCK_FREQ, 22050)) 
				| IISPSR_B(iispsr_value(S_CLOCK_FREQ, 22050)));
			break;											//
		case 11025:											//
			IISPSR = (IISPSR_A(iispsr_value(S_CLOCK_FREQ, 11025)) 
				| IISPSR_B(iispsr_value(S_CLOCK_FREQ, 11025)));
			break;											//
		case 8000:
			IISPSR = (IISPSR_A(iispsr_value(S_CLOCK_FREQ, 8000)) 
				| IISPSR_B(iispsr_value(S_CLOCK_FREQ, 8000)));
			break;											//
		default:											//
			return -1;										//
	}
	audio_rate = val;										//
	
	return audio_rate;										//
}

static int smdk2410_audio_ioctl(struct inode *inode, struct file *file, 
                                uint cmd, ulong arg)
{
	long val;												//

	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;								//
		  	audio_channels = val;							//
		  	break;											//

	  	case SOUND_PCM_READ_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:							//
		  	put_user(audio_fragsize, (long *) arg);			//
		  	break;											//

	  	case SNDCTL_DSP_SETFRAGMENT:						//
		  	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;								//
		  	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 = verify_area(VERIFY_WRITE, inf, sizeof(*inf));
			int i;											//
			int frags = 0, bytes = 0;						//

			if (err)										//
				return err;									//
			for (i = 0; i < s->nbfrags; i++) {				//
				if (atomic_read(&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_RESET:								//
		  	switch (file->f_flags & O_ACCMODE) {			//
		    		case O_RDONLY:							//
		    		case O_RDWR:							//
		    	}
		  	switch (file->f_flags & O_ACCMODE) {			//
		    		case O_WRONLY:							//
		    		case O_RDWR:							//
			    		audio_clear_buf(&output_stream);	//
		    	}
		  	return 0;										//

	 	case SNDCTL_DSP_POST:								//
	      	case SNDCTL_DSP_SUBDIVIDE:						//
	      	case SNDCTL_DSP_NONBLOCK:						//
	      	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;												//
}

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;				//
		audio_clear_buf(&output_stream);					//
	}

	MOD_INC_USE_COUNT;										//
	return 0;												//
}

static int smdk2410_mixer_open(struct inode *inode, struct file *file)
{
	MOD_INC_USE_COUNT;										//
	return 0;												//
}

static int smdk2410_audio_release(struct inode *inode, struct file *file)
{
	DPRINTK("audio_release\n");								//
	switch (file->f_flags & O_ACCMODE) {					// Read buffer operation.
	  	case O_RDONLY:										//
	  	case O_RDWR:										//
		  	if (audio_rd_refcount == 1)						//
			  	audio_rd_refcount = 0;						//
	}

	switch (file->f_flags & O_ACCMODE) {					// Write buffer operation.
	  	case O_WRONLY:										//
	  	case O_RDWR:										//
		  	if (audio_wr_refcount == 1) {					//
			    	audio_sync(file);						//
			    	audio_clear_buf(&output_stream);		//
			    	audio_wr_refcount = 0;					//
		    	}											//
	  	}													//

	MOD_DEC_USE_COUNT;										//
	return 0;												//
}

static int smdk2410_mixer_release(struct inode *inode, struct file *file)
{
	MOD_DEC_USE_COUNT;										//
	return 0;												//
}

/*
 * Philips UDA1380 driver interface description, and initialize operation,
 * exit operation for module server.
 ****************************************************************************/

/**
 * Philips UDA1380 audio device interface description. 
 */
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 struct file_operations smdk2410_mixer_fops = {
	ioctl:		smdk2410_mixer_ioctl,
	open:		smdk2410_mixer_open,
	release:	smdk2410_mixer_release
};

/**
 * Philips UDA1380 audio device initialize.
 */
static void init_uda1380( void )
{
    int flags;												// define value.
    int i,a;												//

    uda1380_volume = 0;										// Master volume.

    local_irq_save(flags);									// keep.

    write_gpio_bit(GPIO_L3CLOCK, 1);						// I/O Port set.
    write_gpio_bit(GPIO_L3DATA, 1);							//

    local_irq_restore(flags);								// restore.

/***** S.Yin insert program begin 2005-03-20 **********/
    _Wr2410Iic( UDA1380_ADDR, 0x7f, 0xFFFF );				// reset operation.
    Delay(50);												// Delay 10ms.

    a = 0;
    for ( i = 0; i < ARRAY_SIZE( uda1380_reg_info ); i++ ) {//
        a += _Wr2410Iic( UDA1380_ADDR, 
            uda1380_reg_info[i].num, 
            uda1380_reg_info[i].default_value );			//
    Delay(50);												// Delay 10ms.
    }
    if ( a > 0 )											//
        printk( "Philips UDA1380 initialization fail ....\n" );
/** S.Yin insert program end 2005-03-20 ****/

/***** S.Yin close program begin 2005-03-20 *

	uda1341_l3_address(UDA1380_ADDR + UDA1341_REG_STATUS);	//
	uda1341_l3_data(SC_384fs | IF_MSB);						// set 384 system clock, MSB
	uda1341_l3_data(0xC1);									//

	uda1341_l3_address(UDA1380_ADDR + UDA1341_REG_DATA);	//
	uda1341_l3_data(uda1380_volume);						// maximum volume
	uda1341_l3_data(uda_sampling);							//NO_DE_EMPHASIS and no muting

***** S.Yin insert program end 2005-03-20 ****/
}

/**
 * Samsung S3C2410 I2C Bus device initialize.
 */
static void init_iic_bus(void)
{
    unsigned int save_E, save_PE;							// define value.

    rtc_address_map();										// Set I2C Bus control register.

    save_E   = rGPECON;										// Keep.
    save_PE  = rGPEUP;										//

    rGPEUP  |= 0xc000;										// Pull-up disable
    rGPECON |= 0xa00000;									// GPE15:IICSDA , GPE14:IICSCL    

    rIICCON		= 0;										// Clear
    rIICSTAT	= 0;										//
    rIICADD		= 0x10;										// S3C2410 slave address=[7:1]

    rIICCON		= 0xe0;										// Enable ACK, Prescaler IICCLK=PCLK/512,
    														// Enable interrupt, Transmit clock value
    														// Tx clock=IICCLK/512
    rIICSTAT	= 0xd0;										// to get to a sane state (also generates a STOP condition)

    rGPEUP  = save_PE;										// restore
    rGPECON = save_E;										//
}

/**
 * Samsung S3C2410 I2S Bus device initialize.
 */
static void init_s3c2410_iis_bus(void)
{
    IISCON		= 0;										// Clear register.
    IISMOD		= 0;										//
    IISFIFOC	= 0;										//

    IISPSR = (IISPSR_A( iispsr_value( S_CLOCK_FREQ,44100 )) 
        | IISPSR_B(iispsr_value(S_CLOCK_FREQ, 44100)));		// 44 KHz , 384fs

    IISCON = (IISCON_TX_DMA									// Transmit DMA service request
        |IISCON_RX_IDLE										// Receive Channel idle
        |IISCON_PRESCALE);									// IIS Prescaler Enable

    IISMOD = (IISMOD_SEL_MA									// Master mode
        | IISMOD_SEL_TX										// Transmit
        | IISMOD_CH_RIGHT									// Low for left channel
        | IISMOD_FMT_MSB									// MSB-justified format
        | IISMOD_BIT_16										// Serial data bit/channel is 16 bit
        | IISMOD_FREQ_384									// Master clock freq = 384 fs
        | IISMOD_SFREQ_32);									// 32 fs

    IISFIFOC = (IISFCON_TX_DMA								// Transmit FIFO access mode: DMA
        | IISFCON_TX_EN);									// Transmit FIFO enable

    IISCON |= IISCON_EN;									// IIS enable(start)
}

/**
 * Samsung S3C2410 DMA device initialize for audio server.
 */
static int __init audio_init_dma(audio_stream_t * s, char *desc)
{
    return s3c2410_request_dma("I2SSDO", s->dma_ch, audio_dmaout_done_callback, NULL);
}

/**
 * Philips UDA1380 audio device close.
 */
static int s3c2410_uda1380_close(void)
{
    _Wr2410Iic( UDA1380_ADDR, 0x0, 0x0 );					// reset operation.
    Delay(50);												// Delay 10ms.
    _Wr2410Iic( UDA1380_ADDR, 0x02, 0x0 );					// reset operation.
    Delay(50);												// Delay 10ms.

    return 0;												// return.
}

/**
 * Clear Samsung S3C2410 DMA device.
 */
static int audio_clear_dma(audio_stream_t * s)
{
    s3c2410_free_dma(s->dma_ch);							// Free DMA device.
    return 0;												// return.
}

/**
 * Philips UDA1380 Audio driver initialize.
 */
int __init s3c2410_uda1380_init(void)
{
    unsigned long flags;									// Define valuse.
   
    local_irq_save(flags);									// keep.

    /***** Initialize Samsung S3C2410 I/O port. *****/
    set_gpio_ctrl(GPIO_L3CLOCK);							// GPB 4: L3CLOCK, OUTPUT
    set_gpio_ctrl(GPIO_L3DATA);								// GPB 3: L3DATA, OUTPUT
    set_gpio_ctrl(GPIO_L3MODE);								// GPB 2: L3MODE, OUTPUT

    set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDI);		// GPE 3: I2SSDI
    set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDI);		// GPE 0: I2SLRCK
    set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_I2SSCLK);	// GPE 1: I2SSCLK
    set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK);		// GPE 2: CDCLK
    set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDO);		// GPE 4: I2SSDO

    local_irq_restore(flags);								// restore.

    /****  Samsung S3C2410 I2C bus and I2S bus, DMA device, UDA1380 Initialize. *****/
    int init_iic_bus();										// I2C bus initialization.
    init_uda1380();											// uda1380 initialization.
    init_s3c2410_iis_bus();									// I2S bus initialization.

    output_stream.dma_ch = DMA_CH2;							// DMA device initialization.
    if (audio_init_dma(&output_stream, "UDA1380 out")) {	//
        audio_clear_dma(&output_stream);					//
        printk( KERN_WARNING AUDIO_NAME_VERBOSE
            ": unable to get DMA channels\n" );				//
        return -EBUSY;										// return busy.
    }

    /***** Register Philips UDA1380 Audio device *****/
    audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
    audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);

    printk(AUDIO_NAME_VERBOSE " device initialized.\n");	// Complete note...
    return 0;												// normal return.
}

/**
 * Drive device EXIT oparation.
 */
void __exit s3c2410_uda1380_exit(void)
{
    s3c2410_uda1380_close();								// Clear UDA1380 control register.

    unregister_sound_dsp(audio_dev_dsp);					// Release audio drive interfase.
    unregister_sound_mixer(audio_dev_mixer);				// Release Mixer device interfase.
    audio_clear_dma(&output_stream);						// Release DMA server.
    printk(AUDIO_NAME_VERBOSE " device unloaded\n");		// Release note...
}

module_init(s3c2410_uda1380_init);
module_exit(s3c2410_uda1380_exit);

MODULE_AUTHOR( "Yinshenk" );
MODULE_DESCRIPTION( "Philips UDA1380 CODEC driver" );
MODULE_LICENSE("GPL");