2011-01-10 18:20:29 +03:00
/*
2012-05-01 19:40:30 +04:00
* card driver for the Xonar DG / DGX
2011-01-10 18:20:29 +03:00
*
* Copyright ( c ) Clemens Ladisch < clemens @ ladisch . de >
2014-01-24 16:18:06 +04:00
* Copyright ( c ) Roman Volkov < v1ron @ mail . ru >
2011-01-10 18:20:29 +03:00
*
* This driver is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License , version 2.
*
* This driver is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this driver ; if not , see < http : //www.gnu.org/licenses/>.
*/
/*
2012-05-01 19:40:30 +04:00
* Xonar DG / DGX
* - - - - - - - - - - - -
2011-01-10 18:20:29 +03:00
*
2014-01-24 16:18:06 +04:00
* CS4245 and CS4361 both will mute all outputs if any clock ratio
* is invalid .
*
2011-01-10 18:20:29 +03:00
* CMI8788 :
*
* SPI 0 - > CS4245
*
2014-01-24 16:18:06 +04:00
* Playback :
2011-01-31 13:47:52 +03:00
* I ² S 1 - > CS4245
* I ² S 2 - > CS4361 ( center / LFE )
* I ² S 3 - > CS4361 ( surround )
* I ² S 4 - > CS4361 ( front )
2014-01-24 16:18:06 +04:00
* Capture :
* I ² S ADC 1 < - CS4245
2011-01-31 13:47:52 +03:00
*
2011-01-10 18:20:29 +03:00
* GPIO 3 < - ?
* GPIO 4 < - headphone detect
2014-01-24 16:18:06 +04:00
* GPIO 5 - > enable ADC analog circuit for the left channel
* GPIO 6 - > enable ADC analog circuit for the right channel
* GPIO 7 - > switch green rear output jack between CS4245 and and the first
* channel of CS4361 ( mechanical relay )
2011-01-10 18:20:29 +03:00
* GPIO 8 - > enable output to speakers
*
* CS4245 :
*
2014-01-24 16:18:06 +04:00
* input 0 < - mic
2011-01-10 18:20:29 +03:00
* input 1 < - aux
* input 2 < - front mic
2014-01-24 16:18:06 +04:00
* input 4 < - line
2011-01-31 13:47:52 +03:00
* DAC out - > headphones
2011-01-10 18:20:29 +03:00
* aux out - > front panel headphones
*/
# include <linux/pci.h>
2011-01-11 05:20:30 +03:00
# include <linux/delay.h>
2011-01-10 18:20:29 +03:00
# include <sound/control.h>
# include <sound/core.h>
# include <sound/info.h>
# include <sound/pcm.h>
# include <sound/tlv.h>
# include "oxygen.h"
# include "xonar_dg.h"
# include "cs4245.h"
2014-01-24 16:18:08 +04:00
int cs4245_write_spi ( struct oxygen * chip , u8 reg )
{
struct dg * data = chip - > model_data ;
unsigned int packet ;
packet = reg < < 8 ;
packet | = ( CS4245_SPI_ADDRESS | CS4245_SPI_WRITE ) < < 16 ;
packet | = data - > cs4245_shadow [ reg ] ;
return oxygen_write_spi ( chip , OXYGEN_SPI_TRIGGER |
OXYGEN_SPI_DATA_LENGTH_3 |
OXYGEN_SPI_CLOCK_1280 |
( 0 < < OXYGEN_SPI_CODEC_SHIFT ) |
OXYGEN_SPI_CEN_LATCH_CLOCK_HI ,
packet ) ;
}
int cs4245_read_spi ( struct oxygen * chip , u8 addr )
{
struct dg * data = chip - > model_data ;
int ret ;
ret = oxygen_write_spi ( chip , OXYGEN_SPI_TRIGGER |
OXYGEN_SPI_DATA_LENGTH_2 |
OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
OXYGEN_SPI_CLOCK_1280 | ( 0 < < OXYGEN_SPI_CODEC_SHIFT ) ,
( ( CS4245_SPI_ADDRESS | CS4245_SPI_WRITE ) < < 8 ) | addr ) ;
if ( ret < 0 )
return ret ;
ret = oxygen_write_spi ( chip , OXYGEN_SPI_TRIGGER |
OXYGEN_SPI_DATA_LENGTH_2 |
OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
OXYGEN_SPI_CLOCK_1280 | ( 0 < < OXYGEN_SPI_CODEC_SHIFT ) ,
( CS4245_SPI_ADDRESS | CS4245_SPI_READ ) < < 8 ) ;
if ( ret < 0 )
return ret ;
data - > cs4245_shadow [ addr ] = oxygen_read8 ( chip , OXYGEN_SPI_DATA1 ) ;
return 0 ;
}
int cs4245_shadow_control ( struct oxygen * chip , enum cs4245_shadow_operation op )
{
struct dg * data = chip - > model_data ;
unsigned char addr ;
int ret ;
for ( addr = 1 ; addr < ARRAY_SIZE ( data - > cs4245_shadow ) ; addr + + ) {
ret = ( op = = CS4245_SAVE_TO_SHADOW ?
cs4245_read_spi ( chip , addr ) :
cs4245_write_spi ( chip , addr ) ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
2011-01-10 18:20:29 +03:00
static void cs4245_init ( struct oxygen * chip )
{
struct dg * data = chip - > model_data ;
2014-01-24 16:18:09 +04:00
/* save the initial state: codec version, registers */
cs4245_shadow_control ( chip , CS4245_SAVE_TO_SHADOW ) ;
/*
* Power up the CODEC internals , enable soft ramp & zero cross , work in
* async . mode , enable aux output from DAC . Invert DAC output as in the
* Windows driver .
*/
data - > cs4245_shadow [ CS4245_POWER_CTRL ] = 0 ;
data - > cs4245_shadow [ CS4245_SIGNAL_SEL ] =
CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH ;
data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] =
2011-01-10 18:20:29 +03:00
CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST ;
2014-01-24 16:18:09 +04:00
data - > cs4245_shadow [ CS4245_DAC_CTRL_2 ] =
CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC ;
data - > cs4245_shadow [ CS4245_ADC_CTRL ] =
2011-01-10 18:20:29 +03:00
CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST ;
2014-01-24 16:18:09 +04:00
data - > cs4245_shadow [ CS4245_ANALOG_IN ] =
CS4245_PGA_SOFT | CS4245_PGA_ZERO ;
data - > cs4245_shadow [ CS4245_PGA_B_CTRL ] = 0 ;
data - > cs4245_shadow [ CS4245_PGA_A_CTRL ] = 0 ;
2014-01-24 16:18:20 +04:00
data - > cs4245_shadow [ CS4245_DAC_A_CTRL ] = 8 ;
data - > cs4245_shadow [ CS4245_DAC_B_CTRL ] = 8 ;
2014-01-24 16:18:09 +04:00
cs4245_shadow_control ( chip , CS4245_LOAD_FROM_SHADOW ) ;
2011-01-10 18:20:29 +03:00
snd_component_add ( chip - > card , " CS4245 " ) ;
}
2014-01-24 16:18:13 +04:00
void dg_init ( struct oxygen * chip )
2011-01-10 18:20:29 +03:00
{
struct dg * data = chip - > model_data ;
2014-01-24 16:18:20 +04:00
data - > output_sel = PLAYBACK_DST_HP_FP ;
data - > input_sel = CAPTURE_SRC_MIC ;
2011-01-10 18:20:29 +03:00
cs4245_init ( chip ) ;
2014-01-24 16:18:09 +04:00
oxygen_write16 ( chip , OXYGEN_GPIO_CONTROL ,
GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE ) ;
2014-01-24 16:18:20 +04:00
/* anti-pop delay, wait some time before enabling the output */
msleep ( 2500 ) ;
2014-01-24 16:18:09 +04:00
oxygen_write16 ( chip , OXYGEN_GPIO_DATA ,
GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE ) ;
2011-01-10 18:20:29 +03:00
}
2014-01-24 16:18:13 +04:00
void dg_cleanup ( struct oxygen * chip )
2011-01-10 18:20:29 +03:00
{
oxygen_clear_bits16 ( chip , OXYGEN_GPIO_DATA , GPIO_OUTPUT_ENABLE ) ;
}
2014-01-24 16:18:13 +04:00
void dg_suspend ( struct oxygen * chip )
2011-01-10 18:20:29 +03:00
{
dg_cleanup ( chip ) ;
}
2014-01-24 16:18:13 +04:00
void dg_resume ( struct oxygen * chip )
2011-01-10 18:20:29 +03:00
{
2014-01-24 16:18:09 +04:00
cs4245_shadow_control ( chip , CS4245_LOAD_FROM_SHADOW ) ;
msleep ( 2500 ) ;
oxygen_set_bits16 ( chip , OXYGEN_GPIO_DATA , GPIO_OUTPUT_ENABLE ) ;
2011-01-10 18:20:29 +03:00
}
2014-01-24 16:18:13 +04:00
void set_cs4245_dac_params ( struct oxygen * chip ,
2011-01-10 18:20:29 +03:00
struct snd_pcm_hw_params * params )
{
struct dg * data = chip - > model_data ;
2014-01-24 16:18:10 +04:00
unsigned char dac_ctrl ;
unsigned char mclk_freq ;
dac_ctrl = data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] & ~ CS4245_DAC_FM_MASK ;
mclk_freq = data - > cs4245_shadow [ CS4245_MCLK_FREQ ] & ~ CS4245_MCLK1_MASK ;
if ( params_rate ( params ) < = 50000 ) {
dac_ctrl | = CS4245_DAC_FM_SINGLE ;
mclk_freq | = CS4245_MCLK_1 < < CS4245_MCLK1_SHIFT ;
} else if ( params_rate ( params ) < = 100000 ) {
dac_ctrl | = CS4245_DAC_FM_DOUBLE ;
mclk_freq | = CS4245_MCLK_1 < < CS4245_MCLK1_SHIFT ;
} else {
dac_ctrl | = CS4245_DAC_FM_QUAD ;
mclk_freq | = CS4245_MCLK_2 < < CS4245_MCLK1_SHIFT ;
}
data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] = dac_ctrl ;
data - > cs4245_shadow [ CS4245_MCLK_FREQ ] = mclk_freq ;
cs4245_write_spi ( chip , CS4245_DAC_CTRL_1 ) ;
cs4245_write_spi ( chip , CS4245_MCLK_FREQ ) ;
2011-01-10 18:20:29 +03:00
}
2014-01-24 16:18:13 +04:00
void set_cs4245_adc_params ( struct oxygen * chip ,
2011-01-10 18:20:29 +03:00
struct snd_pcm_hw_params * params )
{
struct dg * data = chip - > model_data ;
2014-01-24 16:18:10 +04:00
unsigned char adc_ctrl ;
unsigned char mclk_freq ;
adc_ctrl = data - > cs4245_shadow [ CS4245_ADC_CTRL ] & ~ CS4245_ADC_FM_MASK ;
mclk_freq = data - > cs4245_shadow [ CS4245_MCLK_FREQ ] & ~ CS4245_MCLK2_MASK ;
if ( params_rate ( params ) < = 50000 ) {
adc_ctrl | = CS4245_ADC_FM_SINGLE ;
mclk_freq | = CS4245_MCLK_1 < < CS4245_MCLK2_SHIFT ;
} else if ( params_rate ( params ) < = 100000 ) {
adc_ctrl | = CS4245_ADC_FM_DOUBLE ;
mclk_freq | = CS4245_MCLK_1 < < CS4245_MCLK2_SHIFT ;
} else {
adc_ctrl | = CS4245_ADC_FM_QUAD ;
mclk_freq | = CS4245_MCLK_2 < < CS4245_MCLK2_SHIFT ;
}
data - > cs4245_shadow [ CS4245_ADC_CTRL ] = adc_ctrl ;
data - > cs4245_shadow [ CS4245_MCLK_FREQ ] = mclk_freq ;
cs4245_write_spi ( chip , CS4245_ADC_CTRL ) ;
cs4245_write_spi ( chip , CS4245_MCLK_FREQ ) ;
2011-01-10 18:20:29 +03:00
}
2014-03-18 12:31:18 +04:00
static inline unsigned int shift_bits ( unsigned int value ,
unsigned int shift_from ,
unsigned int shift_to ,
unsigned int mask )
{
if ( shift_from < shift_to )
return ( value < < ( shift_to - shift_from ) ) & mask ;
else
return ( value > > ( shift_from - shift_to ) ) & mask ;
}
2014-01-24 16:18:13 +04:00
unsigned int adjust_dg_dac_routing ( struct oxygen * chip ,
2011-01-31 13:47:52 +03:00
unsigned int play_routing )
{
2014-01-24 16:18:11 +04:00
struct dg * data = chip - > model_data ;
2014-01-24 16:18:15 +04:00
switch ( data - > output_sel ) {
2014-01-24 16:18:11 +04:00
case PLAYBACK_DST_HP :
case PLAYBACK_DST_HP_FP :
oxygen_write8_masked ( chip , OXYGEN_PLAY_ROUTING ,
OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |
OXYGEN_PLAY_MUTE67 , OXYGEN_PLAY_MUTE_MASK ) ;
break ;
case PLAYBACK_DST_MULTICH :
oxygen_write8_masked ( chip , OXYGEN_PLAY_ROUTING ,
OXYGEN_PLAY_MUTE01 , OXYGEN_PLAY_MUTE_MASK ) ;
break ;
}
2014-03-18 12:31:18 +04:00
return ( play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK ) |
shift_bits ( play_routing ,
OXYGEN_PLAY_DAC2_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC1_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC1_SOURCE_MASK ) |
shift_bits ( play_routing ,
OXYGEN_PLAY_DAC1_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC2_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC2_SOURCE_MASK ) |
shift_bits ( play_routing ,
OXYGEN_PLAY_DAC0_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC3_SOURCE_SHIFT ,
OXYGEN_PLAY_DAC3_SOURCE_MASK ) ;
2011-01-31 13:47:52 +03:00
}
2014-01-24 16:18:13 +04:00
void dump_cs4245_registers ( struct oxygen * chip ,
2011-01-10 18:20:29 +03:00
struct snd_info_buffer * buffer )
{
struct dg * data = chip - > model_data ;
2014-01-24 16:18:12 +04:00
unsigned int addr ;
2011-01-10 18:20:29 +03:00
snd_iprintf ( buffer , " \n CS4245: " ) ;
2014-01-24 16:18:12 +04:00
cs4245_read_spi ( chip , CS4245_INT_STATUS ) ;
for ( addr = 1 ; addr < ARRAY_SIZE ( data - > cs4245_shadow ) ; addr + + )
snd_iprintf ( buffer , " %02x " , data - > cs4245_shadow [ addr ] ) ;
2011-01-10 18:20:29 +03:00
snd_iprintf ( buffer , " \n " ) ;
}