#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <mach/regs-gpio.h>
#include <mach/dma.h>
#include <asm/io.h>
#include <asm/string.h>
#include <linux/sound.h>
#include <linux/dma-mapping.h>
#include <asm-arm/plat-s3c24xx/regs-iis.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/soundcard.h>
#include <linux/delay.h>
#include <mach/irqs.h>
#include <linux/interrupt.h>
#include "common.h"
#define memzero(p,n) \
({ \
void *__p = (p); size_t __n = n; \
if ((__n) != 0) \
__memzero((__p),(__n)); \
(__p); \
})
#define DGENG
#ifdef DGENG
#define GENG printk
#else
#define GENG //
#endif
#define NEXT_BUF(_s_,_b_) { \
(_s_)->_b_##_idx++; \
(_s_)->_b_##_idx %= (_s_)->nbfrags; \
(_s_)->_b_ = (_s_)->buffers + (_s_)->_b_##_idx; }
/* The S3C2410 has four internal DMA channels. */
#define MAX_S3C2410_DMA_CHANNELS S3C2410_DMA_CHANNELS
#define DMA_CH1 DMACH_I2S_IN
#define DMA_CH2 DMACH_I2S_OUT
#define DMA_BUF_WR 1
#define DMA_BUF_RD 0
#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 8
#define AUDIO_FRAGSIZE_DEFAULT 8192
static int wm8976_volume;
static int mixer_igain=0x4; /* -6db*/
static int audio_rd_refcount;
static int audio_wr_refcount;
#define audio_active (audio_rd_refcount | audio_wr_refcount)
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_dev_dsp;
static int audio_dev_mixer;
static int audio_mix_modcnt;
static struct s3c2410_dma_client s3c2440_iis_dma_out= {
.name = "I2SSDO",
};
static struct s3c2410_dma_client s3c2440_iis_dma_in = {
.name = "I2SSDI",
};
/*
* 驱动对于内存是这样使用的:
* 把buffers所指向的内存分成nbfragsxfragsize的空间(每一块的大小为fragsize,供分成nbfrags块)
* buf指向当前所使用的内存块,buf_idx只是内存块序号
*/
typedef unsigned int dmach_t;
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 */
atomic_t count;
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;
static audio_stream_t output_stream;
static audio_stream_t input_stream; /* input */
struct _wm8976_t {
unsigned long gpio_phys, gpio_virt;
unsigned long iis_phys, iis_virt;
struct fasync_struct *fa;
};
struct _wm8976_t wm;
unsigned long virt_iis_clk;
static void init_audio_device(struct _wm8976_t *w)
{
w->gpio_virt = ioremap(w->gpio_phys, SZ_4K);
w->iis_virt = ioremap(w->iis_phys, SZ_4K);
// GENG("virt:[%p]dma_src_phys[%p]\n", w->dma_src_virt, w->dma_src_phys);
// GENG("gpio[%p][%p]\n", w->gpio_phys, w->gpio_virt);
// GENG("dma[%p][%p]\n", w->dma_phys, w->dma_virt);
// GENG("iis[%p][%p]\n", w->iis_phys, w->iis_virt);
//open clk
virt_iis_clk = ioremap(0x4C00000C, 4);
iowrite32(ioread32(virt_iis_clk) | (1 << 17), virt_iis_clk);
// iis_clk = clk_get(NULL, "iis");
// clk_enable(iis_clk);
}
static void destroy_audio_device(struct _wm8976_t *w)
{
iounmap(virt_iis_clk);
free_irq(IRQ_DMA0, w);
iounmap(w->gpio_virt);
iounmap(w->iis_virt);
}
static long audio_set_dsp_speed(long val)
{
unsigned int prescaler;
/*
prescaler=(IISPSR_A(iispsr_value(S_CLOCK_FREQ, val))
| IISPSR_B(iispsr_value(S_CLOCK_FREQ, val)));
writel(prescaler, iis_base + S3C2410_IISPSR);
*/
printk("S3C2440-WM8976:audio_set_dsp_speed:%ld prescaler:%i\n",val,prescaler);
return (audio_rate = val);
}
/* using when write */
static int audio_sync(struct file *file)
{
audio_stream_t *s = &output_stream;
audio_buf_t *b = s->buf;
if (!s->buffers)
return 0;
if (b->size != 0) {
down(&b->sem);
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 void audio_clear_buf(audio_stream_t * s)
{
if(s->dma_ok)
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;
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 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_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 {
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);
atomic_set(&b->count, 1);
init_waitqueue_head(&b->wait);
dmabuf += s->fragsize;
dmaphys += s->fragsize;
dmasize -= s->fragsize;
}
s->buf_idx = 0;
s->buf = &s->buffers[0];
return 0;
err:
printk("WM8976: unable to allocate audio memory!\n ");
audio_clear_buf(s);
return -ENOMEM;
}
static int s3c2440_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, "wm8976", sizeof(info.id));
strncpy(info.name,"wm8976", 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, "wm8976", sizeof(info.id));
strncpy(info.name,"wm8976", 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;
// wm8976_volume = 63 - (((val & 0xff) + 1) * 63) / 100;
// wm8976_l3_address(wm8976_REG_DATA0);
// wm8976_l3_data(wm8976_volume);
break;
case SOUND_MIXER_READ_VOLUME:
val = ((63 - wm8976_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*