|  | /* | 
|  | * | 
|  | * 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, struct pt_regs *dummy) | 
|  | { | 
|  | 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"); |