patch-2.2.0-pre8 linux/drivers/sound/sb_ess.c
Next file: linux/drivers/sound/sb_ess.h
Previous file: linux/drivers/sound/sb_common.c
Back to the patch index
Back to the overall index
- Lines: 1683
- Date:
Sun Jan 17 18:25:48 1999
- Orig file:
v2.2.0-pre7/linux/drivers/sound/sb_ess.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.2.0-pre7/linux/drivers/sound/sb_ess.c linux/drivers/sound/sb_ess.c
@@ -0,0 +1,1682 @@
+/*
+ * Created: 9-Jan-1999
+ *
+ * TODO: consistency speed calculations!!
+ * what's the sample rate when duplex? Docs contradict.
+ * I broke IRQ detection for non-SMP machines
+ * ????: Did I break MIDI support?
+ *
+ * This files contains ESS chip specifics. It's based on the existing ESS
+ * handling as it resided in sb_common.c, sb_mixer.c and sb_audio.c. This
+ * file adds features like:
+ * - Chip Identification (as shown in /proc/sound)
+ * - RECLEV support for ES1688 and later
+ * - 6 bits playback level support chips later than ES1688
+ * - Recording level support on a per-device basis for ES1887
+ * - Full-Duplex for ES1887 (under development)
+ *
+ * Full duplex is enabled by specifying dma16. While the normal dma must
+ * be one of 0, 1 or 3, dma16 can be one of 0, 1, 3 or 5. DMA 5 is a 16 bit
+ * DMA channel, while the others are 8 bit..
+ *
+ * History:
+ *
+ * Rolf Fokkens (Dec 20 1998): ES188x recording level support on a per
+ * input basis.
+ * (Dec 24 1998): Recognition of ES1788, ES1887, ES1888,
+ * ES1868, ES1869 and ES1878. Could be used for
+ * specific handling in the future. All except
+ * ES1887 and ES1888 and ES688 are handled like
+ * ES1688.
+ * (Dec 27 1998): RECLEV for all (?) ES1688+ chips. ES188x now
+ * have the "Dec 20" support + RECLEV
+ * (jan 2 1999): Preparation for Full Duplex. This means
+ * Audio 2 is now used for playback when dma16
+ * is specified. The next step would be to use
+ * Audio 1 and Audio 2 at the same time.
+ */
+
+#undef FKS_LOGGING
+
+/*
+ * About the documentation
+ *
+ * I don't know if the chips all are OK, but the documentation is buggy. 'cause
+ * I don't have all the cips myself, there's a lot I cannot verify. I'll try to
+ * keep track of my latest insights about his here. If you have additional info,
+ * please enlighten me (fokkensr@vertis.nl)!
+ *
+ * I had the impression that ES1688 also has 6 bit master volume control. The
+ * documentation about ES1888 (rev C, october '95) claims that ES1888 has
+ * the following features ES1688 doesn't have:
+ * - 6 bit master volume
+ * - Full Duplex
+ * So ES1688 apparently doesn't have 6 bit master volume control, but the
+ * ES1688 does have RECLEV control. Makes me wonder: does ES688 have it too?
+ * Without RECLEV ES688 won't be much fun I guess.
+ *
+ * From the ES1888 (rev C, october '95) documentation I got the impression
+ * that registers 0x68 to 0x6e don't exist which means: no recording volume
+ * controls. To my surprise the ES888 documentation (1/14/96) claims that
+ * ES888 does have these record mixer registers, but that ES1888 doesn't have
+ * 0x69 and 0x6b. So the rest should be there.
+ *
+ * I'm trying to get ES1887 Full Duplex. Audio 2 is playback only, while Audio 2
+ * is both record and playback. I think I should use Audio 2 for all playback.
+ *
+ * The documentation is an adventure: it's close but not fully accurate. I
+ * found out that after a reset some registers are *NOT* reset, though the
+ * docs say the would be. Interresting ones are 0x7f, 0x7d and 0x7a. They are
+ * related to the Audio 2 channel. I also was suprised about the consequenses
+ * of writing 0x00 to 0x7f (which should be done by reset): The ES1887 moves
+ * into ES1888 mode. This means that it claims IRQ 11, which happens to be my
+ * ISDN adapter. Needless to say it no longer worked. I now understand why
+ * after rebooting 0x7f already was 0x05, the value of my choise: the BIOS
+ * did it.
+ *
+ * Oh, and this is another trap: in ES1887 docs mixer register 0x70 is decribed
+ * as if it's exactly the same as register 0xa1. This is *NOT* true. The
+ * description of 0x70 in ES1869 docs is accurate however.
+ *
+ * When using audio 2 mixer register 0x72 seems te be meaningless. Only 0xa2
+ * has effect.
+ *
+ * Software reset not being able to reset all registers is great! Especially
+ * the fact that register 0x78 isn't reset is great when you wanna change back
+ * to single dma operation (simplex): audio 2 is still operation, and uses the
+ * same dma as audio 1: your ess changes into a funny echo machine.
+ */
+
+/*
+ * About recognition of ESS chips
+ *
+ * The distinction of ES688, ES1688, ES1788, ES1887 and ES1888 is described in
+ * a (preliminary ??) datasheet on ES1887. It's aim is to identify ES1887, but
+ * during detection the text claims that "this chip may be ..." when a step
+ * fails. This scheme is used to distinct between the above chips.
+ * It appears however that some PnP chips like ES1868 are recognized as ES1788
+ * by the ES1887 detection scheme. These PnP chips can be detected in another
+ * way however: ES1868, ES1869 and ES1878 can be recognized (full proof I think)
+ * by repeatedly reading mixer register 0x40. This is done by ess_identify in
+ * sb_common.c.
+ * This results in the following detection steps:
+ * - distinct between ES688 and ES1688+ (as always done in this driver)
+ * if ES688 we're ready
+ * - try to detect ES1868, ES1869 or ES1878
+ * if successful we're ready
+ * - try to detect ES1888, ES1887 or ES1788
+ * if successful we're ready
+ * - Dunno. Must be 1688. Will do in general
+ *
+ * About RECLEV support:
+ *
+ * The existing ES1688 support didn't take care of the ES1688+ recording
+ * levels very well. Whenever a device was selected (recmask) for recording
+ * it's recording level was loud, and it couldn't be changed. The fact that
+ * internal register 0xb4 could take care of RECLEV, didn't work meaning until
+ * it's value was restored every time the chip was reset; this reset the
+ * value of 0xb4 too. I guess that's what 4front also had (have?) trouble with.
+ *
+ * About ES1887 support:
+ *
+ * The ES1887 has separate registers to control the recording levels, for all
+ * inputs. The ES1887 specific software makes these levels the same as their
+ * corresponding playback levels, unless recmask says they aren't recorded. In
+ * the latter case the recording volumes are 0.
+ * Now recording levels of inputs can be controlled, by changing the playback
+ * levels. Futhermore several devices can be recorded together (which is not
+ * possible with the ES1688.
+ * Besides the separate recording level control for each input, the common
+ * recordig level can also be controlled by RECLEV as described above.
+ *
+ * Not only ES1887 have this recording mixer. I know the following from the
+ * documentation:
+ * ES688 no
+ * ES1688 no
+ * ES1868 no
+ * ES1869 yes
+ * ES1878 no
+ * ES1879 yes
+ * ES1888 no/yes Contradicting documentation; most recent: yes
+ * ES1946 yes This is a PCI chip; not handled by this driver
+ */
+
+#include <linux/delay.h>
+
+#include "sound_config.h"
+#include "sb_mixer.h"
+#include "sb.h"
+
+#include "sb_ess.h"
+
+#ifdef FKS_LOGGING
+static void ess_show_mixerregs (sb_devc *devc);
+#endif
+static int ess_read (sb_devc * devc, unsigned char reg);
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data);
+static void ess_chgmixer
+ (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val);
+
+/****************************************************************************
+ * *
+ * ESS audio *
+ * *
+ ****************************************************************************/
+
+struct ess_command {short cmd; short data;};
+
+/*
+ * Commands for initializing Audio 1 for input (record)
+ */
+static struct ess_command ess_i08m[] = /* input 8 bit mono */
+ { {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_i16m[] = /* input 16 bit mono */
+ { {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_i08s[] = /* input 8 bit stereo */
+ { {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_i16s[] = /* input 16 bit stereo */
+ { {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_inp_cmds[] =
+ { ess_i08m, ess_i16m, ess_i08s, ess_i16s };
+
+
+/*
+ * Commands for initializing Audio 1 for output (playback)
+ */
+static struct ess_command ess_o08m[] = /* output 8 bit mono */
+ { {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_o16m[] = /* output 16 bit mono */
+ { {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_o08s[] = /* output 8 bit stereo */
+ { {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_o16s[] = /* output 16 bit stereo */
+ { {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_out_cmds[] =
+ { ess_o08m, ess_o16m, ess_o08s, ess_o16s };
+
+static void ess_exec_commands
+ (sb_devc *devc, struct ess_command *cmdtab[])
+{
+ struct ess_command *cmd;
+
+ cmd = cmdtab [ ((devc->channels != 1) << 1) + (devc->bits != AFMT_U8) ];
+
+ while (cmd->cmd != -1) {
+ ess_write (devc, cmd->cmd, cmd->data);
+ cmd++;
+ }
+}
+
+static void ess_change
+ (sb_devc *devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ int value;
+
+ value = ess_read (devc, reg);
+ value = (value & ~mask) | (val & mask);
+ ess_write (devc, reg, value);
+}
+
+static void ess_set_output_parms
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (devc->duplex) {
+ devc->trg_buf_16 = buf;
+ devc->trg_bytes_16 = nr_bytes;
+ devc->trg_intrflag_16 = intrflag;
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ } else {
+ devc->trg_buf = buf;
+ devc->trg_bytes = nr_bytes;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_OUTPUT;
+ }
+}
+
+static void ess_set_input_parms
+ (int dev, unsigned long buf, int count, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ devc->trg_buf = buf;
+ devc->trg_bytes = count;
+ devc->trg_intrflag = intrflag;
+ devc->irq_mode = IMODE_INPUT;
+}
+
+static int ess_calc_div (int clock, int revert, int *speedp, int *diffp)
+{
+ int divider;
+ int speed, diff;
+ int retval;
+
+ speed = *speedp;
+ divider = (clock + speed / 2) / *speedp;
+ retval = revert - divider;
+ if (retval > 127) {
+ retval = 127;
+ divider = revert - retval;
+ }
+ /* This line is suggested. Must be wrong I think
+ *speedp = (clock + divider / 2) / divider;
+ So I chose the next one */
+
+ *speedp = clock / divider;
+ diff = speed - *speedp;
+ if (diff < 0) diff =-diff;
+ *diffp = diff;
+
+ return retval;
+}
+
+static int ess_calc_best_speed
+ (int clock1, int rev1, int clock2, int rev2, int *divp, int *speedp)
+{
+ int speed1 = *speedp, speed2 = *speedp;
+ int div1, div2;
+ int diff1, diff2;
+ int retval;
+
+ div1 = ess_calc_div (clock1, rev1, &speed1, &diff1);
+ div2 = ess_calc_div (clock2, rev2, &speed2, &diff2);
+
+ if (diff1 < diff2) {
+ *divp = div1;
+ *speedp = speed1;
+ retval = 1;
+ } else {
+ *divp = div2;
+ *speedp = speed2;
+ retval = 2;
+ }
+
+ return retval;
+}
+
+/*
+ * Depending on the audiochannel ESS devices can
+ * have different clock settings.
+ * callers of ess_speed only do an audionum suggestion, which means
+ * input suggests 1, output suggests 2. This suggestion is only true
+ * however when doing duplex.
+ */
+static void ess_speed (sb_devc *devc, int audionum)
+{
+ int choice;
+ int speed = devc->speed;
+ int clock1, clock2;
+ int rev1, rev2;
+ int div;
+
+ if (!devc->duplex) audionum = 1;
+
+ if (audionum == 1) {
+ rev1 = 128;
+ clock1 = 397700;
+ rev2 = 256;
+ clock2 = 795500;
+ } else {
+ rev1 = 128;
+ clock1 = 793800;
+ rev2 = 128;
+ clock2 = 768000;
+ }
+ choice = ess_calc_best_speed
+ (clock1, rev1, clock2, rev2, &div, &speed);
+
+#ifdef FKS_REG_LOGGING
+printk (KERN_INFO "FKS: ess_speed (%d) b speed = %d, div=%x\n", audionum, speed, div);
+#endif
+
+ if (choice == 2) div |= 0x80;
+
+ if (audionum == 1) {
+ /* Change behaviour of register A1 *
+ sb_chg_mixer(devc, 0x71, 0x20, 0x20)
+ * For ES1869 only??? */
+ ess_write (devc, 0xa1, div);
+ } else {
+ ess_setmixer (devc, 0x70, div);
+ }
+
+ /* Set filter roll-off to 90% of speed/2 */
+ speed = (speed * 9) / 20;
+
+ div = 256 - 7160000 / (speed * 82);
+
+ if (audionum == 1) {
+ ess_write (devc, 0xa2, div);
+ } else {
+ ess_write (devc, 0xa2, div);
+ ess_setmixer (devc, 0x72, div);
+ }
+}
+
+#if 0
+static void ess_speed(sb_devc * devc)
+{
+ int divider;
+ unsigned char bits = 0;
+ int speed = devc->speed;
+
+ if (speed < 4000)
+ speed = 4000;
+ else if (speed > 48000)
+ speed = 48000;
+
+ if (speed > 22000)
+ {
+ bits = 0x80;
+ divider = 256 - (795500 + speed / 2) / speed;
+ }
+ else
+ {
+ divider = 128 - (397700 + speed / 2) / speed;
+ }
+
+ bits |= (unsigned char) divider;
+
+ ess_write (devc, 0xa1, bits);
+
+ /*
+ * Set filter divider register
+ */
+
+ speed = (speed * 9) / 20; /* Set filter roll-off to 90% of speed/2 */
+ divider = 256 - 7160000 / (speed * 82);
+
+ ess_write (devc, 0xa2, divider);
+
+ return;
+}
+#endif
+
+static int ess_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ ess_speed(devc, 1);
+
+ sb_dsp_command(devc, DSP_CMD_SPKOFF);
+
+ ess_write (devc, 0xb8, 0x0e); /* Auto init DMA mode */
+ ess_change (devc, 0xa8, 0x03, 3 - devc->channels); /* Mono/stereo */
+ ess_write (devc, 0xb9, 2); /* Demand mode (4 bytes/DMA request) */
+
+ ess_exec_commands (devc, ess_inp_cmds);
+
+ ess_change (devc, 0xb1, 0xf0, 0x50);
+ ess_change (devc, 0xb2, 0xf0, 0x50);
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output_audio1 (int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ sb_dsp_reset(devc);
+ ess_speed(devc, 1);
+ ess_write (devc, 0xb8, 4); /* Auto init DMA mode */
+ ess_change (devc, 0xa8, 0x03, 3 - devc->channels); /* Mono/stereo */
+ ess_write (devc, 0xb9, 2); /* Demand mode (4 bytes/request) */
+
+ ess_exec_commands (devc, ess_out_cmds);
+
+ ess_change (devc, 0xb1, 0xf0, 0x50); /* Enable DMA */
+ ess_change (devc, 0xb2, 0xf0, 0x50); /* Enable IRQ */
+
+ sb_dsp_command(devc, DSP_CMD_SPKON); /* There be sound! */
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output_audio2 (int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ unsigned char bits;
+
+ sb_dsp_reset(devc);
+
+ /*
+ * Auto-Initialize:
+ * DMA mode + demand mode (8 bytes/request, yes I want it all!)
+ * But leave 16-bit DMA bit untouched!
+ */
+ ess_chgmixer (devc, 0x78, 0xd0, 0xd0);
+
+ ess_speed(devc, 2);
+
+ /* bits 4:3 on ES1887 represent recording source. Keep them! */
+ bits = ess_getmixer (devc, 0x7a) & 0x18;
+
+ /* Set stereo/mono */
+ if (devc->channels != 1) bits |= 0x02;
+
+ /* Init DACs; UNSIGNED mode for 8 bit; SIGNED mode for 16 bit */
+ if (devc->bits != AFMT_U8) bits |= 0x05; /* 16 bit */
+
+ /* Enable DMA, IRQ will be shared (hopefully)*/
+ bits |= 0x60;
+
+ ess_setmixer (devc, 0x7a, bits);
+
+ ess_mixer_reload (devc, SOUND_MIXER_PCM); /* There be sound! */
+
+ devc->trigger_bits = 0;
+ return 0;
+}
+
+static int ess_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "ess_audio_prepare_for_output: dma_out=%d,dma_in=%d\n"
+, audio_devs[dev]->dmap_out->dma, audio_devs[dev]->dmap_in->dma);
+#endif
+
+ if (devc->duplex) {
+ return ess_audio_prepare_for_output_audio2 (dev, bsize, bcount);
+ } else {
+ return ess_audio_prepare_for_output_audio1 (dev, bsize, bcount);
+ }
+}
+
+static void ess_audio_halt_xfer(int dev)
+{
+ unsigned long flags;
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ save_flags(flags);
+ cli();
+ sb_dsp_reset(devc);
+ restore_flags(flags);
+
+ /*
+ * Audio 2 may still be operational! Creates awful sounds!
+ */
+ if (devc->duplex) ess_chgmixer(devc, 0x78, 0x03, 0x00);
+}
+
+static void ess_audio_start_input
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ /*
+ * Start a DMA input to the buffer pointed by dmaqtail
+ */
+
+ if (audio_devs[dev]->dmap_in->dma > 3) count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_INPUT;
+
+ ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+ ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+ ess_change (devc, 0xb8, 0x0f, 0x0f); /* Go */
+ devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio1
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+ count--;
+
+ devc->irq_mode = IMODE_OUTPUT;
+
+ ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+ ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+ ess_change (devc, 0xb8, 0x05, 0x05); /* Go */
+ devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio2
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ int count = nr_bytes;
+ sb_devc *devc = audio_devs[dev]->devc;
+ short c = -nr_bytes;
+
+ if (audio_devs[dev]->dmap_out->dma > 3) count >>= 1;
+ count--;
+
+ ess_setmixer (devc, 0x74, (unsigned char) ((unsigned short) c & 0xff));
+ ess_setmixer (devc, 0x76, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+ ess_chgmixer (devc, 0x78, 0x03, 0x03); /* Go */
+
+ devc->irq_mode_16 = IMODE_OUTPUT;
+ devc->intr_active_16 = 1;
+}
+
+static void ess_audio_output_block
+ (int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (devc->duplex) {
+ ess_audio_output_block_audio2 (dev, buf, nr_bytes, intrflag);
+ } else {
+ ess_audio_output_block_audio1 (dev, buf, nr_bytes, intrflag);
+ }
+}
+
+/*
+ * FKS: the if-statements for both bits and bits_16 are quite alike.
+ * Combine this...
+ */
+static void ess_audio_trigger(int dev, int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ int bits_16 = bits & devc->irq_mode_16;
+ bits &= devc->irq_mode;
+
+ if (!bits && !bits_16) {
+ /* FKS oh oh.... wrong?? for dma 16? */
+ sb_dsp_command(devc, 0xd0); /* Halt DMA */
+ }
+
+ if (bits) {
+ switch (devc->irq_mode)
+ {
+ case IMODE_INPUT:
+ ess_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+
+ case IMODE_OUTPUT:
+ ess_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+ devc->trg_intrflag);
+ break;
+ }
+ }
+
+ if (bits_16) {
+ switch (devc->irq_mode_16) {
+ case IMODE_INPUT:
+ ess_audio_start_input(dev, devc->trg_buf_16, devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+
+ case IMODE_OUTPUT:
+ ess_audio_output_block(dev, devc->trg_buf_16, devc->trg_bytes_16,
+ devc->trg_intrflag_16);
+ break;
+ }
+ }
+
+ devc->trigger_bits = bits | bits_16;
+}
+
+/*
+ * FKS: Change this!! it's the old routine!
+ */
+static int ess_audio_set_speed(int dev, int speed)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+ int divider;
+
+ if (speed > 0)
+ {
+ if (speed < 5000)
+ speed = 5000;
+ if (speed > 48000)
+ speed = 48000;
+
+ if (speed > 22000)
+ {
+ divider = (795500 + speed / 2) / speed;
+ speed = (795500 + divider / 2) / divider;
+ }
+ else
+ {
+ divider = (397700 + speed / 2) / speed;
+ speed = (397700 + divider / 2) / divider;
+ }
+ devc->speed = speed;
+ }
+ return devc->speed;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sb1_audio_set_bits
+ */
+static unsigned int ess_audio_set_bits(int dev, unsigned int bits)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (bits != 0) {
+ if (bits == AFMT_U8 || bits == AFMT_S16_LE) {
+ devc->bits = bits;
+ } else {
+ devc->bits = AFMT_U8;
+ }
+ }
+
+ return devc->bits;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sbpro_audio_set_channels
+ * (*) Modified it!!
+ */
+static short ess_audio_set_channels(int dev, short channels)
+{
+ sb_devc *devc = audio_devs[dev]->devc;
+
+ if (channels == 1 || channels == 2) devc->channels = channels;
+
+ return devc->channels;
+}
+
+static struct audio_driver ess_audio_driver = /* ESS ES688/1688 */
+{
+ sb_audio_open,
+ sb_audio_close,
+ ess_set_output_parms,
+ ess_set_input_parms,
+ NULL,
+ ess_audio_prepare_for_input,
+ ess_audio_prepare_for_output,
+ ess_audio_halt_xfer,
+ NULL, /* local_qlen */
+ NULL, /* copy_from_user */
+ NULL,
+ NULL,
+ ess_audio_trigger,
+ ess_audio_set_speed,
+ ess_audio_set_bits,
+ ess_audio_set_channels
+};
+
+/*
+ * ess_audio_init must be called from sb_audio_init
+ */
+struct audio_driver *ess_audio_init
+ (sb_devc *devc, int *audio_flags, int *format_mask)
+{
+ *audio_flags = DMA_AUTOMODE;
+ *format_mask |= AFMT_S16_LE;
+
+ if (devc->duplex) {
+ int tmp_dma;
+ /*
+ * sb_audio_init thinks dma8 is for playback and
+ * dma16 is for record. Not now! So swap them.
+ */
+ tmp_dma = devc->dma16;
+ devc->dma16 = devc->dma8;
+ devc->dma8 = tmp_dma;
+
+ *audio_flags |= DMA_DUPLEX;
+ }
+
+ return &ess_audio_driver;
+}
+
+/****************************************************************************
+ * *
+ * ESS common *
+ * *
+ ****************************************************************************/
+static void ess_handle_channel
+ (char *channel, int dev, int intr_active, unsigned char flag, int irq_mode)
+{
+ if (!intr_active || !flag) return;
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_handle_channel %s irq_mode=%d\n", channel, irq_mode);
+#endif
+ switch (irq_mode) {
+ case IMODE_OUTPUT:
+ DMAbuf_outputintr (dev, 1);
+ break;
+
+ case IMODE_INPUT:
+ DMAbuf_inputintr (dev);
+ break;
+
+ case IMODE_INIT:
+ break;
+
+ default:
+ /* printk(KERN_WARN "ESS: Unexpected interrupt\n"); */
+ }
+}
+
+/*
+ * FKS: TODO!!! Finish this!
+ *
+ * I think midi stuff uses uart401, without interrupts.
+ * So IMODE_MIDI isn't a value for devc->irq_mode.
+ */
+void ess_intr (sb_devc *devc)
+{
+ int status;
+ unsigned char src;
+
+ if (devc->submodel == SUBMDL_ES1887) {
+ src = ess_getmixer (devc, 0x7f) >> 4;
+ } else {
+ src = 0xff;
+ }
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: sbintr src=%x\n",(int)src);
+#endif
+ ess_handle_channel
+ ( "Audio 1"
+ , devc->dev, devc->intr_active , src & 0x01, devc->irq_mode );
+ ess_handle_channel
+ ( "Audio 2"
+ , devc->dev, devc->intr_active_16, src & 0x02, devc->irq_mode_16);
+ /*
+ * Acknowledge interrupts
+ */
+ if (devc->submodel == SUBMDL_ES1887 && (src & 0x02)) {
+ ess_chgmixer (devc, 0x7a, 0x80, 0x00);
+ }
+
+ if (src & 0x01) {
+ status = inb(DSP_DATA_AVAIL);
+ }
+}
+
+static void ess_extended (sb_devc * devc)
+{
+ /* Enable extended mode */
+
+ sb_dsp_command(devc, 0xc6);
+}
+
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data)
+{
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: write reg %x: %x\n", reg, data);
+#endif
+ /* Write a byte to an extended mode register of ES1688 */
+
+ if (!sb_dsp_command(devc, reg))
+ return 0;
+
+ return sb_dsp_command(devc, data);
+}
+
+static int ess_read (sb_devc * devc, unsigned char reg)
+{
+ /* Read a byte from an extended mode register of ES1688 */
+
+ /* Read register command */
+ if (!sb_dsp_command(devc, 0xc0)) return -1;
+
+ if (!sb_dsp_command(devc, reg )) return -1;
+
+ return sb_dsp_get_byte(devc);
+}
+
+int ess_dsp_reset(sb_devc * devc)
+{
+ int loopc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_dsp_reset 1\n");
+ess_show_mixerregs (devc);
+#endif
+
+ DEB(printk("Entered ess_dsp_reset()\n"));
+
+ outb(3, DSP_RESET); /* Reset FIFO too */
+
+ udelay(10);
+ outb(0, DSP_RESET);
+ udelay(30);
+
+ for (loopc = 0; loopc < 1000 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++);
+
+ if (inb(DSP_READ) != 0xAA) {
+ DDB(printk("sb: No response to RESET\n"));
+ return 0; /* Sorry */
+ }
+ ess_extended (devc);
+
+ DEB(printk("sb_dsp_reset() OK\n"));
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: dsp_reset 2\n");
+ess_show_mixerregs (devc);
+#endif
+
+ return 1;
+}
+
+static int ess_irq_bits (int irq)
+{
+ switch (irq) {
+ case 2:
+ case 9:
+ return 0;
+
+ case 5:
+ return 1;
+
+ case 7:
+ return 2;
+
+ case 10:
+ return 3;
+
+ default:
+ printk(KERN_ERR "ESS1688: Invalid IRQ %d\n", irq);
+ return -1;
+ }
+}
+
+/*
+ * Set IRQ configuration register for all ESS models
+ */
+static int ess_common_set_irq_hw (sb_devc * devc)
+{
+ int irq_bits;
+
+ if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return 0;
+
+ if (!ess_write (devc, 0xb1, 0x50 | (irq_bits << 2))) {
+ printk(KERN_ERR "ES1688: Failed to write to IRQ config register\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * I wanna use modern ES1887 mixer irq handling. Funny is the
+ * fact that my BIOS wants the same. But suppose someone's BIOS
+ * doesn't do this!
+ * This is independent of duplex. If there's a 1887 this will
+ * prevent it from going into 1888 mode.
+ */
+static void ess_es1887_set_irq_hw (sb_devc * devc)
+{
+ int irq_bits;
+
+ if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return;
+
+ ess_chgmixer (devc, 0x7f, 0x0f, 0x01 | ((irq_bits + 1) << 1));
+}
+
+static int ess_set_irq_hw (sb_devc * devc)
+{
+ if (devc->submodel == SUBMDL_ES1887) ess_es1887_set_irq_hw (devc);
+
+ return ess_common_set_irq_hw (devc);
+}
+
+static unsigned int ess_identify (sb_devc * devc)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ outb(((unsigned char) (0x40 & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ val = inb(MIXER_DATA) << 8;
+ udelay(20);
+ val |= inb(MIXER_DATA);
+ udelay(20);
+ restore_flags(flags);
+
+ return val;
+}
+
+/*
+ * ESS technology describes a detection scheme in their docs. It involves
+ * fiddling with the bits in certain mixer registers. ess_probe is supposed
+ * to help.
+ *
+ * FKS: tracing shows ess_probe writes wrong value to 0x64. Bit 3 reads 1, but
+ * should be written 0 only. Check this.
+ */
+static int ess_probe (sb_devc * devc, int reg, int xorval)
+{
+ int val1, val2, val3;
+
+ val1 = ess_getmixer (devc, reg);
+ val2 = val1 ^ xorval;
+ ess_setmixer (devc, reg, val2);
+ val3 = ess_getmixer (devc, reg);
+ ess_setmixer (devc, reg, val1);
+
+ return (val2 == val3);
+}
+
+int ess_init(sb_devc * devc, struct address_info *hw_config)
+{
+ unsigned char cfg;
+ int ess_major = 0, ess_minor = 0;
+ int i;
+ static char name[100];
+
+ /*
+ * Try to detect ESS chips.
+ */
+
+ sb_dsp_command(devc, 0xe7); /* Return identification */
+
+ for (i = 1000; i; i--) {
+ if (inb(DSP_DATA_AVAIL) & 0x80) {
+ if (ess_major == 0) {
+ ess_major = inb(DSP_READ);
+ } else {
+ ess_minor = inb(DSP_READ);
+ break;
+ }
+ }
+ }
+
+ if (ess_major == 0) return 0;
+
+ if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) {
+ sprintf(name, "ESS ES488 AudioDrive (rev %d)",
+ ess_minor & 0x0f);
+ hw_config->name = name;
+ devc->model = MDL_SBPRO;
+ return 1;
+ }
+
+ /*
+ * This the detection heuristic of ESS technology, though somewhat
+ * changed to actually make it work.
+ * This results in the following detection steps:
+ * - distinct between ES688 and ES1688+ (as always done in this driver)
+ * if ES688 we're ready
+ * - try to detect ES1868, ES1869 or ES1878 (ess_identify)
+ * if successful we're ready
+ * - try to detect ES1888, ES1887 or ES1788 (aim: detect ES1887)
+ * if successful we're ready
+ * - Dunno. Must be 1688. Will do in general
+ *
+ * This is the most BETA part of the software: Will the detection
+ * always work?
+ */
+ devc->model = MDL_ESS;
+ devc->submodel = ess_minor & 0x0f;
+
+ if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) {
+ char *chip = NULL;
+
+ if ((ess_minor & 0x0f) < 8) {
+ chip = "ES688";
+ };
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: mixer_reset\n");
+ ess_setmixer (devc, 0x00, 0x00);
+#endif
+ if (chip == NULL) {
+ int type;
+
+ type = ess_identify (devc);
+
+ switch (type) {
+ case 0x1868:
+ chip = "ES1868";
+ devc->submodel = SUBMDL_ES1868;
+ break;
+ case 0x1869:
+ chip = "ES1869";
+ devc->submodel = SUBMDL_ES1869;
+ break;
+ case 0x1878:
+ chip = "ES1878";
+ devc->submodel = SUBMDL_ES1878;
+ break;
+ };
+ };
+ if (chip == NULL && !ess_probe(devc, 0x64, (1 << 3))) {
+ if (ess_probe (devc, 0x70, 0x7f)) {
+ if (ess_probe (devc, 0x64, (1 << 5))) {
+ chip = "ES1887";
+ devc->submodel = SUBMDL_ES1887;
+ } else {
+ chip = "ES1888";
+ devc->submodel = SUBMDL_ES1888;
+ }
+ } else {
+ chip = "ES1788";
+ devc->submodel = SUBMDL_ES1788;
+ }
+ };
+ if (chip == NULL) {
+ chip = "ES1688";
+ };
+
+ sprintf(name,"ESS %s AudioDrive (rev %d)", chip, ess_minor & 0x0f);
+ } else {
+ strcpy(name, "Jazz16");
+ }
+
+ hw_config->name = name;
+ /* FKS: sb_dsp_reset to enable extended mode???? */
+ sb_dsp_reset(devc); /* Turn on extended mode */
+
+ /*
+ * Enable joystick and OPL3
+ */
+ cfg = ess_getmixer (devc, 0x40);
+ ess_setmixer (devc, 0x40, cfg | 0x03);
+ if (devc->submodel >= 8) { /* ES1688 */
+ devc->caps |= SB_NO_MIDI; /* ES1688 uses MPU401 MIDI mode */
+ }
+ sb_dsp_reset (devc);
+
+ /*
+ * This is important! If it's not done, the IRQ probe in sb_dsp_init
+ * may fail.
+ */
+ return ess_set_irq_hw (devc);
+}
+
+static int ess_set_dma_hw(sb_devc * devc)
+{
+ unsigned char cfg, dma_bits = 0, dma16_bits;
+ int dma;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "ess_set_dma_hw: dma8=%d,dma16=%d,dup=%d\n"
+, devc->dma8, devc->dma16, devc->duplex);
+#endif
+
+ /*
+ * FKS: It seems as if this duplex flag isn't set yet. Check it.
+ */
+ dma = devc->dma8;
+
+ if (dma > 3 || dma < 0 || dma == 2) {
+ dma_bits = 0;
+ printk(KERN_ERR "ESS1688: Invalid DMA8 %d\n", dma);
+ return 0;
+ } else {
+ /* Extended mode DMA enable */
+ cfg = 0x50;
+
+ if (dma == 3) {
+ dma_bits = 3;
+ } else {
+ dma_bits = dma + 1;
+ }
+ }
+
+ if (!ess_write (devc, 0xb2, cfg | (dma_bits << 2))) {
+ printk(KERN_ERR "ESS1688: Failed to write to DMA config register\n");
+ return 0;
+ }
+
+ if (devc->duplex) {
+ dma = devc->dma16;
+ dma16_bits = 0;
+
+ if (dma >= 0) {
+ switch (dma) {
+ case 0:
+ dma_bits = 0x04;
+ break;
+ case 1:
+ dma_bits = 0x05;
+ break;
+ case 3:
+ dma_bits = 0x06;
+ break;
+ case 5:
+ dma_bits = 0x07;
+ dma16_bits = 0x20;
+ break;
+ default:
+ printk(KERN_ERR "ESS1887: Invalid DMA16 %d\n", dma);
+ return 0;
+ };
+ ess_chgmixer (devc, 0x78, 0x20, dma16_bits);
+ ess_chgmixer (devc, 0x7d, 0x07, dma_bits);
+ }
+ }
+ return 1;
+}
+
+/*
+ * This one is called from sb_dsp_init.
+ *
+ * Return values:
+ * 0: Failed
+ * 1: Succeeded or doesn't apply (not SUBMDL_ES1887)
+ */
+int ess_dsp_init (sb_devc *devc, struct address_info *hw_config)
+{
+ /*
+ * This for ES1887 to run Full Duplex. Actually ES1888
+ * is allowed to do so too. I have no idea yet if this
+ * will work for ES1888 however.
+ *
+ * For SB16 having both dma8 and dma16 means enable
+ * Full Duplex. Let's try this for ES1887 too
+ *
+ */
+ if (devc->submodel == SUBMDL_ES1887) {
+ if (hw_config->dma2 != -1) {
+ devc->dma16 = hw_config->dma2;
+ }
+ /*
+ * devc->duplex initialization is put here, cause
+ * ess_set_dma_hw needs it.
+ */
+ if (devc->dma8 != devc->dma16 && devc->dma16 != -1) {
+ devc->duplex = 1;
+ }
+
+ if (!ess_set_dma_hw (devc)) {
+ free_irq(devc->irq, devc);
+ return 0;
+ }
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+/****************************************************************************
+ * *
+ * ESS mixer *
+ * *
+ ****************************************************************************/
+
+#define ES688_RECORDING_DEVICES \
+ ( SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD )
+#define ES688_MIXER_DEVICES \
+ ( SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE \
+ | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME \
+ | SOUND_MASK_LINE2 | SOUND_MASK_SPEAKER )
+
+#define ES1688_RECORDING_DEVICES \
+ ( ES688_RECORDING_DEVICES )
+#define ES1688_MIXER_DEVICES \
+ ( ES688_MIXER_DEVICES | SOUND_MASK_RECLEV )
+
+#define ES1887_RECORDING_DEVICES \
+ ( ES1688_RECORDING_DEVICES | SOUND_MASK_LINE2 | SOUND_MASK_SYNTH)
+#define ES1887_MIXER_DEVICES \
+ ( ES1688_MIXER_DEVICES )
+
+/*
+ * Mixer registers of ES1887
+ *
+ * These registers specifically take care of recording levels. To make the
+ * mapping from playback devices to recording devices every recording
+ * devices = playback device + ES_REC_MIXER_RECDIFF
+ */
+#define ES_REC_MIXER_RECBASE (SOUND_MIXER_LINE3 + 1)
+#define ES_REC_MIXER_RECDIFF (ES_REC_MIXER_RECBASE - SOUND_MIXER_SYNTH)
+
+#define ES_REC_MIXER_RECSYNTH (SOUND_MIXER_SYNTH + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECPCM (SOUND_MIXER_PCM + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECSPEAKER (SOUND_MIXER_SPEAKER + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE (SOUND_MIXER_LINE + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECMIC (SOUND_MIXER_MIC + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECCD (SOUND_MIXER_CD + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIMIX (SOUND_MIXER_IMIX + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECALTPCM (SOUND_MIXER_ALTPCM + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECRECLEV (SOUND_MIXER_RECLEV + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIGAIN (SOUND_MIXER_IGAIN + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECOGAIN (SOUND_MIXER_OGAIN + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE1 (SOUND_MIXER_LINE1 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE2 (SOUND_MIXER_LINE2 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE3 (SOUND_MIXER_LINE3 + ES_REC_MIXER_RECDIFF)
+
+static mixer_tab es688_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, 0x14, 7, 4, 0x14, 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, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * The ES1688 specifics... hopefully correct...
+ * - 6 bit master volume
+ * I was wrong, ES1888 docs say ES1688 didn't have it.
+ * - RECLEV control
+ * These may apply to ES688 too. I have no idea.
+ */
+static mixer_tab es1688_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, 0x14, 7, 4, 0x14, 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, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+static mixer_tab es1688later_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+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, 0x14, 7, 4, 0x14, 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, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for all ESS chips with a record mixer.
+ * It's not used (yet) however
+ */
+static mixer_tab es_rec_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+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, 0x14, 7, 4, 0x14, 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, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH, 0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE, 0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC, 0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD, 0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2, 0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for ES1887. It's little different from es_rec_mix: it
+ * has 0x7c for PCM playback level. This is because ES1887 uses
+ * Audio 2 for playback.
+ */
+static mixer_tab es1887_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME, 0x60, 5, 6, 0x62, 5, 6),
+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, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV, 0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2, 0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH, 0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE, 0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC, 0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD, 0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1, 0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2, 0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3, 0x00, 0, 0, 0x00, 0, 0)
+};
+
+static int ess_has_rec_mixer (int submodel)
+{
+ switch (submodel) {
+ case SUBMDL_ES1887:
+ return 1;
+ default:
+ return 0;
+ };
+};
+
+#ifdef FKS_LOGGING
+static int ess_mixer_mon_regs[]
+ = { 0x70, 0x71, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7d, 0x7f
+ , 0xa1, 0xa2, 0xa4, 0xa5, 0xa8, 0xa9
+ , 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, 0xb9
+ , 0x00};
+
+static void ess_show_mixerregs (sb_devc *devc)
+{
+ int *mp = ess_mixer_mon_regs;
+
+return;
+
+ while (*mp != 0) {
+ printk (KERN_INFO "res (%x)=%x\n", *mp, (int)(ess_getmixer (devc, *mp)));
+ mp++;
+ }
+}
+#endif
+
+void ess_setmixer (sb_devc * devc, unsigned int port, unsigned int value)
+{
+ unsigned long flags;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: write mixer %x: %x\n", port, value);
+#endif
+
+ save_flags(flags);
+ cli();
+ if (port >= 0xa0) {
+ ess_write (devc, port, value);
+ } else {
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ outb(((unsigned char) (value & 0xff)), MIXER_DATA);
+ udelay(20);
+ };
+ restore_flags(flags);
+}
+
+unsigned int ess_getmixer (sb_devc * devc, unsigned int port)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if (port >= 0xa0) {
+ val = ess_read (devc, port);
+ } else {
+ outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+ udelay(20);
+ val = inb(MIXER_DATA);
+ udelay(20);
+ }
+ restore_flags(flags);
+
+ return val;
+}
+
+static void ess_chgmixer
+ (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ int value;
+
+ value = ess_getmixer (devc, reg);
+ value = (value & ~mask) | (val & mask);
+ ess_setmixer (devc, reg, value);
+}
+
+/*
+ * ess_mixer_init must be called from sb_mixer_init
+ */
+void ess_mixer_init (sb_devc * devc)
+{
+ devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+
+ /*
+ * Take care of ES1887 specifics...
+ */
+ switch (devc->submodel) {
+ case SUBMDL_ES1887:
+ devc->supported_devices = ES1887_MIXER_DEVICES;
+ devc->supported_rec_devices = ES1887_RECORDING_DEVICES;
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: ess_mixer_init dup = %d\n", devc->duplex);
+#endif
+ if (devc->duplex) {
+ devc->iomap = &es1887_mix;
+ } else {
+ devc->iomap = &es_rec_mix;
+ }
+ break;
+ default:
+ if (devc->submodel < 8) {
+ devc->supported_devices = ES688_MIXER_DEVICES;
+ devc->supported_rec_devices = ES688_RECORDING_DEVICES;
+ devc->iomap = &es688_mix;
+ } else {
+ /*
+ * es1688 has 4 bits master vol.
+ * later chips have 6 bits (?)
+ */
+ devc->supported_devices = ES1688_MIXER_DEVICES;
+ devc->supported_rec_devices = ES1688_RECORDING_DEVICES;
+ if (devc->submodel < 0x10) {
+ devc->iomap = &es1688_mix;
+ } else {
+ devc->iomap = &es1688later_mix;
+ }
+ }
+ }
+}
+
+/*
+ * Changing playback levels at an ESS chip with record mixer means having to
+ * take care of recording levels of recorded inputs (devc->recmask) too!
+ */
+int ess_mixer_set(sb_devc *devc, int dev, int left, int right)
+{
+ if (ess_has_rec_mixer (devc->submodel) && (devc->recmask & (1 << dev))) {
+ sb_common_mixer_set (devc, dev + ES_REC_MIXER_RECDIFF, left, right);
+ }
+ return sb_common_mixer_set (devc, dev, left, right);
+}
+
+/*
+ * After a sb_dsp_reset extended register 0xb4 (RECLEV) is reset too. After
+ * sb_dsp_reset RECLEV has to be restored. This is where ess_mixer_reload
+ * helps.
+ */
+void ess_mixer_reload (sb_devc *devc, int dev)
+{
+ int left, right, value;
+
+ value = devc->levels[dev];
+ left = value & 0x000000ff;
+ right = (value & 0x0000ff00) >> 8;
+
+ sb_common_mixer_set(devc, dev, left, right);
+}
+
+int es_rec_set_recmask(sb_devc * devc, int mask)
+{
+ int i, i_mask, cur_mask, diff_mask;
+ int value, left, right;
+
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: es_rec_set_recmask mask = %x\n", mask);
+#endif
+ /*
+ * Changing the recmask on an ESS chip with recording mixer means:
+ * (1) Find the differences
+ * (2) For "turned-on" inputs: make the recording level the playback level
+ * (3) For "turned-off" inputs: make the recording level zero
+ */
+ cur_mask = devc->recmask;
+ diff_mask = (cur_mask ^ mask);
+
+ for (i = 0; i < 32; i++) {
+ i_mask = (1 << i);
+ if (diff_mask & i_mask) { /* Difference? (1) */
+ if (mask & i_mask) { /* Turn it on (2) */
+ value = devc->levels[i];
+ left = value & 0x000000ff;
+ right = (value & 0x0000ff00) >> 8;
+ } else { /* Turn it off (3) */
+ left = 0;
+ left = 0;
+ right = 0;
+ }
+ sb_common_mixer_set(devc, i + ES_REC_MIXER_RECDIFF, left, right);
+ }
+ }
+ return mask;
+}
+
+int ess_set_recmask(sb_devc * devc, int *mask)
+{
+ /* This applies to ESS chips with record mixers only! */
+
+ if (ess_has_rec_mixer (devc->submodel)) {
+ *mask = es_rec_set_recmask (devc, *mask);
+ return 1; /* Applied */
+ } else {
+ return 0; /* Not applied */
+ }
+}
+
+/*
+ * ess_mixer_reset must be called from sb_mixer_reset
+ */
+int ess_mixer_reset (sb_devc * devc)
+{
+ /*
+ * Separate actions for ESS chips with a record mixer:
+ */
+ if (ess_has_rec_mixer (devc->submodel)) {
+ switch (devc->submodel) {
+ case SUBMDL_ES1887:
+ /*
+ * Separate actions for ES1887:
+ * Change registers 7a and 1c to make the record mixer the
+ * actual recording source.
+ */
+ ess_chgmixer(devc, 0x7a, 0x18, 0x08);
+ ess_chgmixer(devc, 0x1c, 0x07, 0x07);
+ break;
+ };
+ /*
+ * Call set_recmask for proper initialization
+ */
+ devc->recmask = devc->supported_rec_devices;
+ es_rec_set_recmask(devc, 0);
+ devc->recmask = 0;
+
+ return 1; /* We took care of recmask. */
+ } else {
+ return 0; /* We didn't take care; caller do it */
+ }
+}
+
+/****************************************************************************
+ * *
+ * ESS midi *
+ * *
+ ****************************************************************************/
+
+/*
+ * FKS: IRQ may be shared. Hm. And if so? Then What?
+ */
+int ess_midi_init(sb_devc * devc, struct address_info *hw_config)
+{
+ unsigned char cfg, tmp;
+
+ cfg = ess_getmixer (devc, 0x40) & 0x03;
+
+ if (devc->submodel < 8) {
+ ess_setmixer (devc, 0x40, cfg | 0x03); /* Enable OPL3 & joystick */
+ return 0; /* ES688 doesn't support MPU401 mode */
+ }
+ tmp = (hw_config->io_base & 0x0f0) >> 4;
+
+ if (tmp > 3) {
+ ess_setmixer (devc, 0x40, cfg);
+ return 0;
+ }
+ cfg |= tmp << 3;
+
+ tmp = 1; /* MPU enabled without interrupts */
+
+ /* May be shared: if so the value is -ve */
+
+ switch (abs(hw_config->irq)) {
+ case 9:
+ tmp = 0x4;
+ break;
+ case 5:
+ tmp = 0x5;
+ break;
+ case 7:
+ tmp = 0x6;
+ break;
+ case 10:
+ tmp = 0x7;
+ break;
+ default:
+ return 0;
+ }
+
+ cfg |= tmp << 5;
+ ess_setmixer (devc, 0x40, cfg | 0x03);
+
+ return 1;
+}
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov