summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_cmi878x/oss_cmi878x.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_cmi878x/oss_cmi878x.c')
-rw-r--r--kernel/drv/oss_cmi878x/oss_cmi878x.c3104
1 files changed, 3104 insertions, 0 deletions
diff --git a/kernel/drv/oss_cmi878x/oss_cmi878x.c b/kernel/drv/oss_cmi878x/oss_cmi878x.c
new file mode 100644
index 0000000..e19229c
--- /dev/null
+++ b/kernel/drv/oss_cmi878x/oss_cmi878x.c
@@ -0,0 +1,3104 @@
+/*
+ * Purpose: Driver for C-Media CMI8788 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_cmi878x_cfg.h"
+#include <oss_pci.h>
+#include <uart401.h>
+#include <ac97.h>
+
+#define CMEDIA_VENDOR_ID 0x13F6
+#define CMEDIA_CMI8788 0x8788
+
+/*
+ * CM8338 registers definition
+ */
+
+#define RECA_ADDR (devc->base+0x00)
+#define RECA_SIZE (devc->base+0x04)
+#define RECA_FRAG (devc->base+0x06)
+#define RECB_ADDR (devc->base+0x08)
+#define RECB_SIZE (devc->base+0x0C)
+#define RECB_FRAG (devc->base+0x0E)
+#define RECC_ADDR (devc->base+0x10)
+#define RECC_SIZE (devc->base+0x14)
+#define RECC_FRAG (devc->base+0x16)
+#define SPDIF_ADDR (devc->base+0x18)
+#define SPDIF_SIZE (devc->base+0x1C)
+#define SPDIF_FRAG (devc->base+0x1E)
+#define MULTICH_ADDR (devc->base+0x20)
+#define MULTICH_SIZE (devc->base+0x24)
+#define MULTICH_FRAG (devc->base+0x28)
+#define FPOUT_ADDR (devc->base+0x30)
+#define FPOUT_SIZE (devc->base+0x34)
+#define FPOUT_FRAG (devc->base+0x36)
+
+#define DMA_START (devc->base+0x40)
+#define CHAN_RESET (devc->base+0x42)
+#define MULTICH_MODE (devc->base+0x43)
+#define IRQ_MASK (devc->base+0x44)
+#define IRQ_STAT (devc->base+0x46)
+#define MISC_REG (devc->base+0x48)
+#define REC_FORMAT (devc->base+0x4A)
+#define PLAY_FORMAT (devc->base+0x4B)
+#define REC_MODE (devc->base+0x4C)
+#define FUNCTION (devc->base+0x50)
+
+#define I2S_MULTICH_FORMAT (devc->base+0x60)
+#define I2S_ADC1_FORMAT (devc->base+0x62)
+#define I2S_ADC2_FORMAT (devc->base+0x64)
+#define I2S_ADC3_FORMAT (devc->base+0x66)
+
+#define SPDIF_FUNC (devc->base+0x70)
+#define SPDIFOUT_CHAN_STAT (devc->base+0x74)
+#define SPDIFIN_CHAN_STAT (devc->base+0x78)
+
+#define I2C_ADDR (devc->base+0x90)
+#define I2C_MAP (devc->base+0x91)
+#define I2C_DATA (devc->base+0x92)
+#define I2C_CTRL (devc->base+0x94)
+
+#define SPI_CONTROL (devc->base+0x98)
+#define SPI_DATA (devc->base+0x99)
+
+#define MPU401_DATA (devc->base+0xA0)
+#define MPU401_COMMAND (devc->base+0xA1)
+#define MPU401_CONTROL (devc->base+0xA2)
+
+#define GPI_DATA (devc->base+0xA4)
+#define GPI_IRQ_MASK (devc->base+0xA5)
+#define GPIO_DATA (devc->base+0xA6)
+#define GPIO_CONTROL (devc->base+0xA8)
+#define GPIO_IRQ_MASK (devc->base+0xAA)
+#define DEVICE_SENSE (devc->base+0xAC)
+
+#define PLAY_ROUTING (devc->base+0xC0)
+#define REC_ROUTING (devc->base+0xC2)
+#define REC_MONITOR (devc->base+0xC3)
+#define MONITOR_ROUTING (devc->base+0xC4)
+
+#define AC97_CTRL (devc->base+0xD0)
+#define AC97_INTR_MASK (devc->base+0xD2)
+#define AC97_INTR_STAT (devc->base+0xD3)
+#define AC97_OUT_CHAN_CONFIG (devc->base+0xD4)
+#define AC97_IN_CHAN_CONFIG (devc->base+0xD8)
+#define AC97_CMD_DATA (devc->base+0xDC)
+
+#define CODEC_VERSION (devc->base+0xE4)
+#define CTRL_VERSION (devc->base+0xE6)
+
+/* Device IDs */
+#define ASUS_VENDOR_ID 0x1043
+#define SUBID_XONAR_D2 0x8269
+#define SUBID_XONAR_D2X 0x82b7
+#define SUBID_XONAR_D1 0x834f
+#define SUBID_XONAR_DX 0x8275
+#define SUBID_XONAR_STX 0x835c
+#define SUBID_XONAR_DS 0x838e
+#define SUBID_XONAR_ST 0x835d
+
+#define SUBID_GENERIC 0x0000
+
+/* Xonar specific */
+#define XONAR_DX_FRONTDAC 0x9e
+#define XONAR_DX_SURRDAC 0x30
+#define XONAR_STX_FRONTDAC 0x98
+#define XONAR_ST_FRONTDAC 0x98
+#define XONAR_ST_CLOCK 0x9c
+#define XONAR_DS_FRONTDAC 0x1
+#define XONAR_DS_SURRDAC 0x0
+#define XONAR_MCLOCK_256 0x10
+#define XONAR_MCLOCK_512 0x20
+
+/* defs for AKM 4396 DAC */
+#define AK4396_CTL1 0x00
+#define AK4396_CTL2 0x01
+#define AK4396_CTL3 0x02
+#define AK4396_LchATTCtl 0x03
+#define AK4396_RchATTCtl 0x04
+
+/* defs for CS4398 DAC */
+#define CS4398_CHIP_ID 0x01
+#define CS4398_MODE_CTRL 0x02
+#define CS4398_MIXING 0x03
+#define CS4398_MUTE_CTRL 0x04
+#define CS4398_VOLA 0x05
+#define CS4398_VOLB 0x06
+#define CS4398_RAMP_CTRL 0x07
+#define CS4398_MISC_CTRL 0x08
+#define CS4398_MISC2_CTRL 0x09
+
+#define CS4398_POWER_DOWN (1<<7) /* Obvious */
+#define CS4398_CPEN (1<<6) /* Control Port Enable */
+#define CS4398_FREEZE (1<<5) /* Freezes registers, unfreeze to
+ * accept changed registers */
+#define CS4398_MCLKDIV2 (1<<4) /* Divide MCLK by 2 */
+#define CS4398_MCLKDIV3 (1<<3) /* Divive MCLK by 3 */
+#define CS4398_I2S (1<<4) /* Set I2S mode */
+
+/* defs for CS4362A DAC */
+#define CS4362A_MODE1_CTRL 0x01
+#define CS4362A_MODE2_CTRL 0x02
+#define CS4362A_MODE3_CTRL 0x03
+#define CS4362A_FILTER_CTRL 0x04
+#define CS4362A_INVERT_CTRL 0x05
+#define CS4362A_MIX1_CTRL 0x06
+#define CS4362A_VOLA_1 0x07
+#define CS4362A_VOLB_1 0x08
+#define CS4362A_MIX2_CTRL 0x09
+#define CS4362A_VOLA_2 0x0A
+#define CS4362A_VOLB_2 0x0B
+#define CS4362A_MIX3_CTRL 0x0C
+#define CS4362A_VOLA_3 0x0D
+#define CS4362A_VOLB_3 0x0E
+#define CS4362A_CHIP_REV 0x12
+
+/* CS4362A Reg 01h */
+#define CS4362A_CPEN (1<<7)
+#define CS4362A_FREEZE (1<<6)
+#define CS4362A_MCLKDIV (1<<5)
+#define CS4362A_DAC3_ENABLE (1<<3)
+#define CS4362A_DAC2_ENABLE (1<<2)
+#define CS4362A_DAC1_ENABLE (1<<1)
+#define CS4362A_POWER_DOWN (1)
+
+/* CS4362A Reg 02h */
+#define CS4362A_DIF_LJUST 0x00
+#define CS4362A_DIF_I2S 0x10
+#define CS4362A_DIF_RJUST16 0x20
+#define CS4362A_DIF_RJUST24 0x30
+#define CS4362A_DIF_RJUST20 0x40
+#define CS4362A_DIF_RJUST18 0x50
+
+/* CS4362A Reg 03h */
+#define CS4362A_RAMP_IMMEDIATE 0x00
+#define CS4362A_RAMP_ZEROCROSS 0x40
+#define CS4362A_RAMP_SOFT 0x80
+#define CS4362A_RAMP_SOFTZERO 0xC0
+#define CS4362A_SINGLE_VOL 0x20
+#define CS4362A_RAMP_ERROR 0x10
+#define CS4362A_MUTEC_POL 0x08
+#define CS4362A_AUTOMUTE 0x04
+#define CS4362A_SIX_MUTE 0x00
+#define CS4362A_ONE_MUTE 0x01
+#define CS4362A_THREE_MUTE 0x03
+
+/* CS4362A Reg 04h */
+#define CS4362A_FILT_SEL 0x10
+#define CS4362A_DEM_NONE 0x00
+#define CS4362A_DEM_44KHZ 0x02
+#define CS4362A_DEM_48KHZ 0x04
+#define CS4362A_DEM_32KHZ 0x06
+#define CS4362A_RAMPDOWN 0x01
+
+/* CS4362A Reg 05h */
+#define CS4362A_INV_A3 (1<<4)
+#define CS4362A_INV_B3 (1<<5)
+#define CS4362A_INV_A2 (1<<2)
+#define CS4362A_INV_B2 (1<<3)
+#define CS4362A_INV_A1 (1)
+#define CS4362A_INV_B1 (1<<1)
+
+/* CS4362A Reg 06h, 09h, 0Ch */
+/* ATAPI crap, does anyone still use analog CD playback? */
+
+/* CS4362A Reg 07h, 08h, 0Ah, 0Bh, 0Dh, 0Eh */
+/* Volume registers */
+#define CS4362A_VOL_MUTE 0x80
+
+
+/* 0-100. Start at -96dB. */
+#define CS4398_VOL(x) \
+ ((x) == 0 ? 0xFF : (0xC0 - ((x)*192/100)))
+/* 0-100. Start at -96dB. Bit 7 is mute. */
+#define CS4362A_VOL(x) \
+ (char)((x) == 0 ? 0xFF : (0x60 - ((x)*96/100)))
+
+#define UNUSED_CMI9780_CONTROLS ( \
+ SOUND_MASK_VOLUME | \
+ SOUND_MASK_PCM | \
+ SOUND_MASK_REARVOL | \
+ SOUND_MASK_CENTERVOL | \
+ SOUND_MASK_SIDEVOL | \
+ SOUND_MASK_SPEAKER | \
+ SOUND_MASK_ALTPCM | \
+ SOUND_MASK_VIDEO | \
+ SOUND_MASK_DEPTH | \
+ SOUND_MASK_MONO | \
+ SOUND_MASK_PHONE \
+ )
+
+typedef struct cmi8788_portc
+{
+ int speed, bits, channels;
+ int open_mode;
+ int trigger_bits;
+ int audio_enabled;
+ int audiodev;
+ int dac_type;
+#define ADEV_MULTICH 0
+#define ADEV_FRONTPANEL 2
+#define ADEV_SPDIF 3
+ int adc_type;
+#define ADEV_I2SADC1 0
+#define ADEV_I2SADC2 1
+#define ADEV_I2SADC3 2
+ int play_dma_start, rec_dma_start;
+ int play_irq_mask, rec_irq_mask;
+ int play_chan_reset, rec_chan_reset;
+ int min_rate, max_rate, min_chan, max_chan;
+}
+cmi8788_portc;
+
+#define MAX_PORTC 4
+
+typedef struct cmi8788_devc
+{
+ oss_device_t *osdev;
+ oss_native_word base;
+ int fm_attached;
+ int irq;
+ int dma_len;
+ volatile unsigned char intr_mask;
+ int model;
+#define MDL_CMI8788 1
+ char *chip_name;
+
+ /* Audio parameters */
+ oss_mutex_t mutex;
+ oss_mutex_t low_mutex;
+ oss_mutex_t dac_mutex;
+ int open_mode;
+ cmi8788_portc portc[MAX_PORTC];
+
+ /* Mixer */
+ ac97_devc ac97devc, fp_ac97devc;
+ int ac97_mixer_dev, fp_mixer_dev, cmi_mixer_dev;
+ int playvol[4];
+ int recvol;
+ int mute;
+
+ /* uart401 */
+ oss_midi_inputbyte_t midi_input_intr;
+ int midi_opened, midi_disabled;
+ volatile unsigned char input_byte;
+ int midi_dev;
+ int mpu_attached;
+}
+cmi8788_devc;
+
+static const char xd2_codec_map[4] = {
+ 0, 1, 2, 4
+ };
+
+static void cmi8788uartintr (cmi8788_devc * devc);
+static int reset_cmi8788uart (cmi8788_devc * devc);
+static void enter_uart_mode (cmi8788_devc * devc);
+
+static int
+ac97_read (void *devc_, int reg)
+{
+ cmi8788_devc *devc = devc_;
+ oss_native_word flags;
+ int val, data;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+ val = 0L;
+ val |= reg << 16;
+ val |= 1 << 23; /*ac97 read the reg address */
+ val |= 0 << 24; /*codec 0 */
+ OUTL (devc->osdev, val, AC97_CMD_DATA);
+ oss_udelay (200);
+ data = INL (devc->osdev, AC97_CMD_DATA) & 0xFFFF;
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return data;
+}
+
+static int
+ac97_write (void *devc_, int reg, int data)
+{
+ cmi8788_devc *devc = devc_;
+ oss_native_word flags;
+ int val;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+
+ val = 0L;
+ val |= data & 0xFFFF;
+ val |= reg << 16;
+ val |= 0 << 23; /*ac97 write operation */
+ val |= 0 << 24; /*on board codec */
+ OUTL (devc->osdev, val, AC97_CMD_DATA);
+ oss_udelay (200);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 1;
+}
+
+static int
+fp_ac97_read (void *devc_, int reg)
+{
+ cmi8788_devc *devc = devc_;
+ oss_native_word flags;
+ int data, val;
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+ val = 0L;
+ val |= reg << 16;
+ val |= 1 << 23; /*ac97 read the reg address */
+ val |= 1 << 24; /*fp codec1 */
+ OUTL (devc->osdev, val, AC97_CMD_DATA);
+ oss_udelay (200);
+ data = INL (devc->osdev, AC97_CMD_DATA) & 0xFFFF;
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return data;
+}
+
+static int
+fp_ac97_write (void *devc_, int reg, int data)
+{
+ cmi8788_devc *devc = devc_;
+ oss_native_word flags;
+ int val;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+
+ val = 0L;
+ val |= data & 0xFFFF;
+ val |= reg << 16;
+ val |= 0 << 23; /*ac97 write operation */
+ val |= 1 << 24; /*fp codec1 */
+ OUTL (devc->osdev, val, AC97_CMD_DATA);
+ oss_udelay (200);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 1;
+}
+
+static int
+spi_write (cmi8788_devc *devc, int codec_num, unsigned char reg, int val)
+{
+ oss_native_word flags;
+ unsigned int tmp;
+ int latch, shift, count;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+
+ /* check if SPI is busy */
+ count = 10;
+ while ((INB(devc->osdev, SPI_CONTROL) & 0x1) && count-- > 0) {
+ oss_udelay(10);
+ }
+
+ if (devc->model == SUBID_XONAR_DS) {
+ shift = 9;
+ latch = 0;
+ }
+ else {
+ shift = 8;
+ latch = 0x80;
+ }
+
+ /* 2 byte data/reg info to be written */
+ tmp = val;
+ tmp |= (reg << shift);
+
+ /* write 2-byte data values */
+ OUTB (devc->osdev, tmp & 0xff, SPI_DATA + 0);
+ OUTB (devc->osdev, (tmp >> 8) & 0xff, SPI_DATA + 1);
+
+ /* Latch high, clock=160, Len=2byte, mode=write */
+ tmp = (INB (devc->osdev, SPI_CONTROL) & ~0x7E) | latch | 0x1;
+
+ /* now address which codec you want to send the data to */
+ tmp |= (codec_num << 4);
+
+ /* send the command to write the data */
+ OUTB (devc->osdev, tmp, SPI_CONTROL);
+
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 1;
+}
+
+static int
+i2c_write (cmi8788_devc *devc, unsigned char codec_num, unsigned char reg,
+ unsigned char data)
+{
+ oss_native_word flags;
+ int count = 50;
+
+ /* Wait for it to stop being busy */
+ MUTEX_ENTER(devc->dac_mutex, flags);
+ while((INW(devc->osdev, I2C_CTRL) & 0x1) && (count > 0))
+ {
+ oss_udelay(10);
+ count--;
+ }
+ if(count == 0)
+ {
+ cmn_err(CE_WARN, "Time out on Two-Wire interface (busy).");
+ MUTEX_EXIT(devc->dac_mutex, flags);
+ return OSS_EIO;
+ }
+ MUTEX_EXIT(devc->dac_mutex, flags);
+
+ MUTEX_ENTER_IRQDISABLE(devc->low_mutex, flags);
+ /* first write the Register Address into the MAP register */
+ OUTB (devc->osdev, reg, I2C_MAP);
+
+ /* now write the data */
+ OUTB (devc->osdev, data, I2C_DATA);
+
+ /* select the codec number to address */
+ OUTB (devc->osdev, codec_num, I2C_ADDR);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ oss_udelay(100);
+
+ return 1;
+
+}
+
+static void
+cs4398_init (cmi8788_devc *devc, int codec_addr)
+{
+
+ /* Fast Two-Wire. Reduces the wire ready time. */
+ OUTW(devc->osdev, 0x0100, I2C_CTRL);
+
+ /* Power down, enable control mode.
+ * i2c_write(devc, codec_addr, CS4398_MISC_CTRL,
+ * CS4398_CPEN | CS4398_POWER_DOWN);
+ * Left justified PCM (DAC and 8788 support I2S, but doesn't work.
+ * Setting it introduces clipping like hell).
+ */
+ i2c_write(devc, codec_addr, CS4398_MODE_CTRL, 0);
+ /* That's the DAC default, set anyway. */
+ i2c_write(devc, codec_addr, 3, 0x09);
+ /* PCM auto-mute. */
+ i2c_write(devc, codec_addr, 4, 0x82);
+ /* Vol A+B to -64dB. */
+ i2c_write(devc, codec_addr, 5, 0x80);
+ i2c_write(devc, codec_addr, 6, 0x80);
+ /* Soft-ramping. */
+ i2c_write(devc, codec_addr, 7, 0xF0);
+ /* Remove power down flag. */
+ i2c_write(devc, codec_addr, CS4398_MISC_CTRL, CS4398_CPEN);
+}
+
+static void
+cs4362a_init(cmi8788_devc *devc, int codec_addr)
+{
+
+ /* Fast Two-Wire. Reduces the wire ready time. */
+ OUTW(devc->osdev, 0x0100, I2C_CTRL);
+
+ /* Power down and enable control port. */
+ i2c_write(devc, codec_addr, CS4362A_MODE1_CTRL, CS4362A_CPEN | CS4362A_POWER_DOWN);
+ /* Left-justified PCM */
+ i2c_write(devc, codec_addr, CS4362A_MODE2_CTRL, CS4362A_DIF_LJUST);
+ /* Ramp & Automute, re-set DAC defaults. */
+ i2c_write(devc, codec_addr, CS4362A_MODE3_CTRL, 0x84);
+ /* Filter control, DAC defs. */
+ i2c_write(devc, codec_addr, CS4362A_FILTER_CTRL, 0);
+ /* Invert control, DAC defs. */
+ i2c_write(devc, codec_addr, CS4362A_INVERT_CTRL, 0);
+ /* Mixing control, DAC defs. */
+ i2c_write(devc, codec_addr, CS4362A_MIX1_CTRL, 0x24);
+ i2c_write(devc, codec_addr, CS4362A_MIX2_CTRL, 0x24);
+ i2c_write(devc, codec_addr, CS4362A_MIX3_CTRL, 0x24);
+ /* Volume to -64dB. */
+ i2c_write(devc, codec_addr, CS4362A_VOLA_1, 0x40);
+ i2c_write(devc, codec_addr, CS4362A_VOLB_1, 0x40);
+ i2c_write(devc, codec_addr, CS4362A_VOLA_2, 0x40);
+ i2c_write(devc, codec_addr, CS4362A_VOLB_2, 0x40);
+ i2c_write(devc, codec_addr, CS4362A_VOLA_3, 0x40);
+ i2c_write(devc, codec_addr, CS4362A_VOLB_3, 0x40);
+ /* Power up. */
+ i2c_write(devc, codec_addr, CS4362A_MODE1_CTRL, CS4362A_CPEN);
+}
+
+
+static void
+cs2000_init(cmi8788_devc *devc, int addr)
+{
+
+ /* Fast Two-Wire. Reduces the wire ready time. */
+ OUTW(devc->osdev, 0x0100, I2C_CTRL);
+
+ /* first set global config reg to freeze */
+ i2c_write (devc, addr, 0x5, 0x08);
+
+ /* set dev ctrl reg to output enabled (0) */
+ i2c_write (devc, addr, 0x2, 0);
+
+ /* set dev cfg1 reg to defaults and enable */
+ i2c_write (devc, addr, 0x3, 0x0 | (0 << 3) | 0x0 | 0x1);
+
+ /* set dev cfg2 reg to defaults and enable */
+ i2c_write (devc, addr, 0x4, (0 << 1) | 0x0);
+
+ /* set ratio0 MSB to 0 */
+ i2c_write (devc, addr, 0x06, 0x00);
+ /* set ratio0 MSB+8 to 0x10 */
+ i2c_write (devc, addr, 0x07, 0x10);
+ /* set ratio0 LSB+15 to 0 */
+ i2c_write (devc, addr, 0x08, 0x00);
+ /* set ratio0 LSB+7 to 0 */
+ i2c_write (devc, addr, 0x09, 0x00);
+
+ /* Func reg1 to ref clock div 1 */
+ i2c_write (devc, addr, 0x16, 0x10);
+
+ /* set func reg2 to 0 */
+ i2c_write (devc, addr, 0x17, 0);
+
+ /* set global cfg to run */
+ i2c_write (devc, addr, 0x05, 0x1);
+}
+
+
+static unsigned int
+mix_scale (int vol, int bits)
+{
+ vol = mix_cvt[vol];
+ return (vol * ((1 << bits) - 1) / 100);
+}
+
+
+static void
+cmi8788_generic_set_play_volume (cmi8788_devc *devc, int codec_id, int left, int right)
+
+{
+ spi_write (devc, codec_id, AK4396_LchATTCtl | 0x20, mix_scale(left, 8));
+ spi_write (devc, codec_id, AK4396_RchATTCtl | 0x20, mix_scale(right, 8));
+}
+
+static void
+xonar_d1_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
+{
+ switch(codec_id)
+ {
+ case 0:
+ i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLA, CS4398_VOL(left));
+ i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLB, CS4398_VOL(right));
+ break;
+ case 1:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_1, CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_1, CS4362A_VOL(right));
+ break;
+ case 2:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_2, CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_2, CS4362A_VOL(right));
+ break;
+ case 3:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_3, CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_3, CS4362A_VOL(right));
+ break;
+ }
+}
+
+static void
+xonar_d2_set_play_volume(cmi8788_devc *devc, int codec_id, int left, int right)
+{
+ spi_write (devc, xd2_codec_map[codec_id], 16, mix_scale(left,8));
+ spi_write (devc, xd2_codec_map[codec_id], 17, mix_scale(right,8));
+}
+
+static void
+xonar_stx_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
+{
+ if (codec_id == 0)
+ {
+ i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(left,8));
+ i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(right,8));
+ }
+}
+
+static void
+xonar_st_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
+{
+ if (codec_id == 0)
+ {
+ i2c_write(devc, XONAR_ST_FRONTDAC, 16, mix_scale(left,8));
+ i2c_write(devc, XONAR_ST_FRONTDAC, 17, mix_scale(right,8));
+ }
+}
+
+static void
+xonar_ds_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
+{
+ switch (codec_id)
+ {
+ case 0: /* front */
+ spi_write (devc, XONAR_DS_FRONTDAC, 0, mix_scale(left,7)|0x80);
+ spi_write (devc, XONAR_DS_FRONTDAC, 1, mix_scale(right,7)|0x180);
+ spi_write (devc, XONAR_DS_FRONTDAC, 3, mix_scale(left,7)|0x80);
+ spi_write (devc, XONAR_DS_FRONTDAC, 4, mix_scale(right,7)|0x180);
+ break;
+
+ case 1: /* side */
+ spi_write (devc, XONAR_DS_SURRDAC, 0, mix_scale(left,7)|0x80);
+ spi_write (devc, XONAR_DS_SURRDAC, 1, mix_scale(right,7)|0x180);
+ break;
+ case 2: /* rear */
+ spi_write (devc, XONAR_DS_SURRDAC, 4, mix_scale(left,7)|0x80);
+ spi_write (devc, XONAR_DS_SURRDAC, 5, mix_scale(right,7)|0x180);
+ break;
+ case 3: /* center */
+ spi_write (devc, XONAR_DS_SURRDAC, 6, mix_scale(left,7)|0x80);
+ spi_write (devc, XONAR_DS_SURRDAC, 7, mix_scale(right,7)|0x180);
+ break;
+ }
+}
+
+static int
+cmi8788_set_rec_volume (cmi8788_devc * devc, int value)
+{
+ unsigned char left, right;
+
+ left = value & 0xff;
+ right = (value >> 8) & 0xff;
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ devc->recvol = left | (right << 8);
+
+ spi_write (devc, XONAR_DS_FRONTDAC, 0xe, mix_scale(left,8));
+ spi_write (devc, XONAR_DS_FRONTDAC, 0xf, mix_scale(right,8));
+
+ return devc->recvol;
+}
+
+
+
+static int
+cmi8788_set_play_volume (cmi8788_devc * devc, int codec_id, int value)
+{
+ int left, right;
+
+ left = (value & 0x00FF);
+ right = (value & 0xFF00) >> 8;
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ devc->playvol[codec_id] = left | (right<<8);
+
+ switch(devc->model)
+ {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ xonar_d1_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ xonar_d2_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_STX:
+ xonar_stx_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_ST:
+ xonar_st_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_DS:
+ xonar_ds_set_play_volume(devc, codec_id, left, right);
+ break;
+ default:
+ cmi8788_generic_set_play_volume (devc, codec_id, left, right);
+ break;
+ }
+ return devc->playvol[codec_id];
+}
+
+static int
+cmi8788intr (oss_device_t * osdev)
+{
+ cmi8788_devc *devc = (cmi8788_devc *) osdev->devc;
+ unsigned int intstat;
+ int i;
+ int serviced = 0;
+
+
+ intstat = INW (devc->osdev, IRQ_STAT);
+ if (intstat != 0)
+ serviced = 1;
+ else
+ return 0;
+
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ cmi8788_portc *portc = &devc->portc[i];
+
+ /* Handle Playback Ints */
+ if ((intstat & portc->play_irq_mask)
+ && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
+ {
+
+ /* Acknowledge the interrupt by disabling and enabling the irq */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) & ~portc->play_irq_mask,
+ IRQ_MASK);
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) | portc->play_irq_mask, IRQ_MASK);
+
+ /* process buffer */
+ oss_audio_outputintr (portc->audiodev, 0);
+ }
+
+ /* Handle Record Ints */
+ if ((intstat & portc->rec_irq_mask)
+ && (portc->trigger_bits & PCM_ENABLE_INPUT))
+ {
+ /* disable the interrupt first */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) & ~portc->rec_irq_mask, IRQ_MASK);
+ /* enable the interrupt mask */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) | portc->rec_irq_mask, IRQ_MASK);
+ oss_audio_inputintr (portc->audiodev, 0);
+ }
+
+ }
+
+ /* MPU interrupt */
+ if (intstat & 0x1000)
+ {
+ cmi8788uartintr (devc);
+ }
+
+ return serviced;
+}
+
+static int
+cmi8788_audio_set_rate (int dev, int arg)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->speed;
+
+ if (arg > portc->max_rate)
+ arg = portc->max_rate;
+ if (arg < portc->min_rate)
+ arg = portc->min_rate;
+
+ portc->speed = arg;
+ return portc->speed;
+}
+
+static short
+cmi8788_audio_set_channels (int dev, short arg)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+
+ if (audio_engines[dev]->flags & ADEV_STEREOONLY)
+ arg = 2;
+
+ if (arg == 1)
+ arg = 2;
+
+ if (arg>8)
+ arg=8;
+ if ((arg != 2) && (arg != 4) && (arg != 6) && (arg != 8))
+ return portc->channels;
+ portc->channels = arg;
+
+ return portc->channels;
+}
+
+static unsigned int
+cmi8788_audio_set_format (int dev, unsigned int arg)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->bits;
+
+ if (audio_engines[dev]->flags & ADEV_16BITONLY)
+ arg = AFMT_S16_LE;
+
+ if (!(arg & (AFMT_S16_LE | AFMT_AC3 | AFMT_S32_LE)))
+ return portc->bits;
+ portc->bits = arg;
+ return portc->bits;
+}
+
+/*ARGSUSED*/
+static int
+cmi8788_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static void cmi8788_audio_trigger (int dev, int state);
+
+static void
+cmi8788_audio_reset (int dev)
+{
+ cmi8788_audio_trigger (dev, 0);
+}
+
+static void
+cmi8788_audio_reset_input (int dev)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ cmi8788_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
+}
+
+static void
+cmi8788_audio_reset_output (int dev)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ cmi8788_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
+}
+
+/*ARGSUSED*/
+static int
+cmi8788_audio_open (int dev, int mode, int open_flags)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ if (portc->open_mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ if (devc->open_mode & mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ devc->open_mode |= mode;
+
+ portc->open_mode = mode;
+ portc->audio_enabled &= ~mode;
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static void
+cmi8788_audio_close (int dev, int mode)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+
+ cmi8788_audio_reset (dev);
+ portc->open_mode = 0;
+ devc->open_mode &= ~mode;
+ portc->audio_enabled &= ~mode;
+}
+
+/*ARGSUSED*/
+static void
+cmi8788_audio_output_block (int dev, oss_native_word buf, int count,
+ int fragsize, int intrflag)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+
+ portc->audio_enabled |= PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+}
+
+/*ARGSUSED*/
+static void
+cmi8788_audio_start_input (int dev, oss_native_word buf, int count,
+ int fragsize, int intrflag)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+
+ portc->audio_enabled |= PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+}
+
+static void
+cmi8788_audio_trigger (int dev, int state)
+{
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+ oss_native_word flags;
+
+ 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))
+ {
+ /* Enable Interrupt */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) | portc->play_irq_mask,
+ IRQ_MASK);
+ /* enable the dma */
+ OUTW (devc->osdev,
+ INW (devc->osdev, DMA_START) | portc->play_dma_start,
+ DMA_START);
+ portc->trigger_bits |= PCM_ENABLE_OUTPUT;
+ }
+ }
+ else
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
+ (portc->trigger_bits & PCM_ENABLE_OUTPUT))
+ {
+ portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+
+ /* disable dma */
+ OUTW (devc->osdev,
+ INW (devc->osdev, DMA_START) & ~portc->play_dma_start,
+ DMA_START);
+ /* Disable Interrupt */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) & ~portc->play_irq_mask,
+ IRQ_MASK);
+ }
+ }
+ }
+
+ if (portc->open_mode & OPEN_READ)
+ {
+ if (state & PCM_ENABLE_INPUT)
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
+ !(portc->trigger_bits & PCM_ENABLE_INPUT))
+ {
+ /* Enable Interrupt */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) | portc->rec_irq_mask,
+ IRQ_MASK);
+
+ /* enable the channel */
+ OUTW (devc->osdev,
+ INW (devc->osdev, DMA_START) | portc->rec_dma_start,
+ DMA_START);
+ 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;
+ /* disable channel */
+ OUTW (devc->osdev,
+ INW (devc->osdev, DMA_START) & ~portc->rec_dma_start,
+ DMA_START);
+
+ /* Disable Interrupt */
+ OUTW (devc->osdev,
+ INW (devc->osdev, IRQ_MASK) & ~portc->rec_irq_mask,
+ IRQ_MASK);
+
+ }
+ }
+ }
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+static void
+cmi8788_reset_channel (cmi8788_devc * devc, cmi8788_portc * portc,
+ int direction)
+{
+ if (direction == PCM_ENABLE_OUTPUT)
+ {
+ /* reset the channel */
+ OUTB (devc->osdev,
+ INB (devc->osdev, CHAN_RESET) | portc->play_chan_reset,
+ CHAN_RESET);
+ oss_udelay (10);
+ OUTB (devc->osdev,
+ INB (devc->osdev, CHAN_RESET) & ~portc->play_chan_reset,
+ CHAN_RESET);
+ }
+
+ if (direction == PCM_ENABLE_INPUT)
+ {
+ /* reset the channel */
+ OUTB (devc->osdev,
+ INB (devc->osdev, CHAN_RESET) | portc->rec_chan_reset,
+ CHAN_RESET);
+ oss_udelay (10);
+ OUTB (devc->osdev,
+ INB (devc->osdev, CHAN_RESET) & ~portc->rec_chan_reset,
+ CHAN_RESET);
+ }
+
+}
+
+static int
+i2s_calc_rate (int rate)
+{
+ int i2s_rate;
+
+ switch (rate)
+ {
+ case 32000:
+ i2s_rate = 0;
+ break;
+ case 44100:
+ i2s_rate = 1;
+ break;
+ case 48000:
+ i2s_rate = 2;
+ break;
+ case 64000:
+ i2s_rate = 3;
+ break;
+ case 88200:
+ i2s_rate = 4;
+ break;
+ case 96000:
+ i2s_rate = 5;
+ break;
+ case 176400:
+ i2s_rate = 6;
+ break;
+ case 192000:
+ i2s_rate = 7;
+ break;
+ default:
+ i2s_rate = 2;
+ break;
+ }
+
+ return i2s_rate;
+}
+
+int
+i2s_calc_bits (int bits)
+{
+ int i2s_bits;
+
+ switch (bits)
+ {
+#if 0
+ case AFMT_S24_LE:
+ i2s_bits = 0x80;
+ break;
+#endif
+ case AFMT_S32_LE:
+ i2s_bits = 0xC0;
+ break;
+ default: /* AFMT_S16_LE */
+ i2s_bits = 0x00;
+ break;
+ }
+
+ return i2s_bits;
+}
+
+/*ARGSUSED*/
+static int
+cmi8788_audio_prepare_for_input (int dev, int bsize, int bcount)
+{
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_in;
+ oss_native_word flags;
+ int channels, bits, i2s_bits, i2s_rate;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ switch (portc->adc_type)
+ {
+ case ADEV_I2SADC1:
+ {
+ portc->rec_dma_start = 0x1;
+ portc->rec_irq_mask = 0x1;
+ portc->rec_chan_reset = 0x1;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, RECA_ADDR);
+ OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECA_SIZE);
+ OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECA_FRAG);
+
+ switch (portc->channels)
+ {
+ case 4:
+ channels = 0x1;
+ break;
+ case 6:
+ channels = 0x2;
+ break;
+ case 8:
+ channels = 0x4;
+ break;
+ default:
+ channels = 0x0; /* Stereo */
+ break;
+ }
+
+ OUTB (devc->osdev, (INB (devc->osdev, REC_MODE) & ~0x3) | channels,
+ REC_MODE);
+
+ switch (portc->bits)
+ {
+#if 0
+ /* The 24 bit format supported by cmi8788 is not AFMT_S24_LE */
+ case AFMT_S24_LE:
+ bits = 0x1;
+ break;
+#endif
+ case AFMT_S32_LE:
+ bits = 0x2;
+ break;
+ default: /* AFMT_S16_LE */
+ bits = 0x0;
+ break;
+ }
+ OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x3) | bits,
+ REC_FORMAT);
+
+ /* set up the i2s bits as well */
+ i2s_bits = i2s_calc_bits (portc->bits);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC1_FORMAT) & ~0xC0) | i2s_bits,
+ I2S_ADC1_FORMAT);
+
+ /* setup the i2s speed */
+ i2s_rate = i2s_calc_rate (portc->speed);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC1_FORMAT) & ~0x7) | i2s_rate,
+ I2S_ADC1_FORMAT);
+
+ break;
+ }
+
+ case ADEV_I2SADC2:
+ {
+ portc->rec_dma_start = 0x2;
+ portc->rec_irq_mask = 0x2;
+ portc->rec_chan_reset = 0x2;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, RECB_ADDR);
+ OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECB_SIZE);
+ OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECB_FRAG);
+
+ switch (portc->bits)
+ {
+#if 0
+ case AFMT_S24_LE:
+ bits = 0x04;
+ break;
+#endif
+ case AFMT_S32_LE:
+ bits = 0x08;
+ break;
+ default: /* AFMT_S16_LE */
+ bits = 0x0;
+ break;
+ }
+
+ OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x0C) | bits,
+ REC_FORMAT);
+
+ /* setup i2s bits */
+ i2s_bits = i2s_calc_bits (portc->bits);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC2_FORMAT) & ~0xC0) | i2s_bits,
+ I2S_ADC2_FORMAT);
+
+ /* setup speed */
+ i2s_rate = i2s_calc_rate (portc->speed);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC2_FORMAT) & ~0x7) | i2s_rate,
+ I2S_ADC2_FORMAT);
+
+ break;
+ }
+
+ case ADEV_I2SADC3:
+ {
+ portc->rec_dma_start = 0x4;
+ portc->rec_irq_mask = 0x4;
+ portc->rec_chan_reset = 0x4;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, RECC_ADDR);
+ OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECC_SIZE);
+ OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECC_FRAG);
+
+ switch (portc->bits)
+ {
+#if 0
+ case AFMT_S24_LE:
+ bits = 0x10;
+ break;
+#endif
+ case AFMT_S32_LE:
+ bits = 0x20;
+ break;
+ default: /* AFMT_S16_LE */
+ bits = 0x0;
+ break;
+ }
+
+ OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x30) | bits,
+ REC_FORMAT);
+
+ /* setup i2s bits */
+ i2s_bits = i2s_calc_bits (portc->bits);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC3_FORMAT) & ~0xC0) | i2s_bits,
+ I2S_ADC3_FORMAT);
+
+ /* setup speed */
+ i2s_rate = i2s_calc_rate (portc->speed);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_ADC3_FORMAT) & ~0x7) | i2s_rate,
+ I2S_ADC3_FORMAT);
+ break;
+ }
+ }
+ portc->audio_enabled &= ~PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+cmi8788_audio_prepare_for_output (int dev, int bsize, int bcount)
+{
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_out;
+ oss_native_word flags;
+ int i2s_rate, rate, spdif_rate, bits = 0, i2s_bits, channels = 0;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ switch (portc->dac_type)
+ {
+ case ADEV_MULTICH:
+ {
+ portc->play_dma_start = 0x10;
+ portc->play_irq_mask = 0x10;
+ portc->play_chan_reset = 0x10;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, MULTICH_ADDR);
+ OUTL (devc->osdev, dmap->bytes_in_use / 4 - 1, MULTICH_SIZE);
+ OUTL (devc->osdev, dmap->fragment_size / 4 - 1, MULTICH_FRAG);
+
+ switch (portc->channels)
+ {
+ case 2:
+ channels = 0;
+ break;
+ case 4:
+ channels = 1;
+ break;
+ case 6:
+ channels = 2;
+ break;
+ case 8:
+ channels = 3;
+ break;
+ }
+
+ OUTB (devc->osdev,
+ (INB (devc->osdev, MULTICH_MODE) & ~0x3) | channels,
+ MULTICH_MODE);
+
+ /* setup bits per sample */
+ switch (portc->bits)
+ {
+#if 0
+ case AFMT_S24_LE:
+ bits = 4;
+ break;
+#endif
+ case AFMT_S32_LE:
+ bits = 8;
+ break;
+
+ default: /* AFMT_S16_LE */
+ bits = 0;
+ break;
+ }
+
+ /* set the format bits in play format register */
+ OUTB (devc->osdev, (INB (devc->osdev, PLAY_FORMAT) & ~0xC) | bits,
+ PLAY_FORMAT);
+
+ /* setup the i2s bits in the i2s register */
+ i2s_bits = i2s_calc_bits (portc->bits);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_MULTICH_FORMAT) & ~0xC0) | i2s_bits,
+ I2S_MULTICH_FORMAT);
+
+ /* setup speed */
+ i2s_rate = i2s_calc_rate (portc->speed);
+ OUTB (devc->osdev,
+ (INB (devc->osdev, I2S_MULTICH_FORMAT) & ~0x7) | i2s_rate,
+ I2S_MULTICH_FORMAT);
+
+ break;
+ }
+
+ case ADEV_FRONTPANEL:
+ {
+ portc->play_dma_start = 0x20;
+ portc->play_irq_mask = 0x20;
+ portc->play_chan_reset = 0x20;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, FPOUT_ADDR);
+ OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, FPOUT_SIZE);
+ OUTW (devc->osdev, dmap->fragment_size / 4 - 1, FPOUT_FRAG);
+ ac97_playrate (&devc->fp_ac97devc, portc->speed);
+ ac97_spdif_setup (devc->fp_mixer_dev, portc->speed, portc->bits);
+
+ break;
+ }
+
+ case ADEV_SPDIF:
+ {
+ portc->play_dma_start = 0x08;
+ portc->play_irq_mask = 0x08;
+ portc->play_chan_reset = 0x08;
+ cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);
+
+ /* STOP SPDIF Out */
+ OUTL (devc->osdev, (INL (devc->osdev, SPDIF_FUNC) & ~0x00000002),
+ SPDIF_FUNC);
+
+ /* setup AC3 for 16bit 48Khz, Non-Audio */
+ if (portc->bits == AFMT_AC3)
+ {
+ portc->bits = 16;
+ portc->channels = 2;
+ portc->speed = 48000;
+
+ /* set the PCM/Data bit to Non-Audio */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x0002) | 0x0002,
+ SPDIFOUT_CHAN_STAT);
+ }
+ else
+ OUTL (devc->osdev,
+ INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x0002,
+ SPDIFOUT_CHAN_STAT);
+
+ OUTL (devc->osdev, dmap->dmabuf_phys, SPDIF_ADDR);
+ OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, SPDIF_SIZE);
+ OUTW (devc->osdev, dmap->fragment_size / 4 - 1, SPDIF_FRAG);
+
+ /* setup number of bits/sample */
+ switch (portc->bits)
+ {
+ case 16:
+ bits = 0;
+ break;
+ case 24:
+ bits = 1;
+ break;
+ case 32:
+ bits = 2;
+ break;
+ }
+
+ OUTB (devc->osdev, (INB (devc->osdev, PLAY_FORMAT) & ~0x3) | bits,
+ PLAY_FORMAT);
+
+ /* setup sampling rate */
+ switch (portc->speed)
+ {
+ case 32000:
+ rate = 0;
+ spdif_rate = 0x3;
+ break;
+ case 44100:
+ rate = 1;
+ spdif_rate = 0x0;
+ break;
+ case 48000:
+ rate = 2;
+ spdif_rate = 0x2;
+ break;
+ case 64000:
+ rate = 3;
+ spdif_rate = 0xb;
+ break;
+ case 88200:
+ rate = 4;
+ spdif_rate = 0x8;
+ break;
+ case 96000:
+ rate = 5;
+ spdif_rate = 0xa;
+ break;
+ case 176400:
+ rate = 6;
+ spdif_rate = 0xc;
+ break;
+ case 192000:
+ rate = 7;
+ spdif_rate = 0xe;
+ break;
+ default:
+ rate = 2; /* 48000 */
+ spdif_rate = 0x2;
+ break;
+ }
+
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIF_FUNC) & ~0x0f000000) | rate << 24,
+ SPDIF_FUNC);
+
+ /* also program the Channel status */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF000) | spdif_rate
+ << 12, SPDIFOUT_CHAN_STAT);
+
+ /* Enable SPDIF Out */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIF_FUNC) & ~0x00000002) | 0x2,
+ SPDIF_FUNC);
+
+ break;
+ }
+ }
+ portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static int
+cmi8788_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
+{
+ cmi8788_devc *devc = audio_engines[dev]->devc;
+ cmi8788_portc *portc = audio_engines[dev]->portc;
+ unsigned int ptr = 0;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+ if (direction == PCM_ENABLE_INPUT)
+ {
+ switch (portc->adc_type)
+ {
+ case ADEV_I2SADC1:
+ ptr = INL (devc->osdev, RECA_ADDR);
+ break;
+ case ADEV_I2SADC2:
+ ptr = INL (devc->osdev, RECB_ADDR);
+ break;
+ case ADEV_I2SADC3:
+ ptr = INL (devc->osdev, RECC_ADDR);
+ break;
+ }
+ }
+
+ if (direction == PCM_ENABLE_OUTPUT)
+ {
+ switch (portc->dac_type)
+ {
+ case ADEV_MULTICH:
+ ptr = INL (devc->osdev, MULTICH_ADDR);
+ break;
+ case ADEV_FRONTPANEL:
+ ptr = INL (devc->osdev, FPOUT_ADDR);
+ break;
+ case ADEV_SPDIF:
+ ptr = INL (devc->osdev, SPDIF_ADDR);
+ break;
+ }
+ }
+
+ ptr -= dmap->dmabuf_phys;
+ ptr %= dmap->bytes_in_use;
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return ptr;
+}
+
+
+static audiodrv_t cmi8788_audio_driver = {
+ cmi8788_audio_open,
+ cmi8788_audio_close,
+ cmi8788_audio_output_block,
+ cmi8788_audio_start_input,
+ cmi8788_audio_ioctl,
+ cmi8788_audio_prepare_for_input,
+ cmi8788_audio_prepare_for_output,
+ cmi8788_audio_reset,
+ NULL,
+ NULL,
+ cmi8788_audio_reset_input,
+ cmi8788_audio_reset_output,
+ cmi8788_audio_trigger,
+ cmi8788_audio_set_rate,
+ cmi8788_audio_set_format,
+ cmi8788_audio_set_channels,
+ NULL,
+ NULL,
+ NULL, /* cmi8788_check_input, */
+ NULL, /* cmi8788_check_output, */
+ NULL, /* cmi8788_alloc_buffer */
+ NULL, /* cmi8788_free_buffer */
+ NULL,
+ NULL,
+ cmi8788_get_buffer_pointer
+};
+
+#define input_avail(devc) (!(cmi8788uart_status(devc)&INPUT_AVAIL))
+#define output_ready(devc) (!(cmi8788uart_status(devc)&OUTPUT_READY))
+
+static __inline__ int
+cmi8788uart_status (cmi8788_devc * devc)
+{
+ return INB (devc->osdev, MPU401_COMMAND);
+}
+
+static void
+cmi8788uart_cmd (cmi8788_devc * devc, unsigned char cmd)
+{
+ OUTB (devc->osdev, cmd, MPU401_COMMAND);
+}
+
+static __inline__ int
+cmi8788uart_read (cmi8788_devc * devc)
+{
+ return INB (devc->osdev, MPU401_DATA);
+}
+
+static __inline__ void
+cmi8788uart_write (cmi8788_devc * devc, unsigned char byte)
+{
+ OUTB (devc->osdev, byte, MPU401_DATA);
+}
+
+#define OUTPUT_READY 0x40
+#define INPUT_AVAIL 0x80
+#define MPU_ACK 0xFE
+#define MPU_RESET 0xFF
+#define UART_MODE_ON 0x3F
+
+
+static void
+cmi8788uart_input_loop (cmi8788_devc * devc)
+{
+ while (input_avail (devc))
+ {
+ unsigned char c = cmi8788uart_read (devc);
+
+ if (c == MPU_ACK)
+ devc->input_byte = c;
+ else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
+ devc->midi_input_intr (devc->midi_dev, c);
+ }
+}
+
+static void
+cmi8788uartintr (cmi8788_devc * devc)
+{
+ cmi8788uart_input_loop (devc);
+}
+
+/*ARGSUSED*/
+static int
+cmi8788uart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
+ oss_midi_inputbuf_t inputbuf,
+ oss_midi_outputintr_t outputintr)
+{
+ cmi8788_devc *devc = (cmi8788_devc *) midi_devs[dev]->devc;
+
+ if (devc->midi_opened)
+ {
+ return OSS_EBUSY;
+ }
+
+ while (input_avail (devc))
+ cmi8788uart_read (devc);
+
+ devc->midi_input_intr = inputbyte;
+ devc->midi_opened = mode;
+ enter_uart_mode (devc);
+ devc->midi_disabled = 0;
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static void
+cmi8788uart_close (int dev, int mode)
+{
+ cmi8788_devc *devc = (cmi8788_devc *) midi_devs[dev]->devc;
+ reset_cmi8788uart (devc);
+ oss_udelay (10);
+ enter_uart_mode (devc);
+ reset_cmi8788uart (devc);
+ devc->midi_opened = 0;
+}
+
+
+static int
+cmi8788uart_out (int dev, unsigned char midi_byte)
+{
+ int timeout;
+ cmi8788_devc *devc = (cmi8788_devc *) midi_devs[dev]->devc;
+ oss_native_word flags;
+
+ /*
+ * Test for input since pending input seems to block the output.
+ */
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ if (input_avail (devc))
+ cmi8788uart_input_loop (devc);
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ /*
+ * Sometimes it takes about 130000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ for (timeout = 130000; timeout > 0 && !output_ready (devc); timeout--);
+
+ if (!output_ready (devc))
+ {
+ cmn_err (CE_WARN, "UART timeout - Device not responding\n");
+ devc->midi_disabled = 1;
+ reset_cmi8788uart (devc);
+ enter_uart_mode (devc);
+ return 1;
+ }
+
+ cmi8788uart_write (devc, midi_byte);
+ return 1;
+}
+
+/*ARGSUSED*/
+static int
+cmi8788uart_ioctl (int dev, unsigned cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static midi_driver_t cmi8788_midi_driver = {
+ cmi8788uart_open,
+ cmi8788uart_close,
+ cmi8788uart_ioctl,
+ cmi8788uart_out,
+};
+
+static void
+enter_uart_mode (cmi8788_devc * devc)
+{
+ int ok, timeout;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);
+
+ devc->input_byte = 0;
+ cmi8788uart_cmd (devc, UART_MODE_ON);
+
+ ok = 0;
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ if (devc->input_byte == MPU_ACK)
+ ok = 1;
+ else if (input_avail (devc))
+ if (cmi8788uart_read (devc) == MPU_ACK)
+ ok = 1;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+
+void
+attach_cmi8788uart (cmi8788_devc * devc)
+{
+ enter_uart_mode (devc);
+ devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CMI8788", "CMI8788 UART", &cmi8788_midi_driver, sizeof (midi_driver_t),
+ 0, devc, devc->osdev);
+ devc->midi_opened = 0;
+}
+
+static int
+reset_cmi8788uart (cmi8788_devc * devc)
+{
+ int ok, timeout, n;
+
+ /*
+ * Send the RESET command. Try again if no success at the first time.
+ */
+
+ ok = 0;
+
+ for (n = 0; n < 2 && !ok; n++)
+ {
+ for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);
+
+ devc->input_byte = 0;
+ cmi8788uart_cmd (devc, MPU_RESET);
+
+ /*
+ * Wait at least 25 msec. This method is not accurate so let's make the
+ * loop bit longer. Cannot sleep since this is called during boot.
+ */
+
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ if (devc->input_byte == MPU_ACK) /* Interrupt */
+ ok = 1;
+ else if (input_avail (devc))
+ if (cmi8788uart_read (devc) == MPU_ACK)
+ ok = 1;
+
+ }
+
+
+
+ if (ok)
+ cmi8788uart_input_loop (devc); /*
+ * Flush input before enabling interrupts
+ */
+
+ return ok;
+}
+
+
+int
+probe_cmi8788uart (cmi8788_devc * devc)
+{
+ int ok = 0;
+ oss_native_word flags;
+
+ DDB (cmn_err (CE_CONT, "Entered probe_cmi8788uart\n"));
+
+ devc->midi_input_intr = NULL;
+ devc->midi_opened = 0;
+ devc->input_byte = 0;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ ok = reset_cmi8788uart (devc);
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ if (ok)
+ {
+ DDB (cmn_err (CE_CONT, "Reset UART401 OK\n"));
+ }
+ else
+ {
+ DDB (cmn_err
+ (CE_CONT, "Reset UART401 failed (no hardware present?).\n"));
+ DDB (cmn_err
+ (CE_CONT, "mpu401 status %02x\n", cmi8788uart_status (devc)));
+ }
+
+ DDB (cmn_err (CE_CONT, "cmi8788uart detected OK\n"));
+ return ok;
+}
+
+void
+unload_cmi8788uart (cmi8788_devc * devc)
+{
+ reset_cmi8788uart (devc);
+}
+
+
+static void
+attach_mpu (cmi8788_devc * devc)
+{
+ devc->mpu_attached = 1;
+ attach_cmi8788uart (devc);
+}
+
+/*ARGSUSED*/
+static int
+cmi8788_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
+{
+ cmi8788_devc *devc = mixer_devs[dev]->devc;
+
+ if (((cmd >> 8) & 0xff) == 'M')
+ {
+ int val;
+
+ if (IOC_IS_OUTPUT (cmd))
+ switch (cmd & 0xff)
+ {
+ case SOUND_MIXER_RECSRC:
+ return *arg = 0;
+ break;
+
+ case SOUND_MIXER_PCM:
+ val = *arg;
+ return *arg = cmi8788_set_play_volume (devc, 0, val);
+ break;
+
+ case SOUND_MIXER_REARVOL:
+ val = *arg;
+ return *arg = cmi8788_set_play_volume (devc, 1, val);
+ break;
+
+ case SOUND_MIXER_CENTERVOL:
+ val = *arg;
+ return *arg = cmi8788_set_play_volume (devc, 2, val);
+ break;
+
+ case SOUND_MIXER_SIDEVOL:
+ val = *arg;
+ return *arg = cmi8788_set_play_volume (devc, 3, val);
+ break;
+
+ case SOUND_MIXER_RECLEV:
+ val = *arg;
+ return *arg = cmi8788_set_rec_volume (devc, val);
+ break;
+
+ default:
+ val = *arg;
+ return *arg = 0;
+ break;
+ }
+ else
+ switch (cmd & 0xff) /* Return Parameter */
+ {
+ case SOUND_MIXER_RECSRC:
+ return *arg = 0;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ if ((devc->model == SUBID_XONAR_STX) || (devc->model == SUBID_XONAR_ST))
+ *arg = SOUND_MASK_PCM;
+ else
+ *arg = SOUND_MASK_PCM | SOUND_MASK_REARVOL |
+ SOUND_MASK_CENTERVOL | SOUND_MASK_SIDEVOL;
+
+ if (devc->model == SUBID_XONAR_DS)
+ *arg |= SOUND_MASK_RECLEV;
+
+ return *arg;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ if ((devc->model == SUBID_XONAR_STX) || (devc->model == SUBID_XONAR_ST))
+ *arg = SOUND_MASK_PCM;
+ else
+ *arg = SOUND_MASK_PCM | SOUND_MASK_REARVOL |
+ SOUND_MASK_CENTERVOL | SOUND_MASK_SIDEVOL;
+
+ if (devc->model == SUBID_XONAR_DS)
+ *arg |= SOUND_MASK_RECLEV;
+
+ return *arg;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ return *arg = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ case SOUND_MIXER_PCM:
+ return *arg = devc->playvol[0];
+ break;
+
+ case SOUND_MIXER_REARVOL:
+ return *arg = devc->playvol[1];
+ break;
+
+ case SOUND_MIXER_CENTERVOL:
+ return *arg = devc->playvol[2];
+ break;
+
+ case SOUND_MIXER_SIDEVOL:
+ return *arg = devc->playvol[3];
+ break;
+
+ case SOUND_MIXER_RECLEV:
+ return *arg = devc->recvol;
+ break;
+
+ default:
+ return *arg = 0;
+ break;
+ }
+ }
+ else
+ return *arg = 0;
+
+}
+
+static mixer_driver_t cmi8788_mixer_driver = {
+ cmi8788_mixer_ioctl
+};
+
+/********************Record/Play ROUTING Control *************************/
+
+static int
+cmi8788_ext (int dev, int ctrl, unsigned int cmd, int value)
+{
+/*
+ * Access function for CMPCI mixer extension bits
+ */
+ cmi8788_devc *devc = mixer_devs[dev]->devc;
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ value = 0;
+ switch (ctrl)
+ {
+ case 0: /* Record Monitor */
+ value = (INB (devc->osdev, REC_MONITOR) & 0x1) ? 1 : 0;
+ break;
+ case 1: /* SPDIFIN Monitor */
+ value = (INB (devc->osdev, REC_MONITOR) & 0x10) ? 1 : 0;
+ break;
+ case 2: /* Record source select */
+ switch (devc->model)
+ {
+ case SUBID_XONAR_DS:
+ value = (INW (devc->osdev, GPIO_DATA) & 0x40) ? 1 : 0;
+ break;
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_ST:
+ value = (INW (devc->osdev, GPIO_DATA) & 0x100) ? 1 : 0;
+ break;
+ default:
+ value = (ac97_read (devc, 0x72) & 0x1) ? 1 : 0;
+ break;
+ }
+ break;
+ case 3: /* Speaker Spread - check bit15 to see if it's set */
+ value = (INW (devc->osdev, PLAY_ROUTING) & 0x8000) ? 0 : 1;
+ break;
+ case 4: /* SPDIF IN->OUT Loopback */
+ value = (INW (devc->osdev, SPDIF_FUNC) & 0x4) ? 1 : 0;
+ break;
+ case 5:
+ value = (INW (devc->osdev, GPIO_DATA) & 0x80) ? 1 : 0;
+ break;
+ case 6:
+ if (!(INW (devc->osdev, GPIO_DATA) & 0x80))
+ value = 0;
+ else if (INW (devc->osdev, GPIO_DATA) & 0x02)
+ value = 1;
+ else
+ value = 2;
+ break;
+ case 7:
+ value = devc->mute;
+ break;
+
+ default:
+ return OSS_EINVAL;
+ break;
+ }
+
+ return value;
+ }
+
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ switch (ctrl)
+ {
+ case 0: /* Record Monitor */
+ if (value)
+ OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) | 0xF,
+ REC_MONITOR);
+ else
+ OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) & ~0xF,
+ REC_MONITOR);
+ break;
+
+ case 1: /* SPDIFIN Monitor */
+ if (value)
+ OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) | 0x10,
+ REC_MONITOR);
+ else
+ OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) & ~0x10,
+ REC_MONITOR);
+ break;
+
+ case 2:
+ if (value)
+ {
+ switch (devc->model)
+ {
+ case SUBID_XONAR_DS:
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x40, GPIO_DATA);
+ break;
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_ST:
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x100, GPIO_DATA);
+ break;
+ }
+ ac97_write(devc, 0x72, ac97_read(devc, 0x72) | 0x1);
+ }
+ else
+ {
+ switch (devc->model)
+ {
+ case SUBID_XONAR_DS:
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~0x40, GPIO_DATA);
+ break;
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_ST:
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~0x100, GPIO_DATA);
+ break;
+ }
+
+ ac97_write(devc, 0x72, ac97_read(devc, 0x72) & ~0x1);
+ }
+ break;
+
+ case 3: /* Speaker Spread (clone front to all channels) */
+ if (value)
+ OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) & 0x00FF,
+ PLAY_ROUTING);
+ else
+ OUTW (devc->osdev,
+ (INW (devc->osdev, PLAY_ROUTING) & 0x00FF) | 0xE400,
+ PLAY_ROUTING);
+ break;
+
+ case 4: /* SPDIF IN->OUT Loopback */
+ if (value)
+ OUTW (devc->osdev, INW (devc->osdev, SPDIF_FUNC) | 0x4,
+ SPDIF_FUNC);
+ else
+ OUTW (devc->osdev, INW (devc->osdev, SPDIF_FUNC) & ~0x4,
+ SPDIF_FUNC);
+ break;
+
+ case 5:
+ if (value)
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x80, GPIO_DATA) ;
+ else
+ OUTW(devc->osdev, (INW(devc->osdev, GPIO_DATA) & ~0x80), GPIO_DATA);
+ break;
+
+ case 6:
+ switch (value)
+ {
+ /* speaker (line) output */
+ case 0: OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~(0x80|0x02), GPIO_DATA);
+ break;
+ /* rear headphone output */
+ case 1: OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | (0x80|0x02), GPIO_DATA);
+ break;
+ case 2: OUTW (devc->osdev, (INW(devc->osdev, GPIO_DATA) | 0x80) & ~0x02, GPIO_DATA);
+ break;
+
+ }
+ break;
+
+ case 7:
+ /* muting for Xonar ST/STX on PCM1796 */
+ if (value)
+ {
+ i2c_write (devc, XONAR_ST_FRONTDAC, 18, 0x01|0x30|0x80);
+ devc->mute=1;
+ }
+ else
+ {
+ i2c_write (devc, XONAR_ST_FRONTDAC, 18, 0x00|0x30|0x80);
+ devc->mute=0;
+ }
+ break;
+
+ default:
+ return OSS_EINVAL;
+ break;
+ }
+ return (value);
+ }
+ return OSS_EINVAL;
+}
+
+
+/********************SPDIFOUT Control *************************/
+int
+spdifout_ctl (int dev, int ctrl, unsigned int cmd, int value)
+{
+ int tmp = 0;
+ cmi8788_devc *devc = mixer_devs[dev]->devc;
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ value = 0;
+ switch (ctrl)
+ {
+ case SPDIFOUT_ENABLE: /* SPDIF OUT */
+ value = (INL (devc->osdev, SPDIF_FUNC) & 0x2) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_PRO: /* Consumer/PRO */
+ value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x1) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_AUDIO: /* PCM/AC3 */
+ value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x2) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_COPY: /* Copy Prot */
+ value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x4) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_PREEMPH: /* Pre emphasis */
+ value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x8) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_RATE: /* Sampling Rate */
+ tmp = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0xf000);
+ switch (tmp)
+ {
+ case 0x0000: /* 44100 */
+ value = 0;
+ break;
+ case 0x2000: /* 48000 */
+ value = 1;
+ break;
+ case 0x3000: /* 32000 */
+ value = 2;
+ break;
+ case 0x8000: /* 88200 */
+ value = 3;
+ break;
+ case 0xA000: /* 96000 */
+ value = 4;
+ break;
+ case 0xB000: /* 64000 */
+ value = 5;
+ break;
+ case 0xC000: /* 176400 */
+ value = 6;
+ break;
+ case 0xE000: /* 192000 */
+ value = 7;
+ break;
+ default:
+ cmn_err (CE_WARN, "unsupported SPDIF F/S rate\n");
+ break;
+ }
+ break;
+
+ case SPDIFOUT_VBIT: /* V Bit */
+ value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x10000) ? 1 : 0;
+ break;
+
+ case SPDIFOUT_ADC: /* Analog In to SPDIF Out */
+ value = (INW (devc->osdev, PLAY_ROUTING) & 0x80) ? 1 : 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return value;
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ switch (ctrl)
+ {
+ case SPDIFOUT_ENABLE: /* Enable SPDIF OUT */
+ if (value)
+ OUTL (devc->osdev, INL (devc->osdev, SPDIF_FUNC) | 0x2,
+ SPDIF_FUNC);
+ else
+ OUTL (devc->osdev, INL (devc->osdev, SPDIF_FUNC) & ~0x2,
+ SPDIF_FUNC);
+ break;
+
+ case SPDIFOUT_PRO: /* consumer/pro audio */
+ if (value)
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x1,
+ SPDIFOUT_CHAN_STAT);
+ else
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x1,
+ SPDIFOUT_CHAN_STAT);
+ break;
+
+ case SPDIFOUT_AUDIO: /* PCM/AC3 */
+ if (value)
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x2,
+ SPDIFOUT_CHAN_STAT);
+ else
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x2,
+ SPDIFOUT_CHAN_STAT);
+ break;
+
+ case SPDIFOUT_COPY: /* copy prot */
+ if (value)
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x4,
+ SPDIFOUT_CHAN_STAT);
+ else
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x4,
+ SPDIFOUT_CHAN_STAT);
+ break;
+
+ case SPDIFOUT_PREEMPH: /* preemphasis */
+ if (value)
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x8,
+ SPDIFOUT_CHAN_STAT);
+ else
+ OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x8,
+ SPDIFOUT_CHAN_STAT);
+ break;
+
+ case SPDIFOUT_RATE: /* Frequency */
+ switch (value)
+ {
+ case 0: /* 44100 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) | 0x0,
+ SPDIFOUT_CHAN_STAT);
+ break;
+ case 1: /* 48000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0x2000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 2: /* 32000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0x3000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 3: /* 88000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0x8000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 4: /* 96000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0xA000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 5: /* 64000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0xB000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 6: /* 176400 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0xC000, SPDIFOUT_CHAN_STAT);
+ break;
+ case 7: /* 192000 */
+ OUTL (devc->osdev,
+ (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
+ 0xE000, SPDIFOUT_CHAN_STAT);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SPDIFOUT_VBIT: /* V Bit */
+ if (value)
+ OUTL (devc->osdev,
+ INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x10000,
+ SPDIFOUT_CHAN_STAT);
+ else
+ OUTL (devc->osdev,
+ INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x10000,
+ SPDIFOUT_CHAN_STAT);
+ break;
+
+ case SPDIFOUT_ADC: /* Analog In to SPDIF Out */
+ if (value)
+ OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) | 0xA0,
+ PLAY_ROUTING);
+ else
+ OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) & ~0xE0,
+ PLAY_ROUTING);
+ break;
+
+ default:
+ break;
+ }
+
+ return (value);
+ }
+
+ return OSS_EINVAL;
+}
+
+static int
+cmi8788_mix_init (int dev)
+{
+ int group, parent, ctl;
+ cmi8788_devc *devc = mixer_devs[dev]->hw_devc;
+
+ if ((parent = mixer_ext_create_group (dev, 0, "EXT")) < 0)
+ return parent;
+
+/* CREATE MONITOR */
+ if ((group = mixer_ext_create_group (dev, parent, "MONITOR")) < 0)
+ return group;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 0, cmi8788_ext,
+ MIXT_ONOFF, "ANALOG", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 1, cmi8788_ext,
+ MIXT_ONOFF, "SPDIF", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 2, cmi8788_ext, MIXT_ENUM,
+ "INPUTSRC", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ mixer_ext_set_strings (dev, ctl, "Line Mic", 0);
+
+/* Create PLAYBACK ROUTING */
+ if ((group = mixer_ext_create_group (dev, parent, "ROUTING")) < 0)
+ return group;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 3, cmi8788_ext,
+ MIXT_ONOFF, "SPREAD", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 4, cmi8788_ext,
+ MIXT_ONOFF, "SPDIF-LOOPBACK", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if (devc->model == SUBID_XONAR_D2 || devc->model == SUBID_XONAR_D2X)
+ if ((ctl = mixer_ext_create_control (dev, group, 5, cmi8788_ext,
+ MIXT_ONOFF, "PCM-LOOPBACK", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if (devc->model == SUBID_XONAR_STX || devc->model == SUBID_XONAR_ST)
+
+ {
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 6, cmi8788_ext, MIXT_ENUM,
+ "OUTPUTSRC", 3,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ mixer_ext_set_strings (dev, ctl, "Speaker Headphone FrontHeadphone", 0);
+
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, 7, cmi8788_ext,
+ MIXT_ONOFF, "MUTE", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ }
+
+/* Create SPDIF OUTPUT */
+ if ((group = mixer_ext_create_group (dev, 0, "SPDIF-OUT")) < 0)
+ return group;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_ENABLE,
+ spdifout_ctl, MIXT_ONOFF,
+ "SPDOUT_ENABLE", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_ADC,
+ spdifout_ctl, MIXT_ONOFF,
+ "SPDOUT_ADC/DAC", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_PRO,
+ spdifout_ctl, MIXT_ENUM,
+ "SPDOUT_Pro", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ mixer_ext_set_strings (dev, ctl, "Consumer Professional", 0);
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_AUDIO,
+ spdifout_ctl, MIXT_ENUM,
+ "SPDOUT_Audio", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ mixer_ext_set_strings (dev, ctl, "Audio Data", 0);
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_COPY,
+ spdifout_ctl, MIXT_ONOFF,
+ "SPDOUT_Copy", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_PREEMPH,
+ spdifout_ctl, MIXT_ONOFF,
+ "SPDOUT_Pre-emph", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_RATE,
+ spdifout_ctl, MIXT_ENUM,
+ "SPDOUT_Rate", 8,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ mixer_ext_set_strings (dev, ctl,
+ "44.1KHz 48KHz 32KHz 88.2KHz 96KHz 64KHz 176.4KHz 192KHz",
+ 0);
+
+ if ((ctl =
+ mixer_ext_create_control (dev, group, SPDIFOUT_VBIT,
+ spdifout_ctl, MIXT_ONOFF,
+ "SPDOUT_VBit", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ return 0;
+}
+
+
+static void
+ac97_hwinit(cmi8788_devc *devc)
+{
+
+ /* Gpio #0 programmed as output, set CMI9780 Reg0x70 */
+ ac97_write(devc, 0x70, 0x100);
+
+ /* LI2LI,MIC2MIC; let them always on, FOE on, ROE/BKOE/CBOE off */
+ ac97_write(devc, 0x62, 0x180F);
+
+ /* change PCBeep path, set Mix2FR on, option for quality issue */
+ ac97_write(devc, 0x64, 0x8043);
+#if 0
+ /* unmute Master Volume */
+ ac97_write(devc, 0x02, 0x0);
+
+
+ /* mute PCBeep, option for quality issues */
+ ac97_write(devc, 0x0A, 0x8000);
+
+ /* Record Select Control Register (Index 1Ah) */
+ ac97_write(devc, 0x1A, 0x0000);
+
+ /* set Mic Volume Register 0x0Eh umute and enable micboost */
+ ac97_write(devc, 0x0E, 0x0848);
+
+ /* set Line in Volume Register 0x10h mute */
+ ac97_write(devc, 0x10, 0x8808);
+
+ /* set CD Volume Register 0x12h mute */
+ ac97_write(devc, 0x12, 0x8808);
+
+ /* set AUX Volume Register 0x16h max */
+ ac97_write(devc, 0x16, 0x0808);
+
+ /* set record gain Register 0x1Ch to max */
+ ac97_write(devc, 0x1C, 0x0F0F);
+#endif
+ ac97_write(devc, 0x71, 0x0001);
+}
+
+static int
+init_cmi8788 (cmi8788_devc * devc)
+{
+ unsigned short sVal;
+ unsigned short sDac;
+ unsigned char bVal;
+ int i, first_dev = -1, count;
+ int default_vol;
+/*
+ * Init CMI Controller
+ */
+ sVal = INW (devc->osdev, CTRL_VERSION);
+ if (!(sVal & 0x0008))
+ {
+ bVal = INB (devc->osdev, MISC_REG);
+ bVal |= 0x20;
+ OUTB (devc->osdev, bVal, MISC_REG);
+ }
+
+ bVal = INB (devc->osdev, FUNCTION);
+ bVal |= 0x02; /* Reset codec*/
+ OUTB(devc->osdev, bVal, FUNCTION);
+
+
+ /* I2S to 16bit, see below. */
+ sDac = 0x010A;
+
+ /* Non-generic DAC initialization */
+ switch(devc->model)
+ {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_DS:
+ /* Must set master clock. */
+ sDac |= XONAR_MCLOCK_256;
+ break;
+ case SUBID_XONAR_ST:
+ sDac |= XONAR_MCLOCK_512;
+ }
+
+ /* Setup I2S to use 16bit instead of 24Bit */
+ OUTW (devc->osdev, sDac, I2S_MULTICH_FORMAT);
+ OUTW (devc->osdev, sDac, I2S_ADC1_FORMAT);
+ OUTW (devc->osdev, sDac, I2S_ADC2_FORMAT);
+ OUTW (devc->osdev, sDac, I2S_ADC3_FORMAT);
+
+ /* setup Routing regs (default vals) */
+ OUTW (devc->osdev, 0xE400, PLAY_ROUTING);
+ OUTB (devc->osdev, 0x00, REC_ROUTING);
+ OUTB (devc->osdev, 0x00, REC_MONITOR);
+ OUTB (devc->osdev, 0xE4, MONITOR_ROUTING);
+
+
+ /* install the CMI8788 mixer */
+ if ((devc->cmi_mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ "CMedia CMI8788",
+ &cmi8788_mixer_driver,
+ sizeof (mixer_driver_t),
+ devc)) < 0)
+ {
+ return 0;
+ }
+
+ mixer_devs[devc->cmi_mixer_dev]->hw_devc = devc;
+ mixer_devs[devc->cmi_mixer_dev]->priority = 10; /* Possible default mixer candidate */
+ mixer_ext_set_init_fn (devc->cmi_mixer_dev, cmi8788_mix_init, 25);
+
+ /* Cold reset onboard AC97 */
+ OUTW (devc->osdev, 0x1, AC97_CTRL);
+ count = 100;
+ while ((INW (devc->osdev, AC97_CTRL) & 0x2) && (count--))
+ {
+ OUTW (devc->osdev, (INW (devc->osdev, AC97_CTRL) & ~0x2) | 0x2,
+ AC97_CTRL);
+ oss_udelay (100);
+ }
+ if (!count)
+ cmn_err (CE_WARN, "CMI8788 AC97 not ready\n");
+
+
+ sVal = INW (devc->osdev, AC97_CTRL);
+
+ devc->ac97_mixer_dev = devc->fp_mixer_dev = -1;
+
+ /* check if there's an onboard AC97 codec (CODEC 0) and install the mixer */
+ if (sVal & 0x10)
+ {
+ /* disable CODEC0 OUTPUT */
+ OUTW (devc->osdev, 0, /* INW (devc->osdev, AC97_OUT_CHAN_CONFIG) & ~0xFF00,*/
+ AC97_OUT_CHAN_CONFIG);
+
+ /* enable CODEC0 INPUT */
+ OUTW (devc->osdev, 0, /* INW (devc->osdev, AC97_IN_CHAN_CONFIG) | 0x0300,*/
+ AC97_IN_CHAN_CONFIG);
+
+ devc->ac97_mixer_dev =
+ ac97_install (&devc->ac97devc, "AC97 Input Mixer", ac97_read,
+ ac97_write, devc, devc->osdev);
+ }
+
+ /* check if there's an front panel AC97 codec (CODEC1) and install the mixer */
+ if (sVal & 0x20)
+ {
+ /* enable CODEC1 OUTPUT */
+ OUTW (devc->osdev, INW (devc->osdev, AC97_OUT_CHAN_CONFIG) | 0x0033,
+ AC97_OUT_CHAN_CONFIG);
+ /* enable CODEC1 INPUT */
+ OUTW (devc->osdev, INW (devc->osdev, AC97_IN_CHAN_CONFIG) | 0x0033,
+ AC97_IN_CHAN_CONFIG);
+
+ devc->fp_mixer_dev =
+ ac97_install (&devc->fp_ac97devc, "AC97 Mixer (Front Panel)",
+ fp_ac97_read, fp_ac97_write, devc, devc->osdev);
+
+ if (devc->fp_mixer_dev >= 0)
+ {
+ /* enable S/PDIF */
+ devc->fp_ac97devc.spdif_slot = SPDIF_SLOT34;
+ ac97_spdifout_ctl (devc->fp_mixer_dev, SPDIFOUT_ENABLE,
+ SNDCTL_MIX_WRITE, 1);
+ }
+ }
+
+
+
+ switch(devc->model) {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ /*GPIO8 = 0x100 controls mic/line-in */
+ /*GPIO0 = 0x001controls output */
+ /*GPIO2/3 = 0x00C codec output control*/
+
+ /* setup for i2c communication mode */
+ OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);
+ /* setup GPIO direction */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x010D, GPIO_CONTROL);
+ /* setup GPIO pins */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x0101, GPIO_DATA);
+
+ /* init the front and rear dacs */
+ cs4398_init(devc, XONAR_DX_FRONTDAC);
+ cs4362a_init(devc, XONAR_DX_SURRDAC);
+ /* initialize the codec 0 */
+ ac97_hwinit(devc);
+ break;
+
+
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ /*GPIO7 = 0x0080 controls mic/line-in */
+ /*GPIO8 = 0x0100 controls output */
+ /*GPIO2/3 = 0x000C codec output control*/
+
+ /* setup for spi communication mode */
+ OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ /* setup the GPIO direction */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x018C, GPIO_CONTROL);
+ /* setup GPIO Pins */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x0100, GPIO_DATA);
+
+ /* for all 4 codecs: unmute, set to 24Bit SPI */
+ for (i = 0; i < 4; ++i) {
+ spi_write(devc, xd2_codec_map[i], 16, mix_scale(75,8)); /* left vol*/
+ spi_write(devc, xd2_codec_map[i], 17, mix_scale(75,8)); /* right vol */
+ spi_write(devc, xd2_codec_map[i], 18, 0x00| 0x30 | 0x80); /* unmute/24LSB/ATLD */
+ }
+ /* initialize the codec 0 */
+ ac97_hwinit(devc);
+ break;
+
+ case SUBID_XONAR_STX:
+ /*GPIO0 = Antipop control */
+ /*GPIO1 = frontpanel h/p control*/
+ /*GPIO7 = 0x0080 controls analog out*/
+ /*GPIO8 = 0x0100 controls mic/line in*/
+ /*GPIO2/3 = 0x000C codec input control*/
+
+ /* setup for i2c communication mode */
+ OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);
+
+ /* setup the GPIO direction control register */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x018F, GPIO_CONTROL);
+ /* setup GPIO pins mic/output */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x111, GPIO_DATA);
+
+ OUTW(devc->osdev, INW(devc->osdev, I2C_CTRL)|0x0100, I2C_CTRL);
+
+ /* initialize the PCM1796 DAC */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0x40); /*OS_64*/
+ i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0); /*OS_64*/
+ i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(75,8));
+ i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(75,8));
+ i2c_write(devc, XONAR_STX_FRONTDAC, 18, 0x00|0x30|0x80); /*unmute, 24LSB, ATLD */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 19, 0); /*ATS1/FLT_SHARP*/
+
+ /* initialize the codec 0 */
+ ac97_hwinit(devc);
+ break;
+
+ case SUBID_XONAR_ST:
+ /*GPIO0 = enable output*/
+ /*GPIO1 = frontpanel h/p control*/
+ /*GPIO7 = 0x0080 controls analog out line/HP*/
+ /*GPIO8 = 0x0100 controls mic/line in*/
+ /*GPIO2/3 = 0x000C codec input control*/
+
+ /* setup for i2c communication mode */
+ OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);
+
+ /* setup the GPIO direction control register */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x01FF, GPIO_CONTROL);
+ /* setup GPIO pins mic/output */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x101, GPIO_DATA);
+ OUTW(devc->osdev, INW(devc->osdev, I2C_CTRL)|0x0100, I2C_CTRL);
+ /* initialize the CS2000 clock chip */
+ cs2000_init (devc, XONAR_ST_CLOCK);
+
+ /* initialize the PCM1796 DAC */
+ i2c_write(devc, XONAR_ST_FRONTDAC, 20, 0); /*normal*/
+ i2c_write(devc, XONAR_ST_FRONTDAC, 18, 0x00|0x30|0x80); /*unmute, 24LSB, ATLD */
+ i2c_write(devc, XONAR_ST_FRONTDAC, 16, mix_scale(75,8));
+ i2c_write(devc, XONAR_ST_FRONTDAC, 17, mix_scale(75,8));
+ i2c_write(devc, XONAR_ST_FRONTDAC, 19, 0); /*ATS1/FLT_SHARP*/
+
+ /* initialize the codec 0 */
+ ac97_hwinit(devc);
+
+ break;
+
+ case SUBID_XONAR_DS:
+ /* GPIO 8 = 1 output enabled 0 mute */
+ /* GPIO 7 = 1 lineout enabled 0 mute */
+ /* GPIO 6 = 1 mic select 0 line-in select */
+ /* GPIO 4 = 1 FP Headphone plugged in */
+ /* GPIO 3 = 1 FP Mic plugged in */
+
+ /* setup for spi communication mode */
+ OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40)|0x32, FUNCTION);
+ /* setup the GPIO direction */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x1D0, GPIO_CONTROL);
+ /* setup GPIO Pins */
+ OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x1D0, GPIO_DATA);
+
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x17, 0x1); /* reset */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x7, 0x90); /* dac control */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x8, 0); /* unmute */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xC, 0x22 ); /* powerdown hp */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xD, 0x8); /* powerdown hp */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xA, 0x1); /* LJust/16bit*/
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xB, 0x1); /* LJust/16bit*/
+
+ spi_write(devc, XONAR_DS_SURRDAC, 0x1f, 1); /* reset */
+ spi_write(devc, XONAR_DS_SURRDAC, 0x3, 0x1|0x20); /* LJust/24bit*/
+ break;
+
+ default:
+ /* SPI default for anything else, including the */
+ OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ break;
+ }
+
+ /* check if MPU401 is enabled in MISC register */
+ if (INB (devc->osdev, MISC_REG) & 0x40)
+ attach_mpu (devc);
+
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ char tmp_name[100];
+ cmi8788_portc *portc = &devc->portc[i];
+ int caps = ADEV_AUTOMODE;
+ int fmt = AFMT_S16_LE;
+
+ switch (i)
+ {
+ case 0:
+ sprintf (tmp_name, "%s (MultiChannel)", devc->chip_name);
+ caps |= ADEV_DUPLEX;
+ fmt |= AFMT_S32_LE;
+ portc->dac_type = ADEV_MULTICH;
+ switch(devc->model)
+ {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_ST:
+ portc->adc_type = ADEV_I2SADC2;
+ break;
+ case SUBID_XONAR_DS:
+ portc->adc_type = ADEV_I2SADC1;
+ break;
+ default:
+ portc->adc_type = ADEV_I2SADC1;
+ OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
+ break;
+ }
+
+ portc->min_rate = 32000;
+ portc->max_rate = 192000;
+ portc->min_chan = 2;
+ portc->max_chan = 8;
+ break;
+
+ case 1:
+ sprintf (tmp_name, "%s (Multichannel)", devc->chip_name);
+ caps |= ADEV_DUPLEX | ADEV_SHADOW;
+ fmt |= AFMT_S32_LE;
+ portc->dac_type = ADEV_MULTICH;
+ switch(devc->model)
+ {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ case SUBID_XONAR_STX:
+ case SUBID_XONAR_ST:
+ portc->adc_type = ADEV_I2SADC2;
+ break;
+ case SUBID_XONAR_DS:
+ portc->adc_type = ADEV_I2SADC1;
+ break;
+ default:
+ portc->adc_type = ADEV_I2SADC1;
+ OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
+ break;
+ }
+
+ portc->min_rate = 32000;
+ portc->max_rate = 192000;
+ portc->min_chan = 2;
+ portc->max_chan = 8;
+ break;
+
+ case 2:
+ /* if there is no front panel AC97, then skip the device */
+ if (devc->fp_mixer_dev == -1)
+ continue;
+
+ sprintf (tmp_name, "%s (Front Panel)", devc->chip_name);
+ caps |=
+ ADEV_DUPLEX | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_SPECIAL;
+ fmt |= AFMT_AC3;
+ portc->dac_type = ADEV_FRONTPANEL;
+ portc->adc_type = ADEV_I2SADC2;
+ OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
+ portc->min_rate = 8000;
+ portc->max_rate = 48000;
+ portc->min_chan = 2;
+ portc->max_chan = 2;
+ break;
+
+ case 3:
+ sprintf (tmp_name, "%s (SPDIF)", devc->chip_name);
+ caps |= ADEV_DUPLEX | ADEV_STEREOONLY | ADEV_SPECIAL;
+ fmt |= AFMT_AC3 | AFMT_S32_LE;
+ portc->dac_type = ADEV_SPDIF;
+ portc->adc_type = ADEV_I2SADC3;
+ portc->min_rate = 32000;
+ portc->max_rate = 192000;
+ portc->min_chan = 2;
+ portc->max_chan = 2;
+ break;
+ }
+
+ if ((portc->audiodev =
+ oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev,
+ devc->osdev, tmp_name,
+ &cmi8788_audio_driver,
+ sizeof (audiodrv_t), caps, fmt,
+ devc, -1)) < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ if (first_dev == -1)
+ first_dev = portc->audiodev;
+ audio_engines[portc->audiodev]->portc = portc;
+ audio_engines[portc->audiodev]->rate_source = first_dev;
+ if (caps & ADEV_FIXEDRATE)
+ {
+ audio_engines[portc->audiodev]->min_rate = 48000;
+ audio_engines[portc->audiodev]->max_rate = 48000;
+ audio_engines[portc->audiodev]->fixed_rate = 48000;
+ }
+ audio_engines[portc->audiodev]->min_rate = portc->min_rate;
+ audio_engines[portc->audiodev]->max_rate = portc->max_rate;
+
+ audio_engines[portc->audiodev]->caps |= DSP_CAP_FREERATE;
+ audio_engines[portc->audiodev]->min_channels = portc->min_chan;
+ audio_engines[portc->audiodev]->max_channels = portc->max_chan;
+ portc->open_mode = 0;
+ portc->audio_enabled = 0;
+#ifdef CONFIG_OSS_VMIX
+ if (i == 0)
+ vmix_attach_audiodev(devc->osdev, portc->audiodev, -1, 0);
+#endif
+ }
+ }
+
+ /*
+ * Setup the default volumes to 90%
+ */
+
+ devc->mute = 0;
+
+ default_vol = mix_scale(90,8)<<8|mix_scale(90,8);
+
+ devc->playvol[0] =
+ cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
+ MIXER_WRITE (SOUND_MIXER_PCM), &default_vol);
+ devc->playvol[1] =
+ cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
+ MIXER_WRITE (SOUND_MIXER_REARVOL), &default_vol);
+ devc->playvol[2] =
+ cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
+ MIXER_WRITE (SOUND_MIXER_CENTERVOL), &default_vol);
+ devc->playvol[3] =
+ cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
+ MIXER_WRITE (SOUND_MIXER_SIDEVOL), &default_vol);
+
+ return 1;
+}
+
+int
+oss_cmi878x_attach (oss_device_t * osdev)
+{
+ unsigned char pci_irq_line, pci_revision;
+ unsigned short pci_command, vendor, device;
+ unsigned int pci_ioaddr;
+ unsigned short sub_vendor, sub_id;
+ int err;
+ cmi8788_devc *devc;
+
+ DDB (cmn_err (CE_CONT, "Entered CMEDIA CMI8788 attach routine\n"));
+ pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
+ pci_read_config_word (osdev, PCI_DEVICE_ID, &device);
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor);
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &sub_id);
+
+ if (vendor != CMEDIA_VENDOR_ID || device != CMEDIA_CMI8788)
+ 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_ioaddr);
+
+ DDB (cmn_err (CE_WARN, "CMI8788 I/O base %04x\n", pci_ioaddr));
+
+ if (pci_ioaddr == 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;
+
+ /* Map the IO Base address */
+ devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
+
+ /* Remove I/O space marker in bit 0. */
+ devc->base &= ~3;
+
+ /* set the PCI_COMMAND register to master mode */
+ pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
+ pci_write_config_word (osdev, PCI_COMMAND, pci_command);
+
+ if (device == CMEDIA_CMI8788)
+ {
+ /* Detect Xonar device */
+ if(sub_vendor == ASUS_VENDOR_ID)
+ {
+ switch(sub_id)
+ {
+ case SUBID_XONAR_D1:
+ devc->chip_name = "Asus Xonar D1 (AV100)";
+ break;
+ case SUBID_XONAR_DX:
+ devc->chip_name = "Asus Xonar DX (AV100)";
+ break;
+ case SUBID_XONAR_D2:
+ devc->chip_name = "Asus Xonar D2 (AV200)";
+ break;
+ case SUBID_XONAR_D2X:
+ devc->chip_name = "Asus Xonar D2X (AV200)";
+ break;
+ case SUBID_XONAR_STX:
+ devc->chip_name = "Asus Xonar Essence STX (AV100)";
+ break;
+ case SUBID_XONAR_ST:
+ devc->chip_name = "Asus Xonar Essence ST (AV100)";
+ break;
+ case SUBID_XONAR_DS:
+ devc->chip_name = "Asus Xonar DS (AV66)";
+ break;
+ default:
+ devc->chip_name = "Asus Xonar (unknown)";
+ sub_id = SUBID_GENERIC;
+ break;
+ }
+ devc->model = sub_id;
+ }
+ else
+ {
+ /* If not one of the above, regular. */
+ devc->model = MDL_CMI8788;
+ devc->chip_name = "CMedia CMI8788";
+ }
+ }
+ else
+ {
+ cmn_err (CE_WARN, "Unknown CMI8788 model\n");
+ return 0;
+ }
+
+ MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
+ MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1);
+ MUTEX_INIT (devc->osdev, devc->dac_mutex, MH_DRV + 2);
+
+ oss_register_device (osdev, devc->chip_name);
+
+ if ((err = oss_register_interrupts (devc->osdev, 0, cmi8788intr, NULL)) < 0)
+ {
+ cmn_err (CE_WARN, "Can't allocate IRQ%d, err=%d\n", pci_irq_line, err);
+ return 0;
+ }
+
+ return init_cmi8788 (devc); /* Detected */
+}
+
+int
+oss_cmi878x_detach (oss_device_t * osdev)
+{
+ cmi8788_devc *devc = (cmi8788_devc *) osdev->devc;
+
+ if (oss_disable_device (osdev) < 0)
+ return 0;
+
+ if (devc->mpu_attached)
+ unload_cmi8788uart (devc);
+
+ oss_unregister_interrupts (devc->osdev);
+
+ UNMAP_PCI_IOADDR (devc->osdev, 0);
+ MUTEX_CLEANUP (devc->mutex);
+ MUTEX_CLEANUP (devc->low_mutex);
+ MUTEX_CLEANUP (devc->dac_mutex);
+
+ oss_unregister_device (osdev);
+ return 1;
+}