📄 ad1889.c
字号:
/* * Copyright 2001-2004 Randolph Chung <tausq@debian.org> * * Analog Devices 1889 PCI audio driver (AD1819 AC97-compatible codec) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Notes: * 1. Only flat DMA is supported; s-g is not supported right now * *<jsm> tausq: Anyway, to set up sample rates for D to A, you just use the sample rate on the codec. For A to D, you need to set the codec always to 48K (using the split sample rate feature on the codec) and then set the resampler on the AD1889 to the sample rate you want.<jsm> Also, when changing the sample rate on the codec you need to power it down and re power it up for the change to take effect! * * $Id: ad1889.c,v 1.3 2002/10/19 21:31:44 grundler Exp $ */#include <linux/config.h>#include <linux/module.h>#include <linux/init.h>#include <linux/ioport.h>#include <linux/pci.h>#include <linux/poll.h>#include <linux/proc_fs.h>#include <linux/slab.h>#include <linux/soundcard.h>#include <linux/ac97_codec.h>#include <linux/sound.h>#include <linux/interrupt.h>#include <asm/delay.h>#include <asm/io.h>#include <asm/dma.h>#include <asm/uaccess.h>#include "ad1889.h"#define DBG(fmt, arg...) printk(fmt, ##arg)#define DEVNAME "ad1889"#define NR_HW_CH 4#define DAC_RUNNING 1#define ADC_RUNNING 2#define UNDERRUN(dev) (0)#define AD1889_READW(dev,reg) readw(dev->regbase + reg)#define AD1889_WRITEW(dev,reg,val) writew((val), dev->regbase + reg)#define AD1889_READL(dev,reg) readl(dev->regbase + reg)#define AD1889_WRITEL(dev,reg,val) writel((val), dev->regbase + reg)//now 100ms/* #define WAIT_10MS() schedule_timeout(HZ/10) */#define WAIT_10MS() do { int __i; for (__i = 0; __i < 100; __i++) udelay(1000); } while(0)/* currently only support a single device */static ad1889_dev_t *ad1889_dev = NULL;/************************* helper routines ***************************** */static inline void ad1889_set_wav_rate(ad1889_dev_t *dev, int rate){ struct ac97_codec *ac97_codec = dev->ac97_codec; DBG("Setting WAV rate to %d\n", rate); dev->state[AD_WAV_STATE].dmabuf.rate = rate; AD1889_WRITEW(dev, AD_DSWAS, rate); /* Cycle the DAC to enable the new rate */ ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0x0200); WAIT_10MS(); ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0);}static inline void ad1889_set_adc_rate(ad1889_dev_t *dev, int rate){ struct ac97_codec *ac97_codec = dev->ac97_codec; DBG("Setting ADC rate to %d\n", rate); dev->state[AD_ADC_STATE].dmabuf.rate = rate; AD1889_WRITEW(dev, AD_DSRES, rate); /* Cycle the ADC to enable the new rate */ ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0x0100); WAIT_10MS(); ac97_codec->codec_write(dev->ac97_codec, AC97_POWER_CONTROL, 0);}static inline void ad1889_set_wav_fmt(ad1889_dev_t *dev, int fmt){ u16 tmp; DBG("Setting WAV format to 0x%x\n", fmt); tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); if (fmt & AFMT_S16_LE) { //tmp |= 0x0100; /* set WA16 */ tmp |= 0x0300; /* set WA16 stereo */ } else if (fmt & AFMT_U8) { tmp &= ~0x0100; /* clear WA16 */ } AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp);}static inline void ad1889_set_adc_fmt(ad1889_dev_t *dev, int fmt){ u16 tmp; DBG("Setting ADC format to 0x%x\n", fmt); tmp = AD1889_READW(ad1889_dev, AD_DSRAMC); if (fmt & AFMT_S16_LE) { tmp |= 0x0100; /* set WA16 */ } else if (fmt & AFMT_U8) { tmp &= ~0x0100; /* clear WA16 */ } AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp);}static void ad1889_start_wav(ad1889_state_t *state){ unsigned long flags; struct dmabuf *dmabuf = &state->dmabuf; int cnt; u16 tmp; spin_lock_irqsave(&state->card->lock, flags); if (dmabuf->dma_len) /* DMA already in flight */ goto skip_dma; /* setup dma */ cnt = dmabuf->wr_ptr - dmabuf->rd_ptr; if (cnt == 0) /* done - don't need to do anything */ goto skip_dma; /* If the wr_ptr has wrapped, only map to the end */ if (cnt < 0) cnt = DMA_SIZE - dmabuf->rd_ptr; dmabuf->dma_handle = pci_map_single(ad1889_dev->pci, dmabuf->rawbuf + dmabuf->rd_ptr, cnt, PCI_DMA_TODEVICE); dmabuf->dma_len = cnt; dmabuf->ready = 1; DBG("Starting playback at 0x%p for %ld bytes\n", dmabuf->rawbuf + dmabuf->rd_ptr, dmabuf->dma_len); /* load up the current register set */ AD1889_WRITEL(ad1889_dev, AD_DMAWAVCC, cnt); AD1889_WRITEL(ad1889_dev, AD_DMAWAVICC, cnt); AD1889_WRITEL(ad1889_dev, AD_DMAWAVCA, dmabuf->dma_handle); /* TODO: for now we load the base registers with the same thing */ AD1889_WRITEL(ad1889_dev, AD_DMAWAVBC, cnt); AD1889_WRITEL(ad1889_dev, AD_DMAWAVIBC, cnt); AD1889_WRITEL(ad1889_dev, AD_DMAWAVBA, dmabuf->dma_handle); /* and we're off to the races... */ AD1889_WRITEL(ad1889_dev, AD_DMACHSS, 0x8); tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); tmp |= 0x0400; /* set WAEN */ AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp); (void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */ dmabuf->enable |= DAC_RUNNING;skip_dma: spin_unlock_irqrestore(&state->card->lock, flags);}static void ad1889_stop_wav(ad1889_state_t *state){ unsigned long flags; struct dmabuf *dmabuf = &state->dmabuf; spin_lock_irqsave(&state->card->lock, flags); if (dmabuf->enable & DAC_RUNNING) { u16 tmp; unsigned long cnt = dmabuf->dma_len; tmp = AD1889_READW(ad1889_dev, AD_DSWSMC); tmp &= ~0x0400; /* clear WAEN */ AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp); (void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */ pci_unmap_single(ad1889_dev->pci, dmabuf->dma_handle, cnt, PCI_DMA_TODEVICE); dmabuf->enable &= ~DAC_RUNNING; /* update dma pointers */ dmabuf->rd_ptr += cnt; dmabuf->rd_ptr &= (DMA_SIZE - 1); dmabuf->dma_handle = 0; dmabuf->dma_len = 0; dmabuf->ready = 0; wake_up(&dmabuf->wait); } spin_unlock_irqrestore(&state->card->lock, flags);}#if 0static void ad1889_startstop_adc(ad1889_state_t *state, int start){ u16 tmp; unsigned long flags; spin_lock_irqsave(&state->card->lock, flags); tmp = AD1889_READW(ad1889_dev, AD_DSRAMC); if (start) { state->dmabuf.enable |= ADC_RUNNING; tmp |= 0x0004; /* set ADEN */ } else { state->dmabuf.enable &= ~ADC_RUNNING; tmp &= ~0x0004; /* clear ADEN */ } AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp); spin_unlock_irqrestore(&state->card->lock, flags);}#endifstatic ad1889_dev_t *ad1889_alloc_dev(struct pci_dev *pci){ ad1889_dev_t *dev; struct dmabuf *dmabuf; int i; if ((dev = kmalloc(sizeof(ad1889_dev_t), GFP_KERNEL)) == NULL) return NULL; memset(dev, 0, sizeof(ad1889_dev_t)); spin_lock_init(&dev->lock); dev->pci = pci; for (i = 0; i < AD_MAX_STATES; i++) { dev->state[i].card = dev; init_MUTEX(&dev->state[i].sem); init_waitqueue_head(&dev->state[i].dmabuf.wait); } /* allocate dma buffer */ for (i = 0; i < AD_MAX_STATES; i++) { dmabuf = &dev->state[i].dmabuf; dmabuf->rawbuf = kmalloc(DMA_SIZE, GFP_KERNEL|GFP_DMA); if (!dmabuf->rawbuf) goto err_free_dmabuf; dmabuf->rawbuf_size = DMA_SIZE; dmabuf->dma_handle = 0; dmabuf->rd_ptr = dmabuf->wr_ptr = dmabuf->dma_len = 0UL; dmabuf->ready = 0; dmabuf->rate = 48000; } return dev;err_free_dmabuf: while (--i >= 0) kfree(dev->state[i].dmabuf.rawbuf); kfree(dev); return NULL;}static void ad1889_free_dev(ad1889_dev_t *dev){ int j; struct dmabuf *dmabuf; if (dev == NULL) return; if (dev->ac97_codec) ac97_release_codec(dev->ac97_codec); for (j = 0; j < AD_MAX_STATES; j++) { dmabuf = &dev->state[j].dmabuf; if (dmabuf->rawbuf != NULL) kfree(dmabuf->rawbuf); } kfree(dev);}static inline void ad1889_trigger_playback(ad1889_dev_t *dev){#if 0 u32 val; struct dmabuf *dmabuf = &dev->state[AD_WAV_STATE].dmabuf;#endif ad1889_start_wav(&dev->state[AD_WAV_STATE]);}int ad1889_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data){ char *out = page; int len, i; ad1889_dev_t *dev = data; ad1889_reg_t regs[] = { { "WSMC", AD_DSWSMC, 16 }, { "RAMC", AD_DSRAMC, 16 }, { "WADA", AD_DSWADA, 16 }, { "SYDA", AD_DSSYDA, 16 }, { "WAS", AD_DSWAS, 16 }, { "RES", AD_DSRES, 16 }, { "CCS", AD_DSCCS, 16 }, { "ADCBA", AD_DMAADCBA, 32 }, { "ADCCA", AD_DMAADCCA, 32 }, { "ADCBC", AD_DMAADCBC, 32 }, { "ADCCC", AD_DMAADCCC, 32 }, { "ADCIBC", AD_DMAADCIBC, 32 }, { "ADCICC", AD_DMAADCICC, 32 }, { "ADCCTRL", AD_DMAADCCTRL, 16 }, { "WAVBA", AD_DMAWAVBA, 32 }, { "WAVCA", AD_DMAWAVCA, 32 }, { "WAVBC", AD_DMAWAVBC, 32 }, { "WAVCC", AD_DMAWAVCC, 32 }, { "WAVIBC", AD_DMAWAVIBC, 32 }, { "WAVICC", AD_DMAWAVICC, 32 }, { "WAVCTRL", AD_DMAWAVCTRL, 16 }, { "DISR", AD_DMADISR, 32 }, { "CHSS", AD_DMACHSS, 32 }, { "IPC", AD_GPIOIPC, 16 }, { "OP", AD_GPIOOP, 16 }, { "IP", AD_GPIOIP, 16 }, { "ACIC", AD_ACIC, 16 }, { "AC97_RESET", 0x100 + AC97_RESET, 16 }, { "AC97_MASTER_VOL_STEREO", 0x100 + AC97_MASTER_VOL_STEREO, 16 }, { "AC97_HEADPHONE_VOL", 0x100 + AC97_HEADPHONE_VOL, 16 }, { "AC97_MASTER_VOL_MONO", 0x100 + AC97_MASTER_VOL_MONO, 16 }, { "AC97_MASTER_TONE", 0x100 + AC97_MASTER_TONE, 16 }, { "AC97_PCBEEP_VOL", 0x100 + AC97_PCBEEP_VOL, 16 }, { "AC97_PHONE_VOL", 0x100 + AC97_PHONE_VOL, 16 }, { "AC97_MIC_VOL", 0x100 + AC97_MIC_VOL, 16 }, { "AC97_LINEIN_VOL", 0x100 + AC97_LINEIN_VOL, 16 }, { "AC97_CD_VOL", 0x100 + AC97_CD_VOL, 16 }, { "AC97_VIDEO_VOL", 0x100 + AC97_VIDEO_VOL, 16 }, { "AC97_AUX_VOL", 0x100 + AC97_AUX_VOL, 16 }, { "AC97_PCMOUT_VOL", 0x100 + AC97_PCMOUT_VOL, 16 }, { "AC97_RECORD_SELECT", 0x100 + AC97_RECORD_SELECT, 16 }, { "AC97_RECORD_GAIN", 0x100 + AC97_RECORD_GAIN, 16 }, { "AC97_RECORD_GAIN_MIC", 0x100 + AC97_RECORD_GAIN_MIC, 16 }, { "AC97_GENERAL_PURPOSE", 0x100 + AC97_GENERAL_PURPOSE, 16 }, { "AC97_3D_CONTROL", 0x100 + AC97_3D_CONTROL, 16 }, { "AC97_MODEM_RATE", 0x100 + AC97_MODEM_RATE, 16 }, { "AC97_POWER_CONTROL", 0x100 + AC97_POWER_CONTROL, 16 }, { NULL } }; if (dev == NULL) return -ENODEV; for (i = 0; regs[i].name != 0; i++) out += sprintf(out, "%s: 0x%0*x\n", regs[i].name, regs[i].width >> 2, (regs[i].width == 16 ? AD1889_READW(dev, regs[i].offset) : AD1889_READL(dev, regs[i].offset))); for (i = 0; i < AD_MAX_STATES; i++) { out += sprintf(out, "DMA status for %s:\n", (i == AD_WAV_STATE ? "WAV" : "ADC")); out += sprintf(out, "\t\t0x%p (IOVA: 0x%llu)\n", dev->state[i].dmabuf.rawbuf, (unsigned long long)dev->state[i].dmabuf.dma_handle); out += sprintf(out, "\tread ptr: offset %u\n", (unsigned int)dev->state[i].dmabuf.rd_ptr); out += sprintf(out, "\twrite ptr: offset %u\n", (unsigned int)dev->state[i].dmabuf.wr_ptr); out += sprintf(out, "\tdma len: offset %u\n", (unsigned int)dev->state[i].dmabuf.dma_len); } len = out - page - off; if (len < count) { *eof = 1; if (len <= 0) return 0; } else { len = count; } *start = page + off; return len;}/***************************** DMA interfaces ************************** */#if 0static inline unsigned long ad1889_get_dma_addr(ad1889_state_t *state){ struct dmabuf *dmabuf = &state->dmabuf; u32 offset; if (!(dmabuf->enable & (DAC_RUNNING | ADC_RUNNING))) { printk(KERN_ERR DEVNAME ": get_dma_addr called without dma enabled\n"); return 0; } if (dmabuf->enable & DAC_RUNNING) offset = le32_to_cpu(AD1889_READL(state->card, AD_DMAWAVBA)); else offset = le32_to_cpu(AD1889_READL(state->card, AD_DMAADCBA)); return (unsigned long)bus_to_virt((unsigned long)offset) - (unsigned long)dmabuf->rawbuf;}static void ad1889_update_ptr(ad1889_dev_t *dev, int wake){ ad1889_state_t *state; struct dmabuf *dmabuf; unsigned long hwptr; int diff; /* check ADC first */ state = &dev->adc_state; dmabuf = &state->dmabuf; if (dmabuf->enable & ADC_RUNNING) { hwptr = ad1889_get_dma_addr(state); diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; dmabuf->hwptr = hwptr; dmabuf->total_bytes += diff; dmabuf->count += diff; if (dmabuf->count > dmabuf->dmasize) dmabuf->count = dmabuf->dmasize; if (dmabuf->mapped) { if (wake & dmabuf->count >= dmabuf->fragsize) wake_up(&dmabuf->wait); } else { if (wake & dmabuf->count > 0) wake_up(&dmabuf->wait); } } /* check DAC */ state = &dev->wav_state; dmabuf = &state->dmabuf; if (dmabuf->enable & DAC_RUNNING) {XXX}#endif/************************* /dev/dsp interfaces ************************* */static ssize_t ad1889_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos){ return 0;}static ssize_t ad1889_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos){ ad1889_dev_t *dev = (ad1889_dev_t *)file->private_data; ad1889_state_t *state = &dev->state[AD_WAV_STATE]; volatile struct dmabuf *dmabuf = &state->dmabuf; ssize_t ret = 0; DECLARE_WAITQUEUE(wait, current); down(&state->sem);#if 0 if (dmabuf->mapped) { ret = -ENXIO; goto err1; }#endif if (!access_ok(VERIFY_READ, buffer, count)) { ret = -EFAULT; goto err1; } add_wait_queue(&state->dmabuf.wait, &wait); /* start filling dma buffer.... */ while (count > 0) { long rem; long cnt = count; unsigned long flags; for (;;) { long used_bytes; long timeout; /* max time for DMA in jiffies */ /* buffer is full if wr catches up to rd */ spin_lock_irqsave(&state->card->lock, flags); used_bytes = dmabuf->wr_ptr - dmabuf->rd_ptr; timeout = (dmabuf->dma_len * HZ) / dmabuf->rate; spin_unlock_irqrestore(&state->card->lock, flags); /* adjust for buffer wrap around */ used_bytes = (used_bytes + DMA_SIZE) & (DMA_SIZE - 1); /* If at least one page unused */ if (used_bytes < (DMA_SIZE - 0x1000)) break; /* dma buffer full */ if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; goto err2; } set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(timeout + 1); if (signal_pending(current)) { ret = -ERESTARTSYS; goto err2; } } /* watch out for wrapping around static buffer */ spin_lock_irqsave(&state->card->lock, flags); rem = DMA_SIZE - dmabuf->wr_ptr; if (cnt > rem) cnt = rem; rem = dmabuf->wr_ptr; /* update dma pointers */ dmabuf->wr_ptr += cnt; dmabuf->wr_ptr &= DMA_SIZE - 1; /* wrap ptr if necessary */ spin_unlock_irqrestore(&state->card->lock, flags); /* transfer unwrapped chunk */ if (copy_from_user(dmabuf->rawbuf + rem, buffer, cnt)) { ret = -EFAULT; goto err2; } DBG("Writing 0x%lx bytes to +0x%lx\n", cnt, rem); /* update counters */ count -= cnt;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -