![David Howells](/assets/img/avatar_default.png)
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
1139 lines
28 KiB
C
1139 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 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 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 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:
|
|
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));
|
|
|
|
/* 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:
|
|
*/
|