/* * drivers/sound/waveartist.c * * The low level driver for the RWA010 Rockwell Wave Artist * codec chip used in the Corel Computer NetWinder. * * Cleaned up and integrated into 2.1 by Russell King (rmk@arm.linux.org.uk) * and Pat Beirne (patb@corel.ca) * * The first half of this file is WaveArtist generic. * The second half is NetWinder specific. */ /* * Copyright (C) by Corel Computer 1998 * * RWA010 specs received under NDA from Rockwell * * Copyright (C) by Hannu Savolainen 1993-1997 * * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) * Version 2 (June 1991). See the "COPYING" file distributed with this software * for more info. */ /* Debugging */ #define DEBUG_CMD 1 #define DEBUG_OUT 2 #define DEBUG_IN 4 #define DEBUG_INTR 8 #define DEBUG_MIXER 16 #define DEBUG_TRIGGER 32 #define debug_flg 0 // (DEBUG_CMD | DEBUG_MIXER) #include #include #include #include #include #include #include #include #include "soundmodule.h" #include "sound_config.h" #include "waveartist.h" #ifndef _ISA_DMA #define _ISA_DMA(x) (x) #endif #ifndef _ISA_IRQ #define _ISA_IRQ(x) (x) #endif #ifdef CONFIG_ARCH_NETWINDER #define SOUND_MASK_EXTRA_REC SOUND_MASK_PHONEIN #define SOUND_MASK_EXTRA_MIX SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT #else #define SOUND_MASK_EXTRA_REC #define SOUND_MASK_EXTRA_MIX #endif #define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_LINE |\ SOUND_MASK_MIC |\ SOUND_MASK_EXTRA_REC |\ SOUND_MASK_LINE1) #define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH |\ SOUND_MASK_PCM |\ SOUND_MASK_LINE |\ SOUND_MASK_MIC |\ SOUND_MASK_LINE1 |\ SOUND_MASK_RECLEV|\ SOUND_MASK_VOLUME|\ SOUND_MASK_IMIX |\ SOUND_MASK_EXTRA_MIX) static unsigned short levels[SOUND_MIXER_NRDEVICES] = { 0x5555, /* Master Volume */ 0x0000, /* Bass */ 0x0000, /* Treble */ 0x2323, /* Synth (FM) */ 0x4b4b, /* PCM */ 0x0000, /* PC Speaker */ 0x0000, /* Ext Line */ 0x0000, /* Mic */ 0x0000, /* CD */ 0x0000, /* Recording monitor */ 0x0000, /* SB PCM (ALT PCM) */ 0x0000, /* Recording level */ 0x0000, /* Input gain */ 0x0000, /* Output gain */ 0x0000, /* Line1 (Aux1) */ 0x0000, /* Line2 (Aux2) */ 0x0000, /* Line3 (Aux3) */ 0x0000, /* Digital1 */ 0x0000, /* Digital2 */ 0x0000, /* Digital3 */ 0x0000, /* Phone In */ 0x0000, /* Phone Out */ 0x0000, /* Video */ 0x0000, /* Radio */ 0x0000 /* Monitor */ }; typedef struct { struct address_info hw; /* hardware */ char *chip_name; int xfer_count; int audio_mode; int open_mode; int audio_flags; int record_dev; int playback_dev; int dev_no; /* Mixer parameters: common to all sound systems */ unsigned short *levels; /* a cache of the volume settings 0..100 */ int recmask; /* currently enabled recording device! */ int supported_devices; /* SUPPORTED_MIXER_DEVICES */ int rec_devices; /* POSSIBLE_RECORDING_DEVICES */ #ifdef CONFIG_ARCH_NETWINDER /* private to netwinder/waveartist */ signed int slider_vol; /* hardware slider volume */ unsigned int handset_detect :1; unsigned int telephone_detect:1; unsigned int no_autoselect :1;/* handset/telephone autoselects a path */ unsigned int spkr_mute_state :1;/* set by ioctl or autoselect */ unsigned int line_mute_state :1;/* set by ioctl or autoselect */ unsigned int use_slider :1;/* use slider setting for o/p vol */ #endif } wavnc_info; typedef struct wavnc_port_info { int open_mode; int speed; int channels; int audio_format; } wavnc_port_info; static int nr_waveartist_devs; static wavnc_info adev_info[MAX_AUDIO_DEV]; #ifdef CONFIG_ARCH_NETWINDER static struct timer_list vnc_timer; static int vnc_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg); static void vnc_slider_tick(unsigned long data); static void vnc_mixer_reset(wavnc_info* devc); static void vnc_configure_mixer(wavnc_info* devc); static int vnc_mixer_set(wavnc_info *devc, int whichDev, unsigned int level); static void vnc_mixer_update(wavnc_info *devc, int whichDev); #endif static inline void waveartist_set_ctlr(struct address_info *hw, unsigned char clear, unsigned char set) { unsigned int ctlr_port = hw->io_base + CTLR; clear = ~clear & inb(ctlr_port); outb(clear | set, ctlr_port); } /* * Toggle IRQ acknowledge line */ static inline void waveartist_iack(wavnc_info *devc) { unsigned int ctlr_port = devc->hw.io_base + CTLR; int old_ctlr; old_ctlr = inb(ctlr_port) & ~IRQ_ACK; outb(old_ctlr | IRQ_ACK, ctlr_port); outb(old_ctlr, ctlr_port); } static inline int waveartist_sleep(int timeout_ms) { unsigned int timeout = timeout_ms * 10 * HZ / 100; do { current->state = TASK_INTERRUPTIBLE; timeout = schedule_timeout(timeout); } while (timeout); return 0; } static int waveartist_reset(wavnc_info *devc) { struct address_info *hw = &devc->hw; unsigned int timeout, res = -1; waveartist_set_ctlr(hw, -1, RESET); waveartist_sleep(2); waveartist_set_ctlr(hw, RESET, 0); timeout = 500; do { mdelay(2); if (inb(hw->io_base + STATR) & CMD_RF) { res = inw(hw->io_base + CMDR); if (res == 0x55aa) break; } } while (timeout--); if (timeout == 0) { printk(KERN_WARNING "WaveArtist: reset timeout "); if (res != (unsigned int)-1) printk("(res=%04X)", res); printk("\n"); return 1; } return 0; } /* helper function to send and rcv words from WaveArtist * handles all the handshaking * can send/rcv multiple words */ static int waveartist_cmd(wavnc_info *devc, int nr_cmd, unsigned int *cmd, int nr_resp, unsigned int *resp) { unsigned int io_base = devc->hw.io_base; unsigned int timed_out = 0; unsigned int i; if (debug_flg & DEBUG_CMD) { printk("waveartist_cmd: cmd="); for (i = 0; i < nr_cmd; i++) printk("%04X ", cmd[i]); printk("\n"); } if (inb(io_base + STATR) & CMD_RF) { int old_data; /* flush the port */ old_data = inw(io_base + CMDR); if (debug_flg & DEBUG_CMD) printk("flushed %04X...", old_data); udelay(10); } for (i = 0; !timed_out && i < nr_cmd; i++) { int count; for (count = 5000; count; count--) if (inb(io_base + STATR) & CMD_WE) break; if (!count) timed_out = 1; else outw(cmd[i], io_base + CMDR); } for (i = 0; !timed_out && i < nr_resp; i++) { int count; for (count = 5000; count; count--) if (inb(io_base + STATR) & CMD_RF) break; if (!count) timed_out = 1; else resp[i] = inw(io_base + CMDR); } if (debug_flg & DEBUG_CMD) { if (!timed_out) { printk("waveartist_cmd: resp="); for (i = 0; i < nr_resp; i++) printk("%04X ", resp[i]); printk("\n"); } else printk("waveartist_cmd: timed out\n"); } return timed_out ? 1 : 0; } /* send one command */ static inline void waveartist_cmd1(wavnc_info *devc, unsigned int cmd) { waveartist_cmd(devc, 1, &cmd, 0, NULL); } /* send one command, get one word */ static inline unsigned int waveartist_cmd1_r(wavnc_info *devc, unsigned int cmd) { unsigned int ret; waveartist_cmd(devc, 1, &cmd, 1, &ret); return ret; } /* send a double command, get one word (throw it away) */ static inline int waveartist_cmd2(wavnc_info *devc, unsigned int cmd, unsigned int arg) { unsigned int vals[2]; vals[0] = cmd; vals[1] = arg; waveartist_cmd(devc, 2, vals, 1, vals); return 0; } /* send a triple command */ static inline int waveartist_cmd3(wavnc_info *devc, unsigned int cmd, unsigned int arg1, unsigned int arg2) { unsigned int vals[3]; vals[0] = cmd; vals[1] = arg1; vals[2] = arg2; return waveartist_cmd(devc, 3, vals, 0, NULL); } static int waveartist_getrev(wavnc_info *devc, char* rev) { unsigned int temp[2]; unsigned int cmd = 0; /* the get_rev command */ waveartist_cmd(devc, 1, &cmd, 2, temp); /* make a string */ rev[0] = temp[0] >> 8; rev[1] = temp[0] & 255; rev[2] = '\0'; return temp[0]; /* ignore the second word */ } inline void waveartist_mute(wavnc_info *devc, int mute) { } static void waveartist_halt_output(int dev); static void waveartist_halt_input(int dev); static void waveartist_halt(int dev); static void waveartist_trigger(int dev, int state); static int waveartist_open(int dev, int mode) { wavnc_info *devc; wavnc_port_info *portc; unsigned long flags; if (dev < 0 || dev >= num_audiodevs) return -ENXIO; devc = (wavnc_info *) audio_devs[dev]->devc; portc = (wavnc_port_info *) audio_devs[dev]->portc; save_flags(flags); cli(); if (portc->open_mode || (devc->open_mode & mode)) { restore_flags(flags); return -EBUSY; } devc->audio_mode = 0; devc->open_mode |= mode; portc->open_mode = mode; waveartist_trigger(dev, 0); if (mode & OPEN_READ) devc->record_dev = dev; if (mode & OPEN_WRITE) devc->playback_dev = dev; restore_flags(flags); /* * Mute output until the playback really starts. This * decreases clicking (hope so). */ waveartist_mute(devc, 1); return 0; } static void waveartist_close(int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; unsigned long flags; save_flags(flags); cli(); waveartist_halt(dev); devc->audio_mode = 0; devc->open_mode &= ~portc->open_mode; portc->open_mode = 0; waveartist_mute(devc, 1); restore_flags(flags); } static void waveartist_output_block(int dev, unsigned long buf, int __count, int intrflag) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; unsigned int count = __count; if (debug_flg & DEBUG_OUT) printk("waveartist: output block, buf=0x%lx, count=0x%x...\n", buf, count); /* * 16 bit data */ if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) count >>= 1; if (portc->channels > 1) count >>= 1; count -= 1; if (devc->audio_mode & PCM_ENABLE_OUTPUT && audio_devs[dev]->flags & DMA_AUTOMODE && intrflag && count == devc->xfer_count) { devc->audio_mode |= PCM_ENABLE_OUTPUT; return; /* * Auto DMA mode on. No need to react */ } save_flags(flags); cli(); /* * set sample count */ waveartist_cmd2(devc, WACMD_OUTPUTSIZE /* 0x0024 */, count); devc->xfer_count = count; devc->audio_mode |= PCM_ENABLE_OUTPUT; restore_flags(flags); } static void waveartist_start_input(int dev, unsigned long buf, int __count, int intrflag) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; unsigned int count = __count; if (debug_flg & DEBUG_IN) printk("waveartist: start input, buf=0x%lx, count=0x%x...\n", buf, count); if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */ count >>= 1; if (portc->channels > 1) count >>= 1; count -= 1; if (devc->audio_mode & PCM_ENABLE_INPUT && audio_devs[dev]->flags & DMA_AUTOMODE && intrflag && count == devc->xfer_count) { devc->audio_mode |= PCM_ENABLE_INPUT; return; /* * Auto DMA mode on. No need to react */ } save_flags(flags); cli(); /* * set sample count */ waveartist_cmd2(devc, WACMD_INPUTSIZE /* 0x0014 */, count); waveartist_mute(devc, 0); devc->xfer_count = count; devc->audio_mode |= PCM_ENABLE_INPUT; restore_flags(flags); } static int waveartist_ioctl(int dev, unsigned int cmd, caddr_t arg) { return -EINVAL; } static unsigned int waveartist_get_speed(wavnc_port_info *portc) { unsigned int speed; /* * program the speed, channels, bits */ if (portc->speed == 8000) speed = 0x2E71; else if (portc->speed == 11025) speed = 0x4000; else if (portc->speed == 22050) speed = 0x8000; else if (portc->speed == 44100) speed = 0x0; else { /* * non-standard - just calculate */ speed = portc->speed << 16; speed = (speed / 44100) & 65535; } return speed; } static unsigned int waveartist_get_bits(wavnc_port_info *portc) { unsigned int bits; if (portc->audio_format == AFMT_S16_LE) bits = 1; else if (portc->audio_format == AFMT_S8) bits = 0; else bits = 2; //default AFMT_U8 return bits; } static int waveartist_prepare_for_input(int dev, int bsize, int bcount) { unsigned long flags; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; unsigned int speed, bits; if (devc->audio_mode) return 0; speed = waveartist_get_speed(portc); bits = waveartist_get_bits(portc); save_flags(flags); cli(); if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits)) printk(KERN_WARNING "waveartist: error setting the record format to %d\n", portc->audio_format); if (waveartist_cmd2(devc, WACMD_INPUTCHANNELS, portc->channels)) printk(KERN_WARNING "waveartist: error setting record to %d channels\n", portc->channels); /* * write cmd SetSampleSpeedTimeConstant */ if (waveartist_cmd2(devc, WACMD_INPUTSPEED, speed)) printk(KERN_WARNING "waveartist: error setting the record speed " "to %dHz.\n", portc->speed); if (waveartist_cmd2(devc, WACMD_INPUTDMA, 1)) printk(KERN_WARNING "waveartist: error setting the record data path " "to 0x%X\n", 1); if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits)) printk(KERN_WARNING "waveartist: error setting the record format to %d\n", portc->audio_format); devc->xfer_count = 0; restore_flags(flags); waveartist_halt_input(dev); if (debug_flg & DEBUG_INTR) { printk("WA CTLR reg: 0x%02X.\n",inb(devc->hw.io_base + CTLR)); printk("WA STAT reg: 0x%02X.\n",inb(devc->hw.io_base + STATR)); printk("WA IRQS reg: 0x%02X.\n",inb(devc->hw.io_base + IRQSTAT)); } return 0; } static int waveartist_prepare_for_output(int dev, int bsize, int bcount) { unsigned long flags; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; unsigned int speed, bits; /* * program the speed, channels, bits */ speed = waveartist_get_speed(portc); bits = waveartist_get_bits(portc); save_flags(flags); cli(); if (waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed) && waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed)) printk(KERN_WARNING "waveartist: error setting the playback speed " "to %dHz.\n", portc->speed); if (waveartist_cmd2(devc, WACMD_OUTPUTCHANNELS, portc->channels)) printk(KERN_WARNING "waveartist: error setting the playback to" " %d channels\n", portc->channels); if (waveartist_cmd2(devc, WACMD_OUTPUTDMA, 0)) printk(KERN_WARNING "waveartist: error setting the playback data path " "to 0x%X\n", 0); if (waveartist_cmd2(devc, WACMD_OUTPUTFORMAT, bits)) printk(KERN_WARNING "waveartist: error setting the playback format to %d\n", portc->audio_format); devc->xfer_count = 0; restore_flags(flags); waveartist_halt_output(dev); if (debug_flg & DEBUG_INTR) { printk("WA CTLR reg: 0x%02X.\n",inb(devc->hw.io_base + CTLR)); printk("WA STAT reg: 0x%02X.\n",inb(devc->hw.io_base + STATR)); printk("WA IRQS reg: 0x%02X.\n",inb(devc->hw.io_base + IRQSTAT)); } return 0; } static void waveartist_halt(int dev) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; wavnc_info *devc; if (portc->open_mode & OPEN_WRITE) waveartist_halt_output(dev); if (portc->open_mode & OPEN_READ) waveartist_halt_input(dev); devc = (wavnc_info *) audio_devs[dev]->devc; devc->audio_mode = 0; } static void waveartist_halt_input(int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; unsigned int cmd; save_flags(flags); cli(); waveartist_mute(devc, 1); //RMK disable_dma(audio_devs[dev]->dmap_in->dma); /* * Stop capture */ cmd = WACMD_INPUTSTOP /* 0x17 */; waveartist_cmd(devc, 1, &cmd, 0, NULL); //RMK enable_dma(audio_devs[dev]->dmap_in->dma); devc->audio_mode &= ~PCM_ENABLE_INPUT; /* * Clear interrupt by toggling * the IRQ_ACK bit in CTRL */ if (inb(devc->hw.io_base + STATR) & IRQ_REQ) waveartist_iack(devc); // devc->audio_mode &= ~PCM_ENABLE_INPUT; restore_flags(flags); } static void waveartist_halt_output(int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; unsigned int cmd; save_flags(flags); cli(); waveartist_mute(devc, 1); //RMK disable_dma(audio_devs[dev]->dmap_out->dma); cmd = WACMD_OUTPUTSTOP /* 0x27 */; waveartist_cmd(devc, 1, &cmd, 0, NULL); //RMK enable_dma(audio_devs[dev]->dmap_out->dma); devc->audio_mode &= ~PCM_ENABLE_OUTPUT; /* * Clear interrupt by toggling * the IRQ_ACK bit in CTRL */ if (inb(devc->hw.io_base + STATR) & IRQ_REQ) waveartist_iack(devc); // devc->audio_mode &= ~PCM_ENABLE_OUTPUT; restore_flags(flags); } static void waveartist_trigger(int dev, int state) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; unsigned long flags; unsigned int cmd; if (debug_flg & DEBUG_TRIGGER) { printk("wavnc: audio trigger "); if (state & PCM_ENABLE_INPUT) printk("in "); if (state & PCM_ENABLE_OUTPUT) printk("out"); printk("\n"); } save_flags(flags); cli(); state &= devc->audio_mode; cmd = WACMD_INPUTSTART /* 0x15 */; if (portc->open_mode & OPEN_READ && state & PCM_ENABLE_INPUT) /* * enable ADC Data Transfer to PC */ waveartist_cmd(devc,1,&cmd,0,NULL); cmd = WACMD_OUTPUTSTART /* 0x25 */; if (portc->open_mode & OPEN_WRITE && state & PCM_ENABLE_OUTPUT) /* * enable DAC data transfer from PC */ waveartist_cmd(devc, 1, &cmd, 0, NULL); waveartist_mute(devc, 0); restore_flags(flags); } static int waveartist_set_speed(int dev, int arg) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; if (arg <= 0) return portc->speed; if (arg < 5000) arg = 5000; if (arg > 44100) arg = 44100; portc->speed = arg; return portc->speed; } static short waveartist_set_channels(int dev, short arg) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; if (arg != 1 && arg != 2) return portc->channels; portc->channels = arg; return arg; } static unsigned int waveartist_set_bits(int dev, unsigned int arg) { wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; if (arg == 0) return portc->audio_format; if ((arg != AFMT_U8) && (arg != AFMT_S16_LE) && (arg != AFMT_S8)) arg = AFMT_U8; portc->audio_format = arg; return arg; } static struct audio_driver waveartist_audio_driver = { waveartist_open, waveartist_close, waveartist_output_block, waveartist_start_input, waveartist_ioctl, waveartist_prepare_for_input, waveartist_prepare_for_output, waveartist_halt, NULL, NULL, waveartist_halt_input, waveartist_halt_output, waveartist_trigger, waveartist_set_speed, waveartist_set_bits, waveartist_set_channels }; static void waveartist_intr(int irq, void *dev_id, struct pt_regs *regs) { wavnc_info *devc = (wavnc_info *)dev_id; int irqstatus, status; irqstatus = inb(devc->hw.io_base + IRQSTAT); status = inb(devc->hw.io_base + STATR); if (debug_flg & DEBUG_INTR) printk("waveartist_intr: stat=%02x, irqstat=%02x\n", status, irqstatus); if (status & IRQ_REQ) /* Clear interrupt */ waveartist_iack(devc); else printk(KERN_WARNING "waveartist: unexpected interrupt\n"); #ifdef CONFIG_AUDIO if (irqstatus & 0x01) { int temp = 1; /* PCM buffer done */ if ((status & DMA0) && (devc->audio_mode & PCM_ENABLE_OUTPUT)) { DMAbuf_outputintr(devc->playback_dev, 1); temp = 0; } if ((status & DMA1) && (devc->audio_mode & PCM_ENABLE_INPUT)) { DMAbuf_inputintr(devc->record_dev); temp = 0; } if (temp) //default: printk(KERN_WARNING "WaveArtist: Unknown interrupt\n"); } #endif if (irqstatus & 0x2) // We do not use SB mode natively... printk(KERN_WARNING "WaveArtist: Unexpected SB interrupt...\n"); } /* ------------------------------------------------------------------------- * Mixer stuff * * WaveArtist mixer and volume levels can be accessed through * these 16 bit ports: * nn30 read registers nn * where nn = 0..9 for mixer settings * a..13 for various channel volumes * mm31 write the volume setting, in pairs * where mm = (nn-0xA)/2 * rr32 write the mixer settings, in pairs * where rr = nn/2 * xx33 reset all settings to default * 0y34 select mono source left:y=0, right:y=1 register bits nn 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 00 | 0 | 0 | 0 | 1 | 1 |left line mixer gain |left aux1 mixer gain |leftmute| +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 01 | 0 | 0 | 1 | 0 | 1 |left aux2 mixer gain |right 2 left mic gain |monomute| +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 02 | 0 | 0 | 1 | 1 | 1 |left mic mixer gain |left mic |left mixer gain |ditherds| +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 03 | 0 | 1 | 0 | 0 | 1 |left mixer input select |lrfg |left ADC gain | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 04 | 0 | 1 | 0 | 1 | 1 |right line mixer gain |right aux1 mixer gain |rghtmute| +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 05 | 0 | 1 | 1 | 0 | 1 |right aux2 mixer gain |left 2 right mic gain |test | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 06 | 0 | 1 | 1 | 1 | 1 |richt mic mixer gain |right mic |right mixer gain |r bypass| | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 07 | 1 | 0 | 0 | 0 | 1 |right mixer select |rrfg |r ADC gain | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 08 | 1 | 0 | 0 | 1 | 1 |mono mixer gain |right ADC mux sel|left ADC mux select | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 09 | 1 | 0 | 1 | 0 | 1 |loopb|left linout|loopb|ADCch|TxFch|OffCD|test |loopb|loopb|oversamp| +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0a | 0 | left PCM channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0b | 0 | right PCM channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0c | 0 | left FM channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0d | 0 | right FM channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0e | 0 | left wavetable channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 0f | 0 | right wavetable channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 10 | 0 | left PCM expansion channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 11 | 0 | right PCM expansion channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 12 | 0 | left FM expansion channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ 13 | 0 | right FM expansion channel volume | +---+---+---+---+---+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--------+ Other notes: Incoming audio goes two places: 1) Each audio source is routed to a 1-of-5 selector (regs 9 bits 0..5), amplified (regs 4/8 bits 0..3) and sent to the ADC. Choose 1 of -mic -line in -aux1 -aux2 -mixer 2) Each audio source is amplified separately (various), and mixed together. Each audio source can be selectively muted (regs 4/8 bits 4..10). The result of this mix is sent to -the 1-of-5 selector mentioned above -mono out -left/right out This mixed audio can be amplified (regs 3/7 bits 1..3) Note that the mic audio path has a separate preamp stage (regs 3/7 bits 4..5) as well as a mixer gain stage. The mic paths can be crossed on the way into the mixer. Outgoing audio from the DAC first goes through a tx filter. It is then presented to 3 destinations -left/right out -mono out -the mixer Finally, the three output pins get their signal from various sources. The mono output is the sum of right + left mixer output The left output can select from (regs 1 bit 0, reg 10 bits 8..9) -left mixer output -right mixer output -right DAC or left DAC And the right output can select from (reg 5 bit 0, reg 7 bit 0) -right mixer output -right DAC output In the table above, the mute bits are, in fact, inverted: 0=mute Most of the mixer gains are in dB; i.e. logarithmic. In all cases, setting a mixer gain to 0 has the same effect as a mute. For the ADC mux's (1-of-5), 0=quiet, 1=mixer, 2=line, 3=aux2, 4=aux1, 5=mic For the mixer input select, the 7 bits are a mask: 40=DAC, 20=mono, 10=cross channel mic, 8=mic, 4=aux1, 2=aux2, 1=line Comments: The mixer is a resource available to the ADC path and to the DAC path. One could record audio from the mixer, and run the DAC path directly to the output. On the other hand, one could record audio directly from one of the 4 sources, and user the mixer to create the output audio. Plenty of flexibility. For the Linux sound system, we will use the latter: We will record raw audio (mic, line, aux1 or aux2; only 1 active). We will play back through the mixer. Mapping to the Linux sound system: SOUND_MIXER_RECLEV is the master ADC gain SOUND_MIXER_MIC is the microphone preamp gain SOUND_MIXER_LINE is the line in level SOUND_MIXER_LINE1 is the aux1 level the above three are in series with SOUND_MIXER_RECLEV SOUND_MIXER_VOLUME is the left/right mixer output gain, to line out the mono output is simply the sum of left + right and does not have a separate magnitute SOUND_MIXER_IMIX is for record monitor; we need to examine which is the current recording source, and set the mixer input level for that source (mic, line, line1) SOUND_MIXER_SYNTH/PCM are gains applied in the digital section, before the DAC; this gain is in series with the SOUND_MIXER_LINE or SOUND_MIXER_VOLUME This list is enhanced/modified in the NetWinder section below */ /* generic send bits to wa mixer; only suitable for the symmtric * registers 1..8; not for registers 9 & 10 */ /*Parameters: left volume, right volume, * bitmask in the register, * left reg, right reg */ static int waveartist_set_mixer_levels(wavnc_info* devc, int left_vol, int right_vol, /* new volumes 0..100 */ int bits, /* bitmask */ int mixer1, int mixer2) /* mixer registers */ /* Mixer registers to touch */ { int left,left1,left2; int right,right1,right2; /* form the scaling factor, usually 3, 7, 15 or 31 */ /* the shift factor is the number of bits to shift left*/ int scale, shift; for (scale=bits, shift=0; (scale & 1) == 0; scale >>= 1, shift++); left = (left_vol * scale) / 100; right = (right_vol * scale) / 100; if (shift) left <<= shift; if (shift) right <<= shift; left1 = (mixer1-1)<<8; // get ready for command nn30H right1= (mixer2-1)<<8; /* first read current values */ left2 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL + left1); right2 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL + right1); if (debug_flg & DEBUG_MIXER) printk("wa_mixer: Current left=%04X, right=%04X.\n",left2,right2); /* now that both current values are in - update bits... */ left2 &= ~bits; left2 |= left; right2 &= ~bits; right2 |= right; /* and finally - write the reg pair back.... */ waveartist_cmd3(devc, WACMD_SET_MIXER, left2, right2); return (left_vol | (right_vol << 8)); } /* * set the record monitor level */ static void waveartist_set_imix_level(wavnc_info * devc, unsigned int new_level) { unsigned int left,right; left = new_level >> 8; right = new_level & 0xff; /* set the monitor level for the active recording channel */ switch(devc->recmask) { case SOUND_MASK_MIC: waveartist_set_mixer_levels(devc, left, right, 0x7E0,3,7); break; case SOUND_MASK_LINE: waveartist_set_mixer_levels(devc, left, right, 0x7E0,1,5); break; case SOUND_MASK_LINE1: waveartist_set_mixer_levels(devc, left, right, 0x3e,1,5); break; default: /* oops */ printk(KERN_WARNING "%s: bad record monitor mode %x\n", devc->chip_name,devc->recmask); break; } } /* * send the current stored levels to the various mixer ports * note: does NOT trigger any selection changes, only sets * levels */ static void waveartist_mixer_update(wavnc_info *devc, int whichDev) { unsigned int mask=0, reg_l=0, reg_r=0; unsigned int lev_left, lev_right; int bSetRecLevel = 0; lev_left = devc->levels[whichDev] & 0xff; lev_right = devc->levels[whichDev] >> 8; #define SCALE(lev,max) ((lev) * (max) / 100) switch(whichDev) { case SOUND_MIXER_VOLUME: /* line out */ mask = 0x000e; reg_l = 3; reg_r = 7; break; case SOUND_MIXER_LINE: /* line in; only sets if line-in is selected */ if ((devc->recmask & SOUND_MASK_LINE)==0) return; /* adjust the master rec level */ bSetRecLevel = 1; break; case SOUND_MIXER_MIC: /* microphone; only sets if mic is selected */ if ((devc->recmask & SOUND_MASK_MIC)==0) return; /* we have to set the mic gain AND the recording level */ waveartist_set_mixer_levels(devc, lev_left, lev_right, 0x0030, 2, 6); bSetRecLevel = 1; break; case SOUND_MIXER_LINE1: if ((devc->recmask & SOUND_MASK_LINE1)==0) return; bSetRecLevel = 1; break; case SOUND_MIXER_RECLEV: /* adjust in any case */ bSetRecLevel = 1; break; case SOUND_MIXER_PCM: waveartist_cmd3(devc, WACMD_SET_LEVEL, SCALE(lev_left, 32767), SCALE(lev_right, 32767)); return; case SOUND_MIXER_SYNTH: waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL, SCALE(lev_left, 32767), SCALE(lev_right, 32767)); return; case SOUND_MIXER_IMIX: /* used as a recording monitor */ /* only active during recording */ if (devc->recmask==0) return; waveartist_set_imix_level(devc,devc->levels[SOUND_MIXER_IMIX]); return; default: return; } if (bSetRecLevel) { mask = 0x000f; reg_l = 4; reg_r = 8; } /* send the scaled values to the hardware */ waveartist_set_mixer_levels(devc, lev_left, lev_right, mask, reg_l, reg_r); } static void waveartist_select_input(wavnc_info *devc, unsigned int input) { unsigned int reg9, reg10; unsigned select_mask; /* * Get reg 9 */ reg9 = waveartist_cmd1_r(devc, 0x0830); /* * Get reg 10, only so that we can write it back. */ reg10 = waveartist_cmd1_r(devc, 0x0930); if (debug_flg & DEBUG_MIXER) printk("RECSRC: old left: 0x%04X, old right: 0x%04X.\n", reg9 & 0x07, (reg9 >> 3) & 0x07); reg9 &= ~0x03F; //kill current left/right mux input select switch (input) { case SOUND_MASK_MIC: select_mask = 0x2d; break; case SOUND_MASK_LINE: select_mask = 0x12; break; case SOUND_MASK_LINE1: select_mask = 0x24; break; default: select_mask = 0; } /* send the selection back to the device */ reg9 |= select_mask; waveartist_cmd3(devc, WACMD_SET_MIXER, reg9, reg10); } /* * helper for the ioctl calls * * level is 0..100 for right and left channels */ static int waveartist_mixer_set(wavnc_info *devc, int whichDev, unsigned int level) { unsigned int lev_left = level & 0x007f; unsigned int lev_right = (level & 0x7f00) >> 8; int left, right, devmask, changed; left = level & 0x7f; right = (level & 0x7f00) >> 8; if (debug_flg & DEBUG_MIXER) printk("wa_mixer_set(dev=%d, level=%X)\n", whichDev, level); switch (whichDev) { /* Master volume (0-7) * We have 3 bits on the Left/Right Mixer Gain, * bits 3,2,1 on 3 and 7 */ case SOUND_MIXER_VOLUME: /* External line (0-31) * use LOUT/ROUT bits 10...6, reg 1 and 5 */ case SOUND_MIXER_LINE: /* Mono microphone (0-3) mute,0db,10db,20db */ case SOUND_MIXER_MIC: /* Recording level (0-7) */ case SOUND_MIXER_RECLEV: /* Mono External Aux1 (0-31) * use LINE1 bits 5...1, reg 1 and 5 */ case SOUND_MIXER_LINE1: /* WaveArtist PCM (0-32767) */ case SOUND_MIXER_PCM: /* Internal synthesizer (0-31) */ case SOUND_MIXER_SYNTH: devc->levels[whichDev] = lev_left | lev_right << 8; waveartist_mixer_update(devc, whichDev); break; /* Select recording input source */ case SOUND_MIXER_RECSRC: devmask = level & POSSIBLE_RECORDING_DEVICES; changed = devmask ^ devc->recmask; devc->recmask = devmask; waveartist_select_input(devc, level); /* * if record monitoring is on, make sure the bit is set */ if (devc->levels[SOUND_MIXER_IMIX]) waveartist_mixer_update(devc, SOUND_MIXER_IMIX); /* * do not save in "levels", return current setting */ return devc->recmask; default: return -EINVAL; } return devc->levels[whichDev]; } static void waveartist_mixer_reset(wavnc_info *devc) { int i; unsigned int cmd; if (debug_flg & DEBUG_MIXER) printk("%s: mixer_reset\n", devc->hw.name); /* * reset mixer cmd */ cmd = WACMD_RST_MIXER; waveartist_cmd(devc,1,&cmd,0,NULL); /* * set input for ADC to come from 'quiet' * turn on default modes */ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x9800, 0xa836); /* * set mixer input select to none, RX filter gains 0 db */ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x4c00, 0x8c00); /* * set bit 0 reg 2 to 1 - unmute MonoOut */ waveartist_cmd3(devc, WACMD_SET_MIXER, 0x2801, 0x6800); /* set default input device = internal mic * current recording device = none */ devc->recmask = 0; /* send out current levels */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) waveartist_mixer_update(devc, i); devc->supported_devices = SUPPORTED_MIXER_DEVICES; devc->rec_devices = POSSIBLE_RECORDING_DEVICES; #ifdef CONFIG_ARCH_NETWINDER /* * call the arch specific code */ vnc_mixer_reset(devc); #endif } static int waveartist_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg) { #ifdef CONFIG_ARCH_NETWINDER return vnc_mixer_ioctl(dev, cmd, arg); #else if (((cmd >> 8) & 0xff) == 'M') { if (_SIOC_DIR(cmd) & _SIOC_WRITE) { int val; if (get_user(val, (int *)arg)) return -EFAULT; return waveartist_mixer_set(devc, cmd & 0xff, val); } else { int ret; /* * Return parameters */ switch (cmd & 0xff) { case SOUND_MIXER_RECSRC: ret = devc->recmask; break; case SOUND_MIXER_DEVMASK: ret = devc->supported_devices; break; case SOUND_MIXER_STEREODEVS: ret = devc->supported_devices & ~(SOUND_MASK_SPEAKER|SOUND_MASK_IMIX); break; case SOUND_MIXER_RECMASK: ret = devc->rec_devices; break; case SOUND_MIXER_CAPS: ret = SOUND_CAP_EXCL_INPUT; break; default: if ((cmd & 0xff) < SOUND_MIXER_NRDEVICES) ret = devc->levels[cmd & 0xff]; else return -EINVAL; } return put_user(ret, (int *)arg) ? -EINVAL : 0; } } return -ENOIOCTLCMD; #endif } static struct mixer_operations waveartist_mixer_operations = { "WaveArtist", "WaveArtist NetWinder", waveartist_mixer_ioctl }; static int waveartist_init(wavnc_info *devc) { wavnc_port_info *portc; char rev[3], dev_name[64]; int my_dev; waveartist_reset(devc); sprintf(dev_name, "%s (%s", devc->hw.name, devc->chip_name); if (waveartist_getrev(devc, rev)) { strcat(dev_name, " rev. "); strcat(dev_name, rev); } strcat(dev_name, ")"); conf_printf2(dev_name, devc->hw.io_base, devc->hw.irq, devc->hw.dma, devc->hw.dma2); portc = (wavnc_port_info *)kmalloc(sizeof(wavnc_port_info), GFP_KERNEL); if (portc == NULL) goto nomem; memset(portc, 0, sizeof(wavnc_port_info)); my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, dev_name, &waveartist_audio_driver, sizeof(struct audio_driver), devc->audio_flags, AFMT_U8 | AFMT_S16_LE | AFMT_S8, devc, devc->hw.dma, devc->hw.dma2); if (my_dev < 0) goto free; audio_devs[my_dev]->portc = portc; waveartist_mixer_reset(devc); /* * clear any pending interrupt */ waveartist_iack(devc); if (request_irq(devc->hw.irq, waveartist_intr, 0, devc->hw.name, devc) < 0) { printk(KERN_WARNING "%s: IRQ %d in use\n", devc->hw.name, devc->hw.irq); goto uninstall; } if (sound_alloc_dma(devc->hw.dma, devc->hw.name)) { printk(KERN_WARNING "%s: Can't allocate DMA%d\n", devc->hw.name, devc->hw.dma); goto uninstall_irq; } if (devc->hw.dma != devc->hw.dma2 && devc->hw.dma2 != NO_DMA) if (sound_alloc_dma(devc->hw.dma2, devc->hw.name)) { printk(KERN_WARNING "%s: can't allocate DMA%d\n", devc->hw.name, devc->hw.dma2); goto uninstall_dma; } waveartist_set_ctlr(&devc->hw, 0, DMA1_IE | DMA0_IE); audio_devs[my_dev]->mixer_dev = sound_install_mixer(MIXER_DRIVER_VERSION, dev_name, &waveartist_mixer_operations, sizeof(struct mixer_operations), devc); return my_dev; uninstall_dma: sound_free_dma(devc->hw.dma); uninstall_irq: free_irq(devc->hw.irq, devc); uninstall: sound_unload_audiodev(my_dev); free: kfree(portc); nomem: return -1; } int probe_waveartist(struct address_info *hw_config) { wavnc_info *devc = &adev_info[nr_waveartist_devs]; if (nr_waveartist_devs >= MAX_AUDIO_DEV) { printk(KERN_WARNING "waveartist: too many audio devices\n"); return 0; } if (check_region(hw_config->io_base, 15)) { printk(KERN_WARNING "WaveArtist: I/O port conflict\n"); return 0; } if (hw_config->irq > _ISA_IRQ(15) || hw_config->irq < _ISA_IRQ(0)) { printk(KERN_WARNING "WaveArtist: Bad IRQ %d\n", hw_config->irq); return 0; } if (hw_config->dma != _ISA_DMA(3)) { printk(KERN_WARNING "WaveArtist: Bad DMA %d\n", hw_config->dma); return 0; } hw_config->name = "WaveArtist"; devc->hw = *hw_config; devc->open_mode = 0; devc->chip_name = "RWA-010"; return 1; } void attach_waveartist(struct address_info *hw) { wavnc_info *devc = &adev_info[nr_waveartist_devs]; /* * NOTE! If irq < 0, there is another driver which has allocated the * IRQ so that this driver doesn't need to allocate/deallocate it. * The actually used IRQ is ABS(irq). */ devc->hw = *hw; devc->hw.irq = (hw->irq > 0) ? hw->irq : 0; devc->open_mode = 0; devc->playback_dev = 0; devc->record_dev = 0; devc->audio_flags = DMA_AUTOMODE; devc->levels = levels; if (hw->dma != hw->dma2 && hw->dma2 != NO_DMA) devc->audio_flags |= DMA_DUPLEX; request_region(hw->io_base, 15, devc->hw.name); devc->dev_no = waveartist_init(devc); if (devc->dev_no < 0) release_region(hw->io_base, 15); #ifdef CONFIG_ARCH_NETWINDER else { init_timer(&vnc_timer); vnc_timer.function = vnc_slider_tick; vnc_timer.expires = jiffies; vnc_timer.data = nr_waveartist_devs; add_timer(&vnc_timer); nr_waveartist_devs += 1; /* set up the default state */ vnc_configure_mixer(devc); } #endif } void unload_waveartist(struct address_info *hw) { wavnc_info *devc = NULL; int i; for (i = 0; i < nr_waveartist_devs; i++) if (hw->io_base == adev_info[i].hw.io_base) { devc = adev_info + i; break; } if (devc != NULL) { int mixer; release_region(devc->hw.io_base, 15); waveartist_set_ctlr(&devc->hw, DMA1_IE|DMA0_IE, 0); if (devc->hw.irq >= 0) free_irq(devc->hw.irq, devc); sound_free_dma(devc->hw.dma); if (devc->hw.dma != devc->hw.dma2 && devc->hw.dma2 != NO_DMA) sound_free_dma(devc->hw.dma2); #ifdef CONFIG_ARCH_NETWINDER del_timer(&vnc_timer); #endif mixer = audio_devs[devc->dev_no]->mixer_dev; if (mixer >= 0) sound_unload_mixerdev(mixer); if (devc->dev_no >= 0) sound_unload_audiodev(devc->dev_no); nr_waveartist_devs -= 1; for (; i < nr_waveartist_devs; i++) adev_info[i] = adev_info[i + 1]; } else printk(KERN_WARNING "waveartist: can't find device to unload\n"); } /* * Corel Netwinder specifics... */ /* the PRIVATE3 ioctl has sub-commands...*/ #define MIXER_PRIVATE3_RESET 0x53570000 /* SW00 */ #define MIXER_PRIVATE3_READ 0x53570001 /* SW01 */ #define MIXER_PRIVATE3_WRITE 0x53570002 /* SW02 */ #define VNC_TIMER_PERIOD (HZ/4) /* check slider 4 times/sec */ #define VNC_MUTE_INTERNAL_SPKR 0x01 /* sw mute on/off control bit */ #define VNC_MUTE_LINE_OUT 0x40 /* sw mute the line out port */ #define VNC_PHONE_DETECT 0x20 #define VNC_HANDSET_DETECT 0x10 /* the user can always set the spkr/line-out volumes to 0, so these mutes may seem redundant, but they allow the driver to keep persistent the volume settings */ #define VNC_DISABLE_AUTOSWITCH 0x80 /* handset audio controlled by sw */ //static inline int abs(int a, int b) {return (a>b) ? a-b : b-a;} /* On the NetWinder line out (L/R) connected to line out (L/R), SOUND_MIXER_VOLUME mono out sent to speaker and handset-earpiece and telephone uses the level set by SOUND_MIXER_VOLUME line in (L/R) connected to line in (L/R),SOUND_MIXER_LINE aux1 in (L) from telephone mouthpiece, SOUND_MIXER_PHONEIN (mono) aux1 in (R) grounded aux2 in (L/R) grounded mono in grounded mic in (L) from handset mouthpiece, SOUND_MIXER_LINE1 (mono) mic in (R) from built-in electret microphone, SOUND_MIXER_MIC (mono) The mono out path has an external mute control (hw) that quiets the speaker without affecting the handset or telephone. The line out path can be muted on chip. Because we offer SOUND_MIXER_PHONEIN, some apps may expect us to support SOUND_MIXER_PHONEOUT. Any writes to SOUND_MIXER_PHONEOUT are simply mapped onto SOUND_MIXER_VOLUME. The NetWinder logic sets volumes based on 1) software control 2) joystick control: master speaker volume 3) handset detection bit; JA1 goes low when handset is plugged in 4) telephone detection bit; JA2 goes low when the telephone is offhook Rules: The PRIVATE ioctl has bits to -disable hardware auto selection; software selection of in/out -mute the internal speaker -mute line out The Plan: OnTimer() 4 times per second if (the 0x80 bit of the PRIVATE ioctl is set) all control over the levels is by software settings; ioctls else if (handset | telephone change detected) switch case both removed: enable internal mic unmute speaker & line out case handset inserted: clear PRIVATE ioctl settings enable handset mic; disable internal mic & telephone mic mute speaker & lineout case telephone inserted: clear PRIVATE ioctl settings enable telephone mic; disable internal mic & handset mic mute speaker & lineout measure joystick if (joystick moved) if (volume was set by sw) if (joystick value changed by < 20%) ignore it set new volume The sw settings (levels, mutes, selections) are done at ioctl() time; some are inhibited if the auto selection feature is running */ extern spinlock_t gpio_lock; static void vnc_mute_spkr(wavnc_info *devc, int mute) { unsigned long flags; spin_lock_irqsave(&gpio_lock, flags); cpld_modify(CPLD_UNMUTE, mute ? 0 : CPLD_UNMUTE); spin_unlock_irqrestore(&gpio_lock, flags); } static void vnc_mute_lout(wavnc_info *devc, int mute) { unsigned int left, right; /* get register 1 & 5 */ left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL); right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x400); /* set the mute bits */ left &= ~1; right &= ~1; if (!mute) { left |= 1; right |= 1; } /* and send the command back */ waveartist_cmd3(devc, WACMD_SET_MIXER, left, right); } static int vnc_measure_slider(wavnc_info *devc) { static signed int old_slider_volume; unsigned long flags; signed int volume = 255; /* * measure the joystick port */ *CSR_TIMER1_LOAD = 0x00ffffff; save_flags(flags); cli(); outb(0xFF, 0x201); *CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV1; while (volume && (inb(0x201) & 0x01)) volume--; *CSR_TIMER1_CNTL = 0; restore_flags(flags); volume = 0x00ffffff - *CSR_TIMER1_VALUE; #ifndef REVERSE volume = 150 - (volume >> 5); #else volume = (volume >> 6) - 25; #endif if (volume < 0) volume = 0; if (volume > 100) volume = 100; /* * slider quite often reads +-8, so debounce this random noise */ if (abs(volume - old_slider_volume) > 7) { old_slider_volume = volume; if (debug_flg & DEBUG_MIXER) printk("Slider volume: %d.\n", volume); } return old_slider_volume; } static int vnc_slider(wavnc_info *devc) { signed int slider_volume; unsigned int temp; unsigned int old_hs, old_td; /* * read the "buttons" state. * Bit 4 = 0 means handset present, * Bit 5 = 1 means phone offhook */ temp = inb(0x201); old_hs = devc->handset_detect; old_td = devc->telephone_detect; devc->handset_detect = !(temp & 0x10); devc->telephone_detect = !!(temp & 0x20); if ((!devc->no_autoselect) && ((old_hs != devc->handset_detect) || (old_td != devc->telephone_detect))) vnc_configure_mixer(devc); slider_volume = vnc_measure_slider(devc); /* * If we're using software controlled volume, and * the slider moves by more than 20%, then we * switch back to slider controlled volume. */ if (abs(devc->slider_vol - slider_volume) > 20) devc->use_slider = 1; /* * use the left volume */ temp = levels[SOUND_MIXER_VOLUME] & 0xFF; if (slider_volume != temp && devc->use_slider) { devc->slider_vol = slider_volume; /* call as if this was an ioctl */ vnc_mixer_set(devc, SOUND_MIXER_VOLUME, slider_volume | slider_volume << 8); return 1; } return 0; } static void vnc_slider_tick(unsigned long data) { int next_timeout; if (vnc_slider(adev_info + data)) next_timeout = 5; // mixer reported change else next_timeout = VNC_TIMER_PERIOD; mod_timer(&vnc_timer, jiffies + next_timeout); } /* * set the hardware mixer selection to correspond to * a new recmask setting */ static void vnc_set_mixer_src(wavnc_info *devc, unsigned int new_mask) { unsigned int left, right; /* * read register 4 & 8 */ left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x300); right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x700); /* * mask of the mixer selection bits, and turn on the PCM */ left &= ~0x7f0; left |= 0x400; right &= ~0x7f0; right |= 0x400; switch(new_mask) { case SOUND_MASK_MIC: left |= 0x100; /* cross channel mic */ right |= 0x080; /* mic */ break; case SOUND_MASK_LINE1: /* handset */ left |= 0x080; /* mic */ right |= 0x100; /* cross channel mic */ break; case SOUND_MASK_PHONEIN: left |= 0x040; break; case SOUND_MASK_LINE: left |= 0x010; right |= 0x010; break; default: /* no audio source */ /* leave reg4/8 alone, just with PCM */ break; } /* * send register 4 & 8 back to the hardware */ waveartist_cmd3(devc, WACMD_SET_MIXER, left, right); } /* * set the hardware feedback level * * we have to figure out which source is being recorded, and set * the corresponding mixer gain */ static void vnc_set_imix(wavnc_info * devc, unsigned int new_level) { unsigned int left,right; unsigned int cur_src; left = new_level & 0xff; right = new_level >> 8; /* which source are recording: mic, line or aux? */ cur_src = devc->recmask; /* switch on the current recording source; leave the DAC path on */ vnc_set_mixer_src(devc, cur_src); /* set the monitor level for that channel */ switch(cur_src) { case SOUND_MASK_MIC: /* built-in is right mic */ waveartist_set_mixer_levels(devc, 0, left, 0x7E0,3,7); break; case SOUND_MASK_LINE: /* line in */ waveartist_set_mixer_levels(devc, left, right, 0x7E0,1,5); break; case SOUND_MASK_LINE1: /* handset mic is left mic */ waveartist_set_mixer_levels(devc, left, 0, 0x7e0,3,7); break; case SOUND_MASK_PHONEIN: /* telephone mic is l-aux1 */ waveartist_set_mixer_levels(devc, left, 0, 0x3e,1,5); break; default: /* nothing selected for recording */ } } /* supplement to waveartist_mixer_reset() */ static void vnc_mixer_reset(wavnc_info* devc) {} /* * all the logic to handle the autoswitch on the NetWinder */ static void vnc_configure_mixer(wavnc_info* devc) { unsigned int reg9, reg10; if (!devc->no_autoselect) { if (devc->handset_detect) { devc->recmask = SOUND_MASK_LINE1; devc->spkr_mute_state = devc->line_mute_state = 1; } else if (devc->telephone_detect) { devc->recmask = SOUND_MASK_PHONEIN; devc->spkr_mute_state = devc->line_mute_state = 1; } else { /* unless someone has asked for LINE-IN, we default to MIC */ if ((devc->recmask & SOUND_MASK_LINE) == 0) devc->recmask = SOUND_MASK_MIC; devc->spkr_mute_state = devc->line_mute_state = 0; } vnc_mute_spkr(devc,devc->spkr_mute_state); vnc_mute_lout(devc,devc->line_mute_state); } /* ok. At this point, we have done the autoswitch logic, or we * have had a command from an ioctl. We have a valid devc->recmask. * Now we have to connect up the hardware to reflect the recmask */ /* * get the current recording mode */ reg9 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x800); reg10 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x900); /* * knock down the current selection bits */ reg9 &= ~0x3f; switch(devc->recmask) { case SOUND_MASK_MIC: /* the built-in mic */ /* * switch to mono record right */ waveartist_cmd1(devc, WACMD_SET_MONO | 0x100); /* * set the hardware to mic input */ reg9 |= 0x28; break; case SOUND_MASK_LINE1: /* our handset */ /* * switch to mono record from left */ waveartist_cmd1(devc, WACMD_SET_MONO); /* * set the hardware to handset input */ reg9 |= 5; break; case SOUND_MASK_PHONEIN: /* our telephone mic */ /* * switch to mono record from left */ waveartist_cmd1(devc, WACMD_SET_MONO); /* * set the hardware to telephone input */ reg9 |= 4; break; case SOUND_MASK_LINE: /* our streo line in */ /* * I hope the channels know that this is stereo.... */ /* * set the hardware to line in */ reg9 |= 0x12; break; default: /* this is a BadThing */ return; } /* * send the mixer selection back to the hardware */ waveartist_cmd3(devc, WACMD_SET_MIXER, reg9, reg10); /* * update the record monitor level */ vnc_mixer_update(devc, SOUND_MIXER_IMIX); } /* * send the current stored levels to the various mixer ports * note: does NOT trigger any selection changes, only sets * levels */ static void vnc_mixer_update(wavnc_info *devc, int whichDev) { unsigned int mask=0, reg_l=0, reg_r=0; unsigned int lev_left, lev_right; int bSetRecLevel = 0; lev_left = devc->levels[whichDev] & 0xff; lev_right = devc->levels[whichDev] >> 8; #define SCALE(lev,max) ((lev) * (max) / 100) switch(whichDev) { case SOUND_MIXER_VOLUME: /* line out */ case SOUND_MIXER_PHONEOUT: mask = 0x000e; reg_l = 3; reg_r = 7; break; case SOUND_MIXER_LINE: /* line in; only sets if line-in is selected */ if ((devc->recmask & SOUND_MASK_LINE)==0) return; /* adjust the master rec level */ bSetRecLevel = 1; break; case SOUND_MIXER_MIC: /* microphone; only sets if mic is selected */ if ((devc->recmask & SOUND_MASK_MIC)==0) return; /* we have to set the mic gain AND the recording level */ waveartist_set_mixer_levels(devc, lev_left, lev_right, 0x0030, 3, 7); bSetRecLevel = 1; break; case SOUND_MIXER_LINE1: if ((devc->recmask & SOUND_MASK_LINE1)==0) return; bSetRecLevel = 1; break; case SOUND_MIXER_RECLEV: /* adjust in any case */ bSetRecLevel = 1; break; case SOUND_MIXER_PCM: waveartist_cmd3(devc, WACMD_SET_LEVEL, SCALE(lev_left, 32767), SCALE(lev_right, 32767)); return; case SOUND_MIXER_SYNTH: waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL, SCALE(lev_left, 32767), SCALE(lev_right, 32767)); return; case SOUND_MIXER_IMIX: /* used as a recording monitor */ /* only active during recording */ if (devc->recmask==0) return; vnc_set_imix(devc,devc->levels[SOUND_MIXER_IMIX]); return; default: return; } if (bSetRecLevel) { mask = 0x000f; reg_l = 4; reg_r = 8; } /* send the scaled values to the hardware */ waveartist_set_mixer_levels(devc, lev_left, lev_right, mask, reg_l, reg_r); } /* * helper for the ioctl calls * * level is 0..100 for right and left channels */ static int vnc_mixer_set(wavnc_info *devc, int whichDev, unsigned int level) { if (debug_flg & DEBUG_MIXER) printk("wa_mixer_set(dev=%d, level=%X)\n", whichDev, level); switch (whichDev) { /* Line out volume */ case SOUND_MIXER_VOLUME: /* External line in */ case SOUND_MIXER_LINE: /* Recording level */ case SOUND_MIXER_RECLEV: /* WaveArtist PCM out */ case SOUND_MIXER_PCM: /* Internal synthesizer out */ case SOUND_MIXER_SYNTH: /* Line1 is handset mic level */ case SOUND_MIXER_LINE1: /* Mono microphone */ case SOUND_MIXER_MIC: /* recording feedback */ case SOUND_MIXER_IMIX: /* all the above share common code: * save the level in levels[], and issue it to hardware */ devc->levels[whichDev] = level; vnc_mixer_update(devc, whichDev); break; /* * Select recording input source */ case SOUND_MIXER_RECSRC: devc->recmask = level & POSSIBLE_RECORDING_DEVICES; /* * refresh the hardware, taking autoswitch into consideration * note: this may change devc->recmask */ vnc_configure_mixer(devc); /* * if record monitoring is on, make sure the bit is set */ if (devc->levels[SOUND_MIXER_IMIX]) vnc_mixer_update(devc, SOUND_MIXER_IMIX); /* * do not save in "levels", return current setting */ return devc->recmask; default: return -EINVAL; } return devc->levels[whichDev]; } /* * public ioctls for write/read SOUND_MIXER_ registers * * three private ioctls * PRIVATE1 enables/disables auto source select & does software muting * PRIVATE2 pause/run the DSP * PRIVATE3 bulk access to the WA registers z */ static int vnc_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg) { wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc; /* use this call to inhibit the auto source select logic * see VNC_* above */ if (cmd == SOUND_MIXER_PRIVATE1) { int val; int prev_spkr_mute = devc->spkr_mute_state; int prev_line_mute = devc->line_mute_state; int prev_auto_state = devc->no_autoselect; if (get_user(val, (int *)arg)) return -EFAULT; /* check if parameter is logical...*/ if (val & (VNC_MUTE_INTERNAL_SPKR | VNC_MUTE_LINE_OUT | VNC_DISABLE_AUTOSWITCH)) return -EINVAL; devc->no_autoselect = (val & VNC_DISABLE_AUTOSWITCH) ? 1 : 0; devc->spkr_mute_state = (val & VNC_MUTE_INTERNAL_SPKR) ? 1 : 0; devc->line_mute_state = (val & VNC_MUTE_LINE_OUT) ? 1 : 0; if (prev_spkr_mute != devc->spkr_mute_state) vnc_mute_spkr(devc, devc->spkr_mute_state); if (prev_line_mute != devc->line_mute_state) vnc_mute_lout(devc, devc->line_mute_state); /* if the switch state changes, refresh the mixer settings */ if (prev_auto_state != devc->no_autoselect) vnc_configure_mixer(devc); return 0; } /* read back the command from PRIVATE1 */ if (cmd == SOUND_MIXER_PRIVATE4) { int ret = (devc->spkr_mute_state ? VNC_MUTE_INTERNAL_SPKR : 0) | (devc->line_mute_state ? VNC_MUTE_LINE_OUT : 0) | (devc->handset_detect ? VNC_HANDSET_DETECT : 0) | (devc->telephone_detect ? VNC_PHONE_DETECT : 0) | (devc->no_autoselect ? VNC_DISABLE_AUTOSWITCH : 0); return put_user(ret, (int*) arg) ? -EINVAL : 0; } if (cmd == SOUND_MIXER_PRIVATE2) { #define VNC_SOUND_PAUSE 0x53 //to pause the DSP #define VNC_SOUND_RESUME 0x57 //to unpause the DSP int val; unsigned int cmd; if (get_user(val, (int *)arg)) return -EFAULT; if (val == VNC_SOUND_PAUSE) { //PAUSE the ADC cmd = 0x16; waveartist_cmd(devc,1,&cmd,0,NULL); } else if (val == VNC_SOUND_RESUME) { //RESUME the ADC cmd = 0x18; waveartist_cmd(devc,1,&cmd,0,NULL); } else { return -EINVAL; //invalid parameters... } return 0; } /* another private ioctl to allow bulk access to the wa registers */ if (cmd == SOUND_MIXER_PRIVATE3) { long unsigned flags; int mixer_reg[15]; //reg 14 is actually a command: read,write,reset int val; int i; if (get_user(val, (int *)arg)) return -EFAULT; if (verify_area(VERIFY_READ, (void *) val, sizeof(mixer_reg) == -EFAULT)) return (-EFAULT); copy_from_user(&mixer_reg, (void *) val, sizeof(mixer_reg)); if (mixer_reg[0x0E] == MIXER_PRIVATE3_RESET) { //reset command?? vnc_mixer_reset(devc); return (0); } else if (mixer_reg[0x0E] == MIXER_PRIVATE3_WRITE) { /* write complete set */ //printk("WaveArtist Mixer: Private write command.\n"); waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[0], mixer_reg[4]); waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[1], mixer_reg[5]); waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[2], mixer_reg[6]); waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[3], mixer_reg[7]); waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[8], mixer_reg[9]); /* set left and right PCM and FM volumes */ waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[10], mixer_reg[11]); waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[12], mixer_reg[13]); return 0; } else if (mixer_reg[0x0E] == MIXER_PRIVATE3_READ) { /* read complete set */ //printk("WaveArtist Mixer: Private read command.\n"); if (verify_area(VERIFY_WRITE, (void *) val, sizeof(mixer_reg) == -EFAULT)) return (-EFAULT); /* read all current values... and copy to user */ save_flags(flags); cli(); for (i = 0; i < 14; i++) { int cmd = 0x30 + (i<<8); waveartist_cmd(devc, 1, &cmd, 1, mixer_reg+i); } restore_flags(flags); copy_to_user((void *) val, &mixer_reg, sizeof(mixer_reg)); return 0; } else return -EINVAL; } /* the common ioctls */ if (((cmd >> 8) & 0xff) == 'M') { if (_SIOC_DIR(cmd) & _SIOC_WRITE) { int val; if (get_user(val, (int *)arg)) return -EFAULT; /* * special case for speaker volume: if we * received this call - switch from hw * volume control to a software volume * control, till the hw volume is modified * to signal that user wants to be back in * hardware... */ if ((cmd & 0xff) == SOUND_MIXER_VOLUME) devc->use_slider = 0; return vnc_mixer_set(devc, cmd & 0xff, val); } else { int ret; /* * Return parameters */ switch (cmd & 0xff) { case SOUND_MIXER_RECSRC: ret = devc->recmask; break; case SOUND_MIXER_DEVMASK: ret = devc->supported_devices; break; case SOUND_MIXER_STEREODEVS: /* our speaker, internal mike, handset mike and phone are mono */ ret = devc->supported_devices & ~(SOUND_MASK_IMIX | SOUND_MASK_MIC | SOUND_MASK_LINE1 | SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT); break; case SOUND_MIXER_RECMASK: ret = devc->rec_devices; break; case SOUND_MIXER_CAPS: ret = SOUND_CAP_EXCL_INPUT; break; default: if ((cmd & 0xff) < SOUND_MIXER_NRDEVICES) ret = devc->levels[cmd & 0xff]; else return -EINVAL; } return put_user(ret, (int *)arg) ? -EINVAL : 0; } } return -ENOIOCTLCMD; } #ifdef MODULE MODULE_PARM(io, "i"); /* IO base */ MODULE_PARM(irq, "i"); /* IRQ */ MODULE_PARM(dma, "i"); /* DMA */ MODULE_PARM(dma2, "i"); /* DMA2 */ int io = CONFIG_WAVEARTIST_BASE; int irq = CONFIG_WAVEARTIST_IRQ; int dma = CONFIG_WAVEARTIST_DMA; int dma2 = CONFIG_WAVEARTIST_DMA2; static int attached; struct address_info hw_config; int init_module(void) { hw_config.io_base = io; hw_config.irq = irq; hw_config.dma = dma; hw_config.dma2 = dma2; if (!probe_waveartist(&hw_config)) return -ENODEV; attach_waveartist(&hw_config); attached = 1; SOUND_LOCK; return 0; } void cleanup_module(void) { if (attached) { SOUND_LOCK_END; unload_waveartist(&hw_config); } } #endif