mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-28 15:13:55 +08:00
7d12e780e0
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)
1369 lines
33 KiB
C
1369 lines
33 KiB
C
/*
|
|
*
|
|
* AD1816 lowlevel sound driver for Linux 2.6.0 and above
|
|
*
|
|
* Copyright (C) 1998-2003 by Thorsten Knabe <linux@thorsten-knabe.de>
|
|
*
|
|
* Based on the CS4232/AD1848 driver Copyright (C) by Hannu Savolainen 1993-1996
|
|
*
|
|
*
|
|
* version: 1.5
|
|
* status: beta
|
|
* date: 2003/07/15
|
|
*
|
|
* Changes:
|
|
* Oleg Drokin: Some cleanup of load/unload functions. 1998/11/24
|
|
*
|
|
* Thorsten Knabe: attach and unload rewritten,
|
|
* some argument checks added 1998/11/30
|
|
*
|
|
* Thorsten Knabe: Buggy isa bridge workaround added 1999/01/16
|
|
*
|
|
* David Moews/Thorsten Knabe: Introduced options
|
|
* parameter. Added slightly modified patch from
|
|
* David Moews to disable dsp audio sources by setting
|
|
* bit 0 of options parameter. This seems to be
|
|
* required by some Aztech/Newcom SC-16 cards. 1999/04/18
|
|
*
|
|
* Christoph Hellwig: Adapted to module_init/module_exit. 2000/03/03
|
|
*
|
|
* Christoph Hellwig: Added isapnp support 2000/03/15
|
|
*
|
|
* Arnaldo Carvalho de Melo: get rid of check_region 2001/10/07
|
|
*
|
|
* Thorsten Knabe: Compiling with CONFIG_PNP enabled
|
|
* works again. It is now possible to use more than one
|
|
* AD1816 sound card. Sample rate now may be changed during
|
|
* playback/capture. printk() uses log levels everywhere.
|
|
* SMP fixes. DMA handling fixes.
|
|
* Other minor code cleanup. 2003/07/15
|
|
*
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/isapnp.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/spinlock.h>
|
|
#include "sound_config.h"
|
|
|
|
#define DEBUGNOISE(x)
|
|
|
|
#define CHECK_FOR_POWER { int timeout=100; \
|
|
while (timeout > 0 && (inb(devc->base)&0x80)!= 0x80) {\
|
|
timeout--; \
|
|
} \
|
|
if (timeout==0) {\
|
|
printk(KERN_WARNING "ad1816: Check for power failed in %s line: %d\n",__FILE__,__LINE__); \
|
|
} \
|
|
}
|
|
|
|
/* structure to hold device specific information */
|
|
typedef struct
|
|
{
|
|
int base; /* set in attach */
|
|
int irq;
|
|
int dma_playback;
|
|
int dma_capture;
|
|
|
|
int opened; /* open */
|
|
int speed;
|
|
int channels;
|
|
int audio_format;
|
|
int audio_mode;
|
|
|
|
int recmask; /* setup */
|
|
unsigned char format_bits;
|
|
int supported_devices;
|
|
int supported_rec_devices;
|
|
unsigned short levels[SOUND_MIXER_NRDEVICES];
|
|
/* misc */
|
|
struct pnp_dev *pnpdev; /* configured via pnp */
|
|
int dev_no; /* this is the # in audio_devs and NOT
|
|
in ad1816_info */
|
|
spinlock_t lock;
|
|
} ad1816_info;
|
|
|
|
static int nr_ad1816_devs;
|
|
static int ad1816_clockfreq = 33000;
|
|
static int options;
|
|
|
|
/* supported audio formats */
|
|
static int ad_format_mask =
|
|
AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW;
|
|
|
|
/* array of device info structures */
|
|
static ad1816_info dev_info[MAX_AUDIO_DEV];
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* functions for easier access to inderect registers */
|
|
|
|
static int ad_read (ad1816_info * devc, int reg)
|
|
{
|
|
int result;
|
|
|
|
CHECK_FOR_POWER;
|
|
outb ((unsigned char) (reg & 0x3f), devc->base+0);
|
|
result = inb(devc->base+2);
|
|
result+= inb(devc->base+3)<<8;
|
|
return (result);
|
|
}
|
|
|
|
|
|
static void ad_write (ad1816_info * devc, int reg, int data)
|
|
{
|
|
CHECK_FOR_POWER;
|
|
outb ((unsigned char) (reg & 0xff), devc->base+0);
|
|
outb ((unsigned char) (data & 0xff),devc->base+2);
|
|
outb ((unsigned char) ((data>>8)&0xff),devc->base+3);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* function interface required by struct audio_driver */
|
|
|
|
static void ad1816_halt_input (int dev)
|
|
{
|
|
unsigned long flags;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
unsigned char buffer;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_input called\n"));
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
if(!isa_dma_bridge_buggy) {
|
|
disable_dma(audio_devs[dev]->dmap_in->dma);
|
|
}
|
|
|
|
buffer=inb(devc->base+9);
|
|
if (buffer & 0x01) {
|
|
/* disable capture */
|
|
outb(buffer & ~0x01,devc->base+9);
|
|
}
|
|
|
|
if(!isa_dma_bridge_buggy) {
|
|
enable_dma(audio_devs[dev]->dmap_in->dma);
|
|
}
|
|
|
|
/* Clear interrupt status */
|
|
outb (~0x40, devc->base+1);
|
|
|
|
devc->audio_mode &= ~PCM_ENABLE_INPUT;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
static void ad1816_halt_output (int dev)
|
|
{
|
|
unsigned long flags;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
unsigned char buffer;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_output called!\n"));
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
/* Mute pcm output */
|
|
ad_write(devc, 4, ad_read(devc,4)|0x8080);
|
|
|
|
if(!isa_dma_bridge_buggy) {
|
|
disable_dma(audio_devs[dev]->dmap_out->dma);
|
|
}
|
|
|
|
buffer=inb(devc->base+8);
|
|
if (buffer & 0x01) {
|
|
/* disable capture */
|
|
outb(buffer & ~0x01,devc->base+8);
|
|
}
|
|
|
|
if(!isa_dma_bridge_buggy) {
|
|
enable_dma(audio_devs[dev]->dmap_out->dma);
|
|
}
|
|
|
|
/* Clear interrupt status */
|
|
outb ((unsigned char)~0x80, devc->base+1);
|
|
|
|
devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
static void ad1816_output_block (int dev, unsigned long buf,
|
|
int count, int intrflag)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long cnt;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: output_block called buf=%ld count=%d flags=%d\n",buf,count,intrflag));
|
|
|
|
cnt = count/4 - 1;
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
/* set transfer count */
|
|
ad_write (devc, 8, cnt & 0xffff);
|
|
|
|
devc->audio_mode |= PCM_ENABLE_OUTPUT;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
|
|
static void ad1816_start_input (int dev, unsigned long buf, int count,
|
|
int intrflag)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long cnt;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: start_input called buf=%ld count=%d flags=%d\n",buf,count,intrflag));
|
|
|
|
cnt = count/4 - 1;
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
/* set transfer count */
|
|
ad_write (devc, 10, cnt & 0xffff);
|
|
devc->audio_mode |= PCM_ENABLE_INPUT;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
static int ad1816_prepare_for_input (int dev, int bsize, int bcount)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int freq;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
unsigned char fmt_bits;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_input called: bsize=%d bcount=%d\n",bsize,bcount));
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
fmt_bits= (devc->format_bits&0x7)<<3;
|
|
|
|
/* set mono/stereo mode */
|
|
if (devc->channels > 1) {
|
|
fmt_bits |=0x4;
|
|
}
|
|
/* set Mono/Stereo in playback/capture register */
|
|
outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8);
|
|
outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9);
|
|
|
|
freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq;
|
|
|
|
/* write playback/capture speeds */
|
|
ad_write (devc, 2, freq & 0xffff);
|
|
ad_write (devc, 3, freq & 0xffff);
|
|
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
|
|
ad1816_halt_input(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int ad1816_prepare_for_output (int dev, int bsize, int bcount)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int freq;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
unsigned char fmt_bits;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_output called: bsize=%d bcount=%d\n",bsize,bcount));
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
fmt_bits= (devc->format_bits&0x7)<<3;
|
|
/* set mono/stereo mode */
|
|
if (devc->channels > 1) {
|
|
fmt_bits |=0x4;
|
|
}
|
|
|
|
/* write format bits to playback/capture registers */
|
|
outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8);
|
|
outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9);
|
|
|
|
freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq;
|
|
|
|
/* write playback/capture speeds */
|
|
ad_write (devc, 2, freq & 0xffff);
|
|
ad_write (devc, 3, freq & 0xffff);
|
|
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
|
|
ad1816_halt_output(dev);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void ad1816_trigger (int dev, int state)
|
|
{
|
|
unsigned long flags;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: trigger called! (devc=%d,devc->base=%d\n", devc, devc->base));
|
|
|
|
/* mode may have changed */
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
/* mask out modes not specified on open call */
|
|
state &= devc->audio_mode;
|
|
|
|
/* setup soundchip to new io-mode */
|
|
if (state & PCM_ENABLE_INPUT) {
|
|
/* enable capture */
|
|
outb(inb(devc->base+9)|0x01, devc->base+9);
|
|
} else {
|
|
/* disable capture */
|
|
outb(inb(devc->base+9)&~0x01, devc->base+9);
|
|
}
|
|
|
|
if (state & PCM_ENABLE_OUTPUT) {
|
|
/* enable playback */
|
|
outb(inb(devc->base+8)|0x01, devc->base+8);
|
|
/* unmute pcm output */
|
|
ad_write(devc, 4, ad_read(devc,4)&~0x8080);
|
|
} else {
|
|
/* mute pcm output */
|
|
ad_write(devc, 4, ad_read(devc,4)|0x8080);
|
|
/* disable capture */
|
|
outb(inb(devc->base+8)&~0x01, devc->base+8);
|
|
}
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
|
|
/* halt input & output */
|
|
static void ad1816_halt (int dev)
|
|
{
|
|
ad1816_halt_input(dev);
|
|
ad1816_halt_output(dev);
|
|
}
|
|
|
|
static void ad1816_reset (int dev)
|
|
{
|
|
ad1816_halt (dev);
|
|
}
|
|
|
|
/* set playback speed */
|
|
static int ad1816_set_speed (int dev, int arg)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int freq;
|
|
int ret;
|
|
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
spin_lock_irqsave(&devc->lock, flags);
|
|
if (arg == 0) {
|
|
ret = devc->speed;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return ret;
|
|
}
|
|
/* range checking */
|
|
if (arg < 4000) {
|
|
arg = 4000;
|
|
}
|
|
if (arg > 55000) {
|
|
arg = 55000;
|
|
}
|
|
devc->speed = arg;
|
|
|
|
/* change speed during playback */
|
|
freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq;
|
|
/* write playback/capture speeds */
|
|
ad_write (devc, 2, freq & 0xffff);
|
|
ad_write (devc, 3, freq & 0xffff);
|
|
|
|
ret = devc->speed;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static unsigned int ad1816_set_bits (int dev, unsigned int arg)
|
|
{
|
|
unsigned long flags;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
static struct format_tbl {
|
|
int format;
|
|
unsigned char bits;
|
|
} format2bits[] = {
|
|
{ 0, 0 },
|
|
{ AFMT_MU_LAW, 1 },
|
|
{ AFMT_A_LAW, 3 },
|
|
{ AFMT_IMA_ADPCM, 0 },
|
|
{ AFMT_U8, 0 },
|
|
{ AFMT_S16_LE, 2 },
|
|
{ AFMT_S16_BE, 6 },
|
|
{ AFMT_S8, 0 },
|
|
{ AFMT_U16_LE, 0 },
|
|
{ AFMT_U16_BE, 0 }
|
|
};
|
|
|
|
int i, n = sizeof (format2bits) / sizeof (struct format_tbl);
|
|
|
|
spin_lock_irqsave(&devc->lock, flags);
|
|
/* return current format */
|
|
if (arg == 0) {
|
|
arg = devc->audio_format;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return arg;
|
|
}
|
|
devc->audio_format = arg;
|
|
|
|
/* search matching format bits */
|
|
for (i = 0; i < n; i++)
|
|
if (format2bits[i].format == arg) {
|
|
devc->format_bits = format2bits[i].bits;
|
|
devc->audio_format = arg;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return arg;
|
|
}
|
|
|
|
/* Still hanging here. Something must be terribly wrong */
|
|
devc->format_bits = 0;
|
|
devc->audio_format = AFMT_U8;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return(AFMT_U8);
|
|
}
|
|
|
|
static short ad1816_set_channels (int dev, short arg)
|
|
{
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
if (arg != 1 && arg != 2)
|
|
return devc->channels;
|
|
|
|
devc->channels = arg;
|
|
return arg;
|
|
}
|
|
|
|
/* open device */
|
|
static int ad1816_open (int dev, int mode)
|
|
{
|
|
ad1816_info *devc = NULL;
|
|
unsigned long flags;
|
|
|
|
/* is device number valid ? */
|
|
if (dev < 0 || dev >= num_audiodevs)
|
|
return -(ENXIO);
|
|
|
|
/* get device info of this dev */
|
|
devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
/* make check if device already open atomic */
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
|
|
if (devc->opened) {
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
return -(EBUSY);
|
|
}
|
|
|
|
/* mark device as open */
|
|
devc->opened = 1;
|
|
|
|
devc->audio_mode = 0;
|
|
devc->speed = 8000;
|
|
devc->audio_format=AFMT_U8;
|
|
devc->channels=1;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
ad1816_reset(devc->dev_no); /* halt all pending output */
|
|
return 0;
|
|
}
|
|
|
|
static void ad1816_close (int dev) /* close device */
|
|
{
|
|
unsigned long flags;
|
|
ad1816_info *devc = (ad1816_info *) audio_devs[dev]->devc;
|
|
|
|
/* halt all pending output */
|
|
ad1816_reset(devc->dev_no);
|
|
|
|
spin_lock_irqsave(&devc->lock,flags);
|
|
devc->opened = 0;
|
|
devc->audio_mode = 0;
|
|
devc->speed = 8000;
|
|
devc->audio_format=AFMT_U8;
|
|
devc->format_bits = 0;
|
|
spin_unlock_irqrestore(&devc->lock,flags);
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* Audio driver structure */
|
|
|
|
static struct audio_driver ad1816_audio_driver =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.open = ad1816_open,
|
|
.close = ad1816_close,
|
|
.output_block = ad1816_output_block,
|
|
.start_input = ad1816_start_input,
|
|
.prepare_for_input = ad1816_prepare_for_input,
|
|
.prepare_for_output = ad1816_prepare_for_output,
|
|
.halt_io = ad1816_halt,
|
|
.halt_input = ad1816_halt_input,
|
|
.halt_output = ad1816_halt_output,
|
|
.trigger = ad1816_trigger,
|
|
.set_speed = ad1816_set_speed,
|
|
.set_bits = ad1816_set_bits,
|
|
.set_channels = ad1816_set_channels,
|
|
};
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* Interrupt handler */
|
|
|
|
|
|
static irqreturn_t ad1816_interrupt (int irq, void *dev_id)
|
|
{
|
|
unsigned char status;
|
|
ad1816_info *devc = (ad1816_info *)dev_id;
|
|
|
|
if (irq < 0 || irq > 15) {
|
|
printk(KERN_WARNING "ad1816: Got bogus interrupt %d\n", irq);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
spin_lock(&devc->lock);
|
|
|
|
/* read interrupt register */
|
|
status = inb (devc->base+1);
|
|
/* Clear all interrupt */
|
|
outb (~status, devc->base+1);
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: Got interrupt subclass %d\n",status));
|
|
|
|
if (status == 0) {
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: interrupt: Got interrupt, but no source.\n"));
|
|
spin_unlock(&devc->lock);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (devc->opened && (devc->audio_mode & PCM_ENABLE_INPUT) && (status&64))
|
|
DMAbuf_inputintr (devc->dev_no);
|
|
|
|
if (devc->opened && (devc->audio_mode & PCM_ENABLE_OUTPUT) && (status & 128))
|
|
DMAbuf_outputintr (devc->dev_no, 1);
|
|
|
|
spin_unlock(&devc->lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* Mixer stuff */
|
|
|
|
struct mixer_def {
|
|
unsigned int regno: 7;
|
|
unsigned int polarity:1; /* 0=normal, 1=reversed */
|
|
unsigned int bitpos:4;
|
|
unsigned int nbits:4;
|
|
};
|
|
|
|
static char mix_cvt[101] = {
|
|
0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42,
|
|
43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,
|
|
65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79,
|
|
80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90,
|
|
91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99,
|
|
100
|
|
};
|
|
|
|
typedef struct mixer_def mixer_ent;
|
|
|
|
/*
|
|
* Most of the mixer entries work in backwards. Setting the polarity field
|
|
* makes them to work correctly.
|
|
*
|
|
* The channel numbering used by individual soundcards is not fixed. Some
|
|
* cards have assigned different meanings for the AUX1, AUX2 and LINE inputs.
|
|
* The current version doesn't try to compensate this.
|
|
*/
|
|
|
|
#define MIX_ENT(name, reg_l, pola_l, pos_l, len_l, reg_r, pola_r, pos_r, len_r) \
|
|
{{reg_l, pola_l, pos_l, len_l}, {reg_r, pola_r, pos_r, len_r}}
|
|
|
|
|
|
static mixer_ent mix_devices[SOUND_MIXER_NRDEVICES][2] = {
|
|
MIX_ENT(SOUND_MIXER_VOLUME, 14, 1, 8, 5, 14, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_BASS, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_TREBLE, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_SYNTH, 5, 1, 8, 6, 5, 1, 0, 6),
|
|
MIX_ENT(SOUND_MIXER_PCM, 4, 1, 8, 6, 4, 1, 0, 6),
|
|
MIX_ENT(SOUND_MIXER_SPEAKER, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_LINE, 18, 1, 8, 5, 18, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_MIC, 19, 1, 8, 5, 19, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_CD, 15, 1, 8, 5, 15, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_IMIX, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_ALTPCM, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_RECLEV, 20, 0, 8, 4, 20, 0, 0, 4),
|
|
MIX_ENT(SOUND_MIXER_IGAIN, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_OGAIN, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
MIX_ENT(SOUND_MIXER_LINE1, 17, 1, 8, 5, 17, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_LINE2, 16, 1, 8, 5, 16, 1, 0, 5),
|
|
MIX_ENT(SOUND_MIXER_LINE3, 39, 0, 9, 4, 39, 1, 0, 5)
|
|
};
|
|
|
|
|
|
static unsigned short default_mixer_levels[SOUND_MIXER_NRDEVICES] =
|
|
{
|
|
0x4343, /* Master Volume */
|
|
0x3232, /* Bass */
|
|
0x3232, /* Treble */
|
|
0x0000, /* FM */
|
|
0x4343, /* PCM */
|
|
0x0000, /* PC Speaker */
|
|
0x0000, /* Ext Line */
|
|
0x0000, /* Mic */
|
|
0x0000, /* CD */
|
|
0x0000, /* Recording monitor */
|
|
0x0000, /* SB PCM */
|
|
0x0000, /* Recording level */
|
|
0x0000, /* Input gain */
|
|
0x0000, /* Output gain */
|
|
0x0000, /* Line1 */
|
|
0x0000, /* Line2 */
|
|
0x0000 /* Line3 (usually line in)*/
|
|
};
|
|
|
|
#define LEFT_CHN 0
|
|
#define RIGHT_CHN 1
|
|
|
|
|
|
|
|
static int
|
|
ad1816_set_recmask (ad1816_info * devc, int mask)
|
|
{
|
|
unsigned long flags;
|
|
unsigned char recdev;
|
|
int i, n;
|
|
|
|
spin_lock_irqsave(&devc->lock, flags);
|
|
mask &= devc->supported_rec_devices;
|
|
|
|
n = 0;
|
|
/* Count selected device bits */
|
|
for (i = 0; i < 32; i++)
|
|
if (mask & (1 << i))
|
|
n++;
|
|
|
|
if (n == 0)
|
|
mask = SOUND_MASK_MIC;
|
|
else if (n != 1) { /* Too many devices selected */
|
|
/* Filter out active settings */
|
|
mask &= ~devc->recmask;
|
|
|
|
n = 0;
|
|
/* Count selected device bits */
|
|
for (i = 0; i < 32; i++)
|
|
if (mask & (1 << i))
|
|
n++;
|
|
|
|
if (n != 1)
|
|
mask = SOUND_MASK_MIC;
|
|
}
|
|
|
|
switch (mask) {
|
|
case SOUND_MASK_MIC:
|
|
recdev = 5;
|
|
break;
|
|
|
|
case SOUND_MASK_LINE:
|
|
recdev = 0;
|
|
break;
|
|
|
|
case SOUND_MASK_CD:
|
|
recdev = 2;
|
|
break;
|
|
|
|
case SOUND_MASK_LINE1:
|
|
recdev = 4;
|
|
break;
|
|
|
|
case SOUND_MASK_LINE2:
|
|
recdev = 3;
|
|
break;
|
|
|
|
case SOUND_MASK_VOLUME:
|
|
recdev = 1;
|
|
break;
|
|
|
|
default:
|
|
mask = SOUND_MASK_MIC;
|
|
recdev = 5;
|
|
}
|
|
|
|
recdev <<= 4;
|
|
ad_write (devc, 20,
|
|
(ad_read (devc, 20) & 0x8f8f) | recdev | (recdev<<8));
|
|
|
|
devc->recmask = mask;
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return mask;
|
|
}
|
|
|
|
static void
|
|
change_bits (int *regval, int dev, int chn, int newval)
|
|
{
|
|
unsigned char mask;
|
|
int shift;
|
|
|
|
/* Reverse polarity*/
|
|
|
|
if (mix_devices[dev][chn].polarity == 1)
|
|
newval = 100 - newval;
|
|
|
|
mask = (1 << mix_devices[dev][chn].nbits) - 1;
|
|
shift = mix_devices[dev][chn].bitpos;
|
|
/* Scale it */
|
|
newval = (int) ((newval * mask) + 50) / 100;
|
|
/* Clear bits */
|
|
*regval &= ~(mask << shift);
|
|
/* Set new value */
|
|
*regval |= (newval & mask) << shift;
|
|
}
|
|
|
|
static int
|
|
ad1816_mixer_get (ad1816_info * devc, int dev)
|
|
{
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_get called!\n"));
|
|
|
|
/* range check + supported mixer check */
|
|
if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES )
|
|
return (-(EINVAL));
|
|
if (!((1 << dev) & devc->supported_devices))
|
|
return -(EINVAL);
|
|
|
|
return devc->levels[dev];
|
|
}
|
|
|
|
static int
|
|
ad1816_mixer_set (ad1816_info * devc, int dev, int value)
|
|
{
|
|
int left = value & 0x000000ff;
|
|
int right = (value & 0x0000ff00) >> 8;
|
|
int retvol;
|
|
|
|
int regoffs;
|
|
int val;
|
|
int valmute;
|
|
unsigned long flags;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_set called!\n"));
|
|
|
|
if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES )
|
|
return -(EINVAL);
|
|
|
|
if (left > 100)
|
|
left = 100;
|
|
if (left < 0)
|
|
left = 0;
|
|
if (right > 100)
|
|
right = 100;
|
|
if (right < 0)
|
|
right = 0;
|
|
|
|
/* Mono control */
|
|
if (mix_devices[dev][RIGHT_CHN].nbits == 0)
|
|
right = left;
|
|
retvol = left | (right << 8);
|
|
|
|
/* Scale it */
|
|
|
|
left = mix_cvt[left];
|
|
right = mix_cvt[right];
|
|
|
|
/* reject all mixers that are not supported */
|
|
if (!(devc->supported_devices & (1 << dev)))
|
|
return -(EINVAL);
|
|
|
|
/* sanity check */
|
|
if (mix_devices[dev][LEFT_CHN].nbits == 0)
|
|
return -(EINVAL);
|
|
spin_lock_irqsave(&devc->lock, flags);
|
|
|
|
/* keep precise volume internal */
|
|
devc->levels[dev] = retvol;
|
|
|
|
/* Set the left channel */
|
|
regoffs = mix_devices[dev][LEFT_CHN].regno;
|
|
val = ad_read (devc, regoffs);
|
|
change_bits (&val, dev, LEFT_CHN, left);
|
|
|
|
valmute=val;
|
|
|
|
/* Mute bit masking on some registers */
|
|
if ( regoffs==5 || regoffs==14 || regoffs==15 ||
|
|
regoffs==16 || regoffs==17 || regoffs==18 ||
|
|
regoffs==19 || regoffs==39) {
|
|
if (left==0)
|
|
valmute |= 0x8000;
|
|
else
|
|
valmute &= ~0x8000;
|
|
}
|
|
ad_write (devc, regoffs, valmute); /* mute */
|
|
|
|
/*
|
|
* Set the right channel
|
|
*/
|
|
|
|
/* Was just a mono channel */
|
|
if (mix_devices[dev][RIGHT_CHN].nbits == 0) {
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return retvol;
|
|
}
|
|
|
|
regoffs = mix_devices[dev][RIGHT_CHN].regno;
|
|
val = ad_read (devc, regoffs);
|
|
change_bits (&val, dev, RIGHT_CHN, right);
|
|
|
|
valmute=val;
|
|
if ( regoffs==5 || regoffs==14 || regoffs==15 ||
|
|
regoffs==16 || regoffs==17 || regoffs==18 ||
|
|
regoffs==19 || regoffs==39) {
|
|
if (right==0)
|
|
valmute |= 0x80;
|
|
else
|
|
valmute &= ~0x80;
|
|
}
|
|
ad_write (devc, regoffs, valmute); /* mute */
|
|
spin_unlock_irqrestore(&devc->lock, flags);
|
|
return retvol;
|
|
}
|
|
|
|
#define MIXER_DEVICES ( SOUND_MASK_VOLUME | \
|
|
SOUND_MASK_SYNTH | \
|
|
SOUND_MASK_PCM | \
|
|
SOUND_MASK_LINE | \
|
|
SOUND_MASK_LINE1 | \
|
|
SOUND_MASK_LINE2 | \
|
|
SOUND_MASK_LINE3 | \
|
|
SOUND_MASK_MIC | \
|
|
SOUND_MASK_CD | \
|
|
SOUND_MASK_RECLEV \
|
|
)
|
|
#define REC_DEVICES ( SOUND_MASK_LINE2 |\
|
|
SOUND_MASK_LINE |\
|
|
SOUND_MASK_LINE1 |\
|
|
SOUND_MASK_MIC |\
|
|
SOUND_MASK_CD |\
|
|
SOUND_MASK_VOLUME \
|
|
)
|
|
|
|
static void
|
|
ad1816_mixer_reset (ad1816_info * devc)
|
|
{
|
|
int i;
|
|
|
|
devc->supported_devices = MIXER_DEVICES;
|
|
|
|
devc->supported_rec_devices = REC_DEVICES;
|
|
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
|
|
if (devc->supported_devices & (1 << i))
|
|
ad1816_mixer_set (devc, i, default_mixer_levels[i]);
|
|
ad1816_set_recmask (devc, SOUND_MASK_MIC);
|
|
}
|
|
|
|
static int
|
|
ad1816_mixer_ioctl (int dev, unsigned int cmd, void __user * arg)
|
|
{
|
|
ad1816_info *devc = mixer_devs[dev]->devc;
|
|
int val;
|
|
int __user *p = arg;
|
|
|
|
DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_ioctl called!\n"));
|
|
|
|
/* Mixer ioctl */
|
|
if (((cmd >> 8) & 0xff) == 'M') {
|
|
|
|
/* set ioctl */
|
|
if (_SIOC_DIR (cmd) & _SIOC_WRITE) {
|
|
switch (cmd & 0xff){
|
|
case SOUND_MIXER_RECSRC:
|
|
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
val=ad1816_set_recmask (devc, val);
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
default:
|
|
if (get_user(val, p))
|
|
return -EFAULT;
|
|
if ((val=ad1816_mixer_set (devc, cmd & 0xff, val))<0)
|
|
return val;
|
|
else
|
|
return put_user(val, p);
|
|
}
|
|
} else {
|
|
/* read ioctl */
|
|
switch (cmd & 0xff) {
|
|
|
|
case SOUND_MIXER_RECSRC:
|
|
val=devc->recmask;
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
case SOUND_MIXER_DEVMASK:
|
|
val=devc->supported_devices;
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
case SOUND_MIXER_STEREODEVS:
|
|
val=devc->supported_devices & ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
case SOUND_MIXER_RECMASK:
|
|
val=devc->supported_rec_devices;
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
case SOUND_MIXER_CAPS:
|
|
val=SOUND_CAP_EXCL_INPUT;
|
|
return put_user(val, p);
|
|
break;
|
|
|
|
default:
|
|
if ((val=ad1816_mixer_get (devc, cmd & 0xff))<0)
|
|
return val;
|
|
else
|
|
return put_user(val, p);
|
|
}
|
|
}
|
|
} else
|
|
/* not for mixer */
|
|
return -(EINVAL);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* Mixer structure */
|
|
|
|
static struct mixer_operations ad1816_mixer_operations = {
|
|
.owner = THIS_MODULE,
|
|
.id = "AD1816",
|
|
.name = "AD1816 Mixer",
|
|
.ioctl = ad1816_mixer_ioctl
|
|
};
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* stuff for card recognition, init and unloading PNP ...*/
|
|
|
|
|
|
/* check if AD1816 present at specified hw_config and register device with OS
|
|
* return 1 if initialization was successful, 0 otherwise
|
|
*/
|
|
static int __init ad1816_init_card (struct address_info *hw_config,
|
|
struct pnp_dev *pnp)
|
|
{
|
|
ad1816_info *devc = NULL;
|
|
int tmp;
|
|
int oss_devno = -1;
|
|
|
|
printk(KERN_INFO "ad1816: initializing card: io=0x%x, irq=%d, dma=%d, "
|
|
"dma2=%d, clockfreq=%d, options=%d isadmabug=%d "
|
|
"%s\n",
|
|
hw_config->io_base,
|
|
hw_config->irq,
|
|
hw_config->dma,
|
|
hw_config->dma2,
|
|
ad1816_clockfreq,
|
|
options,
|
|
isa_dma_bridge_buggy,
|
|
pnp?"(PNP)":"");
|
|
|
|
/* ad1816_info structure remaining ? */
|
|
if (nr_ad1816_devs >= MAX_AUDIO_DEV) {
|
|
printk(KERN_WARNING "ad1816: no more ad1816_info structures "
|
|
"left\n");
|
|
goto out;
|
|
}
|
|
|
|
devc = &dev_info[nr_ad1816_devs];
|
|
devc->base = hw_config->io_base;
|
|
devc->irq = hw_config->irq;
|
|
devc->dma_playback=hw_config->dma;
|
|
devc->dma_capture=hw_config->dma2;
|
|
devc->opened = 0;
|
|
devc->pnpdev = pnp;
|
|
spin_lock_init(&devc->lock);
|
|
|
|
if (!request_region(devc->base, 16, "AD1816 Sound")) {
|
|
printk(KERN_WARNING "ad1816: I/O port 0x%03x not free\n",
|
|
devc->base);
|
|
goto out;
|
|
}
|
|
|
|
printk(KERN_INFO "ad1816: Examining AD1816 at address 0x%03x.\n",
|
|
devc->base);
|
|
|
|
|
|
/* tests for ad1816 */
|
|
/* base+0: bit 1 must be set but not 255 */
|
|
tmp=inb(devc->base);
|
|
if ( (tmp&0x80)==0 || tmp==255 ) {
|
|
printk (KERN_INFO "ad1816: Chip is not an AD1816 or chip "
|
|
"is not active (Test 0)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* writes to ireg 8 are copied to ireg 9 */
|
|
ad_write(devc,8,12345);
|
|
if (ad_read(devc,9)!=12345) {
|
|
printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 1)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* writes to ireg 8 are copied to ireg 9 */
|
|
ad_write(devc,8,54321);
|
|
if (ad_read(devc,9)!=54321) {
|
|
printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 2)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* writes to ireg 10 are copied to ireg 11 */
|
|
ad_write(devc,10,54321);
|
|
if (ad_read(devc,11)!=54321) {
|
|
printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 3)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* writes to ireg 10 are copied to ireg 11 */
|
|
ad_write(devc,10,12345);
|
|
if (ad_read(devc,11)!=12345) {
|
|
printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 4)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* bit in base +1 cannot be set to 1 */
|
|
tmp=inb(devc->base+1);
|
|
outb(0xff,devc->base+1);
|
|
if (inb(devc->base+1)!=tmp) {
|
|
printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 5)\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
printk(KERN_INFO "ad1816: AD1816 (version %d) successfully detected!\n",
|
|
ad_read(devc,45));
|
|
|
|
/* disable all interrupts */
|
|
ad_write(devc,1,0);
|
|
|
|
/* Clear pending interrupts */
|
|
outb (0, devc->base+1);
|
|
|
|
/* allocate irq */
|
|
if (devc->irq < 0 || devc->irq > 15)
|
|
goto out_release_region;
|
|
if (request_irq(devc->irq, ad1816_interrupt,0,
|
|
"SoundPort", devc) < 0) {
|
|
printk(KERN_WARNING "ad1816: IRQ in use\n");
|
|
goto out_release_region;
|
|
}
|
|
|
|
/* DMA stuff */
|
|
if (sound_alloc_dma (devc->dma_playback, "Sound System")) {
|
|
printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n",
|
|
devc->dma_playback);
|
|
goto out_free_irq;
|
|
}
|
|
|
|
if ( devc->dma_capture >= 0 &&
|
|
devc->dma_capture != devc->dma_playback) {
|
|
if (sound_alloc_dma(devc->dma_capture,
|
|
"Sound System (capture)")) {
|
|
printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n",
|
|
devc->dma_capture);
|
|
goto out_free_dma;
|
|
}
|
|
devc->audio_mode=DMA_AUTOMODE|DMA_DUPLEX;
|
|
} else {
|
|
printk(KERN_WARNING "ad1816: Only one DMA channel "
|
|
"available/configured. No duplex operation possible\n");
|
|
devc->audio_mode=DMA_AUTOMODE;
|
|
}
|
|
|
|
conf_printf2 ("AD1816 audio driver",
|
|
devc->base, devc->irq, devc->dma_playback,
|
|
devc->dma_capture);
|
|
|
|
/* register device */
|
|
if ((oss_devno = sound_install_audiodrv (AUDIO_DRIVER_VERSION,
|
|
"AD1816 audio driver",
|
|
&ad1816_audio_driver,
|
|
sizeof (struct audio_driver),
|
|
devc->audio_mode,
|
|
ad_format_mask,
|
|
devc,
|
|
devc->dma_playback,
|
|
devc->dma_capture)) < 0) {
|
|
printk(KERN_WARNING "ad1816: Can't install sound driver\n");
|
|
goto out_free_dma_2;
|
|
}
|
|
|
|
|
|
ad_write(devc,32,0x80f0); /* sound system mode */
|
|
if (options&1) {
|
|
ad_write(devc,33,0); /* disable all audiosources for dsp */
|
|
} else {
|
|
ad_write(devc,33,0x03f8); /* enable all audiosources for dsp */
|
|
}
|
|
ad_write(devc,4,0x8080); /* default values for volumes (muted)*/
|
|
ad_write(devc,5,0x8080);
|
|
ad_write(devc,6,0x8080);
|
|
ad_write(devc,7,0x8080);
|
|
ad_write(devc,15,0x8888);
|
|
ad_write(devc,16,0x8888);
|
|
ad_write(devc,17,0x8888);
|
|
ad_write(devc,18,0x8888);
|
|
ad_write(devc,19,0xc888); /* +20db mic active */
|
|
ad_write(devc,14,0x0000); /* Master volume unmuted */
|
|
ad_write(devc,39,0x009f); /* 3D effect on 0% phone out muted */
|
|
ad_write(devc,44,0x0080); /* everything on power, 3d enabled for d/a */
|
|
outb(0x10,devc->base+8); /* set dma mode */
|
|
outb(0x10,devc->base+9);
|
|
|
|
/* enable capture + playback interrupt */
|
|
ad_write(devc,1,0xc000);
|
|
|
|
/* set mixer defaults */
|
|
ad1816_mixer_reset (devc);
|
|
|
|
/* register mixer */
|
|
if ((audio_devs[oss_devno]->mixer_dev=sound_install_mixer(
|
|
MIXER_DRIVER_VERSION,
|
|
"AD1816 audio driver",
|
|
&ad1816_mixer_operations,
|
|
sizeof (struct mixer_operations),
|
|
devc)) < 0) {
|
|
printk(KERN_WARNING "Can't install mixer\n");
|
|
}
|
|
/* make ad1816_info active */
|
|
nr_ad1816_devs++;
|
|
printk(KERN_INFO "ad1816: card successfully installed!\n");
|
|
return 1;
|
|
/* error handling */
|
|
out_free_dma_2:
|
|
if (devc->dma_capture >= 0 && devc->dma_capture != devc->dma_playback)
|
|
sound_free_dma(devc->dma_capture);
|
|
out_free_dma:
|
|
sound_free_dma(devc->dma_playback);
|
|
out_free_irq:
|
|
free_irq(devc->irq, devc);
|
|
out_release_region:
|
|
release_region(devc->base, 16);
|
|
out:
|
|
return 0;
|
|
}
|
|
|
|
static void __exit unload_card(ad1816_info *devc)
|
|
{
|
|
int mixer, dev = 0;
|
|
|
|
if (devc != NULL) {
|
|
printk("ad1816: Unloading card at address 0x%03x\n",devc->base);
|
|
|
|
dev = devc->dev_no;
|
|
mixer = audio_devs[dev]->mixer_dev;
|
|
|
|
/* unreg mixer*/
|
|
if(mixer>=0) {
|
|
sound_unload_mixerdev(mixer);
|
|
}
|
|
/* unreg audiodev */
|
|
sound_unload_audiodev(dev);
|
|
|
|
/* free dma channels */
|
|
if (devc->dma_capture>=0 &&
|
|
devc->dma_capture != devc->dma_playback) {
|
|
sound_free_dma(devc->dma_capture);
|
|
}
|
|
sound_free_dma (devc->dma_playback);
|
|
/* free irq */
|
|
free_irq(devc->irq, devc);
|
|
/* free io */
|
|
release_region (devc->base, 16);
|
|
#ifdef __ISAPNP__
|
|
if (devc->pnpdev) {
|
|
pnp_disable_dev(devc->pnpdev);
|
|
pnp_device_detach(devc->pnpdev);
|
|
}
|
|
#endif
|
|
|
|
} else
|
|
printk(KERN_WARNING "ad1816: no device/card specified\n");
|
|
}
|
|
|
|
static int __initdata io = -1;
|
|
static int __initdata irq = -1;
|
|
static int __initdata dma = -1;
|
|
static int __initdata dma2 = -1;
|
|
|
|
#ifdef __ISAPNP__
|
|
/* use isapnp for configuration */
|
|
static int isapnp = 1;
|
|
static int isapnpjump;
|
|
module_param(isapnp, bool, 0);
|
|
module_param(isapnpjump, int, 0);
|
|
#endif
|
|
|
|
module_param(io, int, 0);
|
|
module_param(irq, int, 0);
|
|
module_param(dma, int, 0);
|
|
module_param(dma2, int, 0);
|
|
module_param(ad1816_clockfreq, int, 0);
|
|
module_param(options, int, 0);
|
|
|
|
#ifdef __ISAPNP__
|
|
static struct {
|
|
unsigned short card_vendor, card_device;
|
|
unsigned short vendor;
|
|
unsigned short function;
|
|
struct ad1816_data *data;
|
|
} isapnp_ad1816_list[] __initdata = {
|
|
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
|
|
ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7150),
|
|
NULL },
|
|
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
|
|
ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7180),
|
|
NULL },
|
|
{0}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(isapnp, isapnp_ad1816_list);
|
|
|
|
|
|
static void __init ad1816_config_pnp_card(struct pnp_card *card,
|
|
unsigned short vendor,
|
|
unsigned short function)
|
|
{
|
|
struct address_info cfg;
|
|
struct pnp_dev *card_dev = pnp_find_dev(card, vendor, function, NULL);
|
|
if (!card_dev) return;
|
|
if (pnp_device_attach(card_dev) < 0) {
|
|
printk(KERN_WARNING "ad1816: Failed to attach PnP device\n");
|
|
return;
|
|
}
|
|
if (pnp_activate_dev(card_dev) < 0) {
|
|
printk(KERN_WARNING "ad1816: Failed to activate PnP device\n");
|
|
pnp_device_detach(card_dev);
|
|
return;
|
|
}
|
|
cfg.io_base = pnp_port_start(card_dev, 2);
|
|
cfg.irq = pnp_irq(card_dev, 0);
|
|
cfg.dma = pnp_irq(card_dev, 0);
|
|
cfg.dma2 = pnp_irq(card_dev, 1);
|
|
if (!ad1816_init_card(&cfg, card_dev)) {
|
|
pnp_disable_dev(card_dev);
|
|
pnp_device_detach(card_dev);
|
|
}
|
|
}
|
|
|
|
static void __init ad1816_config_pnp_cards(void)
|
|
{
|
|
int nr_pnp_cfg;
|
|
int i;
|
|
|
|
/* Count entries in isapnp_ad1816_list */
|
|
for (nr_pnp_cfg = 0; isapnp_ad1816_list[nr_pnp_cfg].card_vendor != 0;
|
|
nr_pnp_cfg++);
|
|
/* Check and adjust isapnpjump */
|
|
if( isapnpjump < 0 || isapnpjump >= nr_pnp_cfg) {
|
|
printk(KERN_WARNING
|
|
"ad1816: Valid range for isapnpjump is 0-%d. "
|
|
"Adjusted to 0.\n", nr_pnp_cfg-1);
|
|
isapnpjump = 0;
|
|
}
|
|
for (i = isapnpjump; isapnp_ad1816_list[i].card_vendor != 0; i++) {
|
|
struct pnp_card *card = NULL;
|
|
/* iterate over all pnp cards */
|
|
while ((card = pnp_find_card(isapnp_ad1816_list[i].card_vendor,
|
|
isapnp_ad1816_list[i].card_device, card)))
|
|
ad1816_config_pnp_card(card,
|
|
isapnp_ad1816_list[i].vendor,
|
|
isapnp_ad1816_list[i].function);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* module initialization */
|
|
static int __init init_ad1816(void)
|
|
{
|
|
printk(KERN_INFO "ad1816: AD1816 sounddriver "
|
|
"Copyright (C) 1998-2003 by Thorsten Knabe and "
|
|
"others\n");
|
|
#ifdef AD1816_CLOCK
|
|
/* set ad1816_clockfreq if set during compilation */
|
|
ad1816_clockfreq=AD1816_CLOCK;
|
|
#endif
|
|
if (ad1816_clockfreq<5000 || ad1816_clockfreq>100000) {
|
|
ad1816_clockfreq=33000;
|
|
}
|
|
|
|
#ifdef __ISAPNP__
|
|
/* configure PnP cards */
|
|
if(isapnp) ad1816_config_pnp_cards();
|
|
#endif
|
|
/* configure card by module params */
|
|
if (io != -1 && irq != -1 && dma != -1) {
|
|
struct address_info cfg;
|
|
cfg.io_base = io;
|
|
cfg.irq = irq;
|
|
cfg.dma = dma;
|
|
cfg.dma2 = dma2;
|
|
ad1816_init_card(&cfg, NULL);
|
|
}
|
|
if (nr_ad1816_devs <= 0)
|
|
return -ENODEV;
|
|
return 0;
|
|
}
|
|
|
|
/* module cleanup */
|
|
static void __exit cleanup_ad1816 (void)
|
|
{
|
|
int i;
|
|
ad1816_info *devc = NULL;
|
|
|
|
/* remove any soundcard */
|
|
for (i = 0; i < nr_ad1816_devs; i++) {
|
|
devc = &dev_info[i];
|
|
unload_card(devc);
|
|
}
|
|
nr_ad1816_devs=0;
|
|
printk(KERN_INFO "ad1816: driver unloaded!\n");
|
|
}
|
|
|
|
module_init(init_ad1816);
|
|
module_exit(cleanup_ad1816);
|
|
|
|
#ifndef MODULE
|
|
/* kernel command line parameter evaluation */
|
|
static int __init setup_ad1816(char *str)
|
|
{
|
|
/* io, irq, dma, dma2 */
|
|
int ints[5];
|
|
|
|
str = get_options(str, ARRAY_SIZE(ints), ints);
|
|
|
|
io = ints[1];
|
|
irq = ints[2];
|
|
dma = ints[3];
|
|
dma2 = ints[4];
|
|
return 1;
|
|
}
|
|
|
|
__setup("ad1816=", setup_ad1816);
|
|
#endif
|
|
MODULE_LICENSE("GPL");
|