/* * 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 #include #include #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; }