/* * sound/wavnc.c * * The low level driver for the RWA010 Rockwell Wave Artist codec chip * used in the Corel Computer VNC. * */ /* * 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. */ #include #define DEB(x) #define DDB(x) #define DEB1(x) #include "sound_config.h" //#ifdef CONFIG_AD1848 #if 1 #include "wa_vnc.h" #define WA_TIMER_PERIOD 250/10 //check slider 4 times/sec #define PCM_NON 0 #define PCM_DAC 2 #define PCM_ADC 1 #define MIXER_PRIVATE3_RESET 0x53570000 #define MIXER_PRIVATE3_READ 0x53570001 #define MIXER_PRIVATE3_WRITE 0x53570002 // use RECSRC = speaker to mark the internal microphone #define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_LINE |\ SOUND_MASK_MIC |\ SOUND_MASK_LINE1) #define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH |\ SOUND_MASK_PCM |\ SOUND_MASK_LINE |\ SOUND_MASK_MIC | \ SOUND_MASK_LINE1 |\ SOUND_MASK_VOLUME |\ SOUND_MASK_RECLEV) typedef struct { int base; int irq; int dma1, dma2; int dual_dma; /* 1, when two DMA channels allocated */ int debug_flag; int audio_flags; int record_dev, playback_dev; int xfer_count; int audio_mode; int open_mode; int intr_active; char *chip_name, *name; int *wa_osp; /* Mixer parameters */ int recmask; int supported_devices; int rec_devices; int dev_no; int irq_ok; int handset_state; int mute_state; int soft_volume_flag; //1 - volume controlled by mixer prog int hw_volume; //remember old setting of hw vol } wavnc_info; static unsigned short levels[SOUND_MIXER_NRDEVICES] = { 0x5555, /* Master Volume */ 0x0000, /* Bass */ 0x0000, /* Treble */ 0x5555, /* FM */ 0x4b4b, /* PCM */ 0x0000, /* PC Speaker */ 0x5555, /* Ext Line */ 0x0000, /* Mic */ 0x0000, /* CD */ 0x0000, /* Recording monitor */ 0x0000, /* SB PCM */ 0x0000, /* Recording level */ 0x0000, /* Input gain */ 0x0000, /* Output gain */ 0x0000, /* Aux1 */ 0x0000, /* Aux2 */ 0x0000 /* Aux3 */ }; typedef struct wavnc_port_info { int open_mode; int speed; int channels; int audio_format; } wavnc_port_info; static int nr_wavnc_devs = 0; static wavnc_info adev_info; static struct timer_list wa_timer; static int wavnc_open (int dev, int mode); static void wavnc_close (int dev); static int wavnc_ioctl (int dev, unsigned int cmd, caddr_t arg); static void wavnc_output_block (int dev, unsigned long buf, int count, int intrflag); static void wavnc_start_input (int dev, unsigned long buf, int count, int intrflag); static int wavnc_prepare_for_output (int dev, int bsize, int bcount); static int wavnc_prepare_for_input (int dev, int bsize, int bcount); static void wavnc_halt (int dev); static void wavnc_halt_input (int dev); static void wavnc_halt_output (int dev); static void wavnc_trigger (int dev, int bits); void waintr (int irq, void *dev_id, struct pt_regs *dummy); int vnc_slider(wavnc_info * devc); static void wa_slider(unsigned long data); int wa_sendcmd (unsigned int cmd); int wa_writecmd (unsigned int cmd, unsigned int arg); void delay10ms(void); static int mixer_output (int right_vol, int left_vol, int div, int bits, int mixer1, int mixer2, int shift); static int wa_mixer_set (wavnc_info * devc, int whichDev, unsigned int level); static void wavnc_mixer_reset (wavnc_info * devc); void mute_mono(wavnc_info * devc, int mute); void wa_mute(wavnc_info * devc, int mute); void delay10ms(void) { //wait 10 ms current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + 1; //wait 10 ms schedule(); } static void wa_slider(unsigned long data) { if (vnc_slider(&adev_info)) { wa_timer.expires = jiffies + 5; //mixer reported change } else { wa_timer.expires = jiffies + WA_TIMER_PERIOD; } add_timer(&wa_timer); } int wa_sendcmd (unsigned int cmd) { int count; DEB1(printk("wa_sendcmd: cmd=0x%04X...", cmd)); tenmicrosec(adev_info.wa_osp); if(inb(STATR) & CMD_RF) { count = inw(CMDR); //flush the port printk("", count); //useless, but produces a needed delay... DEB1(printk("\nwa_sendcmd: flushed old response: %04X.\n",count)); } count = 5000; //preset timeout at 5000 loops.... while(!(inb(STATR) & CMD_WE) && count--) {}; //wait till bit HI if(count) //ready BEFORE timeout? { outw(cmd, CMDR); //output the command DEB1(printk(" Done OK.\n")); tenmicrosec(adev_info.wa_osp); return 1; } else { DEB1(printk(" Error!\n")); return 0; } } int wa_writecmd (unsigned int cmd, unsigned int arg) { int count; DEB1(printk("wa_writecmd: cmd=0x%04X, arg=0x%04X ...", cmd, arg)); tenmicrosec(adev_info.wa_osp); if(inb(STATR) & CMD_RF) { count = inw(CMDR); //flush the port DEB1(printk("\nwa_writecmd: flushed %04X response.\n",count)); } count = 5000; //preset timeout at 5000 loops.... while(!(inb(STATR) & CMD_WE) && count--) {}; //wait till bit HI if(count) //ready BEFORE timeout? { //start with writing the command word outw(cmd, CMDR); //output the command count = 5000; //preset timeout at 5000 loops.... while(!(inb(STATR) & CMD_WE) && count--) {}; //wait till bit HI if(!count) { DEB1(printk(" Timeout 1!.\n")); return(0); } else { //write the parameter outw(arg,CMDR); tenmicrosec(adev_info.wa_osp); //Rockwell answers in 5 us max. if(!(inb(STATR) & CMD_RF)) tenmicrosec(adev_info.wa_osp); count = inw(CMDR); DEB1(printk("Done=%04X.\n",count)); if (!count) tenmicrosec(adev_info.wa_osp); return(count); //0 = error, 1 = OK } } else { DEB1(printk(" Timeout 2!.\n")); return 0; } } //----------------------------------------------------------------------- //This function is suitable to update settings in all "symmetrical" registers, //namely 1..8. Registers 9 and 10 are not adhering to the left-right concept... static int mixer_output (int right_vol, int left_vol, int div, int bits, int mixer1, int mixer2, int shift)/* Mixer registers to touch */ { int left = left_vol * div / 100; int right = right_vol * div / 100; int left1 = 0; int right1 = 0; int left2 = 0; int right2 = 0; left1 = (mixer1-1)<<8; // get ready for command nn30H right1= (mixer2-1)<<8; //first read current values wa_sendcmd(left1+0x30); while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... left2 = inw(CMDR); wa_sendcmd(right1+0x30); while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... right2 = inw(CMDR); DDB(printk("wa_mixer: Current left=%04X, right=%04X.\n",left2,right2)); //now that both current values are in - update bits... if (shift) left <<= shift; left2 &= ~bits; left2 |= left; if (shift) right <<= shift; right2 &= ~bits; right2 |= right; //and finally - write the reg pair back.... wa_sendcmd(0x32); wa_sendcmd(left2); wa_sendcmd(right2); return (left_vol | (right_vol << 8)); } static int wa_mixer_set (wavnc_info * devc, int whichDev, unsigned int level) { int left, right, devmask, changed, i, mixer = 0; int left1, right1; left = level & 0x7f; right = (level & 0x7f00) >> 8; if (whichDev < SOUND_MIXER_NRDEVICES) if ((1 << whichDev) & devc->rec_devices) mixer = 0x20; else mixer = 0x00; DDB (printk ("wa_mixer_set(dev = %d, level = %X, mixer = %X.)\n", whichDev, level, mixer)); switch (whichDev) { // We have 3 bits on the Left/Right Mixer Gain, bits 3,2,1 on 3 and 7 case SOUND_MIXER_VOLUME: /* Master volume (0-7) */ levels[whichDev] = mixer_output (right, left, 7, 0x000E, 3, 7, 1); break; // use LOUT/ROUT bits 10...6, reg 1 and 5 case SOUND_MIXER_LINE: /* External line (0-31) */ levels[whichDev] = mixer_output (right,left,31,0x07C0,1,5,6); break; case SOUND_MIXER_MIC: /* Mono microphone (0-31) */ levels[whichDev] = mixer_output (right,left,31,0x07C0,3,7,6); break; case SOUND_MIXER_RECLEV: /* Recording level (0-7) */ levels[whichDev] = mixer_output (right,left,7,0x0003,4,8,0); break; // use LINE1 bits 5...1, reg 1 and 5 case SOUND_MIXER_LINE1: /* Mono External Aux1 (0-31) */ levels[whichDev] = mixer_output (right,left,31,0x003E,1,5,1); break; case SOUND_MIXER_PCM: /* WA PCM (0-7FFFH) */ left1 = left * 0x7FFF /100; right1= right* 0x7FFF /100; wa_sendcmd(0x0031); //change left and right PCM wa_sendcmd(left1); wa_sendcmd(right1); //we cannot store full 15 bits in 2*8 bits - so store only upper bytes levels[whichDev] = left | (right <<8); break; case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-31) */ left1 = left * 0x7FFF /100; right1= right* 0x7FFF /100; wa_sendcmd(0x0131); //change left and right FM wa_sendcmd(left1); wa_sendcmd(right1); //we cannot store full 15 bits in 2*8 bits - so store only upper bytes levels[whichDev] = left | (right <<8); break; case SOUND_MIXER_RECSRC: devmask = level & POSSIBLE_RECORDING_DEVICES; changed = devmask ^ devc->rec_devices; devc->rec_devices = devmask; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (changed & (1 << i)) { wa_mixer_set (devc, i, levels[i]); } //new mixer programming - switch recording source using R/L_ADC_Mux_Select //we are playing with left/right mux bit fields in reg 9. wa_sendcmd(0x0830); //get current reg 9 while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... left1 = inw(CMDR); wa_sendcmd(0x0930); // and reg 10 - just to write it back... while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... right1 = inw(CMDR); DDB(printk("RECSRC: old left: 0x%04X, old right: 0x%04X.\n", left1&0x07, (left1>>3)&0x07)); left1 &= ~0x03F; //kill current left/right mux input select if (level == SOUND_MASK_MIC) // handset or internal mic { if (devc->handset_state & 0x10) // handset not plugged in? { wa_sendcmd(0x0134); //set mono recording from right mic left1 |= 0x0028; //right=mic, left=none devc->rec_devices |= SOUND_MASK_SPEAKER; //pretend int mic } else { wa_sendcmd(0x0034); //set mono rec from left mic left1 |= 0x0005; // right=none, left=mic devc->rec_devices &= ~SOUND_MASK_SPEAKER; //show no int mic } } else if (level == SOUND_MASK_LINE1) { wa_sendcmd(0x0034); //set mono rec from left aux1 left1 |= 0x0004; // right=none, left=Aux1; } else if (level == SOUND_MASK_LINE) { left1 |= 0x0012; // right=Line, left=Line; } DDB(printk("RECSRC %d: left=0x%04X, right=0x%04X.\n",level, left1&0x07, (left1>>3)&0x07)); //and finally - write the reg pair back.... wa_sendcmd(0x32); wa_sendcmd(left1); wa_sendcmd(right1); #if 0 wa_sendcmd(0x0330); //get current reg 4 while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... left1 = inw(CMDR); wa_sendcmd(0x0730); // and reg 8 while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... right1 = inw(CMDR); DDB(printk("RECSRC: old left: 0x%04X, old right: 0x%04X.\n",left1, right1)); left1 &= ~0x07F8; //kill current left/right mux input select right1 &= ~0x07F8; if (level == SOUND_MASK_MIC) // handset or internal mic { if (devc->handset_state & 0x10) // handset not plugged in? { wa_sendcmd(0x0134); //set mono recording from right mic left1 |= 0x0C00; // == none; right1 |= 0x0C88; // == mic, RX filter gain; devc->rec_devices |= SOUND_MASK_SPEAKER; //pretend int mic } else { wa_sendcmd(0x0034); //set mono rec from left mic left1 |= 0x0C88; // == mic, RX filter gain; right1 |= 0x0C00; // == none; devc->rec_devices &= ~SOUND_MASK_SPEAKER; //show no int mic } } else if (level == SOUND_MASK_LINE1) { wa_sendcmd(0x0034); //set mono rec from left aux1 left1 |= 0x0C40; // == Aux1; right1 |= 0x0C00; // == none; } else if (level == SOUND_MASK_LINE) { left1 |= 0x0C10; // == Line; right1 |= 0x0C10; // == Line; } DDB(printk("RECSRC %d: left(4) 0x%04X, right(8) 0x%04X.\n",level,left1,right1)); //and finally - write the reg pair back.... wa_sendcmd(0x32); wa_sendcmd(left1); wa_sendcmd(right1); #endif return devc->rec_devices; //do not save in "levels",return current setting break; default: return -(EINVAL); } return (levels[whichDev]); } int vnc_slider(wavnc_info * devc) { unsigned int volume, temp, hw_vol; long unsigned flags; unsigned int start_time; /* // joystick stuff - called from a timer callback... */ //read the "buttons" state. Bit 0 = handset present, bit 1 = offhook // the state should be "querable" via a private IOCTL call temp = inb(0x201) & 0x30; //any change in handset plugged in/out? if ((temp&0x10) != (devc->handset_state & 0x10)) { DEB(printk("wa_mixer: handset: old = %02X, new = %02X.\n",devc->handset_state, temp)); devc->handset_state = temp; //handset on (bit=0)? enable handset mic, disable internal mic //only if current setting is for the other mic (not the line...) // actually changed: switch to apriopriate microphone if (!(temp&0x10)) { mute_mono(devc,1); //cut - off speaker } else { //speaker on, internal mic on mute_mono(devc,0); } // if (devc->rec_devices != SOUND_MASK_MIC) wa_mixer_set(devc, SOUND_MIXER_RECSRC, SOUND_MASK_MIC); } volume = 0xFF; save_flags(flags); cli(); outb(0xFF, 0x201); //fire joystick timer start_time = *(volatile unsigned int*)(0xE1000304); while (volume && (inb(0x201) & 0x01)) { volume--; } volume = *(volatile unsigned int*)(0xE1000304); restore_flags(flags); volume = start_time - volume; hw_vol = volume; //save it for debug display // if (rev==3) // { // volume >>= 5; // volume = 154-volume; // } // else { volume >>= 6; volume -= 30; } if(volume > 0x80000000) volume = 0; //if negative - make it 0 if(volume > 100) volume = 100; temp = levels[SOUND_MIXER_VOLUME] & 0xFF; //use only left channel /* volume = (volume + old_slider_volume + temp)/3;//average the new volume if (volume>=24) volume += 3; //fudge factor... old_slider_volume = volume; */ /* if (hw_vol != old_slider_volume) { temp=vncdebug; vncdebug=1; debprintf("Start_time: 0x%X, end: 0x%X, diff=%d.\n", start_time, hw_vol, start_time-hw_vol); vncdebug=temp; old_slider_volume=hw_vol; } */ //slider quite often reads +-8, so debounce this random noise if ((volume != temp) && ((volumetemp+7))) { // extern void putstr(char *s); // char szTemp[48]; // sprintf(szTemp,"Slider read: %d, volume: %d.\n", hw_vol, volume); // putstring(szTemp); DEB(printk("Slider read: %d, volume: %d.\n", hw_vol, volume)); // dbprintf("Slider read: %d, volume: %d.\n", hw_vol, volume); if (devc->soft_volume_flag) // in software mode??? { // if so - check if the current reading varies by more then 15 // from the read at the moment we switch to soft_volume. if (devc->hw_volume > volume) { if (devc->hw_volume - volume > 20) //take over? { //printk("waMixer: to hw vol, hw_vol = %d, cur vol = %d.\n",devc->hw_volume, volume); devc->soft_volume_flag = 0; wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume); } } else { if (volume - devc->hw_volume > 20) //take over? { //printk("waMixer: to hw vol, hw_vol = %d, cur vol = %d.\n",devc->hw_volume, volume); devc->soft_volume_flag = 0; wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume); } } } else { wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume); } return (1); } else return(0); //no change } /*****/ static void wavnc_mixer_reset (wavnc_info * devc) { // char name[32]; int foo; DDB (printk ("wa_mixer: wa_mixer_reset()\n")); // sprintf (name, "%s_%d", devc->chip_name, nr_wavnc_devs); wa_sendcmd(0x33); //reset mixer cmd // set input for ADC to come from a mixer device (left and right) == reg 9 wa_sendcmd(0x32); wa_sendcmd(0x982D); wa_sendcmd(0xA836); // set input gain for mics: left (handset = 10dB, right (internal) = 20dB wa_sendcmd(0x32); wa_sendcmd(0x382C); wa_sendcmd(0x783C); // set mixer input select to none, boost RX filter gains to 12dB wa_sendcmd(0x32); wa_sendcmd(0x4C08); wa_sendcmd(0x8C08); // set bit 0 reg 2 to 1 - unmute MonoOut wa_sendcmd(0x32); //set mixer pair wa_sendcmd(0x2801); //reg 2 - unmute MonoOut wa_sendcmd(0x6800); //reg 6 for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++) wa_mixer_set (devc, foo, levels[foo]); //set default input device = internal mic devc->rec_devices = 0; devc->handset_state = 0x10; //assume no handset wa_mixer_set(devc, SOUND_MIXER_RECSRC, SOUND_MASK_MIC); devc->soft_volume_flag = 0; //start from enabling the hw setting vnc_slider(devc); //adjust master volume as per slider devc->supported_devices = SUPPORTED_MIXER_DEVICES; devc->rec_devices = POSSIBLE_RECORDING_DEVICES; } //----------------------------------------------------------------------- void wa_mute(wavnc_info * devc, int mute) { } //----------------------------------------------------------------------- void mute_mono(wavnc_info * devc, int mute) { if (mute) { //we want to write a bit pattern 1000 to Xilinx to end resets //mute the mono speaker and disable flash write //turn on GREEN led... outb(0x22, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x02, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x02, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x02, 0x338); //data low, clock low outb(0x32, 0x338); //data low, clock hi outb(0x02, 0x338); //data low, clock low //by now all 4 bits are shifted in. copy to IO reg outb(0x01, 0x33A); outb(0x00, 0x33A); } else { //we want to write a bit pattern 1010 to Xilinx to end resets //unmute the mono speaker and disable flash write //turn on GREEN led... outb(0x22, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x02, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x22, 0x338); //data hi, clock low outb(0x32, 0x338); //data hi, clock hi outb(0x02, 0x338); //data low, clock low outb(0x32, 0x338); //data low, clock hi outb(0x02, 0x338); //data low, clock low //by now all 4 bits are shifted in. copy to IO reg outb(0x01, 0x33A); outb(0x00, 0x33A); } devc->mute_state = mute; //remember the current setting... } static int wavnc_mixer_ioctl (int dev, unsigned int cmd, caddr_t arg) { // wavnc_info *devc = mixer_devs[dev]->devc; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; if (cmd == SOUND_MIXER_PRIVATE1) { int val; val = *(int *) arg; #if 0 // if (val == 0xffff) // return (*(int *) arg = devc->mixer_output_port); val &= (AUDIO_SPEAKER | AUDIO_HEADPHONE | AUDIO_LINE_OUT); devc->mixer_output_port = val; val |= AUDIO_HEADPHONE | AUDIO_LINE_OUT; /* Always on */ devc->mixer_output_port = val; if (val & AUDIO_SPEAKER) ad_write (devc, 26, ad_read (devc, 26) & ~0x40); /* Unmute mono out */ else ad_write (devc, 26, ad_read (devc, 26) | 0x40); /* Mute mono out */ return (*(int *) arg = devc->mixer_output_port); #endif return(0); } 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; val = *(int *) arg; if (verify_area(VERIFY_READ, (void*)val, sizeof(mixer_reg) == -EFAULT)) return(-EFAULT); memcpy_fromfs(&mixer_reg, (void*)val, sizeof(mixer_reg)); if (mixer_reg[0x0E] == MIXER_PRIVATE3_RESET) //reset command?? { wavnc_mixer_reset (devc); return(0); } else if (mixer_reg[0x0E] == MIXER_PRIVATE3_WRITE)//write command?? { // printk("WaveArtist Mixer: Private write command.\n"); wa_sendcmd(0x32); //Pair1 - word 1 and 5 wa_sendcmd(mixer_reg[0]); wa_sendcmd(mixer_reg[4]); wa_sendcmd(0x32); //Pair2 - word 2 and 6 wa_sendcmd(mixer_reg[1]); wa_sendcmd(mixer_reg[5]); wa_sendcmd(0x32); //Pair3 - word 3 and 7 wa_sendcmd(mixer_reg[2]); wa_sendcmd(mixer_reg[6]); wa_sendcmd(0x32); //Pair4 - word 4 and 8 wa_sendcmd(mixer_reg[3]); wa_sendcmd(mixer_reg[7]); wa_sendcmd(0x32); //Pair5 - word 9 and 10 wa_sendcmd(mixer_reg[8]); wa_sendcmd(mixer_reg[9]); wa_sendcmd(0x0031); //set left and right PCM wa_sendcmd(mixer_reg[0x0A]); wa_sendcmd(mixer_reg[0x0B]); wa_sendcmd(0x0131); //set left and right FM wa_sendcmd(mixer_reg[0x0C]); wa_sendcmd(mixer_reg[0x0D]); return(0); } else if (mixer_reg[0x0E] == MIXER_PRIVATE3_READ)//read command? { // printk("WaveArtist Mixer: Private read command.\n"); //first read all current values... save_flags(flags); cli(); for (i=0; i<14; i++) { wa_sendcmd((i<<8)+0x30); // get ready for command nn30H while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready... mixer_reg[i] = inw(CMDR); } restore_flags(flags); if (verify_area(VERIFY_WRITE, (void*)val, sizeof(mixer_reg) == -EFAULT)) return(-EFAULT); memcpy_tofs((void*)val, &mixer_reg, sizeof(mixer_reg)); return(0); } else return (-EINVAL); } if (((cmd >> 8) & 0xff) == 'M') { int val; if (_SIOC_DIR (cmd) & _SIOC_WRITE) // switch (cmd & 0xff) { // case SOUND_MIXER_RECSRC: // val = *(int *) arg; // return (*(int *) arg = wavnc_set_recmask (devc, val)); // break; // // default: val = *(int *) arg; // special case for master 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) /* Master volume */ { if (!devc->soft_volume_flag) //the switching call? { devc->soft_volume_flag = 1;//mark going to soft volume // remember this hw setting to compare devc->hw_volume = levels[SOUND_MIXER_VOLUME]&0xFF; // printk("waMixer: soft vol, curr vol = %d.\n",devc->hw_volume); } // if already in soft volume, just set the volume } return (*(int *) arg = wa_mixer_set (devc, cmd & 0xff, val)); } else switch (cmd & 0xff) /* * Return parameters */ { case SOUND_MIXER_RECSRC: return (*(int *) arg = devc->recmask); break; case SOUND_MIXER_DEVMASK: return (*(int *) arg = devc->supported_devices); break; case SOUND_MIXER_STEREODEVS: // if (devc->model == MD_C930) // return (*(int *) arg = devc->supported_devices); // else return (*(int *) arg = devc->supported_devices & ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX)); break; case SOUND_MIXER_RECMASK: return (*(int *) arg = devc->rec_devices); break; case SOUND_MIXER_CAPS: return (*(int *) arg = SOUND_CAP_EXCL_INPUT); break; default: return (*(int *) arg = levels[cmd & 0xff]); } } else return -EINVAL; } static int wavnc_set_speed (int dev, int arg) { // wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; 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 wavnc_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 wavnc_set_bits (int dev, unsigned int arg) { // wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; 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 wavnc_audio_driver = { wavnc_open, wavnc_close, wavnc_output_block, wavnc_start_input, wavnc_ioctl, wavnc_prepare_for_input, wavnc_prepare_for_output, wavnc_halt, NULL, NULL, wavnc_halt_input, wavnc_halt_output, wavnc_trigger, wavnc_set_speed, wavnc_set_bits, wavnc_set_channels }; static struct mixer_operations wavnc_mixer_operations = { "WaveArtisr", "WaveArtist VNC", wavnc_mixer_ioctl }; static int wavnc_open (int dev, int mode) { wavnc_info *devc = NULL; 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->dual_dma = 0; if (audio_devs[dev]->flags & DMA_DUPLEX) { devc->dual_dma = 1; } devc->intr_active = 0; devc->audio_mode = 0; devc->open_mode |= mode; portc->open_mode = mode; wavnc_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). */ wa_mute (devc,1); return 0; } static void wavnc_close (int dev) { unsigned long flags; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; // DEB (printk ("wavnc_close(void)\n")); save_flags (flags); cli (); devc->intr_active = 0; wavnc_halt (dev); devc->audio_mode = 0; devc->open_mode &= ~portc->open_mode; portc->open_mode = 0; wa_mute (devc,1); restore_flags (flags); } static int wavnc_ioctl (int dev, unsigned int cmd, caddr_t arg) { return -EINVAL; } static void wavnc_output_block (int dev, unsigned long buf, int count, int intrflag) { unsigned long flags, cnt; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; DEB1(printk("wavnc: output block...\n")); cnt = count; if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */ cnt >>= 1; if (portc->channels > 1) cnt >>= 1; cnt--; if (devc->audio_mode & PCM_ENABLE_OUTPUT && audio_devs[dev]->flags & DMA_AUTOMODE && intrflag && cnt == devc->xfer_count) { devc->audio_mode |= PCM_ENABLE_OUTPUT; devc->intr_active = 1; return; /* * Auto DMA mode on. No need to react */ } save_flags (flags); cli (); wa_writecmd(0x24,cnt); devc->xfer_count = cnt; devc->audio_mode |= PCM_ENABLE_OUTPUT; devc->intr_active = 1; restore_flags (flags); } static void wavnc_start_input (int dev, unsigned long buf, int count, int intrflag) { unsigned long flags, cnt; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; DEB1(printk("wavnc: start input, buf=0x%X, count=0x%X...\n",buf,count)); cnt = count; if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */ cnt >>= 1; if (portc->channels > 1) cnt >>= 1; cnt--; if (devc->audio_mode & PCM_ENABLE_INPUT && audio_devs[dev]->flags & DMA_AUTOMODE && intrflag && cnt == devc->xfer_count) { devc->audio_mode |= PCM_ENABLE_INPUT; devc->intr_active = 1; return; /* * Auto DMA mode on. No need to react */ } save_flags (flags); cli (); wa_writecmd(0x14,cnt); //set sample count wa_mute (devc,0); devc->xfer_count = cnt; devc->audio_mode |= PCM_ENABLE_INPUT; devc->intr_active = 1; restore_flags (flags); } static int wavnc_prepare_for_output (int dev, int bsize, int bcount) { int tmp, foo; unsigned long flags; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; //DEB1(printk("wavnc: prepare for output...\n")); //program the speed, channels, bits if (portc->speed == 8000) foo = 0x2E71; else if (portc->speed == 11025) foo = 0x4000; else if (portc->speed == 22050) foo = 0x8000; else if (portc->speed == 44100) foo = 0x0; else { tmp = portc->speed << 16; foo = tmp / 44100; foo &= 0xFFFF; } save_flags (flags); cli (); tmp = wa_writecmd(0x22, foo); //write cmd SetSampleSpeedTimeConstant if (tmp != 1) { //lets retry... tmp = wa_writecmd(0x22, foo); if (tmp != 1) printk("WaveArtist: error setting the playback speed to %dHz.\n", portc->speed); } tmp = wa_writecmd(0x21,portc->channels); if (tmp != 1) printk("WaveArtist: error setting the playback MONO/STEREO to 0x%X...\n", portc->channels); if (portc->audio_format == AFMT_S16_LE) foo = 1; else if (portc->audio_format == AFMT_S8) foo = 0; else foo = 2; //default AFMT_U8 tmp = wa_writecmd(0x20,foo); if (tmp != 1) printk("WaveArtist: error setting the playback format to 0x%X...\n", portc->audio_format); restore_flags (flags); devc->xfer_count = 0; wavnc_halt_output (dev); return 0; } static int wavnc_prepare_for_input (int dev, int bsize, int bcount) { int tmp, wa_speed, wa_bits; unsigned long flags; wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; if (devc->audio_mode) return 0; //program the speed, channels, bits if (portc->speed == 8000) wa_speed = 0x2E71; else if (portc->speed == 11025) wa_speed = 0x4000; else if (portc->speed == 22050) wa_speed = 0x8000; else if (portc->speed == 44100) wa_speed = 0x0; else { tmp = portc->speed << 16; //non-standard - just calculate wa_speed = tmp / 44100; wa_speed &= 0xFFFF; } if (portc->audio_format == AFMT_S16_LE) wa_bits = 1; else if (portc->audio_format == AFMT_S8) wa_bits = 0; else wa_bits = 2; //default AFMT_U8 save_flags (flags); cli (); tmp = wa_writecmd(0x10,wa_bits); if (tmp != 1) printk("WaveArtist: error setting the record format to 0x%X...\n", portc->audio_format); tmp = wa_writecmd(0x11,portc->channels); if (tmp != 1) printk("WaveArtist: error setting the record MONO/STEREO to 0x%X...\n", portc->channels); tmp = wa_writecmd(0x12, wa_speed); //write cmd SetSampleSpeedTimeConstant if (tmp != 1) printk("WaveArtist: error setting the record speed to %dHz.\n", portc->speed); tmp = wa_writecmd(0x13,1); if (tmp != 1) printk("WaveArtist: error setting the record data path to 0x%X...\n", 1); tmp = wa_writecmd(0x10,wa_bits); if (tmp != 1) printk("WaveArtist: error setting the record format to 0x%X...\n", portc->audio_format); restore_flags (flags); devc->xfer_count = 0; wavnc_halt_input (dev); //DEB(printk("WA CTLR reg: 0x%02X.\n",inb(CTLR))); //DEB(printk("WA STAT reg: 0x%02X.\n",inb(STATR))); //DEB(printk("WA IRQS reg: 0x%02X.\n",inb(IRQSTAT))); return 0; } static void wavnc_halt (int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc; if (portc->open_mode & OPEN_WRITE) wavnc_halt_output (dev); if (portc->open_mode & OPEN_READ) wavnc_halt_input (dev); devc->audio_mode = 0; } static void wavnc_halt_input (int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; save_flags (flags); cli (); wa_mute (devc,1); disable_dma (audio_devs[dev]->dmap_in->dma); wa_sendcmd(0x17); /* Stop capture */ enable_dma (audio_devs[dev]->dmap_in->dma); devc->audio_mode &= ~PCM_ENABLE_INPUT; if (inb(STATR) & IRQ_REQ) /* Clear interrupt */ { int temp; temp = inb(CTLR); //by toggling the IRQ_ACK bit in CTRL outb(temp|IRQ_ACK,CTLR); outb(temp&(~IRQ_ACK),CTLR); } // devc->audio_mode &= ~PCM_ENABLE_INPUT; restore_flags (flags); } static void wavnc_halt_output (int dev) { wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc; unsigned long flags; save_flags (flags); cli (); wa_mute (devc,1); disable_dma (audio_devs[dev]->dmap_out->dma); wa_sendcmd(0x27); enable_dma (audio_devs[dev]->dmap_out->dma); devc->audio_mode &= ~PCM_ENABLE_OUTPUT; if (inb(STATR) & IRQ_REQ) /* Clear interrupt */ { int temp; temp = inb(CTLR); //by toggling the IRQ_ACK bit in CTRL outb(temp|IRQ_ACK,CTLR); outb(temp&(~IRQ_ACK),CTLR); } // devc->audio_mode &= ~PCM_ENABLE_OUTPUT; restore_flags (flags); } static void wavnc_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; DEB1(printk("wavnc: audio trigger...\n")); save_flags (flags); cli (); state &= devc->audio_mode; if (portc->open_mode & OPEN_READ) { if (state & PCM_ENABLE_INPUT) { wa_sendcmd(0x15); } } if (portc->open_mode & OPEN_WRITE) { if (state & PCM_ENABLE_OUTPUT) { wa_sendcmd(0x25); } } wa_mute(devc,0); restore_flags (flags); } static void wavnc_init_hw (wavnc_info * devc) { /* * Initial values for the indirect registers of CS4248/AD1848. */ // wa_mute (devc,1); /* Initialize some variables */ wa_mute (devc,0); /* Leave it unmuted now */ devc->audio_flags |= DMA_DUPLEX; wavnc_mixer_reset (devc); } int wavnc_detect (int io_base, int *ad_flags, int *osp) { wavnc_info *devc = &adev_info; if (nr_wavnc_devs >= MAX_AUDIO_DEV) { printk ("WaveArtist: Too many audio devices\n"); return 0; } devc->base = io_base; devc->irq_ok = 0; devc->irq = 0; devc->open_mode = 0; devc->chip_name = devc->name = "RWA010"; devc->debug_flag = 0; devc->wa_osp = osp; return 1; } void wavnc_init (char *name, int io_base, int irq, int dma_playback, int dma_capture, int share_dma, int *wa_osp) { /* * 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). */ int my_dev; char dev_name[100]; int temp,temp1,temp2; wavnc_info *devc = &adev_info; wavnc_port_info *portc = NULL; devc->irq = (irq > 0) ? irq : 0; devc->open_mode = 0; devc->dma1 = dma_playback; devc->dma2 = dma_capture; devc->audio_flags = DMA_AUTOMODE; devc->playback_dev = devc->record_dev = 0; if (name != NULL) devc->name = name; //first reset the WA chip by toggling the RESET bit in CTRL temp2 = 0x100000; outb(RESET,CTLR); temp1 = jiffies + 2; //for at least 10 ms while ((temp1 > jiffies) && temp2) { --temp2; } outb(0x00,CTLR); //toggle it back temp2 = 0x0F000000; temp1 = jiffies + 100; //wait up-to 1 sec temp = 0; while ((temp1 > jiffies) && (--temp2) && (temp != 0x55AA)) { temp = jiffies+2; //wait couple miliseconds while (temp != jiffies) {}; temp = 0; if(inb(STATR) & CMD_RF) { temp = inw(CMDR); //as the result of reset - we want to see 55AA if (temp != 0x55AA) printk("WaveArtist reset error! (0x%04X).\n",temp); } } wa_sendcmd(00); tenmicrosec(wa_osp); // while(!(inb(STATR) & CMD_RF)){};//wait for response temp = inw(CMDR); tenmicrosec(wa_osp); tenmicrosec(wa_osp); // while(!(inb(STATR) & CMD_RF)){};//wait for response inw(CMDR); // discard second word == 0 printk("Sound by WaveArtist, rev.%c%c...\n", temp>>8, temp&0xFF); if (name != NULL && name[0] != 0) { if (!temp) sprintf (dev_name,"%s (%s)", name, devc->chip_name); else sprintf (dev_name,"%s (%s rev.%c%c)", name, devc->chip_name, temp>>8, temp&0xFF); } else sprintf (dev_name, "Generic audio codec (%s)", devc->chip_name); request_region (devc->base, 0x0F, devc->name); conf_printf2 (dev_name, devc->base, devc->irq, dma_playback, dma_capture); if (devc->dma1 == devc->dma2 || devc->dma2 == -1 || devc->dma1 == -1) devc->audio_flags &= ~DMA_DUPLEX; else devc->audio_flags |= DMA_DUPLEX; if ((my_dev = sound_install_audiodrv (AUDIO_DRIVER_VERSION, dev_name, &wavnc_audio_driver, sizeof (struct audio_driver), devc->audio_flags, AFMT_U8 | AFMT_S16_LE | AFMT_S8, devc, dma_playback, dma_capture)) < 0) { return; } portc = (wavnc_port_info *) (sound_mem_blocks[sound_nblocks] = vmalloc (sizeof (wavnc_port_info))); sound_mem_sizes[sound_nblocks] = sizeof (wavnc_port_info); if (sound_nblocks < 1024) sound_nblocks++;; audio_devs[my_dev]->portc = portc; memset ((char *) portc, 0, sizeof (*portc)); nr_wavnc_devs++; wavnc_init_hw (devc); if (inb(STATR) & IRQ_REQ) /* Clear interrupt */ { int temp; temp = inb(CTLR); //by toggling the IRQ_ACK bit in CTRL outb(temp|IRQ_ACK,CTLR); outb(temp&(~IRQ_ACK),CTLR); } if (snd_set_irq_handler (devc->irq, waintr, devc->name, NULL) < 0) { printk ("WaveArtist: IRQ in use\n"); } devc->irq_ok = 1; /* Couldn't test. assume it's OK */ //enable WaveArtist DMA interrupts.. outb( (inb(CTLR)|(DMA1_IE | DMA0_IE)), CTLR); init_timer(&wa_timer); wa_timer.function = wa_slider; wa_timer.expires = jiffies + WA_TIMER_PERIOD; add_timer(&wa_timer); if (!share_dma) { if (sound_alloc_dma (dma_playback, devc->name)) printk ("WaveArtist: Can't allocate DMA%d\n", dma_playback); if (dma_capture != dma_playback) if (sound_alloc_dma (dma_capture, devc->name)) printk ("WaveArtist: Can't allocate DMA%d\n", dma_capture); } if (sound_install_mixer (MIXER_DRIVER_VERSION, dev_name, &wavnc_mixer_operations, sizeof (struct mixer_operations), devc) >= 0) { audio_devs[my_dev]->mixer_dev = num_mixers - 1; } } void wavnc_control (int cmd, int arg) { wavnc_info *devc; if (nr_wavnc_devs < 1) return; // devc = &adev_info[nr_wavnc_devs - 1]; devc = &adev_info; // switch (cmd) { #if 0 case wavnc_SET_XTAL: /* Change clock frequency of AD1845 (only ) */ if (devc->model != MD_1845) return; ad_enter_MCE (devc); ad_write (devc, 29, (ad_read (devc, 29) & 0x1f) | (arg << 5)); ad_leave_MCE (devc); break; #endif #if 0 case wavnc_MIXER_REROUTE: { int o = (arg >> 8) & 0xff; int n = arg & 0xff; if (n == SOUND_MIXER_NONE) { /* Just hide this control */ wavnc_mixer_set (devc, o, 0); /* Shut up it */ devc->supported_devices &= ~(1 << o); devc->supported_rec_devices &= ~(1 << o); return; } /* Make the mixer control identified by o to appear as n */ if (o < 0 || o > SOUND_MIXER_NRDEVICES) return; if (n < 0 || n > SOUND_MIXER_NRDEVICES) return; if (!(devc->supported_devices & (1 << o))) return; /* Not supported */ devc->mixer_reroute[n] = o; /* Rename the control */ devc->supported_devices &= ~(1 << o); devc->supported_devices |= (1 << n); if (devc->supported_rec_devices & (1 << o)) devc->supported_rec_devices |= (1 << n); devc->supported_rec_devices &= ~(1 << o); } break; #endif } } void wavnc_unload (int io_base, int irq, int dma_playback, int dma_capture, int share_dma) { int dev = 0; wavnc_info *devc = NULL; devc = &adev_info; release_region (devc->base, 0x0F); if (!share_dma) { //disable WaveArtist DMA interrupts.. outb((inb(CTLR) & (~(DMA1_IE | DMA0_IE))),CTLR); if (irq > 0) snd_release_irq (devc->irq); sound_free_dma (audio_devs[dev]->dmap_out->dma); if (audio_devs[dev]->dmap_in->dma != audio_devs[dev]->dmap_out->dma) sound_free_dma (audio_devs[dev]->dmap_in->dma); } del_timer(&wa_timer); } void waintr (int irq, void *dev_id, struct pt_regs *dummy) { int status; int irqstatus; int temp; wavnc_info *devc = &adev_info; irqstatus = inb(IRQSTAT); status = inb(STATR); DEB(printk("wavnc: interrupt: status=0x%02X, irqstatus=0x%02X...\n",status,irqstatus)); if (status & IRQ_REQ) /* Clear interrupt */ { temp = inb(CTLR); //by toggling the IRQ_ACK bit in CTRL outb(temp|IRQ_ACK,CTLR); outb(temp&(~IRQ_ACK),CTLR); } else { printk("WaveArtist: unexpected audio interrupt!\n"); } if (irqstatus & 0x01) //WA IRQ { #ifdef CONFIG_AUDIO /* * PCM buffer done */ #if 0 /* * Halt the PCM first. Otherwise we don't have time to start a new * block before the PCM chip proceeds to the next sample */ if (!(devc->flags & DMA_AUTOMODE)) { wa_sendcmd(0x27); } #endif temp = 1; if ((status & DMA0) && (devc->audio_mode & PCM_DAC)) { DMAbuf_outputintr (devc->playback_dev, 1); temp = 0; } if ((status & DMA1) && (devc->audio_mode & PCM_ADC)) { DMAbuf_inputintr (devc->record_dev); temp = 0; } if (temp) //default: { printk ("WaveArtist: Unknown interrupt\n"); } #endif } if (irqstatus & 0x2) //SB IRQ { // We do not use SB mode natively... printk("WaveArtist: Unexpected SB interrupt...\n"); //#ifdef CONFIG_MIDI // wa_midi_interrupt (); //#endif } } int probe_ms_sound (struct address_info *hw_config) { DDB (printk ("Entered probe_ms_sound(%x, %d)\n", hw_config->io_base, hw_config->card_subtype)); if (check_region (hw_config->io_base, 0x0F)) { printk ("WaveArtist: I/O port conflict\n"); return 0; } if (hw_config->irq > 31 || hw_config->irq < 16 ) { printk ("WaveArtist: Bad IRQ %d\n", hw_config->irq); return 0; } if (hw_config->dma != 3) { printk ("WaveArtist: Bad DMA %d\n", hw_config->dma); return 0; } return wavnc_detect (hw_config->io_base, NULL, hw_config->osp); } void attach_ms_sound (struct address_info *hw_config) { int dma = hw_config->dma; int dma2 = hw_config->dma2; wavnc_init ("WaveArtist VNC", hw_config->io_base, hw_config->irq, dma, dma2, 0, hw_config->osp); } void unload_ms_sound (struct address_info *hw_config) { wavnc_unload (hw_config->io_base, hw_config->irq, hw_config->dma, hw_config->dma2, 0); } #endif #if 0 //Source for a wamixer test program capable to change all mixer registers // in real time... //Woody, April 1998 #include #include #include #include #include #include #define MIXER_PRIVATE3_RESET 0x53570000 #define MIXER_PRIVATE3_READ 0x53570001 #define MIXER_PRIVATE3_WRITE 0x53570002 int mixer_fd=-1; unsigned supported; int source; int mixer_regs[15]; main(int argc, char *argv[]) { unsigned int r, d; mixer_fd=open("/dev/mixer",O_RDWR); if(mixer_fd<0){ perror("wamixer: Mixer not found!"); exit(-1); } mixer_regs[0x0E] = MIXER_PRIVATE3_READ; source = &mixer_regs; source = ioctl(mixer_fd,SOUND_MIXER_PRIVATE3,&source); if (source < 0) printf("Mixer Private read returned error 0x%X!\n", source); if (argc == 1) { printf("0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012 0013 0014\n"); for (r=0; r<14; r++) printf("%04X ", mixer_regs[r]); printf("\n"); } else if (argc%2 == 1) { while (argc > 1) { r = atoi(argv[1]); sscanf(argv[2], "%i", &d); printf("Register %d <- %04X\n", r, d); mixer_regs[r-1] = d; argc -= 2; argv += 2; } mixer_regs[0x0E] = MIXER_PRIVATE3_WRITE; source = &mixer_regs; source = ioctl(mixer_fd,SOUND_MIXER_PRIVATE3,&source); if (source < 0) printf("Mixer Private write returned error 0x%X!\n", source); } else { printf("usage: wamixer - dump registers\n"); printf(" wamixer reg val [reg val ...] - program WA mixer register\n"); printf(" reg is decimal, value is hex (0x..) or decimal\n"); printf(" For example:-\n"); printf(" wamixer 9 0x982D 4 0x4C08 8 0x8C08\n"); exit(1); } close(mixer_fd); } #endif