/* This file contains the driver for the mixer on * a SoundBlaster 16 (ASP) soundcard. * * The driver supports the following operations (using message format m2): * * m_type DEVICE PROC_NR COUNT POSITION ADRRESS * ---------------------------------------------------------------- * | DEV_OPEN | device | proc nr | | | | * |------------+---------+---------+---------+---------+---------| * | DEV_CLOSE | device | proc nr | | | | * |------------+---------+---------+---------+---------+---------| * | DEV_IOCTL | device | proc nr |func code| | buf_ptr | * ---------------------------------------------------------------- * * The file contains one entry point: * * mixer_task: main entry when system is brought up * * May 20 1995 Author: Michel R. Prevenier */ #include "kernel.h" #include <minix/com.h> #include <minix/callnr.h> #include <sys/ioctl.h> #include <minix/sound.h> #if __minix_vmd #include "config.h" #endif #include "sb16.h" #if ENABLE_SB_AUDIO /* Function prototypes */ FORWARD _PROTOTYPE( int mixer_init, (void)); FORWARD _PROTOTYPE( int mixer_open, (message *m_ptr)); FORWARD _PROTOTYPE( int mixer_close, (message *m_ptr)); FORWARD _PROTOTYPE( int mixer_ioctl, (message *m_ptr)); FORWARD _PROTOTYPE( int mixer_get, (int reg)); FORWARD _PROTOTYPE( int get_set_volume, (message *m_ptr, int flag)); FORWARD _PROTOTYPE( int get_set_input, (message *m_ptr, int flag, int channel)); FORWARD _PROTOTYPE( int get_set_output, (message *m_ptr, int flag)); PRIVATE int mixer_avail = 0; /* Mixer exists? */ /*=========================================================================* * mixer_task * *=========================================================================*/ PUBLIC void mixer_task() { message mess; int err, caller, proc_nr; /* Here is the main loop of the mixer task. It waits for a message, carries * it out, and sends a reply. */ while (TRUE) { receive(ANY, &mess); caller = mess.m_source; proc_nr = mess.PROC_NR; switch (caller) { case HARDWARE: /* Leftover interrupt. */ continue; case FS_PROC_NR: /* The only legitimate caller. */ break; default: printf("sb16: got message from %d\n", caller); continue; } /* Now carry out the work. */ switch(mess.m_type) { case DEV_OPEN: err = mixer_open(&mess);break; case DEV_CLOSE: err = mixer_close(&mess);break; case DEV_IOCTL: err = mixer_ioctl(&mess);break; default: err = EINVAL;break; } /* Finally, prepare and send the reply message. */ mess.m_type = TASK_REPLY; mess.REP_PROC_NR = proc_nr; mess.REP_STATUS = err; /* error code */ send(caller, &mess); /* send reply to caller */ } } /*=========================================================================* * mixer_open * *=========================================================================*/ PRIVATE int mixer_open(m_ptr) message *m_ptr; { #if SB_DEBUG printf("mixer_open\n"); #endif /* try to detect the mixer type */ if (!mixer_avail && !mixer_init() != OK) return EIO; return OK; } /*=========================================================================* * mixer_close * *=========================================================================*/ PRIVATE int mixer_close(m_ptr) message *m_ptr; { #if SB_DEBUG printf("mixer_close\n"); #endif return OK; } /*=========================================================================* * mixer_ioctl * *=========================================================================*/ PRIVATE int mixer_ioctl(m_ptr) message *m_ptr; { int status; #if SB_DEBUG printf("mixer: got ioctl %d\n", m_ptr->REQUEST); #endif switch(m_ptr->REQUEST) { case MIXIOGETVOLUME: status = get_set_volume(m_ptr, 0);break; case MIXIOSETVOLUME: status = get_set_volume(m_ptr, 1);break; case MIXIOGETINPUTLEFT: status = get_set_input(m_ptr, 0, 0);break; case MIXIOGETINPUTRIGHT: status = get_set_input(m_ptr, 0, 1);break; case MIXIOGETOUTPUT: status = get_set_output(m_ptr, 0);break; case MIXIOSETINPUTLEFT: status = get_set_input(m_ptr, 1, 0);break; case MIXIOSETINPUTRIGHT: status = get_set_input(m_ptr, 1, 1);break; case MIXIOSETOUTPUT: status = get_set_output(m_ptr, 1);break; default: status = ENOTTY; } return status; } /*=========================================================================* * mixer_init * *=========================================================================*/ PRIVATE int mixer_init() { /* Try to detect the mixer by writing to MIXER_DAC_LEVEL if the * value written can be read back the mixer is there */ mixer_set(MIXER_DAC_LEVEL, 0x10); /* write something to it */ if (mixer_get(MIXER_DAC_LEVEL) != 0x10) { printf("sb16: Mixer not detected\n"); return EIO; } /* Enable Automatic Gain Control */ mixer_set(MIXER_AGC, 0x01); #if SB_DEBUG printf("Mixer detected\n"); #endif mixer_avail = 1; return OK; } /*=========================================================================* * mixer_set * *=========================================================================*/ PUBLIC int mixer_set(reg, data) int reg; int data; { int i; out_byte(MIXER_REG, reg); for(i=0;i<100;i++); out_byte(MIXER_DATA, data); return OK; } /*=========================================================================* * mixer_get * *=========================================================================*/ PRIVATE int mixer_get(reg) int reg; { int i; out_byte(MIXER_REG, reg); for(i=0;i<100;i++); return (in_byte(MIXER_DATA) & 0xff); } /*=========================================================================* * get_set_volume * *=========================================================================*/ PRIVATE int get_set_volume(m_ptr, flag) message *m_ptr; int flag; /* 0 = get, 1 = set */ { phys_bytes user_phys; struct volume_level level; int cmd_left, cmd_right, shift, max_level; user_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, sizeof(struct volume_level)); if (user_phys == 0) return(EFAULT); phys_copy(user_phys, vir2phys(&level), (phys_bytes) sizeof(level)); shift = 3; max_level = 0x1F; switch (level.device) { case Master: { cmd_left = MIXER_MASTER_LEFT; cmd_right = MIXER_MASTER_RIGHT; };break; case Dac: { cmd_left = MIXER_DAC_LEFT; cmd_right = MIXER_DAC_RIGHT; };break; case Fm: { cmd_left = MIXER_FM_LEFT; cmd_right = MIXER_FM_RIGHT; };break; case Cd: { cmd_left = MIXER_CD_LEFT; cmd_right = MIXER_CD_RIGHT; };break; case Line: { cmd_left = MIXER_LINE_LEFT; cmd_right = MIXER_LINE_RIGHT; };break; case Mic: { cmd_left = cmd_right = MIXER_MIC_LEVEL; };break; case Speaker: { cmd_left = cmd_right = MIXER_PC_LEVEL; shift = 6; max_level = 0x03; };break; case Treble: { cmd_left = MIXER_TREBLE_LEFT; cmd_right = MIXER_TREBLE_RIGHT; shift = 4; max_level = 0x0F; };break; case Bass: { cmd_left = MIXER_BASS_LEFT; cmd_right = MIXER_BASS_RIGHT; shift = 4; max_level = 0x0F; };break; default: return EINVAL; } if (flag) /* Set volume level */ { if (level.right < 0) level.right = 0; else if (level.right > max_level) level.right = max_level; if (level.left < 0) level.left = 0; else if (level.left > max_level) level.left = max_level; mixer_set(cmd_right, (level.right << shift)); mixer_set(cmd_left, (level.left << shift)); } else /* Get volume level */ { level.left = mixer_get(cmd_left); level.right = mixer_get(cmd_right); level.left >>= shift; level.right >>= shift; /* Copy back to user */ phys_copy(vir2phys(&level), user_phys, (phys_bytes) sizeof(level)); } return OK; } /*=========================================================================* * get_set_input * *=========================================================================*/ PRIVATE int get_set_input(m_ptr, flag, channel) message *m_ptr; int flag; /* 0 = get, 1 = set */ int channel; /* 0 = left, 1 = right */ { phys_bytes user_phys; struct inout_ctrl input; int input_cmd, input_mask, mask, del_mask, shift; user_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, sizeof(struct inout_ctrl)); if (user_phys == 0) return(EFAULT); phys_copy(user_phys, vir2phys(&input), (phys_bytes) sizeof(input)); input_cmd = (channel == 0 ? MIXER_IN_LEFT : MIXER_IN_RIGHT); mask = mixer_get(input_cmd); switch (input.device) { case Fm: { shift = 5; del_mask = 0x1F; };break; case Cd: { shift = 1; del_mask = 0x79; };break; case Line: { shift = 3; del_mask = 0x67; };break; case Mic: { shift = 0; del_mask = 0x7E; };break; default: return EINVAL; } if (flag) /* Set input */ { input_mask = ((input.left == ON ? 1 : 0) << 1) | (input.right == ON ? 1 : 0); if (shift > 0) input_mask <<= shift; else input_mask >>= 1; mask &= del_mask; mask |= input_mask; mixer_set(input_cmd, mask); } else /* Get input */ { if (shift > 0) { input.left = ((mask >> (shift+1)) & 1 == 1 ? ON : OFF); input.right = ((mask >> shift) & 1 == 1 ? ON : OFF); } else input.left = ((mask & 1) == 1 ? ON : OFF); /* Copy back to user */ phys_copy(vir2phys(&input), user_phys, (phys_bytes) sizeof(input)); } return OK; } /*=========================================================================* * get_set_output * *=========================================================================*/ PRIVATE int get_set_output(m_ptr, flag) message *m_ptr; int flag; /* 0 = get, 1 = set */ { phys_bytes user_phys; struct inout_ctrl output; int output_mask, mask, del_mask, shift; user_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, sizeof(struct inout_ctrl)); if (user_phys == 0) return(EFAULT); phys_copy(user_phys, vir2phys(&output), (phys_bytes) sizeof(output)); mask = mixer_get(MIXER_OUTPUT_CTRL); switch (output.device) { case Cd: { shift = 1; del_mask = 0x79; };break; case Line: { shift = 3; del_mask = 0x67; };break; case Mic: { shift = 0; del_mask = 0x7E; };break; default: return EINVAL; } if (flag) /* Set input */ { output_mask = ((output.left == ON ? 1 : 0) << 1) | (output.right == ON ? 1 : 0); if (shift > 0) output_mask <<= shift; else output_mask >>= 1; mask &= del_mask; mask |= output_mask; mixer_set(MIXER_OUTPUT_CTRL, mask); } else /* Get input */ { if (shift > 0) { output.left = ((mask >> (shift+1)) & 1 == 1 ? ON : OFF); output.right = ((mask >> shift) & 1 == 1 ? ON : OFF); } else output.left = ((mask & 1) == 1 ? ON : OFF); /* Copy back to user */ phys_copy(vir2phys(&output), user_phys, (phys_bytes) sizeof(output)); } return OK; } #endif /* ENABLE_AUDIO */