📄 uda1380.c
字号:
/* * Philips UDA1380 Audio Device Driver for S3C2410 Linux * * Copyright (C) 2002 MIZI Research, Inc. * ChangeLog: * 30-April-2005 Port from kernel2.4.18 to linux2.6.11.1 * for MatrixV(based on s3c2410) (Dexiang Huang,GuangDong Linux Center) * 29-Dec-2005 make it work for recording(Dexiang Huang,GuangDong Linux Center) * 29-Dec-2005 make it work to support Full-Duplex.(Dexiang Huang,GuangDong Linux Center) */#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/delay.h>#include <linux/sched.h>#include <linux/poll.h>#include <linux/interrupt.h>#include <linux/errno.h>#include <linux/sound.h>#include <linux/soundcard.h>#include <linux/pm.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/hardware.h>#include <asm/semaphore.h>#include <asm/dma.h>#include <asm/arch/regs-gpio.h>#include <asm/arch/regs-iis.h>#include <asm/arch/regs-clock.h>#include <linux/dma-mapping.h>#undef DEBUG#ifdef DEBUG#define DPRINTK( x... ) printk( ##x )#else#define DPRINTK( x... )#endif#define S3C2410_IISREG(x) ((x)+S3C24XX_VA_IIS)#define S3C2410_SBC_IISCON S3C2410_IISREG(0X00)#define S3C2410_SBC_IISMOD S3C2410_IISREG(0x04)#define S3C2410_SBC_IISPSR S3C2410_IISREG(0x08)#define S3C2410_SBC_IISFCON S3C2410_IISREG(0x0c)#define S3C2410_SBC_IISFIFO S3C2410_IISREG(0x10)static void init_s3c2410_iis_bus_rx(void);static void init_s3c2410_iis_bus_tx(void);static void init_s3c2410_iis_bus_txrx(void);#define DEF_VOLUME 65/* UDA1380 Register bits */#define UDA1380_ADDR 0x14#define UDA1380_REG_DATA0 (UDA1380_ADDR + 0)#define UDA1380_REG_STATUS (UDA1380_ADDR + 2)#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 44100#define AUDIO_NBFRAGS_DEFAULT 16#define AUDIO_FRAGSIZE_DEFAULT 8192#define S_CLOCK_FREQ 384#define PCM_ABS(a) (a < 0 ? -a : a)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;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;static audio_stream_t input_stream; /* input */#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;/* * For the dma chanel user. This is added in the 2.6 kernel. */static struct s3c2410_dma_client s3c2410_dma_client_out ={ .name ="I2SSDO",};static struct s3c2410_dma_client s3c2410_dma_client_in={ .name ="I2SSDI",};/*Registers of uda1380(only the used)*/enum uda1380_registers { EVAMODE_CLK = 0, IISBUS = 0x01, POWERCTL = 0x02, ALG_MIXER =0x03, HP_AMP = 0x04, MASTER_VOL_CTL = 0x10, MIXER_VOLUME_CTL = 0x11, MODE_BASS_TREBLE = 0x12, MASTER_MUTE_DEEMP_MUTE = 0x13, MIXER_SILENCE_DETECTOR = 0x14, DECIMATOR_VOLUME_CONTROL = 0x20, PGA_MUTE = 0x21, ADC = 0x22, AGC = 0x23, RESTORE_L3DEFAULT = 0x7f, INTERPOLATION_FILTER_STATUS = 0x18, DECIMATE_STATUS = 0x28};#define FIN_NEW 12000000#define GET_PCLK_NEW 0#define DMA_CH1 1#define DMA_CH2 2#ifndef __ASSEMBLY__#define UData(Data) ((unsigned long) (Data))#else#define UData(Data) (Data)#endif#define FInsrt(Value, Field) (UData (Value) << FShft (Field))#define fIISPSR_A Fld(5, 5) /* Prescaler Control A */#define IISPSR_A(x) FInsrt((x), fIISPSR_A)#define fIISPSR_B Fld(5, 0) /* Prescaler Control B */#define IISPSR_B(x) FInsrt((x), fIISPSR_B) #define fPLL_MDIV Fld(8,12)#define fPLL_PDIV Fld(6,4)#define fPLL_SDIV Fld(2,0)#define Fld(Size, Shft) (((Size) << 16) + (Shft))#define FShft(Field) ((Field) & 0x0000FFFF)#define FAlnMsk(Field) ((UData (1) << FSize (Field)) - 1)#define FSize(Field) ((Field) >> 16)#define FExtr(Data, Field) ((UData (Data) >> FShft (Field)) & FAlnMsk (Field))#define GET_MDIV(x) FExtr(x, fPLL_MDIV)#define GET_PDIV(x) FExtr(x, fPLL_PDIV)#define GET_SDIV(x) FExtr(x, fPLL_SDIV)/* Functions for the callback when dma transfered done */static void audio_dmaout_done_callback(s3c2410_dma_chan_t *r_value,void *buf_id, int size,s3c2410_dma_buffresult_t result);static void audio_dmain_done_callback(s3c2410_dma_chan_t *r_value,void *buf_id, int size,s3c2410_dma_buffresult_t result);extern int s3c2410_uda_codec_write(unsigned char adr, unsigned short value); //defined in the driver/i2c/chips/uda1380.c/*write to the register of uda1380 bu CPU, controlled by iic*/static void wrcodec(u8 ctlreg, u16 val){ int rcnt = 0; /* The codec is a write only device, with a 16-bit control/data * word. Although it is written as two bytes on the I2C,the * format is actually 7 bits of register and 9 bits of data. * The ls bit of the first byte is the ms bit of * the data. */ while ((s3c2410_uda_codec_write(ctlreg, val)) && (rcnt < 50)) rcnt++; if (rcnt >= 50) printk("timeout uda1380_wrcodec: reg:%x val:%x\n", ctlreg, val); udelay(10);}static void audio_clear_buf(audio_stream_t * s){ DPRINTK("audio_clear_buf\n"); s3c2410_dma_ctrl(s->dma_ch, S3C2410_DMAOP_FLUSH); if (s->buffers) { int frag; for (frag = 0; frag < s->nbfrags; frag++) { if (!s->buffers[frag].master) continue; /* To free the dma buffer */ dma_free_coherent(NULL,s->buffers[frag].master, s->buffers[frag].start, s->buffers[frag].dma_addr); } kfree(s->buffers); s->buffers = NULL; } s->buf_idx = 0; s->buf = NULL;}static int audio_setup_buf(audio_stream_t * s){ int frag; int dmasize = 0; char *dmabuf = 0; dma_addr_t dmaphys = 0; 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++) { audio_buf_t *b = &s->buffers[frag]; if (!dmasize) { dmasize = (s->nbfrags - frag) * s->fragsize; do { /* To allocate the coherent address buffer for dma transfer*/ dmabuf=dma_alloc_coherent(NULL,dmasize,&dmaphys,GFP_KERNEL|GFP_DMA); 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 %d\n", frag, b->start, b->dma_addr); dmabuf += s->fragsize; dmaphys += s->fragsize; dmasize -= s->fragsize; } 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;}static void audio_dmaout_done_callback(s3c2410_dma_chan_t *r_value, void *buf_id, int size, s3c2410_dma_buffresult_t result){ audio_buf_t *b = (audio_buf_t *) buf_id; DPRINTK("DMA-OUT%d\n", size); up(&b->sem); wake_up(&b->sem.wait);}static void audio_dmain_done_callback(s3c2410_dma_chan_t *r_value, void *buf_id, int size, s3c2410_dma_buffresult_t result){ audio_buf_t *b = (audio_buf_t *) buf_id; DPRINTK("DMA-IN%d\n", size); b->size = size; up(&b->sem); wake_up(&b->sem.wait); } /* using when write */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); /* To enqueue the dma task */ s3c2410_dma_enqueue(s->dma_ch,(void *)b,b->dma_addr,b->size); 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 (!access_ok(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 {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -