mirror of
https://github.com/adulau/aha.git
synced 2024-12-29 04:06:22 +00:00
9c2e08c592
Many struct file_operations in the kernel can be "const". Marking them const moves these to the .rodata section, which avoids false sharing with potential dirty data. In addition it'll catch accidental writes at compile time to these shared resources. Signed-off-by: Arjan van de Ven <arjan@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1140 lines
28 KiB
C
1140 lines
28 KiB
C
/*
|
|
btaudio - bt878 audio dma driver for linux 2.4.x
|
|
|
|
(c) 2000-2002 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/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/types.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 <linux/kdev_t.h>
|
|
#include <linux/mutex.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 __iomem *mmio;
|
|
|
|
/* locking */
|
|
int users;
|
|
struct mutex 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;
|
|
int rate;
|
|
};
|
|
|
|
struct cardinfo {
|
|
char *name;
|
|
int rate;
|
|
};
|
|
|
|
static struct btaudio *btaudios;
|
|
static unsigned int debug;
|
|
static unsigned int irq_debug;
|
|
|
|
/* -------------------------------------------------------------- */
|
|
|
|
#define BUF_DEFAULT 128*1024
|
|
#define BUF_MIN 8192
|
|
|
|
static 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) {
|
|
pci_free_consistent(bta->pci, bta->buf_size, bta->buf_cpu, bta->buf_dma);
|
|
bta->buf_cpu = NULL;
|
|
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 = iminor(inode);
|
|
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;
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
if (cmd == SOUND_MIXER_INFO) {
|
|
mixer_info info;
|
|
memset(&info,0,sizeof(info));
|
|
strlcpy(info.id,"bt878",sizeof(info.id));
|
|
strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name));
|
|
info.modify_counter = bta->mixcount;
|
|
if (copy_to_user(argp, &info, sizeof(info)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
if (cmd == SOUND_OLD_MIXER_INFO) {
|
|
_old_mixer_info info;
|
|
memset(&info,0,sizeof(info));
|
|
strlcpy(info.id,"bt878",sizeof(info.id)-1);
|
|
strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name));
|
|
if (copy_to_user(argp, &info, sizeof(info)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
if (cmd == OSS_GETVERSION)
|
|
return put_user(SOUND_VERSION, (int __user *)argp);
|
|
|
|
/* read */
|
|
if (_SIOC_DIR(cmd) & _SIOC_WRITE)
|
|
if (get_user(val, (int __user *)argp))
|
|
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 __user *)argp))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static const 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)
|
|
{
|
|
mutex_lock(&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;
|
|
|
|
mutex_unlock(&bta->lock);
|
|
return 0;
|
|
|
|
busy:
|
|
mutex_unlock(&bta->lock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
static int btaudio_dsp_open_digital(struct inode *inode, struct file *file)
|
|
{
|
|
int minor = iminor(inode);
|
|
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 = iminor(inode);
|
|
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;
|
|
|
|
mutex_lock(&bta->lock);
|
|
if (bta->recording)
|
|
stop_recording(bta);
|
|
bta->users--;
|
|
mutex_unlock(&bta->lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t btaudio_dsp_read(struct file *file, char __user *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);
|
|
mutex_lock(&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;
|
|
}
|
|
mutex_unlock(&bta->lock);
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
schedule();
|
|
mutex_lock(&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;
|
|
ndst = nsrc >> bta->sampleshift;
|
|
|
|
if ((bta->analog && 0 == bta->sampleshift) ||
|
|
(!bta->analog && 2 == bta->channels)) {
|
|
/* just copy */
|
|
if (copy_to_user(buffer + ret, bta->buf_cpu + bta->read_offset, nsrc)) {
|
|
if (0 == ret)
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
} else if (!bta->analog) {
|
|
/* stereo => mono (digital audio) */
|
|
__s16 *src = (__s16*)(bta->buf_cpu + bta->read_offset);
|
|
__s16 __user *dst = (__s16 __user *)(buffer + ret);
|
|
__s16 avg;
|
|
int n = ndst>>1;
|
|
if (!access_ok(VERIFY_WRITE, dst, ndst)) {
|
|
if (0 == ret)
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
for (; n; n--, dst++) {
|
|
avg = (__s16)le16_to_cpu(*src) / 2; src++;
|
|
avg += (__s16)le16_to_cpu(*src) / 2; src++;
|
|
__put_user(cpu_to_le16(avg),dst);
|
|
}
|
|
|
|
} else if (8 == bta->bits) {
|
|
/* copy + byte downsampling (audio A/D) */
|
|
__u8 *src = bta->buf_cpu + bta->read_offset;
|
|
__u8 __user *dst = buffer + ret;
|
|
int n = ndst;
|
|
if (!access_ok(VERIFY_WRITE, dst, ndst)) {
|
|
if (0 == ret)
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
for (; n; n--, src += (1 << bta->sampleshift), dst++)
|
|
__put_user(*src, dst);
|
|
|
|
} else {
|
|
/* copy + word downsampling (audio A/D) */
|
|
__u16 *src = (__u16*)(bta->buf_cpu + bta->read_offset);
|
|
__u16 __user *dst = (__u16 __user *)(buffer + ret);
|
|
int n = ndst>>1;
|
|
if (!access_ok(VERIFY_WRITE,dst,ndst)) {
|
|
if (0 == ret)
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
for (; n; n--, src += (1 << bta->sampleshift), dst++)
|
|
__put_user(*src, dst);
|
|
}
|
|
|
|
ret += ndst;
|
|
swcount -= ndst;
|
|
hwcount -= nsrc;
|
|
bta->read_count -= nsrc;
|
|
bta->read_offset += nsrc;
|
|
if (bta->read_offset == bta->buf_size)
|
|
bta->read_offset = 0;
|
|
}
|
|
mutex_unlock(&bta->lock);
|
|
remove_wait_queue(&bta->readq, &wait);
|
|
current->state = TASK_RUNNING;
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t btaudio_dsp_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int btaudio_dsp_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct btaudio *bta = file->private_data;
|
|
int s, i, ret, val = 0;
|
|
void __user *argp = (void __user *)arg;
|
|
int __user *p = argp;
|
|
|
|
switch (cmd) {
|
|
case OSS_GETVERSION:
|
|
return put_user(SOUND_VERSION, p);
|
|
case SNDCTL_DSP_GETCAPS:
|
|
return 0;
|
|
|
|
case SNDCTL_DSP_SPEED:
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
if (bta->analog) {
|
|
for (s = 0; s < 16; s++)
|
|
if (val << s >= HWBASE_AD*4/15)
|
|
break;
|
|
for (i = 15; i >= 5; i--)
|
|
if (val << s <= HWBASE_AD*4/i)
|
|
break;
|
|
bta->sampleshift = s;
|
|
bta->decimation = i;
|
|
if (debug)
|
|
printk(KERN_DEBUG "btaudio: rate: req=%d "
|
|
"dec=%d shift=%d hwrate=%d swrate=%d\n",
|
|
val,i,s,(HWBASE_AD*4/i),(HWBASE_AD*4/i)>>s);
|
|
} else {
|
|
bta->sampleshift = (bta->channels == 2) ? 0 : 1;
|
|
bta->decimation = 0;
|
|
}
|
|
if (bta->recording) {
|
|
mutex_lock(&bta->lock);
|
|
stop_recording(bta);
|
|
start_recording(bta);
|
|
mutex_unlock(&bta->lock);
|
|
}
|
|
/* fall through */
|
|
case SOUND_PCM_READ_RATE:
|
|
if (bta->analog) {
|
|
return put_user(HWBASE_AD*4/bta->decimation>>bta->sampleshift, p);
|
|
} else {
|
|
return put_user(bta->rate, p);
|
|
}
|
|
|
|
case SNDCTL_DSP_STEREO:
|
|
if (!bta->analog) {
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
bta->channels = (val > 0) ? 2 : 1;
|
|
bta->sampleshift = (bta->channels == 2) ? 0 : 1;
|
|
if (debug)
|
|
printk(KERN_INFO
|
|
"btaudio: stereo=%d channels=%d\n",
|
|
val,bta->channels);
|
|
} else {
|
|
if (val == 1)
|
|
return -EFAULT;
|
|
else {
|
|
bta->channels = 1;
|
|
if (debug)
|
|
printk(KERN_INFO
|
|
"btaudio: stereo=0 channels=1\n");
|
|
}
|
|
}
|
|
return put_user((bta->channels)-1, p);
|
|
|
|
case SNDCTL_DSP_CHANNELS:
|
|
if (!bta->analog) {
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
bta->channels = (val > 1) ? 2 : 1;
|
|
bta->sampleshift = (bta->channels == 2) ? 0 : 1;
|
|
if (debug)
|
|
printk(KERN_DEBUG
|
|
"btaudio: val=%d channels=%d\n",
|
|
val,bta->channels);
|
|
}
|
|
/* fall through */
|
|
case SOUND_PCM_READ_CHANNELS:
|
|
return put_user(bta->channels, p);
|
|
|
|
case SNDCTL_DSP_GETFMTS: /* Returns a mask */
|
|
if (bta->analog)
|
|
return put_user(AFMT_S16_LE|AFMT_S8, p);
|
|
else
|
|
return put_user(AFMT_S16_LE, p);
|
|
|
|
case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
if (val != AFMT_QUERY) {
|
|
if (bta->analog)
|
|
bta->bits = (val == AFMT_S8) ? 8 : 16;
|
|
else
|
|
bta->bits = 16;
|
|
if (bta->recording) {
|
|
mutex_lock(&bta->lock);
|
|
stop_recording(bta);
|
|
start_recording(bta);
|
|
mutex_unlock(&bta->lock);
|
|
}
|
|
}
|
|
if (debug)
|
|
printk(KERN_DEBUG "btaudio: fmt: bits=%d\n",bta->bits);
|
|
return put_user((bta->bits==16) ? AFMT_S16_LE : AFMT_S8,
|
|
p);
|
|
break;
|
|
case SOUND_PCM_READ_BITS:
|
|
return put_user(bta->bits, p);
|
|
|
|
case SNDCTL_DSP_NONBLOCK:
|
|
file->f_flags |= O_NONBLOCK;
|
|
return 0;
|
|
|
|
case SNDCTL_DSP_RESET:
|
|
if (bta->recording) {
|
|
mutex_lock(&bta->lock);
|
|
stop_recording(bta);
|
|
mutex_unlock(&bta->lock);
|
|
}
|
|
return 0;
|
|
case SNDCTL_DSP_GETBLKSIZE:
|
|
if (!bta->recording) {
|
|
if (0 != (ret = alloc_buffer(bta)))
|
|
return ret;
|
|
if (0 != (ret = make_risc(bta)))
|
|
return ret;
|
|
}
|
|
return put_user(bta->block_bytes>>bta->sampleshift,p);
|
|
|
|
case SNDCTL_DSP_SYNC:
|
|
/* NOP */
|
|
return 0;
|
|
case SNDCTL_DSP_GETISPACE:
|
|
{
|
|
audio_buf_info info;
|
|
if (!bta->recording)
|
|
return -EINVAL;
|
|
info.fragsize = bta->block_bytes>>bta->sampleshift;
|
|
info.fragstotal = bta->block_count;
|
|
info.bytes = bta->read_count;
|
|
info.fragments = info.bytes / info.fragsize;
|
|
if (debug)
|
|
printk(KERN_DEBUG "btaudio: SNDCTL_DSP_GETISPACE "
|
|
"returns %d/%d/%d/%d\n",
|
|
info.fragsize, info.fragstotal,
|
|
info.bytes, info.fragments);
|
|
if (copy_to_user(argp, &info, sizeof(info)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
#if 0 /* TODO */
|
|
case SNDCTL_DSP_GETTRIGGER:
|
|
case SNDCTL_DSP_SETTRIGGER:
|
|
case SNDCTL_DSP_SETFRAGMENT:
|
|
#endif
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static unsigned int btaudio_dsp_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct btaudio *bta = file->private_data;
|
|
unsigned int mask = 0;
|
|
|
|
poll_wait(file, &bta->readq, wait);
|
|
|
|
if (0 != bta->read_count)
|
|
mask |= (POLLIN | POLLRDNORM);
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations btaudio_digital_dsp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.open = btaudio_dsp_open_digital,
|
|
.release = btaudio_dsp_release,
|
|
.read = btaudio_dsp_read,
|
|
.write = btaudio_dsp_write,
|
|
.ioctl = btaudio_dsp_ioctl,
|
|
.poll = btaudio_dsp_poll,
|
|
};
|
|
|
|
static const struct file_operations btaudio_analog_dsp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.open = btaudio_dsp_open_analog,
|
|
.release = btaudio_dsp_release,
|
|
.read = btaudio_dsp_read,
|
|
.write = btaudio_dsp_write,
|
|
.ioctl = btaudio_dsp_ioctl,
|
|
.poll = btaudio_dsp_poll,
|
|
};
|
|
|
|
/* -------------------------------------------------------------- */
|
|
|
|
static char *irq_name[] = { "", "", "", "OFLOW", "", "", "", "", "", "", "",
|
|
"RISCI", "FBUS", "FTRGT", "FDSR", "PPERR",
|
|
"RIPERR", "PABORT", "OCERR", "SCERR" };
|
|
|
|
static irqreturn_t btaudio_irq(int irq, void *dev_id)
|
|
{
|
|
int count = 0;
|
|
u32 stat,astat;
|
|
struct btaudio *bta = dev_id;
|
|
int handled = 0;
|
|
|
|
for (;;) {
|
|
count++;
|
|
stat = btread(REG_INT_STAT);
|
|
astat = stat & btread(REG_INT_MASK);
|
|
if (!astat)
|
|
return IRQ_RETVAL(handled);
|
|
handled = 1;
|
|
btwrite(astat,REG_INT_STAT);
|
|
|
|
if (irq_debug) {
|
|
int i;
|
|
printk(KERN_DEBUG "btaudio: irq loop=%d risc=%x, bits:",
|
|
count, stat>>28);
|
|
for (i = 0; i < (sizeof(irq_name)/sizeof(char*)); i++) {
|
|
if (stat & (1 << i))
|
|
printk(" %s",irq_name[i]);
|
|
if (astat & (1 << i))
|
|
printk("*");
|
|
}
|
|
printk("\n");
|
|
}
|
|
if (stat & IRQ_RISCI) {
|
|
int blocks;
|
|
blocks = (stat >> 28) - bta->dma_block;
|
|
if (blocks < 0)
|
|
blocks += bta->block_count;
|
|
bta->dma_block = stat >> 28;
|
|
if (bta->read_count + 2*bta->block_bytes > bta->buf_size) {
|
|
stop_recording(bta);
|
|
printk(KERN_INFO "btaudio: buffer overrun\n");
|
|
}
|
|
if (blocks > 0) {
|
|
bta->read_count += blocks * bta->block_bytes;
|
|
wake_up_interruptible(&bta->readq);
|
|
}
|
|
}
|
|
if (count > 10) {
|
|
printk(KERN_WARNING
|
|
"btaudio: Oops - irq mask cleared\n");
|
|
btwrite(0, REG_INT_MASK);
|
|
}
|
|
}
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- */
|
|
|
|
static unsigned int dsp1 = -1;
|
|
static unsigned int dsp2 = -1;
|
|
static unsigned int mixer = -1;
|
|
static int latency = -1;
|
|
static int digital = 1;
|
|
static int analog = 1;
|
|
static int rate;
|
|
|
|
#define BTA_OSPREY200 1
|
|
|
|
static struct cardinfo cards[] = {
|
|
[0] = {
|
|
.name = "default",
|
|
.rate = 32000,
|
|
},
|
|
[BTA_OSPREY200] = {
|
|
.name = "Osprey 200",
|
|
.rate = 44100,
|
|
},
|
|
};
|
|
|
|
static int __devinit btaudio_probe(struct pci_dev *pci_dev,
|
|
const struct pci_device_id *pci_id)
|
|
{
|
|
struct btaudio *bta;
|
|
struct cardinfo *card = &cards[pci_id->driver_data];
|
|
unsigned char revision,lat;
|
|
int rc = -EBUSY;
|
|
|
|
if (pci_enable_device(pci_dev))
|
|
return -EIO;
|
|
if (!request_mem_region(pci_resource_start(pci_dev,0),
|
|
pci_resource_len(pci_dev,0),
|
|
"btaudio")) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
bta = kmalloc(sizeof(*bta),GFP_ATOMIC);
|
|
if (!bta) {
|
|
rc = -ENOMEM;
|
|
goto fail0;
|
|
}
|
|
memset(bta,0,sizeof(*bta));
|
|
|
|
bta->pci = pci_dev;
|
|
bta->irq = pci_dev->irq;
|
|
bta->mem = pci_resource_start(pci_dev,0);
|
|
bta->mmio = ioremap(pci_resource_start(pci_dev,0),
|
|
pci_resource_len(pci_dev,0));
|
|
|
|
bta->source = 1;
|
|
bta->bits = 8;
|
|
bta->channels = 1;
|
|
if (bta->analog) {
|
|
bta->decimation = 15;
|
|
} else {
|
|
bta->decimation = 0;
|
|
bta->sampleshift = 1;
|
|
}
|
|
|
|
/* sample rate */
|
|
bta->rate = card->rate;
|
|
if (rate)
|
|
bta->rate = rate;
|
|
|
|
mutex_init(&bta->lock);
|
|
init_waitqueue_head(&bta->readq);
|
|
|
|
if (-1 != latency) {
|
|
printk(KERN_INFO "btaudio: setting pci latency timer to %d\n",
|
|
latency);
|
|
pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
|
|
}
|
|
pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &revision);
|
|
pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &lat);
|
|
printk(KERN_INFO "btaudio: Bt%x (rev %d) at %02x:%02x.%x, ",
|
|
pci_dev->device,revision,pci_dev->bus->number,
|
|
PCI_SLOT(pci_dev->devfn),PCI_FUNC(pci_dev->devfn));
|
|
printk("irq: %d, latency: %d, mmio: 0x%lx\n",
|
|
bta->irq, lat, bta->mem);
|
|
printk("btaudio: using card config \"%s\"\n", card->name);
|
|
|
|
/* init hw */
|
|
btwrite(0, REG_GPIO_DMA_CTL);
|
|
btwrite(0, REG_INT_MASK);
|
|
btwrite(~0U, REG_INT_STAT);
|
|
pci_set_master(pci_dev);
|
|
|
|
if ((rc = request_irq(bta->irq, btaudio_irq, IRQF_SHARED|IRQF_DISABLED,
|
|
"btaudio",(void *)bta)) < 0) {
|
|
printk(KERN_WARNING
|
|
"btaudio: can't request irq (rc=%d)\n",rc);
|
|
goto fail1;
|
|
}
|
|
|
|
/* register devices */
|
|
if (digital) {
|
|
rc = bta->dsp_digital =
|
|
register_sound_dsp(&btaudio_digital_dsp_fops,dsp1);
|
|
if (rc < 0) {
|
|
printk(KERN_WARNING
|
|
"btaudio: can't register digital dsp (rc=%d)\n",rc);
|
|
goto fail2;
|
|
}
|
|
printk(KERN_INFO "btaudio: registered device dsp%d [digital]\n",
|
|
bta->dsp_digital >> 4);
|
|
}
|
|
if (analog) {
|
|
rc = bta->dsp_analog =
|
|
register_sound_dsp(&btaudio_analog_dsp_fops,dsp2);
|
|
if (rc < 0) {
|
|
printk(KERN_WARNING
|
|
"btaudio: can't register analog dsp (rc=%d)\n",rc);
|
|
goto fail3;
|
|
}
|
|
printk(KERN_INFO "btaudio: registered device dsp%d [analog]\n",
|
|
bta->dsp_analog >> 4);
|
|
rc = bta->mixer_dev = register_sound_mixer(&btaudio_mixer_fops,mixer);
|
|
if (rc < 0) {
|
|
printk(KERN_WARNING
|
|
"btaudio: can't register mixer (rc=%d)\n",rc);
|
|
goto fail4;
|
|
}
|
|
printk(KERN_INFO "btaudio: registered device mixer%d\n",
|
|
bta->mixer_dev >> 4);
|
|
}
|
|
|
|
/* hook into linked list */
|
|
bta->next = btaudios;
|
|
btaudios = bta;
|
|
|
|
pci_set_drvdata(pci_dev,bta);
|
|
return 0;
|
|
|
|
fail4:
|
|
unregister_sound_dsp(bta->dsp_analog);
|
|
fail3:
|
|
if (digital)
|
|
unregister_sound_dsp(bta->dsp_digital);
|
|
fail2:
|
|
free_irq(bta->irq,bta);
|
|
fail1:
|
|
iounmap(bta->mmio);
|
|
kfree(bta);
|
|
fail0:
|
|
release_mem_region(pci_resource_start(pci_dev,0),
|
|
pci_resource_len(pci_dev,0));
|
|
return rc;
|
|
}
|
|
|
|
static void __devexit btaudio_remove(struct pci_dev *pci_dev)
|
|
{
|
|
struct btaudio *bta = pci_get_drvdata(pci_dev);
|
|
struct btaudio *walk;
|
|
|
|
/* turn off all DMA / IRQs */
|
|
btand(~15, REG_GPIO_DMA_CTL);
|
|
btwrite(0, REG_INT_MASK);
|
|
btwrite(~0U, REG_INT_STAT);
|
|
|
|
/* unregister devices */
|
|
if (digital) {
|
|
unregister_sound_dsp(bta->dsp_digital);
|
|
}
|
|
if (analog) {
|
|
unregister_sound_dsp(bta->dsp_analog);
|
|
unregister_sound_mixer(bta->mixer_dev);
|
|
}
|
|
|
|
/* free resources */
|
|
free_buffer(bta);
|
|
free_irq(bta->irq,bta);
|
|
release_mem_region(pci_resource_start(pci_dev,0),
|
|
pci_resource_len(pci_dev,0));
|
|
iounmap(bta->mmio);
|
|
|
|
/* remove from linked list */
|
|
if (bta == btaudios) {
|
|
btaudios = NULL;
|
|
} else {
|
|
for (walk = btaudios; walk->next != bta; walk = walk->next)
|
|
; /* if (NULL == walk->next) BUG(); */
|
|
walk->next = bta->next;
|
|
}
|
|
|
|
pci_set_drvdata(pci_dev, NULL);
|
|
kfree(bta);
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- */
|
|
|
|
static struct pci_device_id btaudio_pci_tbl[] = {
|
|
{
|
|
.vendor = PCI_VENDOR_ID_BROOKTREE,
|
|
.device = 0x0878,
|
|
.subvendor = 0x0070,
|
|
.subdevice = 0xff01,
|
|
.driver_data = BTA_OSPREY200,
|
|
},{
|
|
.vendor = PCI_VENDOR_ID_BROOKTREE,
|
|
.device = 0x0878,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},{
|
|
.vendor = PCI_VENDOR_ID_BROOKTREE,
|
|
.device = 0x0878,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},{
|
|
/* --- end of list --- */
|
|
}
|
|
};
|
|
|
|
static struct pci_driver btaudio_pci_driver = {
|
|
.name = "btaudio",
|
|
.id_table = btaudio_pci_tbl,
|
|
.probe = btaudio_probe,
|
|
.remove = __devexit_p(btaudio_remove),
|
|
};
|
|
|
|
static int btaudio_init_module(void)
|
|
{
|
|
printk(KERN_INFO "btaudio: driver version 0.7 loaded [%s%s%s]\n",
|
|
digital ? "digital" : "",
|
|
analog && digital ? "+" : "",
|
|
analog ? "analog" : "");
|
|
return pci_register_driver(&btaudio_pci_driver);
|
|
}
|
|
|
|
static void btaudio_cleanup_module(void)
|
|
{
|
|
pci_unregister_driver(&btaudio_pci_driver);
|
|
return;
|
|
}
|
|
|
|
module_init(btaudio_init_module);
|
|
module_exit(btaudio_cleanup_module);
|
|
|
|
module_param(dsp1, int, S_IRUGO);
|
|
module_param(dsp2, int, S_IRUGO);
|
|
module_param(mixer, int, S_IRUGO);
|
|
module_param(debug, int, S_IRUGO | S_IWUSR);
|
|
module_param(irq_debug, int, S_IRUGO | S_IWUSR);
|
|
module_param(digital, int, S_IRUGO);
|
|
module_param(analog, int, S_IRUGO);
|
|
module_param(rate, int, S_IRUGO);
|
|
module_param(latency, int, S_IRUGO);
|
|
MODULE_PARM_DESC(latency,"pci latency timer");
|
|
|
|
MODULE_DEVICE_TABLE(pci, btaudio_pci_tbl);
|
|
MODULE_DESCRIPTION("bt878 audio dma driver");
|
|
MODULE_AUTHOR("Gerd Knorr");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|