diff options
Diffstat (limited to 'kernel/drv/oss_solo')
-rw-r--r-- | kernel/drv/oss_solo/.config | 1 | ||||
-rw-r--r-- | kernel/drv/oss_solo/.devices | 1 | ||||
-rw-r--r-- | kernel/drv/oss_solo/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_solo/oss_solo.c | 1230 | ||||
-rw-r--r-- | kernel/drv/oss_solo/oss_solo.man | 19 |
5 files changed, 1252 insertions, 0 deletions
diff --git a/kernel/drv/oss_solo/.config b/kernel/drv/oss_solo/.config new file mode 100644 index 0000000..5280084 --- /dev/null +++ b/kernel/drv/oss_solo/.config @@ -0,0 +1 @@ +platform=i86pc diff --git a/kernel/drv/oss_solo/.devices b/kernel/drv/oss_solo/.devices new file mode 100644 index 0000000..6003e9e --- /dev/null +++ b/kernel/drv/oss_solo/.devices @@ -0,0 +1 @@ +oss_solo pci125d,1969 ESS Solo-1 diff --git a/kernel/drv/oss_solo/.name b/kernel/drv/oss_solo/.name new file mode 100644 index 0000000..4711141 --- /dev/null +++ b/kernel/drv/oss_solo/.name @@ -0,0 +1 @@ +ESS Solo-1 diff --git a/kernel/drv/oss_solo/oss_solo.c b/kernel/drv/oss_solo/oss_solo.c new file mode 100644 index 0000000..03605d9 --- /dev/null +++ b/kernel/drv/oss_solo/oss_solo.c @@ -0,0 +1,1230 @@ +/* + * Purpose: Driver for ESS Solo PCI audio controller. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "oss_solo_cfg.h" +#include "oss_pci.h" + +#define ESS_VENDOR_ID 0x125d +#define ESS_SOLO1 0x1969 + +#define DSP_RESET (devc->sb_base + 0x6) +#define DSP_READ (devc->sb_base + 0xA) +#define DSP_WRITE (devc->sb_base + 0xC) +#define DSP_WRSTATUS (devc->sb_base + 0xC) +#define DSP_STATUS (devc->sb_base + 0xE) +#define DSP_STATUS16 (devc->sb_base + 0xF) +#define MIXER_ADDR (devc->sb_base + 0x4) +#define MIXER_DATA (devc->sb_base + 0x5) +#define OPL3_LEFT (devc->sb_base + 0x0) +#define OPL3_RIGHT (devc->sb_base + 0x2) +#define OPL3_BOTH (devc->sb_base + 0x8) + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 + +#define SBPRO_RECORDING_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) +#define SBPRO_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) +#define SOLO_RECORDING_DEVICES (SBPRO_RECORDING_DEVICES) +#define SOLO_MIXER_DEVICES (SBPRO_MIXER_DEVICES|SOUND_MASK_LINE2|SOUND_MASK_SPEAKER|SOUND_MASK_RECLEV|SOUND_MASK_LINE1) + +#define LEFT_CHN 0 +#define RIGHT_CHN 1 + +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E +#define IRQ_NR 0x80 +#define DMA_NR 0x81 +#define IRQ_STAT 0x82 +#define OPSW 0x3c + +static int default_levels[32] = { + 0x5a5a, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x2020, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ + 0x4b4b, /* Output gain */ + 0x4040, /* Line1 */ + 0x4040, /* Line2 */ + 0x1515 /* Line3 */ +}; + +#define MAX_PORTC 2 + +typedef struct solo_portc +{ + int speed, bits, channels; + int open_mode; + int audiodev; + int trigger_bits; + int audio_enabled; +} +solo_portc; + +typedef struct solo_devc +{ + oss_device_t *osdev; + oss_native_word base, ddma_base, sb_base, mpu_base; + + int mpu_attached, fm_attached; + int irq; + char *chip_name; + oss_mutex_t mutex; + oss_mutex_t low_mutex; + oss_native_word last_capture_addr; + /* Audio parameters */ + solo_portc portc[MAX_PORTC]; + + /* Mixer parameters */ + int *levels; + int recmask; +} +solo_devc; + + +static int +solo_command (solo_devc * devc, unsigned char val) +{ + int i; + + for (i = 0; i < 0x10000; i++) + { + if ((INB (devc->osdev, DSP_WRSTATUS) & 0x80) == 0) + { + OUTB (devc->osdev, val, DSP_WRITE); + return 1; + } + } + + cmn_err (CE_WARN, "Command(%x) Timeout.\n", val); + return 0; +} + +static int +solo_get_byte (solo_devc * devc) +{ + int i; + + for (i=0; i < 0x10000; i++) + if (INB (devc->osdev, DSP_STATUS) & 0x80) + { + return INB (devc->osdev, DSP_READ); + } + + return 0xffff; +} + +static int +ext_read (solo_devc * devc, unsigned char reg) +{ + + if (!solo_command (devc, 0xc0)) /* Read register command */ + return OSS_EIO; + + if (!solo_command (devc, reg)) + return OSS_EIO; + + return solo_get_byte (devc); +} + +static int +ext_write (solo_devc * devc, unsigned char reg, unsigned char data) +{ + if (!solo_command (devc, reg)) + return 0; + return solo_command (devc, data); +} + +static unsigned int +solo_getmixer (solo_devc * devc, unsigned int port) +{ + unsigned int val; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR); + oss_udelay (50); + val = INB (devc->osdev, MIXER_DATA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return val; +} + +static void +solo_setmixer (solo_devc * devc, unsigned int port, unsigned int value) +{ + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR); + oss_udelay (50); + OUTB (devc->osdev, (unsigned char) (value & 0xff), MIXER_DATA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); +} + +static int +solo_reset (solo_devc * devc) +{ + int loopc; + + DDB (cmn_err (CE_WARN, "Entered solo_reset()\n")); + + OUTB (devc->osdev, 3, DSP_RESET); /* Reset FIFO too */ + INB (devc->osdev, DSP_RESET); /* Reset FIFO too */ + OUTB (devc->osdev, 0, DSP_RESET); + oss_udelay (10); + + for (loopc = 0; loopc < 0x10000; loopc++) + if (INB (devc->osdev, DSP_STATUS) & 0x80) + if (INB (devc->osdev, DSP_READ) != 0xAA) + { + DDB (cmn_err (CE_WARN, "No response to RESET\n")); + return 0; /* Sorry */ + } + solo_command (devc, 0xc6); /* Enable extended mode */ + + ext_write (devc, 0xb9, 3); /* Demand mode - set to reserve mode */ + solo_setmixer (devc, 0x71, 0x32); /* 4x sampling + DAC2 asynch */ + + /* enable DMA/IRQ */ + ext_write (devc, 0xb1, (ext_read (devc, 0xb1) & 0x0F) | 0x50); + ext_write (devc, 0xb2, (ext_read (devc, 0xb2) & 0x0F) | 0x50); + + DDB (cmn_err (CE_WARN, "solo_reset() OK\n")); + return 1; +} + +struct mixer_def +{ + unsigned int regno:8; + unsigned int bitoffs:4; + unsigned int nbits:4; +}; + +typedef struct mixer_def mixer_tab[32][2]; +typedef struct mixer_def mixer_ent; + +#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r) \ + {{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}} + +static mixer_tab solo_mix = { + MIX_ENT (SOUND_MIXER_VOLUME, 0x32, 7, 4, 0x32, 3, 4), + MIX_ENT (SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_SYNTH, 0x36, 7, 4, 0x36, 3, 4), + MIX_ENT (SOUND_MIXER_PCM, 0x7c, 7, 4, 0x7c, 3, 4), + MIX_ENT (SOUND_MIXER_SPEAKER, 0x3c, 2, 3, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_LINE, 0x3e, 7, 4, 0x3e, 3, 4), + MIX_ENT (SOUND_MIXER_MIC, 0x1a, 7, 4, 0x1a, 3, 4), + MIX_ENT (SOUND_MIXER_CD, 0x38, 7, 4, 0x38, 3, 4), + MIX_ENT (SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_ALTPCM, 0x7c, 7, 4, 0x7c, 3, 4), + MIX_ENT (SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4), + MIX_ENT (SOUND_MIXER_IGAIN, 0x68, 7, 4, 0x68, 3, 4), + MIX_ENT (SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_LINE1, 0x6d, 7, 4, 0x6d, 3, 4), + MIX_ENT (SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4), + MIX_ENT (SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0) +}; + +/*ARGSUSED*/ +static void +change_bits (solo_devc * devc, unsigned char *regval, int dev, int chn, + int newval) +{ + unsigned char mask; + int shift; + + mask = (1 << solo_mix[dev][chn].nbits) - 1; + newval = (int) ((newval * mask) + 50) / 100; /* Scale */ + + shift = solo_mix[dev][chn].bitoffs - solo_mix[dev][LEFT_CHN].nbits + 1; + + *regval &= ~(mask << shift); /* Mask out previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + +#if 0 +static int +ess_set_reclev (solo_devc * devc, int dev, int left, int right) +{ + unsigned char b; + + b = (((15 * right) / 100) << 4) | (((15 * left) / 100)); + + ext_write (devc, 0xb4, b); /* Change input volume control */ + devc->levels[dev] = left | (right << 8); + return left | (right << 8); +} + +static int +ess_set_altpcm (solo_devc * devc, int dev, int left, int right) +{ + unsigned char b; + + b = (((15 * right) / 100) << 4) | (((15 * left) / 100)); + solo_setmixer (devc, 0x7C, b); /* Change dac2 volume control */ + devc->levels[dev] = left | (right << 8); + return left | (right << 8); +} +#endif + +static int set_recmask (solo_devc * devc, int mask); + +static int +solo_mixer_set (solo_devc * devc, int dev, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + + int regoffs; + unsigned char val; + + if (left > 100) + left = 100; + if (right > 100) + right = 100; + +#if 0 + if (dev == SOUND_MIXER_RECLEV) + return ess_set_reclev (devc, dev, left, right); + if (dev == SOUND_MIXER_ALTPCM) + return ess_set_altpcm (devc, dev, left, right); +#endif + + if (dev > 31) + return OSS_EINVAL; + + if (!(SOLO_MIXER_DEVICES & (1 << dev))) /* + * Not supported + */ + return OSS_EINVAL; + + regoffs = solo_mix[dev][LEFT_CHN].regno; + + if (regoffs == 0) + return OSS_EINVAL; + + val = solo_getmixer (devc, regoffs); + change_bits (devc, &val, dev, LEFT_CHN, left); + + devc->levels[dev] = left | (left << 8); + + if (solo_mix[dev][RIGHT_CHN].regno != regoffs) /* + * Change register + */ + { + solo_setmixer (devc, regoffs, val); /* + * Save the old one + */ + regoffs = solo_mix[dev][RIGHT_CHN].regno; + + if (regoffs == 0) + return left | (left << 8); /* + * Just left channel present + */ + + val = solo_getmixer (devc, regoffs); /* + * Read the new one + */ + } + + change_bits (devc, &val, dev, RIGHT_CHN, right); + + solo_setmixer (devc, regoffs, val); + + devc->levels[dev] = left | (right << 8); + return left | (right << 8); +} + +/*ARGSUSED*/ +static int +solo_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + solo_devc *devc = mixer_devs[dev]->devc; + int val; + + if (cmd == SOUND_MIXER_PRIVATE1) + { + val = *arg; + if (val != 0 && val != 1) + return (OSS_EINVAL); + + if (val) + { + cmn_err (CE_WARN, "turning on 26db mic boost\n"); + ext_write (devc, 0xa9, ext_read (devc, 0xa9) | 0xC); + } + else + { + cmn_err (CE_WARN, "turning off 26db mic boost\n"); + ext_write (devc, 0xa9, ext_read (devc, 0xa9) & ~0x4); + } + return *arg = val; + } + + + if (((cmd >> 8) & 0xff) == 'M') + { + if (IOC_IS_OUTPUT (cmd)) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + val = *arg; + return *arg = set_recmask (devc, val); + break; + + default: + + val = *arg; + return *arg = solo_mixer_set (devc, cmd & 0xff, val); + } + else + switch (cmd & 0xff) + { + + case SOUND_MIXER_RECSRC: + return *arg = devc->recmask; + break; + + case SOUND_MIXER_DEVMASK: + return *arg = SOLO_MIXER_DEVICES; + break; + + case SOUND_MIXER_STEREODEVS: + return *arg = SOLO_MIXER_DEVICES & + ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX); + break; + + case SOUND_MIXER_RECMASK: + return *arg = SOLO_RECORDING_DEVICES; + break; + + case SOUND_MIXER_CAPS: + return *arg = SOUND_CAP_EXCL_INPUT; + break; + + default: + return *arg = devc->levels[cmd & 0x1f]; + } + } + else + return OSS_EINVAL; +} + +static void +set_recsrc (solo_devc * devc, int src) +{ + solo_setmixer (devc, RECORD_SRC, + (solo_getmixer (devc, RECORD_SRC) & ~7) | (src & 0x7)); +} + +static int +set_recmask (solo_devc * devc, int mask) +{ + int devmask = mask & SOLO_RECORDING_DEVICES; + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && devmask != SOUND_MASK_CD) + { /* + * More than one devices selected. + * Drop the previous selection + */ + devmask &= ~devc->recmask; + } + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && devmask != SOUND_MASK_CD) + { /* + * More than one devices selected. + * Default to mic + */ + devmask = SOUND_MASK_MIC; + } + + + if (devmask ^ devc->recmask) /* + * Input source changed + */ + { + switch (devmask) + { + + case SOUND_MASK_MIC: + set_recsrc (devc, 0); + break; + + case SOUND_MASK_LINE: + set_recsrc (devc, 6); + break; + + case SOUND_MASK_CD: + set_recsrc (devc, 2); + break; + + default: + set_recsrc (devc, 0); + } + } + + devc->recmask = devmask; + return devc->recmask; +} + +static void +solo_mixer_reset (solo_devc * devc) +{ + char name[32]; + int i; + + sprintf (name, "SOLO"); + + devc->levels = load_mixer_volumes (name, default_levels, 1); + devc->recmask = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + solo_mixer_set (devc, i, devc->levels[i]); + + set_recmask (devc, SOUND_MASK_MIC); +} + +static mixer_driver_t solo_mixer_driver = { + solo_mixer_ioctl +}; + +static int +solointr (oss_device_t * osdev) +{ + solo_devc *devc = (solo_devc *) osdev->devc; + int status; + int serviced = 0; + int i; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + status = INB (devc->osdev, devc->base + 7); + + for (i = 0; i < MAX_PORTC; i++) + { + if (status & 0x10) /* ESS Native Mode */ + { + int instat; + solo_portc *portc = &devc->portc[i]; + serviced = 1; + + instat = INB (devc->osdev, DSP_STATUS); /* Ack the interrupt */ + if (portc->trigger_bits & PCM_ENABLE_INPUT) + oss_audio_inputintr (portc->audiodev, 1); + } + + if (status & 0x20) /* ESS DAC2 Mode */ + { + solo_portc *portc = &devc->portc[i]; + serviced = 1; + + if (portc->trigger_bits & PCM_ENABLE_OUTPUT) + oss_audio_outputintr (portc->audiodev, 1); + solo_setmixer (devc, 0x7A, solo_getmixer (devc, 0x7A) & 0x47); + } + } + + if (status & 0x80) /* MPU interrupt */ + { + serviced = 1; + /* uart401intr (INT_HANDLER_CALL (devc->irq)); *//* UART401 interrupt */ + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return serviced; +} + +static int +solo_audio_set_rate (int dev, int arg) +{ + solo_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->speed; + + if (arg > 48000) + arg = 48000; + if (arg < 8000) + arg = 8000; + portc->speed = arg; + return portc->speed; +} + +static short +solo_audio_set_channels (int dev, short arg) +{ + solo_portc *portc = audio_engines[dev]->portc; + + if ((arg != 1) && (arg != 2)) + return portc->channels; + portc->channels = arg; + + return portc->channels; +} + +static unsigned int +solo_audio_set_format (int dev, unsigned int arg) +{ + solo_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->bits; + + if (!(arg & (AFMT_U8 | AFMT_S16_LE))) + return portc->bits; + portc->bits = arg; + + return portc->bits; +} + +/*ARGSUSED*/ +static int +solo_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void solo_audio_trigger (int dev, int state); + +static void +solo_audio_reset (int dev) +{ + solo_audio_trigger (dev, 0); +} + +static void +solo_audio_reset_input (int dev) +{ + solo_portc *portc = audio_engines[dev]->portc; + solo_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +solo_audio_reset_output (int dev) +{ + solo_portc *portc = audio_engines[dev]->portc; + solo_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +solo_audio_open (int dev, int mode, int open_flags) +{ + oss_native_word flags; + solo_portc *portc = audio_engines[dev]->portc; + solo_devc *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + portc->open_mode = mode; + portc->audio_enabled = ~mode; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +static void +solo_audio_close (int dev, int mode) +{ + solo_portc *portc = audio_engines[dev]->portc; + + solo_audio_reset (dev); + portc->open_mode = 0; + portc->audio_enabled &= ~mode; +} + +/*ARGSUSED*/ +static void +solo_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + solo_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + +} + +/*ARGSUSED*/ +static void +solo_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + solo_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +static void +solo_audio_trigger (int dev, int state) +{ + oss_native_word flags; + solo_portc *portc = audio_engines[dev]->portc; + solo_devc *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->open_mode & OPEN_WRITE) + { + if (state & PCM_ENABLE_OUTPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && + !(portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + solo_setmixer (devc, 0x78, 0x92); /* stablilze fifos */ + oss_udelay(100); + solo_setmixer (devc, 0x78, 0x93); /* Go */ + OUTB (devc->osdev, 0x0A, devc->base + 6); /*unmask dac2 intr */ + portc->trigger_bits |= PCM_ENABLE_OUTPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + solo_setmixer (devc, 0x78, 0); /* stop the audio dac2 dma */ + } + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + ext_write (devc, 0xb8, 0x0f); /* Go */ + OUTB (devc->osdev, 0x00, devc->ddma_base + 0x0f); /*start dma*/ + portc->trigger_bits |= PCM_ENABLE_INPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + ext_write (devc, 0xb8, 0x00); /* stop */ + } + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static void +ext_speed (int dev, int direction) +{ + solo_devc *devc = audio_engines[dev]->devc; + solo_portc *portc = audio_engines[dev]->portc; + int divider, div, filter; + unsigned int rate; + int speed, s0, s1, use0; + int dif0, dif1; + unsigned char t0, t1; + + /* rate = source / (256 - divisor) */ + /* divisor = 256 - (source / rate) */ + speed = portc->speed; + + t0 = 128 - (793800 / speed); + s0 = 793800 / (128 - t0); + + t1 = 128 - (768000 / speed); + s1 = 768000 / (128 - t1); + t1 |= 0x80; + + dif0 = speed - s0; + if (dif0 < 0) + dif0 *= -1; + dif1 = speed - s1; + if (dif1 < 0) + dif1 *= -1; + + use0 = (dif0 < dif1) ? 1 : 0; + + if (use0) + { + rate = s0; + div = t0; + } + else + { + rate = s1; + div = t1; + } + portc->speed = rate; +/* + * Set filter divider register + */ + filter = (rate * 8 * 82) / 20; /* 80% of the rate */ + divider = 256 - 7160000 / (filter); + if (direction) + { + ext_write (devc, 0xa1, div); + ext_write (devc, 0xa2, divider); + } + else + { + solo_setmixer (devc, 0x70, div); + solo_setmixer (devc, 0x72, divider); + } +} + +/*ARGSUSED*/ +static int +solo_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + solo_devc *devc = audio_engines[dev]->devc; + solo_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + unsigned int left, right, reclev; + dmap_t *dmap = audio_engines[dev]->dmap_in; + int c; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + OUTB (devc->osdev, 2, DSP_RESET); /* Reset FIFO too */ + INB (devc->osdev, DSP_RESET); /* Reset FIFO too */ + OUTB (devc->osdev, 0, DSP_RESET); + oss_udelay (10); + + + ext_speed (dev, 1); + + ext_write (devc, 0xa8, (ext_read (devc, 0xa8) & ~0x3) | (3 - portc->channels)); /* Mono/stereo */ + + solo_command (devc, DSP_CMD_SPKOFF); + + if (portc->channels == 1) + { + if (portc->bits == AFMT_U8) + { /* 8 bit mono */ + ext_write (devc, 0xb7, 0x51); + ext_write (devc, 0xb7, 0xd0); + } + else + { /* 16 bit mono */ + ext_write (devc, 0xb7, 0x71); + ext_write (devc, 0xb7, 0xf4); + } + } + else + { /* Stereo */ + if (portc->bits == AFMT_U8) + { /* 8 bit stereo */ + ext_write (devc, 0xb7, 0x51); + ext_write (devc, 0xb7, 0x98); + } + else + { /* 16 bit stereo */ + ext_write (devc, 0xb7, 0x71); + ext_write (devc, 0xb7, 0xbc); + } + } + + /* + * reset the 0xb4 register to the stored value of RECLEV - for some + * reason it gets reset when you enter ESS Extension mode. + */ + left = devc->levels[SOUND_MIXER_RECLEV] & 0xff; + right = (devc->levels[SOUND_MIXER_RECLEV] >> 8) & 0xff; + reclev = (((15 * right) / 100) << 4) | (((15 * left) / 100)); + ext_write (devc, 0xb4, reclev); + + OUTB (devc->osdev, 0xc4, devc->ddma_base + 0x08); + OUTB (devc->osdev, 0xff, devc->ddma_base + 0x0d); /* clear DMA */ + OUTB (devc->osdev, 0x01, devc->ddma_base + 0x0f); /* stop DMA */ + OUTB (devc->osdev, 0x14, devc->ddma_base + 0x0b); /*Demand/Single Mode */ + + OUTL (devc->osdev, dmap->dmabuf_phys, devc->ddma_base + 0x00); + OUTW (devc->osdev, dmap->bytes_in_use, devc->ddma_base + 0x04); + + c = -(dmap->fragment_size); + /* Reload DMA Count */ + ext_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff)); + ext_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff)); + + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +solo_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + solo_devc *devc = audio_engines[dev]->devc; + solo_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + dmap_t *dmap = audio_engines[dev]->dmap_out; + int c; + + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + OUTB (devc->osdev, 2, DSP_RESET); /* Reset FIFO too */ + INB (devc->osdev, DSP_RESET); + OUTB (devc->osdev, 0, DSP_RESET); + ext_speed (dev, 0); + if (portc->channels == 1) + { + if (portc->bits == AFMT_U8) + solo_setmixer (devc, 0x7A, 0x40 | 0x00); /*8bit mono unsigned */ + else + solo_setmixer (devc, 0x7A, 0x40 | 0x05); /*16bit mono signed */ + } + else + { + if (portc->bits == AFMT_U8) + solo_setmixer (devc, 0x7A, 0x40 | 0x02); /*8bit stereo unsigned */ + else + solo_setmixer (devc, 0x7A, 0x40 | 0x07); /*16bit stereo signed */ + } + + OUTL (devc->osdev, dmap->dmabuf_phys, devc->base + 0); + OUTW (devc->osdev, dmap->bytes_in_use, devc->base + 4); + + OUTB (devc->osdev, 0x0, devc->base + 6); /* disable the DMA mask */ + + c = -(dmap->fragment_size>>1); + solo_setmixer (devc, 0x74, (unsigned char) ((unsigned short) c & 0xff)); + solo_setmixer (devc, 0x76, (unsigned char) (((unsigned short) c >> 8) & 0xff)); + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static int +solo_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + solo_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + oss_native_word ptr=0; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + if (direction == PCM_ENABLE_OUTPUT) + { + ptr = dmap->bytes_in_use - INW(devc->osdev, devc->base + 4); + } + + if (direction == PCM_ENABLE_INPUT) + { + int count; + unsigned int diff; + + ptr = INL(devc->osdev, devc->ddma_base + 0x00); + count = INL(devc->osdev, devc->ddma_base + 0x04); + + diff = dmap->dmabuf_phys + dmap->bytes_in_use - ptr - count; + + if (ptr < dmap->dmabuf_phys || + ptr >= dmap->dmabuf_phys + dmap->bytes_in_use) + + ptr = devc->last_capture_addr; /* use prev value */ + else + devc->last_capture_addr = ptr; /* save it */ + + ptr -= dmap->dmabuf_phys; + } + + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return ptr; +} + + +static audiodrv_t solo_audio_driver = { + solo_audio_open, + solo_audio_close, + solo_audio_output_block, + solo_audio_start_input, + solo_audio_ioctl, + solo_audio_prepare_for_input, + solo_audio_prepare_for_output, + solo_audio_reset, + NULL, + NULL, + solo_audio_reset_input, + solo_audio_reset_output, + solo_audio_trigger, + solo_audio_set_rate, + solo_audio_set_format, + solo_audio_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, /* solo_alloc_buffer, */ + NULL, /* solo_free_buffer, */ + NULL, + NULL, + solo_get_buffer_pointer +}; + +static int +init_solo (solo_devc * devc) +{ + int my_mixer; + int i, adev; + int first_dev = 0; + + devc->mpu_attached = devc->fm_attached = 0; + +/* + * Initialize and attach the legacy devices + */ + + if (!solo_reset (devc)) + { + cmn_err (CE_WARN, "Reset command failed\n"); + return 0; + } + +/* setup mixer regs */ + solo_setmixer (devc, 0x7d, 0x0c); + OUTB (devc->osdev, 0xf0, devc->base + 7); + OUTB (devc->osdev, 0x00, devc->ddma_base + 0x0f); + + if ((my_mixer = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "ESS Solo", + &solo_mixer_driver, + sizeof (mixer_driver_t), devc)) < 0) + { + return 0; + } + solo_mixer_reset (devc); + + for (i = 0; i < MAX_PORTC; i++) + { + char tmp_name[100]; + solo_portc *portc = &devc->portc[i]; + int caps = ADEV_AUTOMODE; + strcpy (tmp_name, devc->chip_name); + + if (i == 0) + { + strcpy (tmp_name, devc->chip_name); + caps |= ADEV_DUPLEX; + } + else + { + caps |= ADEV_DUPLEX | ADEV_SHADOW; + } + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &solo_audio_driver, + sizeof (audiodrv_t), + caps, + AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0) + { + adev = -1; + return 0; + } + else + { + if (i == 0) + first_dev = adev; + audio_engines[adev]->portc = portc; + audio_engines[adev]->rate_source = first_dev; + audio_engines[adev]->mixer_dev = my_mixer; + audio_engines[adev]->min_rate = 8000; + audio_engines[adev]->max_rate = 48000; + audio_engines[adev]->dmabuf_maxaddr = MEMLIMIT_ISA; + audio_engines[adev]->dmabuf_alloc_flags |= DMABUF_SIZE_16BITS; + audio_engines[adev]->caps |= PCM_CAP_FREERATE; + portc->open_mode = 0; + portc->audiodev = adev; + portc->audio_enabled = 0; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + } + } + return 1; +} + +int +oss_solo_attach (oss_device_t * osdev) +{ + + unsigned char pci_irq_line, pci_revision; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr0, pci_ioaddr1, pci_ioaddr2, pci_ioaddr3; + solo_devc *devc; + + DDB (cmn_err (CE_WARN, "Entered ESS Solo probe routine\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != ESS_VENDOR_ID || device != ESS_SOLO1) + return 0; + + pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision); + pci_read_config_word (osdev, PCI_COMMAND, &pci_command); + pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr0); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_2, &pci_ioaddr2); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_3, &pci_ioaddr3); + + if (pci_ioaddr0 == 0) + { + cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n"); + return 0; + } + + if (pci_irq_line == 0) + { + cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d).\n", pci_irq_line); + return 0; + } + + if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) + { + cmn_err (CE_WARN, "Out of memory\n"); + return 0; + } + + devc->osdev = osdev; + osdev->devc = devc; + devc->irq = pci_irq_line; + devc->chip_name = "ESS Solo-1"; + + devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr0); + /* Remove I/O space marker in bit 0. */ + devc->base &= ~0x3; + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1); + + oss_register_device (osdev, devc->chip_name); + + if (oss_register_interrupts (devc->osdev, 0, solointr, NULL) < 0) + { + cmn_err (CE_WARN, "Can't allocate IRQ%d\n", pci_irq_line); + return 0; + } + + + /* Read the VCBase register */ + if (pci_ioaddr2 == 0) + { + cmn_err (CE_WARN, "DMA I/O base not set\n"); + /*return 0; */ + } + /* Copy it's contents to the DDMA register */ + pci_write_config_dword (osdev, 0x60, pci_ioaddr2 | 0x1); /* enabled DDMA */ + devc->ddma_base = MAP_PCI_IOADDR (devc->osdev, 2, pci_ioaddr2); + devc->ddma_base &= ~0x3; + + /* Init other SB base registers */ + if (pci_ioaddr1 == 0) + { + cmn_err (CE_WARN, "SB I/O base not set\n"); + return 0; + } + devc->sb_base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1); + devc->sb_base &= ~0x3; + + + /* Init MPU base register */ + if (pci_ioaddr3 == 0) + { + cmn_err (CE_WARN, "MPU I/O base not set\n"); + return 0; + } + devc->mpu_base = MAP_PCI_IOADDR (devc->osdev, 3, pci_ioaddr3); + devc->mpu_base &= ~0x3; + + /* Setup Legacy audio register - disable legacy audio */ + pci_write_config_word (osdev, 0x40, 0x805f); + + /* Select DDMA and Disable IRQ emulation */ + pci_write_config_dword (osdev, 0x50, 0); + pci_read_config_dword (osdev, 0x50, &pci_ioaddr0); + pci_ioaddr0 &= (~(0x0700 | 0x6000)); + pci_write_config_dword (osdev, 0x50, pci_ioaddr0); + + return init_solo (devc); /* Detected */ +} + + +int +oss_solo_detach (oss_device_t * osdev) +{ + solo_devc *devc = (solo_devc *) osdev->devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + /* disable all interrupts */ + /*OUTB (devc->osdev, 0, devc->base + 7); */ + +#ifdef OBSOLETED_STUFF + if (devc->mpu_attached) + unload_mpu (devc); +#endif + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + UNMAP_PCI_IOADDR (devc->osdev, 1); + UNMAP_PCI_IOADDR (devc->osdev, 2); + UNMAP_PCI_IOADDR (devc->osdev, 3); + + oss_unregister_device (devc->osdev); + return 1; +} diff --git a/kernel/drv/oss_solo/oss_solo.man b/kernel/drv/oss_solo/oss_solo.man new file mode 100644 index 0000000..a86e810 --- /dev/null +++ b/kernel/drv/oss_solo/oss_solo.man @@ -0,0 +1,19 @@ +NAME +oss_solo - ESS Solo-1 audio driver + +DESCRIPTION +Open Sound System driver for ESS Solo1/1938/1968 audio controllers. +ESS Solo1 device characteristics: + o 8/16 bit playback/record + o mono/stereo playback/recording + o 8KHz to 48Khz sample rate. + +OPTIONS +None + +FILES +CONFIGFILEPATH/oss_solo.conf Device configuration file + +AUTHOR +4Front Technologies + |