| /* |
| * |
| * 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"); |