📄 btaudio.c
字号:
/* btaudio - bt878 audio dma driver for linux 2.4.x (c) 2000 Gerd Knorr <kraxel@bytesex.org> 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.*/#include <linux/version.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/pci.h>#include <linux/sched.h>#include <linux/signal.h>#include <linux/types.h>#include <linux/wrapper.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/poll.h>#include <linux/sound.h>#include <linux/soundcard.h>#include <linux/slab.h>#include <asm/uaccess.h>#include <asm/io.h>/* mmio access */#define btwrite(dat,adr) writel((dat), (bta->mmio+(adr)))#define btread(adr) readl(bta->mmio+(adr))#define btand(dat,adr) btwrite((dat) & btread(adr), adr)#define btor(dat,adr) btwrite((dat) | btread(adr), adr)#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)/* registers (shifted because bta->mmio is long) */#define REG_INT_STAT (0x100 >> 2)#define REG_INT_MASK (0x104 >> 2)#define REG_GPIO_DMA_CTL (0x10c >> 2)#define REG_PACKET_LEN (0x110 >> 2)#define REG_RISC_STRT_ADD (0x114 >> 2)#define REG_RISC_COUNT (0x120 >> 2)/* IRQ bits - REG_INT_(STAT|MASK) */#define IRQ_SCERR (1 << 19)#define IRQ_OCERR (1 << 18)#define IRQ_PABORT (1 << 17)#define IRQ_RIPERR (1 << 16)#define IRQ_PPERR (1 << 15)#define IRQ_FDSR (1 << 14)#define IRQ_FTRGT (1 << 13)#define IRQ_FBUS (1 << 12)#define IRQ_RISCI (1 << 11)#define IRQ_OFLOW (1 << 3)#define IRQ_BTAUDIO (IRQ_SCERR | IRQ_OCERR | IRQ_PABORT | IRQ_RIPERR |\ IRQ_PPERR | IRQ_FDSR | IRQ_FTRGT | IRQ_FBUS |\ IRQ_RISCI)/* REG_GPIO_DMA_CTL bits */#define DMA_CTL_A_PWRDN (1 << 26)#define DMA_CTL_DA_SBR (1 << 14)#define DMA_CTL_DA_ES2 (1 << 13)#define DMA_CTL_ACAP_EN (1 << 4)#define DMA_CTL_RISC_EN (1 << 1)#define DMA_CTL_FIFO_EN (1 << 0)/* RISC instructions */#define RISC_WRITE (0x01 << 28)#define RISC_JUMP (0x07 << 28)#define RISC_SYNC (0x08 << 28)/* RISC bits */#define RISC_WR_SOL (1 << 27)#define RISC_WR_EOL (1 << 26)#define RISC_IRQ (1 << 24)#define RISC_SYNC_RESYNC (1 << 15)#define RISC_SYNC_FM1 0x06#define RISC_SYNC_VRO 0x0c#define HWBASE_AD (448000)/* -------------------------------------------------------------- */struct btaudio { /* linked list */ struct btaudio *next; /* device info */ int dsp_digital; int dsp_analog; int mixer_dev; struct pci_dev *pci; unsigned int irq; unsigned long mem; unsigned long *mmio; /* locking */ int users; struct semaphore lock; /* risc instructions */ unsigned int risc_size; unsigned long *risc_cpu; dma_addr_t risc_dma; /* audio data */ unsigned int buf_size; unsigned char *buf_cpu; dma_addr_t buf_dma; /* buffer setup */ int line_bytes; int line_count; int block_bytes; int block_count; /* read fifo management */ int recording; int dma_block; int read_offset; int read_count; wait_queue_head_t readq; /* settings */ int gain[3]; int source; int bits; int decimation; int mixcount; int sampleshift; int channels; int analog;};static struct btaudio *btaudios = NULL;static unsigned int dsp1 = -1;static unsigned int dsp2 = -1;static unsigned int mixer = -1;static unsigned int debug = 0;static unsigned int irq_debug = 0;static int digital = 1;static int analog = 1;static int rate = 32000;/* -------------------------------------------------------------- */#define BUF_DEFAULT 128*1024#define BUF_MIN 8192static int alloc_buffer(struct btaudio *bta){ if (NULL == bta->buf_cpu) { for (bta->buf_size = BUF_DEFAULT; bta->buf_size >= BUF_MIN; bta->buf_size = bta->buf_size >> 1) { bta->buf_cpu = pci_alloc_consistent (bta->pci, bta->buf_size, &bta->buf_dma); if (NULL != bta->buf_cpu) break; } if (NULL == bta->buf_cpu) return -ENOMEM; memset(bta->buf_cpu,0,bta->buf_size); } if (NULL == bta->risc_cpu) { bta->risc_size = PAGE_SIZE; bta->risc_cpu = pci_alloc_consistent (bta->pci, bta->risc_size, &bta->risc_dma); if (NULL == bta->risc_cpu) return -ENOMEM; } return 0;}static void free_buffer(struct btaudio *bta){ if (NULL != bta->buf_cpu) { pci_free_consistent(bta->pci, bta->buf_size, bta->buf_cpu, bta->buf_dma); bta->buf_cpu = NULL; } if (NULL != bta->risc_cpu) { pci_free_consistent(bta->pci, bta->risc_size, bta->risc_cpu, bta->risc_dma); bta->risc_cpu = NULL; }}static int make_risc(struct btaudio *bta){ int rp, bp, line, block; unsigned long risc; bta->block_bytes = bta->buf_size >> 4; bta->block_count = 1 << 4; bta->line_bytes = bta->block_bytes; bta->line_count = bta->block_count; while (bta->line_bytes > 4095) { bta->line_bytes >>= 1; bta->line_count <<= 1; } if (bta->line_count > 255) return -EINVAL; if (debug) printk(KERN_DEBUG "btaudio: bufsize=%d - bs=%d bc=%d - ls=%d, lc=%d\n", bta->buf_size,bta->block_bytes,bta->block_count, bta->line_bytes,bta->line_count); rp = 0; bp = 0; block = 0; bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_FM1); bta->risc_cpu[rp++] = cpu_to_le32(0); for (line = 0; line < bta->line_count; line++) { risc = RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL; risc |= bta->line_bytes; if (0 == (bp & (bta->block_bytes-1))) { risc |= RISC_IRQ; risc |= (block & 0x0f) << 16; risc |= (~block & 0x0f) << 20; block++; } bta->risc_cpu[rp++] = cpu_to_le32(risc); bta->risc_cpu[rp++] = cpu_to_le32(bta->buf_dma + bp); bp += bta->line_bytes; } bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_VRO); bta->risc_cpu[rp++] = cpu_to_le32(0); bta->risc_cpu[rp++] = cpu_to_le32(RISC_JUMP); bta->risc_cpu[rp++] = cpu_to_le32(bta->risc_dma); return 0;}static int start_recording(struct btaudio *bta){ int ret; if (0 != (ret = alloc_buffer(bta))) return ret; if (0 != (ret = make_risc(bta))) return ret; btwrite(bta->risc_dma, REG_RISC_STRT_ADD); btwrite((bta->line_count << 16) | bta->line_bytes, REG_PACKET_LEN); btwrite(IRQ_BTAUDIO, REG_INT_MASK); if (bta->analog) { btwrite(DMA_CTL_ACAP_EN | DMA_CTL_RISC_EN | DMA_CTL_FIFO_EN | DMA_CTL_DA_ES2 | ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) | (bta->gain[bta->source] << 28) | (bta->source << 24) | (bta->decimation << 8), REG_GPIO_DMA_CTL); } else { btwrite(DMA_CTL_ACAP_EN | DMA_CTL_RISC_EN | DMA_CTL_FIFO_EN | DMA_CTL_DA_ES2 | DMA_CTL_A_PWRDN | (1 << 6) | ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) | (bta->gain[bta->source] << 28) | (bta->source << 24) | (bta->decimation << 8), REG_GPIO_DMA_CTL); } bta->dma_block = 0; bta->read_offset = 0; bta->read_count = 0; bta->recording = 1; if (debug) printk(KERN_DEBUG "btaudio: recording started\n"); return 0;}static void stop_recording(struct btaudio *bta){ btand(~15, REG_GPIO_DMA_CTL); bta->recording = 0; if (debug) printk(KERN_DEBUG "btaudio: recording stopped\n");}/* -------------------------------------------------------------- */static int btaudio_mixer_open(struct inode *inode, struct file *file){ int minor = MINOR(inode->i_rdev); struct btaudio *bta; for (bta = btaudios; bta != NULL; bta = bta->next) if (bta->mixer_dev == minor) break; if (NULL == bta) return -ENODEV; if (debug) printk("btaudio: open mixer [%d]\n",minor); file->private_data = bta; return 0;}static int btaudio_mixer_release(struct inode *inode, struct file *file){ return 0;}static int btaudio_mixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ struct btaudio *bta = file->private_data; int ret,val=0,i=0; if (cmd == SOUND_MIXER_INFO) { mixer_info info; memset(&info,0,sizeof(info)); strncpy(info.id,"bt878",sizeof(info.id)-1); strncpy(info.name,"Brooktree Bt878 audio",sizeof(info.name)-1); info.modify_counter = bta->mixcount; if (copy_to_user((void *)arg, &info, sizeof(info))) return -EFAULT; return 0; } if (cmd == SOUND_OLD_MIXER_INFO) { _old_mixer_info info; memset(&info,0,sizeof(info)); strncpy(info.id,"bt878",sizeof(info.id)-1); strncpy(info.name,"Brooktree Bt878 audio",sizeof(info.name)-1); if (copy_to_user((void *)arg, &info, sizeof(info))) return -EFAULT; return 0; } if (cmd == OSS_GETVERSION) return put_user(SOUND_VERSION, (int *)arg); /* read */ if (_SIOC_DIR(cmd) & _SIOC_WRITE) if (get_user(val, (int *)arg)) return -EFAULT; switch (cmd) { case MIXER_READ(SOUND_MIXER_CAPS): ret = SOUND_CAP_EXCL_INPUT; break; case MIXER_READ(SOUND_MIXER_STEREODEVS): ret = 0; break; case MIXER_READ(SOUND_MIXER_RECMASK): case MIXER_READ(SOUND_MIXER_DEVMASK): ret = SOUND_MASK_LINE1|SOUND_MASK_LINE2|SOUND_MASK_LINE3; break; case MIXER_WRITE(SOUND_MIXER_RECSRC): if (val & SOUND_MASK_LINE1 && bta->source != 0) bta->source = 0; else if (val & SOUND_MASK_LINE2 && bta->source != 1) bta->source = 1; else if (val & SOUND_MASK_LINE3 && bta->source != 2) bta->source = 2; btaor((bta->gain[bta->source] << 28) | (bta->source << 24), 0x0cffffff, REG_GPIO_DMA_CTL); case MIXER_READ(SOUND_MIXER_RECSRC): switch (bta->source) { case 0: ret = SOUND_MASK_LINE1; break; case 1: ret = SOUND_MASK_LINE2; break; case 2: ret = SOUND_MASK_LINE3; break; default: ret = 0; } break; case MIXER_WRITE(SOUND_MIXER_LINE1): case MIXER_WRITE(SOUND_MIXER_LINE2): case MIXER_WRITE(SOUND_MIXER_LINE3): if (MIXER_WRITE(SOUND_MIXER_LINE1) == cmd) i = 0; if (MIXER_WRITE(SOUND_MIXER_LINE2) == cmd) i = 1; if (MIXER_WRITE(SOUND_MIXER_LINE3) == cmd) i = 2; bta->gain[i] = (val & 0xff) * 15 / 100; if (bta->gain[i] > 15) bta->gain[i] = 15; if (bta->gain[i] < 0) bta->gain[i] = 0; if (i == bta->source) btaor((bta->gain[bta->source]<<28), 0x0fffffff, REG_GPIO_DMA_CTL); ret = bta->gain[i] * 100 / 15; ret |= ret << 8; break; case MIXER_READ(SOUND_MIXER_LINE1): case MIXER_READ(SOUND_MIXER_LINE2): case MIXER_READ(SOUND_MIXER_LINE3): if (MIXER_READ(SOUND_MIXER_LINE1) == cmd) i = 0; if (MIXER_READ(SOUND_MIXER_LINE2) == cmd) i = 1; if (MIXER_READ(SOUND_MIXER_LINE3) == cmd) i = 2; ret = bta->gain[i] * 100 / 15; ret |= ret << 8; break; default: return -EINVAL; } if (put_user(ret, (int *)arg)) return -EFAULT; return 0;}static struct file_operations btaudio_mixer_fops = { owner: THIS_MODULE, llseek: no_llseek, open: btaudio_mixer_open, release: btaudio_mixer_release, ioctl: btaudio_mixer_ioctl,};/* -------------------------------------------------------------- */static int btaudio_dsp_open(struct inode *inode, struct file *file, struct btaudio *bta, int analog){ down(&bta->lock); if (bta->users) goto busy; bta->users++; file->private_data = bta; bta->analog = analog; bta->dma_block = 0; bta->read_offset = 0; bta->read_count = 0; bta->sampleshift = 0; up(&bta->lock); return 0; busy: up(&bta->lock); return -EBUSY;}static int btaudio_dsp_open_digital(struct inode *inode, struct file *file){ int minor = MINOR(inode->i_rdev); struct btaudio *bta; for (bta = btaudios; bta != NULL; bta = bta->next) if (bta->dsp_digital == minor) break; if (NULL == bta) return -ENODEV; if (debug) printk("btaudio: open digital dsp [%d]\n",minor); return btaudio_dsp_open(inode,file,bta,0);}static int btaudio_dsp_open_analog(struct inode *inode, struct file *file){ int minor = MINOR(inode->i_rdev); struct btaudio *bta; for (bta = btaudios; bta != NULL; bta = bta->next) if (bta->dsp_analog == minor) break; if (NULL == bta) return -ENODEV; if (debug) printk("btaudio: open analog dsp [%d]\n",minor); return btaudio_dsp_open(inode,file,bta,1);}static int btaudio_dsp_release(struct inode *inode, struct file *file){ struct btaudio *bta = file->private_data; down(&bta->lock); if (bta->recording) stop_recording(bta); bta->users--; up(&bta->lock); return 0;}static ssize_t btaudio_dsp_read(struct file *file, char *buffer, size_t swcount, loff_t *ppos){ struct btaudio *bta = file->private_data; int hwcount = swcount << bta->sampleshift; int nsrc, ndst, err, ret = 0; DECLARE_WAITQUEUE(wait, current); add_wait_queue(&bta->readq, &wait); down(&bta->lock); while (swcount > 0) { if (0 == bta->read_count) { if (!bta->recording) { if (0 != (err = start_recording(bta))) { if (0 == ret) ret = err; break; } } if (file->f_flags & O_NONBLOCK) { if (0 == ret) ret = -EAGAIN; break; } up(&bta->lock); current->state = TASK_INTERRUPTIBLE; schedule(); down(&bta->lock); if(signal_pending(current)) { if (0 == ret) ret = -EINTR; break; } } nsrc = (bta->read_count < hwcount) ? bta->read_count : hwcount; if (nsrc > bta->buf_size - bta->read_offset) nsrc = bta->buf_size - bta->read_offset;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -