/* * Purpose: Card specific routines for several Envy24 based cards. */ /* * * 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_config.h" #include "ac97.h" #include "envy24.h" #if 0 # define PRT_STATUS(v) outb(v&0xff, 0x378) #else # define PRT_STATUS(v) #endif extern int envy24_gain_sliders; extern int envy24_virtualout; extern int envy24_zerolatency; /* Testing in progress */ #define EWX_DIG_ADDR 0x20 /* IIC address of digital traceiver chip of EWX2496 */ /* * Playback engine management */ static void write_spdif (envy24_devc * devc, int d) { int i, cmd; cmd = envy24_read_cci (devc, 0x20); for (i = 7; i >= 0; i--) { cmd &= ~0x04; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); if (d & (1 << i)) cmd |= 0x08; else cmd &= ~0x08; cmd |= 0x04; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); } cmd &= ~0x04; envy24_write_cci (devc, 0x20, cmd); } static void write_codec (envy24_devc * devc, int codec, int addr, int data) { unsigned char cmd; int i; int tmp; cmd = envy24_read_cci (devc, 0x20); cmd &= ~codec; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); addr &= 0x07; tmp = 0xa000 | (addr << 8) | data; for (i = 15; i >= 0; i--) { cmd &= ~0x30; if (tmp & (1 << i)) cmd |= 0x10; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); cmd |= 0x20; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); } cmd |= codec; envy24_write_cci (devc, 0x20, cmd); oss_udelay (1); } #define CDC_CLK 1 /* Clock input to the CODEC's, rising edge clocks data. */ #define CDC_DIN 2 /* Data input to Envy from the CODEC. */ #define CDC_DOUT 3 /* Data output from Envy to the CODEC. */ #define DIG_CS 4 /* Chip select (0=select) for the SPDIF tx/rx. */ #define CDC_CS 5 /* Chip select (0=select) for the CODEC. */ #define D410_MUTE 7 /* Delta 410 codec mute */ #define CS_ASSERT 0 /* Asserted chip select (selects are inverted). */ #define CS_RELEASE 1 /* Idle chip select (selects are inverted). */ #define CS8_CLK CDC_CLK #define CS8_DIN CDC_DIN #define CS8_DOUT CDC_DOUT #define CS8_CS DIG_CS #define CS_1 CS_ASSERT #define CS_0 CS_RELEASE #define CS8_ADDR 0x20 /* Chip SPI/I2C address */ #define CS8_RD 0x01 #define CS8_WR 0x00 #define EWX_DIG_CS 0 #define EWX_CDC_CLK 5 #define EWX_CDC_DOUT 4 #define EWX_CDC_DIN EWX_CDC_DOUT #define EWX_IIC_WRITE 3 #define CX_ASSERT 0 /* Asserted chip select (selects are inverted). */ #define CX_RELEASE 1 /* Idle chip select (selects are inverted). */ void WriteGPIObit (envy24_devc * devc, int sel, int what) { unsigned char gpio; gpio = envy24_read_cci (devc, 0x20); gpio &= ~(1 << sel); gpio |= (what << sel); envy24_write_cci (devc, 0x20, gpio); } int ReadGPIObit (envy24_devc * devc, int sel) { unsigned char gpio; gpio = envy24_read_cci (devc, 0x20); return !!(gpio & (1 << sel)); } static void write_ap_codec (envy24_devc * devc, int bRegister, unsigned char bData) /* ***************************************************************************** * Writes a byte to a specific register of the Delta-AP CODEC. * Register must be (0..15). ****************************************************************************/ { unsigned char bMask; if (devc->model_data->flags & MF_D410) bRegister = (bRegister & 0x0F) | 0x20; /* Add I2C address field. */ else bRegister = (bRegister & 0x0F) | 0xA0; /* Add I2C address field. */ /* Assert the CODEC chip select and wait at least 150 nS. */ /* */ WriteGPIObit (devc, CDC_CS, CS_ASSERT); WriteGPIObit (devc, DIG_CS, CS_RELEASE); /* Write the register address byte. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop SPI clock low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Write current data bit. */ if (bMask & bRegister) WriteGPIObit (devc, CDC_DOUT, 1); else WriteGPIObit (devc, CDC_DOUT, 0); /* Raise SPI clock to "clock data in". */ WriteGPIObit (devc, CDC_CLK, 1); } /* Write the data byte. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop SPI clock low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Write current data bit. */ if (bMask & bData) WriteGPIObit (devc, CDC_DOUT, 1); else WriteGPIObit (devc, CDC_DOUT, 0); /* Raise SPI clock to "clock data in". */ WriteGPIObit (devc, CDC_CLK, 1); } /* De-assert chip select. */ /* */ WriteGPIObit (devc, CDC_CS, CS_RELEASE); } static int read_ap_spdif_reg (envy24_devc * devc, int bRegister) /* ***************************************************************************** * Reads a byte from a specific CS8427 register. ****************************************************************************/ { unsigned char bMask; unsigned char bRet = 0; unsigned char bSPI; /****** WRITE MAP ADDRESS FIRST ******/ /* Drop the chip select low. */ /* Wait at least 150 nS. */ /* */ WriteGPIObit (devc, DIG_CS, CS_ASSERT); /* Write the SPI address/cmd byte. */ /* */ bSPI = CS8_ADDR + CS8_WR; /* SPI address field plus WRITE operation. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop clock (GPIO5) low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Write current data bit. */ if (bMask & bSPI) WriteGPIObit (devc, CDC_DOUT, 1); else WriteGPIObit (devc, CDC_DOUT, 0); /* Raise clock (GPIO5). */ WriteGPIObit (devc, CDC_CLK, 1); } /* Write the address (MAP) byte. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop clock (GPIO5) low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Write current data bit. */ if (bMask & bRegister) WriteGPIObit (devc, CDC_DOUT, 1); else WriteGPIObit (devc, CDC_DOUT, 0); /* Raise clock (GPIO5). */ WriteGPIObit (devc, CDC_CLK, 1); } /* De-assert chip select(s). */ /* */ WriteGPIObit (devc, DIG_CS, CS_RELEASE); /****** NOW READ THE DATA ******/ /* Drop the chip select low. */ /* Wait at least 150 nS. */ /* */ WriteGPIObit (devc, DIG_CS, CS_ASSERT); /* Write the SPI address/cmd byte. */ /* */ bSPI = CS8_ADDR + CS8_RD; /* SPI address field plus READ operation. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop clock (GPIO5) low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Write current data bit. */ if (bMask & bSPI) WriteGPIObit (devc, CDC_DOUT, 1); else WriteGPIObit (devc, CDC_DOUT, 0); /* Raise clock (GPIO5). */ WriteGPIObit (devc, CDC_CLK, 1); } /* Read the data byte. */ /* */ bRet = 0; /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop clock low. */ WriteGPIObit (devc, CDC_CLK, 0); /* Read current data bit. */ if (ReadGPIObit (devc, CDC_DIN)) bRet |= bMask; /* Raise clock. */ WriteGPIObit (devc, CDC_CLK, 1); } /* De-assert chip selects. */ /* */ WriteGPIObit (devc, DIG_CS, CS_RELEASE); /* Return value. */ return bRet; } static unsigned char ewx2496_iic_read (envy24_devc * devc, int addr, unsigned char bRegIdx); static int read_ewx2496_spdif_reg (envy24_devc * devc, int bRegister) /* ***************************************************************************** * Reads a byte from a specific CS8427 register. ****************************************************************************/ { unsigned char ret; ret = ewx2496_iic_read (devc, EWX_DIG_ADDR, bRegister); return ret; } #define read_cs8427_spdif_reg(d, r) devc->model_data->auxdrv->spdif_read_reg(d, r) static void write_ap_spdif_reg (envy24_devc * devc, int bRegister, int bData) /* ***************************************************************************** * Writes a byte to a specific register of the Delta-AP S/PDIF chip. * Register must be (0..55). ****************************************************************************/ { unsigned char bMask; unsigned char bSPI; /* Assert the CODEC chip select and wait at least 150 nS. */ /* */ WriteGPIObit (devc, CDC_CS, CS_0); WriteGPIObit (devc, CS8_CS, CS_1); /* Write the SPI address/cmd byte. */ /* */ bSPI = CS8_ADDR | CS8_WR; /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop SPI clock low. */ WriteGPIObit (devc, CS8_CLK, 0); /* Write current data bit. */ if (bMask & bSPI) WriteGPIObit (devc, CS8_DOUT, 1); else WriteGPIObit (devc, CS8_DOUT, 0); /* Raise SPI clock to "clock data in". */ WriteGPIObit (devc, CS8_CLK, 1); } /* Write the address (MAP) byte. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop SPI clock low. */ WriteGPIObit (devc, CS8_CLK, 0); /* Write current data bit. */ if (bMask & bRegister) WriteGPIObit (devc, CS8_DOUT, 1); else WriteGPIObit (devc, CS8_DOUT, 0); /* Raise SPI clock to "clock data in". */ WriteGPIObit (devc, CS8_CLK, 1); } /* Write the data byte. */ /* */ for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F) { /* Drop SPI clock low. */ WriteGPIObit (devc, CS8_CLK, 0); /* Write current data bit. */ if (bMask & bData) WriteGPIObit (devc, CS8_DOUT, 1); else WriteGPIObit (devc, CS8_DOUT, 0); /* Raise SPI clock to "clock data in". */ WriteGPIObit (devc, CS8_CLK, 1); } /* De-assert chip select. */ /* */ WriteGPIObit (devc, CS8_CS, CS_0); } static void ewx2496_iic_write (envy24_devc * devc, int addr, unsigned char bRegIdx, unsigned char bRegData); static void write_ewx2496_spdif_reg (envy24_devc * devc, int bRegister, int bData) { ewx2496_iic_write (devc, EWX_DIG_ADDR, bRegister, bData); } #define write_cs8427_spdif_reg(d, r, b) devc->model_data->auxdrv->spdif_write_reg(d, r, b) #define BIT0 0x01 #define BIT1 0x02 #define BIT2 0x04 #define BIT3 0x08 #define BIT4 0x10 #define BIT5 0x20 #define BIT6 0x40 #define BIT7 0x80 static void lock_cs8427_spdif (envy24_devc * devc) { write_cs8427_spdif_reg (devc, 18, read_cs8427_spdif_reg (devc, 18) & ~BIT5); write_cs8427_spdif_reg (devc, 18, read_cs8427_spdif_reg (devc, 18) | BIT2); } static void unlock_cs8427_spdif (envy24_devc * devc) { write_cs8427_spdif_reg (devc, 18, read_cs8427_spdif_reg (devc, 18) & ~BIT2); } static unsigned char bitswap (unsigned char bIn) /* ***************************************************************************** * Endian reversing routine. ****************************************************************************/ { unsigned char bOut = 0; unsigned char bImask = 0x01; unsigned char bOmask = 0x80; while (bImask) { if (bIn & bImask) bOut |= bOmask; bImask = bImask << 1; bOmask = (bOmask >> 1) & 0x7F; } return bOut; } static unsigned char ReadCsByte (envy24_devc * devc, unsigned char bByteNum) /* ***************************************************************************** * Reads a byte from Channel Status block buffer in CS8427. * * bByteNum is in the range (0..23) * * This routine assumes that CS8427 register 18 bit 5 is cleared so that the * CS buffer is windowed, and that register 18 bit 2 is set so that CS output * transfers are currently disabled. ****************************************************************************/ { unsigned char bTemp; /* CS block window starts at reg #32... */ bTemp = read_cs8427_spdif_reg (devc, bByteNum + 32); /* CS block access is reverse endian. */ return bitswap (bTemp); } static void WriteCsByte (envy24_devc * devc, unsigned char bByteNum, unsigned char bData) /* ***************************************************************************** * Writes a byte to Channel Status block buffer in CS8427. * * bByteNum is in the range (0..23) * * This routine assumes that CS8427 register 18 bit 5 is cleared so that the * CS buffer is windowed, and that register 18 bit 2 is set so that CS output * transfers are currently disabled. ****************************************************************************/ { /* CS block access is reverse endian. */ bData = bitswap (bData); /* CS Window starts at reg #32... */ write_cs8427_spdif_reg (devc, bByteNum + 32, bData); } void InitConsumerModeCS (envy24_devc * devc) { int i; /* Set CS8427 registers 32-55 to window CS block, and disable CS output. */ lock_cs8427_spdif (devc); /* Zero all the general CS bits. */ /* */ for (i = 0; i < 24; i++) WriteCsByte (devc, i, 0x00); /* */ /* Consumer (usually SPDIF or AC3) mode static bit settings. */ /* */ WriteCsByte (devc, 0, 0x00); /* Consumer format (bit0 = 0). */ WriteCsByte (devc, 1, 0x02); /* Category = PCM encoder/decoder. */ unlock_cs8427_spdif (devc); } void init_cs8427_spdif (envy24_devc * devc) { int tmp; /* Select iunternal sync */ write_cs8427_spdif_reg (devc, 4, read_cs8427_spdif_reg (devc, 4) & (~BIT0)); /* ***************************************************************************** * Initializes core (mainly static) registers of the CS8427. * Returns 1 if initialization OK, otherwise 0. ****************************************************************************/ /* Assumes Envy24 GPIO's have been initialized. They should be just fine */ /* in the Windows driver as they are initialized from EEPROM info. */ /* Verify device ID register. Must be 0x71. */ if ((tmp = read_cs8427_spdif_reg (devc, 127)) != 0x71 && tmp != 0) { cmn_err (CE_CONT, "Envy24: Unrecognized S/PDIF chip ID %02x\n", read_cs8427_spdif_reg (devc, 127)); cmn_err (CE_CONT, " Hardware stalled. Please reboot and try again.\n"); return; } /* Turn off RUN bit while making changes to configuration. */ write_cs8427_spdif_reg (devc, 4, read_cs8427_spdif_reg (devc, 4) & (~BIT6)); /* RMCK default function, set Validity, disable mutes, TCBL=output. */ write_cs8427_spdif_reg (devc, 1, 0x01); /* validity* is BIT6. */ /* Hold last valid audio sample, RMCK=256*Fs, normal stereo operation. */ write_cs8427_spdif_reg (devc, 2, 0x00); /* Output drivers normal operation, Tx <== serial audio port, */ /* Rx ==> serial audio port. */ write_cs8427_spdif_reg (devc, 3, 0x0C); /* RUN off, OMCK=256xFs, output time base = OMCK, input time base = */ /* recovered input clock, recovered input clock source is Envy24. */ write_cs8427_spdif_reg (devc, 4, 0x00); /* Serial audio input port data format = I2S. */ write_cs8427_spdif_reg (devc, 5, BIT2 | BIT0); /* SIDEL=1, SILRPOL=1. */ /* Serial audio output port data format = I2S. */ write_cs8427_spdif_reg (devc, 6, BIT2 | BIT0); /* SODEL=1, SOLRPOL=1. */ /* Turn off CS8427 interrupt stuff that we don't implement in our hardware. */ write_cs8427_spdif_reg (devc, 9, 0x00); write_cs8427_spdif_reg (devc, 10, 0x00); write_cs8427_spdif_reg (devc, 11, 0x00); write_cs8427_spdif_reg (devc, 12, 0x00); write_cs8427_spdif_reg (devc, 13, 0x00); write_cs8427_spdif_reg (devc, 14, 0x00); /* Unmask the input PLL lock, V, confidence, biphase, parity status bits. */ write_cs8427_spdif_reg (devc, 17, (unsigned char) BIT4 | BIT3 | BIT2 | BIT1 | BIT0); /* Registers 32-55 window to CS buffer. */ /* Inhibit D->E transfers from overwriting first 5 bytes of CS data. */ /* Inhibit D->E transfers (all) of CS data. */ /* Allow E->F transfers of CS data. */ /* One-byte mode: both A/B channels get same written CS data. */ /* A channel info is output to chip's EMPH* pin. */ /* */ write_cs8427_spdif_reg (devc, 18, 0x18); /* Use internal buffer to transmit User (U) data. */ /* Chip's U pin is an output. */ /* Transmit all 0's for user data. */ /* */ write_cs8427_spdif_reg (devc, 19, 0x10); /* Turn on chip's RUN bit, rock and roll! */ /* */ write_cs8427_spdif_reg (devc, 4, read_cs8427_spdif_reg (devc, 4) | BIT6); InitConsumerModeCS (devc); } #if 0 static void WriteCsField (envy24_devc * devc, unsigned char bByteNum, unsigned short bMask, unsigned short bBits) /* ***************************************************************************** * Writes a specific field within the Channel Status block buffer. * bByteNum is CS byte number to access (0..23). * bMask is the field to be written (set bits define field). * bBits is the value to write into that field. * * Assumes: Reg 18 Bit 5 is cleared so CS buffer is accessible. * Reg 18 Bit 2 is set so E->F buffer transfer is stopped. ****************************************************************************/ { /* Get current reg value. */ unsigned char bTemp = ReadCsByte (devc, bByteNum); /* Clear field to be written. */ bTemp &= ~(bMask); /* Set new values. */ WriteCsByte (devc, bByteNum, (unsigned char) (bTemp | (bBits & bMask))); } #endif /*ARGSUSED*/ void write_cs8427_spdif (envy24_devc * devc, int d) { int i; if (devc->syncsource == SYNC_INTERNAL) { write_cs8427_spdif_reg (devc, 4, read_cs8427_spdif_reg (devc, 4) & ~BIT0); } else { write_cs8427_spdif_reg (devc, 4, read_cs8427_spdif_reg (devc, 4) | BIT0); } lock_cs8427_spdif (devc); for (i = 0; i < 24; i++) { WriteCsByte (devc, i, devc->spdif_cbits[i]); } unlock_cs8427_spdif (devc); } /* * Terratec stuff */ /*============================================================================= Function : IIC_GetSDA ------------------------------------------------------------------------------- Description : Returns : unsigned char -> Parameters : unsigned long dwPortAddr -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ /*ARGSUSED*/ static unsigned char IIC_GetSDA (envy24_devc * devc, unsigned long dwPortAddr) { unsigned char bReg, bSDA; /* FMB TEST: RW line */ /* set write mask */ bReg = ~(0x08); /* writeable */ envy24_write_cci (devc, 0x21, bReg); /* set RW line LOW */ bReg = 0; /* writeable */ envy24_write_cci (devc, 0x20, bReg); /* Set direction: SDA to input and SCL to output */ bReg = envy24_read_cci (devc, 0x22); bReg |= 0x20; /* SCL output = 1 */ bReg &= ~0x10; /* SDA input = 0 */ envy24_write_cci (devc, 0x22, bReg); /* Get SDA line state */ bSDA = envy24_read_cci (devc, 0x20); /* set RW line HIGH */ bReg = 0x08; /* writeable */ envy24_write_cci (devc, 0x20, bReg); return 1; /* return ((bSDA & 0x10) == 0x10); */ } /*============================================================================= Function : IIC_SetIic ------------------------------------------------------------------------------- Description : Returns : void -> Parameters : unsigned int dwPortAddr -> : unsigned char fSDA -> : unsigned char fSCL -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ /*ARGSUSED*/ static void IIC_SetIic (envy24_devc * devc, unsigned int dwPortAddr, unsigned char fSDA, unsigned char fSCL) { unsigned char bReg; /* Set direction: SDA and SCL to output */ bReg = envy24_read_cci (devc, 0x22); bReg |= (0x20 | 0x10); /* 1 -> output */ envy24_write_cci (devc, 0x22, bReg); /* set write mask */ bReg = ~(0x20 | 0x10); /* writeable */ envy24_write_cci (devc, 0x21, bReg); /* Set line state */ /* FMB TEST: RW line */ bReg = 0x08; /* bReg = 0; */ if (fSDA) bReg += 0x10; if (fSCL) bReg += 0x20; envy24_write_cci (devc, 0x20, bReg); } /*============================================================================= Function : IIC_Start ------------------------------------------------------------------------------- Description : Returns : void -> Parameters : unsigned int dwPortAddr -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ /*__inline */ static void IIC_Start (envy24_devc * devc, unsigned int dwPortAddr) { /* falling edge of SDA while SCL is HIGH */ IIC_SetIic (devc, dwPortAddr, 1, 1); IIC_SetIic (devc, dwPortAddr, 0, 1); } /*============================================================================= Function : IIC_Stop ------------------------------------------------------------------------------- Description : Returns : void -> Parameters : unsigned int dwPortAddr -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ /*__inline */ static void IIC_Stop (envy24_devc * devc, unsigned int dwPortAddr) { /* rising edge of SDA while SCL is HIGH */ IIC_SetIic (devc, dwPortAddr, 0, 1); IIC_SetIic (devc, dwPortAddr, 1, 1); } /*============================================================================= Function : IIC_SendByte ------------------------------------------------------------------------------- Description : Returns : unsigned char -> Parameters : unsigned int dwPortAddr -> : unsigned char bByte -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ /*__inline */ static unsigned char IIC_SendByte (envy24_devc * devc, unsigned int dwPortAddr, unsigned char bByte) { unsigned char bDataBit, bAck; int i; for (i = 7; i >= 0; i--) /* send byte (MSB first) */ { bDataBit = (bByte >> i) & 0x01; IIC_SetIic (devc, dwPortAddr, bDataBit, 0); IIC_SetIic (devc, dwPortAddr, bDataBit, 1); } /* end for i */ IIC_SetIic (devc, dwPortAddr, 1, 0); /* Get acknowledge */ IIC_SetIic (devc, dwPortAddr, 1, 1); bAck = IIC_GetSDA (devc, dwPortAddr); /* FMB this is a start condition but never mind */ IIC_SetIic (devc, dwPortAddr, 0, 0); return 1; /* return (!bAck); *//* bAck = 0 --> success */ } /*============================================================================= Function : IIC_WriteByte ------------------------------------------------------------------------------- Description : Returns : unsigned char -> Parameters : unsigned int dwPortAddr -> : unsigned char bIicAddress -> : unsigned char bByte -> ------------------------------------------------------------------------------- Notes : =============================================================================*/ static unsigned char IIC_WriteByte (envy24_devc * devc, int dwPortAddr, unsigned char bIicAddress, unsigned char bByte) { IIC_Start (devc, dwPortAddr); /* send IIC address and data byte */ if (!IIC_SendByte (devc, dwPortAddr, bIicAddress)) { cmn_err (CE_CONT, "IIC_SendByte 1 failed\n"); goto FAILED; } if (!IIC_SendByte (devc, dwPortAddr, bByte)) { cmn_err (CE_CONT, "IIC_SendByte 2 failed\n"); goto FAILED; } IIC_Stop (devc, dwPortAddr); return 1; FAILED: IIC_Stop (devc, dwPortAddr); return 0; } /*============================================================================= FUNCTION: HW_AK4525_SetChipSelect ------------------------------------------------------------------------------- PURPOSE: PARAMETERS: PDEVICE_EXTENSION pDevExt - contains the port address and the OUT_SEL state unsigned char bCsMask - AK4525 CS Mask: 1 means selected RETURNS: int ------------------------------------------------------------------------------- NOTES: - We use the fact that the parameter bCsMask has the same layout as the IIC command byte we have to send to the PCF8574 (wrong polarity, however) - We also have to care that we don't change the output switch setting. We remember the value in the Device Extension - It is important that the CODECS are deselected (CS to HIGH) after sue. Otherwise some IIC functions might accidentally change our CODEC registers!!! =============================================================================*/ static unsigned char HW_AK4525_SetChipSelect (envy24_devc * devc, unsigned char bCsMask) { unsigned char bDataByte; /* check validity */ if (bCsMask > 0x0F) { cmn_err (CE_CONT, "Invalid bCsMask %02x\n", bCsMask); return 0; /* invalid */ } /* Format data byte */ bDataByte = (~bCsMask) & 0x0F; /* FMB PCF8574 HIGH means -10dbV, */ /* OutputLevelSwitch is 1 for +4dbU */ /* if (!pDevExt->Ews88mt.OutputLevelSwitch) */ /* bDataByte += 0x40; */ /* Set Chip select to LOW for selected Codecs (via IIC) */ if (!IIC_WriteByte (devc, devc->ccs_base, 0x48, bDataByte)) { cmn_err (CE_CONT, "IIC_WriteByte failed\n"); return 0; } return 1; } #define HW_AK4525_SetLines IIC_SetIic /*============================================================================= FUNCTION: HW_AK4525_WriteRegister ------------------------------------------------------------------------------- PURPOSE: PARAMETERS: unsigned int port - port address for IIC port unsigned char bCsMask - CoDec bitmask (bits 0..3) or 0xFF for all codecs together unsigned char bRegIdx - offset to desired register unsigned char bReg - new register value RETURNS: ------------------------------------------------------------------------------- NOTES: The Chip Selects must have been set prior to this call PCF8574_CODEC_IIC_ADDRESS +-----------------------------------------------------------------+ | B15 B14 B13 B12 B11 B10 B09 B08|B07 B06 B05 B04 B03 B02 B01 B00 | +--------+---+-------------------+--------------------------------+ | C1 C0 |R/W|A4 A3 A2 A1 A0 |D7 D6 D5 D4 D3 D2 D1 D0 | | 1 0 |1 | <= 7 | data | +--------+---+-------------------+--------------------------------+ =============================================================================*/ static unsigned char HW_AK4525_WriteRegister (envy24_devc * devc, unsigned char bCsMask, unsigned char bRegIdx, unsigned char bRegData) { unsigned short wCmd; unsigned char i; unsigned int port = devc->ccs_base; unsigned char fData; /* format buffer */ wCmd = 0xA000 + /* chip address + R/W */ (((unsigned short) bRegIdx) << 8) + /* register address */ bRegData; /* start write cycle */ if (!HW_AK4525_SetChipSelect (devc, bCsMask)) { cmn_err (CE_CONT, "HW_AK4525_SetChipSelect failed\n"); return 0; /* = CS to LOW -> data latched */ } for (i = 0; i < 16; i++) { fData = (wCmd & 0x8000) ? 1 : 0; HW_AK4525_SetLines (devc, port, fData, 0); HW_AK4525_SetLines (devc, port, fData, 1); /* data is clocked in on rising edge of CCLK */ wCmd <<= 1; } /* leave data line HIGH (= default for IIC) */ HW_AK4525_SetLines (devc, port, fData, 0); /* data is clocked in on rising edge of CCLK */ /* end write cycle */ if (!HW_AK4525_SetChipSelect (devc, 0x00)) { cmn_err (CE_CONT, "HW_AK4525_SetChipSelect 2 failed\n"); return 0; /* = all CS to HIGH -> CS to default */ } /* default */ HW_AK4525_SetLines (devc, port, 1, 1); /* data is clocked in on rising edge of CCLK */ return 1; } #define EWX_CLK 0x20 #define EWX_DTA 0x10 static void ewx2496_iic_write_byte (envy24_devc * devc, unsigned char data, unsigned char save) { int i; unsigned char gpio; for (i = 0; i < 8; i++) { int fData = (data & 0x80) ? 1 : 0; gpio = 0x08; if (fData) gpio |= EWX_DTA; /* DATA */ envy24_write_cci (devc, 0x20, gpio | save); envy24_write_cci (devc, 0x20, gpio | EWX_CLK | save); /* Clock pulse */ envy24_write_cci (devc, 0x20, gpio | save); data <<= 1; } envy24_write_cci (devc, 0x20, EWX_DTA | save); oss_udelay (3); envy24_write_cci (devc, 0x20, EWX_DTA | EWX_CLK | save); /* Clock pulse (ACK) */ oss_udelay (1); envy24_write_cci (devc, 0x20, EWX_DTA | save); oss_udelay (1); } static unsigned char ewx2496_iic_read_byte (envy24_devc * devc, unsigned char save) { int i; unsigned char data = 0; int b; PRT_STATUS (0x01); save |= EWX_DTA; envy24_write_cci (devc, 0x20, save); oss_udelay (10); #if 0 save &= ~8; /* R/W bit */ envy24_write_cci (devc, 0x22, ~EWX_DTA); /* GPIO direction */ envy24_write_cci (devc, 0x21, EWX_DTA); /* GPIO write mask */ #endif for (i = 0; i < 8; i++) { envy24_write_cci (devc, 0x20, EWX_CLK | save); /* Clock pulse */ b = envy24_read_cci (devc, 0x20); if (b & EWX_DTA) data |= 0x80; envy24_write_cci (devc, 0x20, save); data >>= 1; } envy24_write_cci (devc, 0x22, 0xff); /* GPIO direction */ envy24_write_cci (devc, 0x21, 0x00); /* GPIO write mask */ envy24_write_cci (devc, 0x20, (save & ~EWX_DTA) | 0x08); /* Low for the ACK bit */ envy24_write_cci (devc, 0x20, (save & ~EWX_DTA) | 0x08 | EWX_CLK); /* Clock for the ACK bit */ envy24_write_cci (devc, 0x20, save | 0x08); oss_udelay (10); return data; } static void ewx2496_iic_write (envy24_devc * devc, int addr, unsigned char bRegIdx, unsigned char bRegData) { unsigned char save; /* start write cycle */ envy24_write_cci (devc, 0x22, 0xff); /* GPIO direction */ envy24_write_cci (devc, 0x21, 0x00); /* GPIO write mask */ save = (envy24_read_cci (devc, 0x20) & ~(EWX_CLK | EWX_DTA)) | 0x08 /* R/W bit */ ; /* Send start */ oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK); oss_udelay (1); envy24_write_cci (devc, 0x20, save); oss_udelay (1); ewx2496_iic_write_byte (devc, addr, save); ewx2496_iic_write_byte (devc, bRegIdx, save); ewx2496_iic_write_byte (devc, bRegData, save); /* Send stop */ oss_udelay (1); envy24_write_cci (devc, 0x20, save); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA); } static unsigned char ewx2496_iic_read (envy24_devc * devc, int addr, unsigned char bRegIdx) { unsigned char save, data; /* ewx2496_iic_write(devc, addr, bRegIdx, 0x55); */ PRT_STATUS (0x80); envy24_write_cci (devc, 0x22, 0xff); /* GPIO direction */ envy24_write_cci (devc, 0x21, 0x00); /* GPIO write mask */ save = (envy24_read_cci (devc, 0x20) & ~(EWX_DTA | EWX_CLK)) | 0x08 /* R/W bit */ ; PRT_STATUS (0x02); /* Send start */ oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK); oss_udelay (1); envy24_write_cci (devc, 0x20, save); oss_udelay (1); ewx2496_iic_write_byte (devc, addr | 0x01, save); oss_udelay (10); data = ewx2496_iic_read_byte (devc, save); /* Send stop */ oss_udelay (1); envy24_write_cci (devc, 0x20, save); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK); oss_udelay (1); envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA); oss_udelay (1); PRT_STATUS (0x00); return data; } static unsigned char write_ewx2496_codec (envy24_devc * devc, unsigned char bRegIdx, unsigned char bRegData) { unsigned short wCmd; unsigned char i; unsigned char fData; unsigned char gpio, save; /* format buffer */ wCmd = 0xA000 + /* chip address + R/W */ (((unsigned short) bRegIdx) << 8) + /* register address */ bRegData; /* start write cycle */ envy24_write_cci (devc, 0x22, 0xff); /* GPIO direction */ envy24_write_cci (devc, 0x21, 0x00); /* GPIO write mask */ save = envy24_read_cci (devc, 0x20) & ~0x39; envy24_write_cci (devc, 0x20, save | 0x09); /* CS inactive */ oss_udelay (200); envy24_write_cci (devc, 0x20, 0x08); /* CS active */ oss_udelay (200); gpio = 0x08; for (i = 0; i < 16; i++) { fData = (wCmd & 0x8000) ? 1 : 0; gpio = 0x08; if (fData) gpio |= 0x10; /* DATA */ envy24_write_cci (devc, 0x20, gpio | save); oss_udelay (20); envy24_write_cci (devc, 0x20, gpio | 0x20 | save); /* Clock pulse */ oss_udelay (200); envy24_write_cci (devc, 0x20, gpio | save); wCmd <<= 1; } oss_udelay (50); envy24_write_cci (devc, 0x20, 0x01); /* CS inactive */ oss_udelay (20); return 1; } /* Register offsets */ enum { AK4525_REG_POWER, AK4525_REG_RESET, AK4525_REG_FORMAT, AK4525_REG_DEEMPHASIS, AK4525_REG_LEFT_INPUT, AK4525_REG_RIGHT_INPUT, AK4525_REG_LEFT_OUTPUT, AK4525_REG_RIGHT_OUTPUT }; /*============================================================================= Function : HW_AK4525_Reset ------------------------------------------------------------------------------- Description : Returns : unsigned char Parameters : PCUST_HW_INSTANCE_DATA devc - int nCodecIdx -> 0..3 index of Codec ,-1 -> all ------------------------------------------------------------------------------- Notes : =============================================================================*/ static unsigned char HW_AK4525_Reset (envy24_devc * devc, int nCodecIdx) { unsigned char bCodecMask = 0; if (nCodecIdx == -1) bCodecMask = 0x0F; else bCodecMask = 0x01 << (nCodecIdx); if (!HW_AK4525_WriteRegister (devc, bCodecMask, /* 0x0F -> set mode for all codecs */ AK4525_REG_RESET, 0x00)) /* DACs,ADCs -> reset */ { cmn_err (CE_CONT, "REG_RESET failed\n"); return 0; } if (!HW_AK4525_WriteRegister (devc, bCodecMask, /* 0x0F -> set mode for all codecs */ AK4525_REG_FORMAT, 0x60)) /* IIS, 256 fsn, normal speed */ return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, /* 0x0F -> set mode for all codecs */ AK4525_REG_RESET, 0x03)) /* DACs,ADCs -> normal operation */ return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, /* 0x0F -> set mode for all codecs */ AK4525_REG_POWER, 0x07)) /* power on */ return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, /* soft mute timeout --> short */ AK4525_REG_DEEMPHASIS, 0x19)) return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, AK4525_REG_LEFT_INPUT, 0x7f)) return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, AK4525_REG_RIGHT_INPUT, 0x7f)) return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, AK4525_REG_LEFT_OUTPUT, 0x7f)) return 0; if (!HW_AK4525_WriteRegister (devc, bCodecMask, AK4525_REG_RIGHT_OUTPUT, 0x7f)) return 0; return 1; } unsigned char ewx2496_AK4525_Reset (envy24_devc * devc, int nCodecIdx) { unsigned char bCodecMask = 0; if (nCodecIdx == -1) bCodecMask = 0x0F; else bCodecMask = 0x01 << (nCodecIdx); envy24_write_cci (devc, 0x20, 0x03); /* Default value */ if (!write_ewx2496_codec (devc, AK4525_REG_RESET, 0x00)) /* DACs,ADCs -> reset */ { cmn_err (CE_CONT, "REG_RESET failed\n"); return 0; } if (!write_ewx2496_codec (devc, AK4525_REG_FORMAT, 0x60)) /* IIS, 256 fsn, normal speed */ return 0; if (!write_ewx2496_codec (devc, AK4525_REG_RESET, 0x03)) /* DACs,ADCs -> normal operation */ return 0; if (!write_ewx2496_codec (devc, AK4525_REG_POWER, 0x07)) /* power on */ return 0; if (!write_ewx2496_codec (devc, AK4525_REG_DEEMPHASIS, 0x19)) return 0; if (!write_ewx2496_codec (devc, AK4525_REG_LEFT_INPUT, 0x7f)) return 0; if (!write_ewx2496_codec (devc, AK4525_REG_RIGHT_INPUT, 0x7f)) return 0; if (!write_ewx2496_codec (devc, AK4525_REG_LEFT_OUTPUT, 0x7f)) return 0; if (!write_ewx2496_codec (devc, AK4525_REG_RIGHT_OUTPUT, 0x7f)) return 0; return 1; } static void ews88_init_codecs (envy24_devc * devc) { if (!HW_AK4525_Reset (devc, -1)) cmn_err (CE_CONT, "Envy24: EWS88MT reset failed\n");; } static void ewx2496_init_codecs (envy24_devc * devc) { if (!ewx2496_AK4525_Reset (devc, -1)) cmn_err (CE_CONT, "Envy24: EWS88MT reset failed\n");; } static int envy24_set_akm (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int codec, level, i; static unsigned char levels[] = { 0x7f, 0x8c, 0x98 }; if (ctrl >= 0xff) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { if (envy24_gain_sliders) return devc->akm_gains[ctrl]; return devc->akm_gains[ctrl]; } else if (cmd == SNDCTL_MIX_WRITE) { if (envy24_gain_sliders) level = value & 0xff; else { if (value > 2) return OSS_EINVAL; level = levels[value]; } codec = ctrl & 0xf0; for (i = 0; i < 4; i++) { if (!(ctrl & (1 << i))) continue; write_codec (devc, codec, 4 + i, level); } return devc->akm_gains[ctrl] = value; } return OSS_EINVAL; } static int envy24_set_ap (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int level; static unsigned char levels[] = { 0x60, 0x6f, 0x7f }; if (ctrl >= 0xff) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { if (envy24_gain_sliders) return devc->akm_gains[ctrl]; return devc->akm_gains[ctrl]; } else if (cmd == SNDCTL_MIX_WRITE) { if (envy24_gain_sliders) level = value & 0xff; else { if (value > 2) return OSS_EINVAL; level = levels[value]; } write_ap_codec (devc, 4, level); write_ap_codec (devc, 5, level); return devc->akm_gains[ctrl] = value; } return OSS_EINVAL; } static int envy24_set_d410 (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int level, n; static unsigned char levels[] = { 0x00, 0x00, 0x0c }; if (ctrl >= 8) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { if (envy24_gain_sliders) return devc->akm_gains[ctrl]; return devc->akm_gains[ctrl]; } else if (cmd == SNDCTL_MIX_WRITE) { if (envy24_gain_sliders) level = 255 - value * 2; else { if (value > 2) return OSS_EINVAL; level = levels[value]; } if (ctrl < 6) n = ctrl + 2; else n = ctrl + 5; write_ap_codec (devc, n, level); if (devc->skipdevs == 2) write_ap_codec (devc, n + 1, level); return devc->akm_gains[ctrl] = value; } return OSS_EINVAL; } static unsigned char write_ewx2496_codec (envy24_devc * devc, unsigned char bRegIdx, unsigned char bRegData); static int envy24_set_ewx2496 (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int level; static unsigned char levels[] = { 0x60, 0x6f, 0x7f }; if (ctrl >= 0xff) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { if (envy24_gain_sliders) return devc->akm_gains[ctrl]; return devc->akm_gains[ctrl]; } else if (cmd == SNDCTL_MIX_WRITE) { if (envy24_gain_sliders) level = value & 0xff; else { if (value > 2) return OSS_EINVAL; level = levels[value]; } write_ewx2496_codec (devc, 6, level); write_ewx2496_codec (devc, 7, level); return devc->akm_gains[ctrl] = value; } return OSS_EINVAL; } static unsigned char HW_AK4525_WriteRegister (envy24_devc * devc, unsigned char bCsMask, unsigned char bRegIdx, unsigned char bRegData); static int envy24_set_ews88 (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int codec, level, i; static unsigned char levels[] = { 0x7f, 0x8c, 0x98 }; if (ctrl >= 0xff) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { if (envy24_gain_sliders) return devc->akm_gains[ctrl]; return devc->akm_gains[ctrl]; } else if (cmd == SNDCTL_MIX_WRITE) { if (envy24_gain_sliders) level = value & 0xff; else { if (value > 2) return OSS_EINVAL; level = levels[value]; } codec = ctrl & 0xf0; for (i = 0; i < 4; i++) { if (!(ctrl & (1 << i))) continue; if (!HW_AK4525_WriteRegister (devc, codec >> 4, 4 + i, level)) return OSS_EIO; } return devc->akm_gains[ctrl] = value; } return OSS_EINVAL; } /*ARGSUSED*/ static int create_akm_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip, codec, ports; int typ = MIXT_ENUM, range = 3; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_GAIN")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; if (envy24_gain_sliders) { typ = MIXT_MONOSLIDER; range = 144; for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0x7f; } else for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); codec = (i > 1) ? AKM_B : AKM_A; ports = 0x0c; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_akm, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); codec = (i > 1) ? AKM_B : AKM_A; ports = (i & 1) ? 0x08 : 0x04; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_akm, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } mask = devc->inportmask; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDIN"); else sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); codec = (i > 1) ? AKM_B : AKM_A; ports = 0x03; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_akm, typ, tmp, range, MIXF_RECVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDINL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDINR"); else sprintf (tmp, "ENVY24_IN%d", i + 1); codec = (i > 1) ? AKM_B : AKM_A; ports = (i & 1) ? 0x02 : 0x01; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_akm, typ, tmp, range, MIXF_RECVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } return 0; } /*ARGSUSED*/ static int create_ap_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip, codec, ports; int typ = MIXT_ENUM, range = 3; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_GAIN")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; if (envy24_gain_sliders) { typ = MIXT_MONOSLIDER; range = 164; for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0x7f; } else for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); codec = (i > 1) ? AKM_B : AKM_A; ports = 0x0c; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ap, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); codec = (i > 1) ? AKM_B : AKM_A; ports = (i & 1) ? 0x08 : 0x04; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ap, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } return 0; } /*ARGSUSED*/ static int create_d410_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip; int typ = MIXT_ENUM, range = 3; int enum_mask = 0xffffff; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_GAIN")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; if (envy24_gain_sliders) { typ = MIXT_MONOSLIDER; range = 127; for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 127; } else { for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 1; enum_mask = 0x6; } for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); if ((err = mixer_ext_create_control (dev, group, i, envy24_set_d410, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); if ((err = mixer_ext_create_control (dev, group, i, envy24_set_d410, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } envy24_set_enum_mask (dev, err, enum_mask); } return 0; } /*ARGSUSED*/ static int create_ewx2496_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip, codec, ports; int typ = MIXT_ENUM, range = 3; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_GAIN")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; if (envy24_gain_sliders) { typ = MIXT_MONOSLIDER; range = 164; for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0x7f; } else for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); codec = (i > 1) ? AKM_B : AKM_A; ports = 0x0c; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ewx2496, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); codec = (i > 1) ? AKM_B : AKM_A; ports = (i & 1) ? 0x08 : 0x04; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ewx2496, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } return 0; } /*ARGSUSED*/ static int create_ews88_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip, codec, ports; int typ = MIXT_ENUM, range = 3; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_GAIN")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; if (envy24_gain_sliders) { typ = MIXT_MONOSLIDER; range = 164; for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0x7f; } else for (i = 0; i < 0xff; i++) devc->akm_gains[i] = 0; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); codec = 0x10 << (i / 2); ports = 0x0c; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ews88, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); codec = 0x10 << (i / 2); ports = (i & 1) ? 0x08 : 0x04; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ews88, typ, tmp, range, MIXF_MAINVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } mask = devc->inportmask; for (i = 0; i < 8; i += skip) { if (!(mask & (1 << i))) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDIN"); else sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); codec = 0x10 << (i / 2); ports = 0x03; /* Both output ports */ if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ews88, typ, tmp, range, MIXF_RECVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDINL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDINR"); else sprintf (tmp, "ENVY24_IN%d", i + 1); codec = 0x10 << (i / 2); ports = (i & 1) ? 0x02 : 0x01; if ((err = mixer_ext_create_control (dev, group, codec | ports, envy24_set_ews88, typ, tmp, range, MIXF_RECVOL | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } } return 0; } /* * Hoontech DSP24 family */ typedef union { struct { unsigned int data:5; unsigned int clock:1; unsigned int res0:2; } b; unsigned int dwVal; } CLBYTE; #define HOONTECH_CLOCK 0x20 static void hoontech_write_gpio (envy24_devc * devc, unsigned char data) { envy24_write_cci (devc, 0x22, 0xff); /* GPIO direction */ envy24_write_cci (devc, 0x21, 0xc0); /* GPIO write mask */ envy24_write_cci (devc, 0x20, data | HOONTECH_CLOCK); /* GPIO data */ envy24_write_cci (devc, 0x20, data & ~HOONTECH_CLOCK); /* GPIO data */ envy24_write_cci (devc, 0x20, data | HOONTECH_CLOCK); /* GPIO data */ } static void hoontech_init (envy24_devc * devc) { devc->adsp.b.box = 0; devc->adsp.b.darear = 0; devc->adsp.b.id0 = 0; devc->adsp.b.clock0 = 1; devc->adsp.b.res0 = 0x0; devc->adsp.b.ch1 = 0x01; devc->adsp.b.ch2 = 0x01; devc->adsp.b.ch3 = 0x01; devc->adsp.b.id1 = 1; devc->adsp.b.clock1 = 1; devc->adsp.b.res1 = 0x0; devc->adsp.b.ch4 = 0x01; devc->adsp.b.midiin = 0x01; devc->adsp.b.midi1 = 0x0; devc->adsp.b.id2 = 2; devc->adsp.b.clock2 = 1; devc->adsp.b.res2 = 0x0; devc->adsp.b.midi2 = 0x0; devc->adsp.b.mute = 0x0; devc->adsp.b.insel = 0x1; devc->adsp.b.id3 = 3; devc->adsp.b.clock3 = 1; devc->adsp.b.res3 = 0x0; hoontech_write_gpio (devc, devc->adsp.w.b0); hoontech_write_gpio (devc, devc->adsp.w.b1); hoontech_write_gpio (devc, devc->adsp.w.b2); hoontech_write_gpio (devc, devc->adsp.w.b3); } /**************************************************/ static void default_card_init (envy24_devc * devc) { if (devc->model_data->flags & MF_AKMCODEC) { write_codec (devc, AKM_A | AKM_B, 0, 0x07); write_codec (devc, AKM_A | AKM_B, 1, 0x03); write_codec (devc, AKM_A | AKM_B, 2, 0x60); write_codec (devc, AKM_A | AKM_B, 3, 0x19); write_codec (devc, AKM_A | AKM_B, 4, 0x7f); write_codec (devc, AKM_A | AKM_B, 5, 0x7f); write_codec (devc, AKM_A | AKM_B, 6, 0x7f); write_codec (devc, AKM_A | AKM_B, 7, 0x7f); } if (devc->model_data->flags & MF_AP) { /* Init the AK4528 codec of Audiophile 2496 */ write_ap_codec (devc, 0, 0x07); write_ap_codec (devc, 1, 0x00); write_ap_codec (devc, 2, 0x60); write_ap_codec (devc, 3, 0x0d); write_ap_codec (devc, 4, 0x7f); write_ap_codec (devc, 1, 0x03); write_ap_codec (devc, 5, 0x7f); } if (devc->model_data->flags & MF_D410) { /* Init the AK4529 codec of Delta 410 */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x02) & ~0x04, devc->mt_base + 0x02); write_ap_codec (devc, 0x00, 0x0c); write_ap_codec (devc, 0x01, 0x02); write_ap_codec (devc, 0x02, 0x00); write_ap_codec (devc, 0x03, 0x00); write_ap_codec (devc, 0x04, 0x00); write_ap_codec (devc, 0x05, 0x00); write_ap_codec (devc, 0x06, 0x00); write_ap_codec (devc, 0x07, 0x00); write_ap_codec (devc, 0x0b, 0x00); write_ap_codec (devc, 0x0c, 0x00); write_ap_codec (devc, 0x08, 0x15); write_ap_codec (devc, 0x0a, 0x3f); } if (devc->model_data->flags & MF_EWS88) ews88_init_codecs (devc); if (devc->model_data->flags & MF_EWX2496) ewx2496_init_codecs (devc); if (devc->model_data->flags & MF_HOONTECH) hoontech_init (devc); } static int default_mix_init (envy24_devc * devc, int dev, int group) { int err; if (devc->model_data->flags & MF_AKMCODEC) { if ((err = create_akm_mixer (dev, devc, group)) < 0) return err; } else if (devc->model_data->flags & MF_AP) { if ((err = create_ap_mixer (dev, devc, group)) < 0) return err; } else if (devc->model_data->flags & MF_D410) { if ((err = create_d410_mixer (dev, devc, group)) < 0) return err; } else if (devc->model_data->flags & MF_EWS88) { if ((err = create_ews88_mixer (dev, devc, group)) < 0) return err; } else if (devc->model_data->flags & MF_EWX2496) { if ((err = create_ewx2496_mixer (dev, devc, group)) < 0) return err; } return 0; } /*ARGSUSED*/ int cs8427_spdif_ioctl (envy24_devc * devc, int dev, unsigned int cmd, ioctl_arg arg) { oss_digital_control *ctrl = (oss_digital_control *) arg; if (arg == NULL) return OSS_EINVAL; switch (cmd) { case SNDCTL_DSP_READCTL: { int i, rq; unsigned char status; rq = ctrl->valid; memset (ctrl, 0, sizeof (*ctrl)); ctrl->caps = DIG_CBITIN_FULL | DIG_CBITOUT_FULL; if (rq & VAL_CBITIN) { for (i = 0; i < 24; i++) ctrl->cbitin[i] = ReadCsByte (devc, i); ctrl->valid |= VAL_CBITIN; } if (rq & VAL_CBITOUT) { for (i = 0; i < 24; i++) ctrl->cbitin[i] = devc->spdif_cbits[i]; ctrl->valid |= VAL_CBITOUT; } if (rq & VAL_ISTATUS) { ctrl->valid |= VAL_ISTATUS; status = read_cs8427_spdif_reg (devc, 15); if (status & 0x04) ctrl->in_data = IND_DATA; else ctrl->in_data = IND_AUDIO; status = read_cs8427_spdif_reg (devc, 16); if (status & 0x40) ctrl->in_errors |= INERR_QCODE_CRC; if (status & 0x20) ctrl->in_errors |= INERR_CRC; if (status & 0x10) ctrl->in_locked = LOCK_UNLOCKED; else ctrl->in_locked = LOCK_LOCKED; if (status & 0x08) ctrl->in_vbit = VBIT_ON; else ctrl->in_vbit = VBIT_OFF; if (status & 0x04) ctrl->in_quality = IN_QUAL_POOR; else ctrl->in_quality = IN_QUAL_GOOD; if (status & 0x02) ctrl->in_errors |= INERR_BIPHASE; if (status & 0x01) ctrl->in_errors |= INERR_PARITY; #if 1 /* TODO: Better handling required */ write_cs8427_spdif_reg (devc, 18, 0x00); #endif } } return 0; break; case SNDCTL_DSP_WRITECTL: { int i, rq; rq = ctrl->valid; memset (ctrl, 0, sizeof (*ctrl)); ctrl->caps = DIG_CBITOUT_FULL; if (rq & VAL_CBITOUT) { for (i = 0; i < 24; i++) devc->spdif_cbits[i] = ctrl->cbitout[i]; ctrl->valid |= VAL_CBITOUT; lock_cs8427_spdif (devc); for (i = 0; i < 24; i++) { WriteCsByte (devc, i, devc->spdif_cbits[i]); } unlock_cs8427_spdif (devc); } } return 0; break; default:; } return OSS_EINVAL; } static int set_spdif_control (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; if (cmd == SNDCTL_MIX_READ) { switch (ctrl) { case 1: return devc->spdif_pro_mode; break; } return OSS_EIO; } if (cmd == SNDCTL_MIX_WRITE) { switch (ctrl) { case 1: return devc->spdif_pro_mode = !!value; break; } return OSS_EIO; } return OSS_EIO; } /*ARGSUSED*/ int cs8427_spdif_mixer_init (envy24_devc * devc, int dev, int group) { int err; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_SPDIF")) < 0) return group; if ((err = mixer_ext_create_control (dev, group, 1, set_spdif_control, MIXT_ENUM, "SPDIF_MODE", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; return 0; } static void set_ap_speed (envy24_devc * devc) { unsigned char gpio, tmp; tmp = devc->speedbits; if (devc->syncsource != SYNC_INTERNAL) { tmp |= 0x10; /* S/PDIF input clock select */ if (devc->model_data->flags & MF_WCLOCK) /* Has world clock too */ { int cmd = envy24_read_cci (devc, 0x20); cmd |= 0x10; /* S/PDIF */ if (devc->syncsource == SYNC_WCLOCK) cmd &= ~0x10; /* World clock */ envy24_write_cci (devc, 0x20, cmd); } } OUTB (devc->osdev, tmp, devc->mt_base + 0x01); if (devc->speed > 48000) { write_ap_codec (devc, 0x01, 0x00); gpio = envy24_read_cci (devc, 0x20); gpio |= 0x01; /* Turn DFS ON */ envy24_write_cci (devc, 0x20, gpio); write_ap_codec (devc, 0x02, 0x65); write_ap_codec (devc, 0x01, 0x03); } else { write_ap_codec (devc, 0x01, 0x00); gpio = envy24_read_cci (devc, 0x20); gpio &= ~0x01; /* Turn DFS OFF */ envy24_write_cci (devc, 0x20, gpio); write_ap_codec (devc, 0x02, 0x60); write_ap_codec (devc, 0x01, 0x03); } } envy24_auxdrv_t default_auxdrv = { default_card_init, default_mix_init, NULL, write_spdif }; envy24_auxdrv_t ap2496_auxdrv = { default_card_init, default_mix_init, init_cs8427_spdif, write_cs8427_spdif, cs8427_spdif_ioctl, read_ap_spdif_reg, write_ap_spdif_reg, set_ap_speed, NULL, cs8427_spdif_mixer_init }; envy24_auxdrv_t d410_auxdrv = { default_card_init, default_mix_init, init_cs8427_spdif, write_cs8427_spdif, cs8427_spdif_ioctl, read_ap_spdif_reg, write_ap_spdif_reg, NULL, NULL, cs8427_spdif_mixer_init }; envy24_auxdrv_t ewx2496_auxdrv = { default_card_init, default_mix_init, init_cs8427_spdif, write_cs8427_spdif, NULL, read_ewx2496_spdif_reg, write_ewx2496_spdif_reg, NULL, NULL, cs8427_spdif_mixer_init };