SYSLIB CHANGES: - DS calls to publish / retrieve labels consider endpoints instead of u32_t. VFS CHANGES: - mapdriver() only adds an entry in the dmap table in VFS. - dev_up() is only executed upon reception of a driver up event. INET CHANGES: - INET no longer searches for existing drivers instances at startup. - A newtwork driver is (re)initialized upon reception of a driver up event. - Networking startup is now race-free by design. No need to waste 5 seconds at startup any more. DRIVER CHANGES: - Every driver publishes driver up events when starting for the first time or in case of restart when recovery actions must be taken in the upper layers. - Driver up events are published by drivers through DS. - For regular drivers, VFS is normally the only subscriber, but not necessarily. For instance, when the filter driver is in use, it must subscribe to driver up events to initiate recovery. - For network drivers, inet is the only subscriber for now. - Every VFS driver is statically linked with libdriver, every network driver is statically linked with libnetdriver. DRIVER LIBRARIES CHANGES: - Libdriver is extended to provide generic receive() and ds_publish() interfaces for VFS drivers. - driver_receive() is a wrapper for sef_receive() also used in driver_task() to discard spurious messages that were meant to be delivered to a previous version of the driver. - driver_receive_mq() is the same as driver_receive() but integrates support for queued messages. - driver_announce() publishes a driver up event for VFS drivers and marks the driver as initialized and expecting a DEV_OPEN message. - Libnetdriver is introduced to provide similar receive() and ds_publish() interfaces for network drivers (netdriver_announce() and netdriver_receive()). - Network drivers all support live update with no state transfer now. KERNEL CHANGES: - Added kernel call statectl for state management. Used by driver_announce() to unblock eventual callers sendrecing to the driver.
417 lines
11 KiB
C
417 lines
11 KiB
C
/* This file contains the driver for the mixer on
|
|
* a SoundBlaster 16 soundcard.
|
|
*
|
|
* The driver supports the following operations (using message format m2):
|
|
*
|
|
* m_type DEVICE IO_ENDPT 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:
|
|
*
|
|
* sb16mixer_task: main entry when system is brought up
|
|
*
|
|
* August 24 2005 Ported driver to user space (Peter Boonstoppel)
|
|
* May 20 1995 Author: Michel R. Prevenier
|
|
*/
|
|
|
|
|
|
#include "sb16.h"
|
|
|
|
|
|
_PROTOTYPE(void main, (void));
|
|
FORWARD _PROTOTYPE( int mixer_init, (void));
|
|
FORWARD _PROTOTYPE( int mixer_open, (const message *m_ptr));
|
|
FORWARD _PROTOTYPE( int mixer_close, (const message *m_ptr));
|
|
FORWARD _PROTOTYPE( int mixer_ioctl, (const message *m_ptr));
|
|
FORWARD _PROTOTYPE( int mixer_get, (int reg));
|
|
FORWARD _PROTOTYPE( int get_set_volume, (const message *m_ptr, int flag));
|
|
FORWARD _PROTOTYPE( int get_set_input, (const message *m_ptr, int flag, int channel));
|
|
FORWARD _PROTOTYPE( int get_set_output, (const message *m_ptr, int flag));
|
|
|
|
|
|
PRIVATE int mixer_avail = 0; /* Mixer exists? */
|
|
|
|
|
|
#define dprint (void)
|
|
|
|
/* SEF functions and variables. */
|
|
FORWARD _PROTOTYPE( void sef_local_startup, (void) );
|
|
FORWARD _PROTOTYPE( int sef_cb_init_fresh, (int type, sef_init_info_t *info) );
|
|
|
|
/*===========================================================================*
|
|
* main
|
|
*===========================================================================*/
|
|
PUBLIC void main() {
|
|
message mess;
|
|
int ipc_status;
|
|
int err, caller, proc_nr;
|
|
|
|
/* SEF local startup. */
|
|
sef_local_startup();
|
|
|
|
/* Here is the main loop of the mixer task. It waits for a message, carries
|
|
* it out, and sends a reply.
|
|
*/
|
|
while (TRUE) {
|
|
driver_receive(ANY, &mess, &ipc_status);
|
|
|
|
caller = mess.m_source;
|
|
proc_nr = mess.IO_ENDPT;
|
|
|
|
switch (caller) {
|
|
case HARDWARE: /* Leftover interrupt. */
|
|
continue;
|
|
case FS_PROC_NR: /* The only legitimate caller. */
|
|
break;
|
|
default:
|
|
dprint("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;
|
|
#ifdef DEV_IOCTL
|
|
case DEV_IOCTL: err = mixer_ioctl(&mess); break;
|
|
#endif
|
|
default: err = EINVAL; break;
|
|
}
|
|
|
|
/* Finally, prepare and send the reply message. */
|
|
mess.m_type = TASK_REPLY;
|
|
mess.REP_ENDPT = proc_nr;
|
|
|
|
dprint("%d %d", err, OK);
|
|
|
|
mess.REP_STATUS = err; /* error code */
|
|
send(caller, &mess); /* send reply to caller */
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_local_startup *
|
|
*===========================================================================*/
|
|
PRIVATE void sef_local_startup()
|
|
{
|
|
/* Register init callbacks. */
|
|
sef_setcb_init_fresh(sef_cb_init_fresh);
|
|
sef_setcb_init_lu(sef_cb_init_fresh);
|
|
sef_setcb_init_restart(sef_cb_init_fresh);
|
|
|
|
/* Register live update callbacks. */
|
|
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
|
|
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
|
|
|
|
/* Let SEF perform startup. */
|
|
sef_startup();
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_cb_init_fresh *
|
|
*===========================================================================*/
|
|
PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *info)
|
|
{
|
|
/* Initialize the sb16 mixer driver. */
|
|
/* Announce we are up! */
|
|
driver_announce();
|
|
|
|
return(OK);
|
|
}
|
|
|
|
/*=========================================================================*
|
|
* mixer_open
|
|
*=========================================================================*/
|
|
PRIVATE int mixer_open(const message *m_ptr)
|
|
{
|
|
dprint("mixer_open\n");
|
|
|
|
/* try to detect the mixer type */
|
|
if (!mixer_avail && mixer_init() != OK) return EIO;
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* mixer_close
|
|
*=========================================================================*/
|
|
PRIVATE int mixer_close(const message *m_ptr)
|
|
{
|
|
dprint("mixer_close\n");
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* mixer_ioctl
|
|
*=========================================================================*/
|
|
PRIVATE int mixer_ioctl(const message *m_ptr)
|
|
{
|
|
int status;
|
|
|
|
dprint("mixer: got ioctl %d\n", m_ptr->REQUEST);
|
|
|
|
|
|
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) {
|
|
dprint("sb16: Mixer not detected\n");
|
|
return EIO;
|
|
}
|
|
|
|
/* Enable Automatic Gain Control */
|
|
mixer_set(MIXER_AGC, 0x01);
|
|
|
|
dprint("Mixer detected\n");
|
|
|
|
mixer_avail = 1;
|
|
return OK;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* mixer_get
|
|
*=========================================================================*/
|
|
PRIVATE int mixer_get(int reg)
|
|
{
|
|
int i;
|
|
|
|
sb16_outb(MIXER_REG, reg);
|
|
for(i = 0; i < 100; i++);
|
|
return sb16_inb(MIXER_DATA) & 0xff;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* get_set_volume *
|
|
*=========================================================================*/
|
|
PRIVATE int get_set_volume(const message *m_ptr, int flag)
|
|
/* flag 0 = get, 1 = set */
|
|
{
|
|
struct volume_level level;
|
|
int cmd_left, cmd_right, shift, max_level;
|
|
|
|
sys_datacopy(m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, SELF, (vir_bytes)&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 */
|
|
sys_datacopy(SELF, (vir_bytes)&level, m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, (phys_bytes)sizeof(level));
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* get_set_input *
|
|
*=========================================================================*/
|
|
PRIVATE int get_set_input(const message *m_ptr, int flag, int channel)
|
|
/*
|
|
* flag 0 = get, 1 = set
|
|
* channel 0 = left, 1 = right
|
|
*/
|
|
{
|
|
struct inout_ctrl input;
|
|
int input_cmd, input_mask, mask, del_mask, shift;
|
|
|
|
sys_datacopy(m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, SELF, (vir_bytes)&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 */
|
|
sys_datacopy(SELF, (vir_bytes)&input, m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, (phys_bytes)sizeof(input));
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/*=========================================================================*
|
|
* get_set_output *
|
|
*=========================================================================*/
|
|
PRIVATE int get_set_output(const message *m_ptr, int flag)
|
|
/* flag 0 = get, 1 = set */
|
|
{
|
|
struct inout_ctrl output;
|
|
int output_mask, mask, del_mask, shift;
|
|
|
|
sys_datacopy(m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, SELF, (vir_bytes)&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 */
|
|
sys_datacopy(SELF, (vir_bytes)&output, m_ptr->IO_ENDPT, (vir_bytes)m_ptr->ADDRESS, (phys_bytes)sizeof(output));
|
|
}
|
|
|
|
return OK;
|
|
}
|