2014-01-24 16:18:13 +04:00
/*
* Mixer controls for the Xonar DG / DGX
*
* Copyright ( c ) Clemens Ladisch < clemens @ ladisch . de >
* Copyright ( c ) Roman Volkov < v1ron @ mail . ru >
*
* 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/>.
*/
# include <linux/pci.h>
# include <linux/delay.h>
# 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:15 +04:00
/* analog output select */
static int output_select_apply ( struct oxygen * chip )
{
struct dg * data = chip - > model_data ;
data - > cs4245_shadow [ CS4245_SIGNAL_SEL ] & = ~ CS4245_A_OUT_SEL_MASK ;
if ( data - > output_sel = = PLAYBACK_DST_HP ) {
/* mute FP (aux output) amplifier, switch rear jack to CS4245 */
oxygen_set_bits8 ( chip , OXYGEN_GPIO_DATA , GPIO_HP_REAR ) ;
} else if ( data - > output_sel = = PLAYBACK_DST_HP_FP ) {
/*
* Unmute FP amplifier , switch rear jack to CS4361 ;
* I2S channels 2 , 3 , 4 should be inactive .
*/
oxygen_clear_bits8 ( chip , OXYGEN_GPIO_DATA , GPIO_HP_REAR ) ;
data - > cs4245_shadow [ CS4245_SIGNAL_SEL ] | = CS4245_A_OUT_SEL_DAC ;
} else {
/*
* 2.0 , 4.0 , 5.1 : switch to CS4361 , mute FP amp . ,
* and change playback routing .
*/
oxygen_clear_bits8 ( chip , OXYGEN_GPIO_DATA , GPIO_HP_REAR ) ;
}
return cs4245_write_spi ( chip , CS4245_SIGNAL_SEL ) ;
}
static int output_select_info ( struct snd_kcontrol * ctl ,
2014-01-24 16:18:13 +04:00
struct snd_ctl_elem_info * info )
{
static const char * const names [ 3 ] = {
2014-01-24 16:18:15 +04:00
" Stereo Headphones " ,
" Stereo Headphones FP " ,
" Multichannel " ,
2014-01-24 16:18:13 +04:00
} ;
return snd_ctl_enum_info ( info , 1 , 3 , names ) ;
}
2014-01-24 16:18:15 +04:00
static int output_select_get ( struct snd_kcontrol * ctl ,
2014-01-24 16:18:13 +04:00
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
mutex_lock ( & chip - > mutex ) ;
value - > value . enumerated . item [ 0 ] = data - > output_sel ;
mutex_unlock ( & chip - > mutex ) ;
return 0 ;
}
2014-01-24 16:18:15 +04:00
static int output_select_put ( struct snd_kcontrol * ctl ,
2014-01-24 16:18:13 +04:00
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
2014-01-24 16:18:15 +04:00
unsigned int new = value - > value . enumerated . item [ 0 ] ;
int changed = 0 ;
int ret ;
2014-01-24 16:18:13 +04:00
mutex_lock ( & chip - > mutex ) ;
2014-01-24 16:18:15 +04:00
if ( data - > output_sel ! = new ) {
data - > output_sel = new ;
ret = output_select_apply ( chip ) ;
changed = ret > = 0 ? 1 : ret ;
oxygen_update_dac_routing ( chip ) ;
2014-01-24 16:18:13 +04:00
}
mutex_unlock ( & chip - > mutex ) ;
2014-01-24 16:18:15 +04:00
2014-01-24 16:18:13 +04:00
return changed ;
}
2014-01-24 16:18:16 +04:00
/* CS4245 Headphone Channels A&B Volume Control */
static int hp_stereo_volume_info ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_info * info )
2014-01-24 16:18:13 +04:00
{
2014-01-24 16:18:16 +04:00
info - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
info - > count = 2 ;
info - > value . integer . min = 0 ;
info - > value . integer . max = 255 ;
return 0 ;
}
2014-01-24 16:18:13 +04:00
2014-01-24 16:18:16 +04:00
static int hp_stereo_volume_get ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * val )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
unsigned int tmp ;
mutex_lock ( & chip - > mutex ) ;
tmp = ( ~ data - > cs4245_shadow [ CS4245_DAC_A_CTRL ] ) & 255 ;
val - > value . integer . value [ 0 ] = tmp ;
tmp = ( ~ data - > cs4245_shadow [ CS4245_DAC_B_CTRL ] ) & 255 ;
val - > value . integer . value [ 1 ] = tmp ;
mutex_unlock ( & chip - > mutex ) ;
return 0 ;
2014-01-24 16:18:13 +04:00
}
2014-01-24 16:18:16 +04:00
static int hp_stereo_volume_put ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * val )
2014-01-24 16:18:13 +04:00
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
2014-01-24 16:18:16 +04:00
int ret ;
int changed = 0 ;
long new1 = val - > value . integer . value [ 0 ] ;
long new2 = val - > value . integer . value [ 1 ] ;
if ( ( new1 > 255 ) | | ( new1 < 0 ) | | ( new2 > 255 ) | | ( new2 < 0 ) )
return - EINVAL ;
2014-01-24 16:18:13 +04:00
mutex_lock ( & chip - > mutex ) ;
2014-01-24 16:18:16 +04:00
if ( ( data - > cs4245_shadow [ CS4245_DAC_A_CTRL ] ! = ~ new1 ) | |
( data - > cs4245_shadow [ CS4245_DAC_B_CTRL ] ! = ~ new2 ) ) {
data - > cs4245_shadow [ CS4245_DAC_A_CTRL ] = ~ new1 ;
data - > cs4245_shadow [ CS4245_DAC_B_CTRL ] = ~ new2 ;
ret = cs4245_write_spi ( chip , CS4245_DAC_A_CTRL ) ;
if ( ret > = 0 )
ret = cs4245_write_spi ( chip , CS4245_DAC_B_CTRL ) ;
changed = ret > = 0 ? 1 : ret ;
}
mutex_unlock ( & chip - > mutex ) ;
return changed ;
}
/* Headphone Mute */
static int hp_mute_get ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * val )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
mutex_lock ( & chip - > mutex ) ;
val - > value . integer . value [ 0 ] =
! ( data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] & CS4245_MUTE_DAC ) ;
2014-01-24 16:18:13 +04:00
mutex_unlock ( & chip - > mutex ) ;
return 0 ;
}
2014-01-24 16:18:16 +04:00
static int hp_mute_put ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * val )
2014-01-24 16:18:13 +04:00
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
2014-01-24 16:18:16 +04:00
int ret ;
2014-01-24 16:18:13 +04:00
int changed ;
2014-01-24 16:18:16 +04:00
if ( val - > value . integer . value [ 0 ] > 1 )
2014-01-24 16:18:13 +04:00
return - EINVAL ;
mutex_lock ( & chip - > mutex ) ;
2014-01-24 16:18:16 +04:00
data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] & = ~ CS4245_MUTE_DAC ;
data - > cs4245_shadow [ CS4245_DAC_CTRL_1 ] | =
( ~ val - > value . integer . value [ 0 ] < < 2 ) & CS4245_MUTE_DAC ;
ret = cs4245_write_spi ( chip , CS4245_DAC_CTRL_1 ) ;
changed = ret > = 0 ? 1 : ret ;
2014-01-24 16:18:13 +04:00
mutex_unlock ( & chip - > mutex ) ;
return changed ;
}
2014-01-24 16:18:17 +04:00
/* capture volume for all sources */
static int input_volume_apply ( struct oxygen * chip , char left , char right )
{
struct dg * data = chip - > model_data ;
int ret ;
data - > cs4245_shadow [ CS4245_PGA_A_CTRL ] = left ;
data - > cs4245_shadow [ CS4245_PGA_B_CTRL ] = right ;
ret = cs4245_write_spi ( chip , CS4245_PGA_A_CTRL ) ;
if ( ret < 0 )
return ret ;
return cs4245_write_spi ( chip , CS4245_PGA_B_CTRL ) ;
}
2014-01-24 16:18:13 +04:00
static int input_vol_info ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_info * info )
{
info - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
info - > count = 2 ;
info - > value . integer . min = 2 * - 12 ;
info - > value . integer . max = 2 * 12 ;
return 0 ;
}
static int input_vol_get ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
unsigned int idx = ctl - > private_value ;
mutex_lock ( & chip - > mutex ) ;
value - > value . integer . value [ 0 ] = data - > input_vol [ idx ] [ 0 ] ;
value - > value . integer . value [ 1 ] = data - > input_vol [ idx ] [ 1 ] ;
mutex_unlock ( & chip - > mutex ) ;
return 0 ;
}
static int input_vol_put ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
unsigned int idx = ctl - > private_value ;
int changed = 0 ;
2014-01-24 16:18:17 +04:00
int ret = 0 ;
2014-01-24 16:18:13 +04:00
if ( value - > value . integer . value [ 0 ] < 2 * - 12 | |
value - > value . integer . value [ 0 ] > 2 * 12 | |
value - > value . integer . value [ 1 ] < 2 * - 12 | |
value - > value . integer . value [ 1 ] > 2 * 12 )
return - EINVAL ;
mutex_lock ( & chip - > mutex ) ;
changed = data - > input_vol [ idx ] [ 0 ] ! = value - > value . integer . value [ 0 ] | |
data - > input_vol [ idx ] [ 1 ] ! = value - > value . integer . value [ 1 ] ;
if ( changed ) {
data - > input_vol [ idx ] [ 0 ] = value - > value . integer . value [ 0 ] ;
data - > input_vol [ idx ] [ 1 ] = value - > value . integer . value [ 1 ] ;
if ( idx = = data - > input_sel ) {
2014-01-24 16:18:17 +04:00
ret = input_volume_apply ( chip ,
data - > input_vol [ idx ] [ 0 ] ,
data - > input_vol [ idx ] [ 1 ] ) ;
2014-01-24 16:18:13 +04:00
}
2014-01-24 16:18:17 +04:00
changed = ret > = 0 ? 1 : ret ;
2014-01-24 16:18:13 +04:00
}
mutex_unlock ( & chip - > mutex ) ;
return changed ;
}
2014-01-24 16:18:18 +04:00
/* Capture Source */
static int input_source_apply ( struct oxygen * chip )
{
struct dg * data = chip - > model_data ;
data - > cs4245_shadow [ CS4245_ANALOG_IN ] & = ~ CS4245_SEL_MASK ;
if ( data - > input_sel = = CAPTURE_SRC_FP_MIC )
data - > cs4245_shadow [ CS4245_ANALOG_IN ] | = CS4245_SEL_INPUT_2 ;
else if ( data - > input_sel = = CAPTURE_SRC_LINE )
data - > cs4245_shadow [ CS4245_ANALOG_IN ] | = CS4245_SEL_INPUT_4 ;
else if ( data - > input_sel ! = CAPTURE_SRC_MIC )
data - > cs4245_shadow [ CS4245_ANALOG_IN ] | = CS4245_SEL_INPUT_1 ;
return cs4245_write_spi ( chip , CS4245_ANALOG_IN ) ;
}
2014-01-24 16:18:13 +04:00
static int input_sel_info ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_info * info )
{
static const char * const names [ 4 ] = {
2014-01-24 16:18:18 +04:00
" Mic " , " Front Mic " , " Line " , " Aux "
2014-01-24 16:18:13 +04:00
} ;
return snd_ctl_enum_info ( info , 1 , 4 , names ) ;
}
static int input_sel_get ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
mutex_lock ( & chip - > mutex ) ;
value - > value . enumerated . item [ 0 ] = data - > input_sel ;
mutex_unlock ( & chip - > mutex ) ;
return 0 ;
}
static int input_sel_put ( struct snd_kcontrol * ctl ,
struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
int changed ;
2014-01-24 16:18:18 +04:00
int ret ;
2014-01-24 16:18:13 +04:00
if ( value - > value . enumerated . item [ 0 ] > 3 )
return - EINVAL ;
mutex_lock ( & chip - > mutex ) ;
changed = value - > value . enumerated . item [ 0 ] ! = data - > input_sel ;
if ( changed ) {
data - > input_sel = value - > value . enumerated . item [ 0 ] ;
2014-01-24 16:18:18 +04:00
ret = input_source_apply ( chip ) ;
if ( ret > = 0 )
ret = input_volume_apply ( chip ,
data - > input_vol [ data - > input_sel ] [ 0 ] ,
data - > input_vol [ data - > input_sel ] [ 1 ] ) ;
changed = ret > = 0 ? 1 : ret ;
2014-01-24 16:18:13 +04:00
}
mutex_unlock ( & chip - > mutex ) ;
return changed ;
}
2014-01-24 16:18:19 +04:00
/* ADC high-pass filter */
2014-01-24 16:18:13 +04:00
static int hpf_info ( struct snd_kcontrol * ctl , struct snd_ctl_elem_info * info )
{
static const char * const names [ 2 ] = { " Active " , " Frozen " } ;
return snd_ctl_enum_info ( info , 1 , 2 , names ) ;
}
static int hpf_get ( struct snd_kcontrol * ctl , struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
value - > value . enumerated . item [ 0 ] =
! ! ( data - > cs4245_shadow [ CS4245_ADC_CTRL ] & CS4245_HPF_FREEZE ) ;
return 0 ;
}
static int hpf_put ( struct snd_kcontrol * ctl , struct snd_ctl_elem_value * value )
{
struct oxygen * chip = ctl - > private_data ;
struct dg * data = chip - > model_data ;
u8 reg ;
int changed ;
mutex_lock ( & chip - > mutex ) ;
reg = data - > cs4245_shadow [ CS4245_ADC_CTRL ] & ~ CS4245_HPF_FREEZE ;
if ( value - > value . enumerated . item [ 0 ] )
reg | = CS4245_HPF_FREEZE ;
changed = reg ! = data - > cs4245_shadow [ CS4245_ADC_CTRL ] ;
2014-01-24 16:18:19 +04:00
if ( changed ) {
data - > cs4245_shadow [ CS4245_ADC_CTRL ] = reg ;
cs4245_write_spi ( chip , CS4245_ADC_CTRL ) ;
}
2014-01-24 16:18:13 +04:00
mutex_unlock ( & chip - > mutex ) ;
return changed ;
}
# define INPUT_VOLUME(xname, index) { \
. iface = SNDRV_CTL_ELEM_IFACE_MIXER , \
. name = xname , \
2014-01-24 16:18:17 +04:00
. access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ , \
2014-01-24 16:18:13 +04:00
. info = input_vol_info , \
. get = input_vol_get , \
. put = input_vol_put , \
2014-01-24 16:18:17 +04:00
. tlv = { . p = pga_db_scale } , \
2014-01-24 16:18:13 +04:00
. private_value = index , \
}
2014-01-24 16:18:16 +04:00
static const DECLARE_TLV_DB_MINMAX ( hp_db_scale , - 12550 , 0 ) ;
2014-01-24 16:18:17 +04:00
static const DECLARE_TLV_DB_MINMAX ( pga_db_scale , - 1200 , 1200 ) ;
2014-01-24 16:18:13 +04:00
static const struct snd_kcontrol_new dg_controls [ ] = {
{
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Analog Output Playback Enum " ,
2014-01-24 16:18:15 +04:00
. info = output_select_info ,
. get = output_select_get ,
. put = output_select_put ,
2014-01-24 16:18:13 +04:00
} ,
{
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
2014-01-24 16:18:16 +04:00
. name = " Headphone Playback Volume " ,
. access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ ,
. info = hp_stereo_volume_info ,
. get = hp_stereo_volume_get ,
. put = hp_stereo_volume_put ,
. tlv = { . p = hp_db_scale , } ,
} ,
{
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Headphone Playback Switch " ,
. access = SNDRV_CTL_ELEM_ACCESS_READWRITE ,
. info = snd_ctl_boolean_mono_info ,
. get = hp_mute_get ,
. put = hp_mute_put ,
2014-01-24 16:18:13 +04:00
} ,
2014-01-24 16:18:18 +04:00
INPUT_VOLUME ( " Mic Capture Volume " , CAPTURE_SRC_MIC ) ,
INPUT_VOLUME ( " Front Mic Capture Volume " , CAPTURE_SRC_FP_MIC ) ,
INPUT_VOLUME ( " Line Capture Volume " , CAPTURE_SRC_LINE ) ,
INPUT_VOLUME ( " Aux Capture Volume " , CAPTURE_SRC_AUX ) ,
2014-01-24 16:18:13 +04:00
{
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Capture Source " ,
. info = input_sel_info ,
. get = input_sel_get ,
. put = input_sel_put ,
} ,
{
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " ADC High-pass Filter Capture Enum " ,
. info = hpf_info ,
. get = hpf_get ,
. put = hpf_put ,
} ,
} ;
static int dg_control_filter ( struct snd_kcontrol_new * template )
{
if ( ! strncmp ( template - > name , " Master Playback " , 16 ) )
return 1 ;
return 0 ;
}
static int dg_mixer_init ( struct oxygen * chip )
{
unsigned int i ;
int err ;
2014-01-24 16:18:20 +04:00
output_select_apply ( chip ) ;
input_source_apply ( chip ) ;
oxygen_update_dac_routing ( chip ) ;
2014-01-24 16:18:13 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( dg_controls ) ; + + i ) {
err = snd_ctl_add ( chip - > card ,
snd_ctl_new1 ( & dg_controls [ i ] , chip ) ) ;
if ( err < 0 )
return err ;
}
2014-01-24 16:18:20 +04:00
2014-01-24 16:18:13 +04:00
return 0 ;
}
struct oxygen_model model_xonar_dg = {
. longname = " C-Media Oxygen HD Audio " ,
. chip = " CMI8786 " ,
. init = dg_init ,
. control_filter = dg_control_filter ,
. mixer_init = dg_mixer_init ,
. cleanup = dg_cleanup ,
. suspend = dg_suspend ,
. resume = dg_resume ,
. set_dac_params = set_cs4245_dac_params ,
. set_adc_params = set_cs4245_adc_params ,
. adjust_dac_routing = adjust_dg_dac_routing ,
. dump_registers = dump_cs4245_registers ,
. model_data_size = sizeof ( struct dg ) ,
. device_config = PLAYBACK_0_TO_I2S |
PLAYBACK_1_TO_SPDIF |
2014-01-24 16:18:14 +04:00
CAPTURE_0_FROM_I2S_1 |
2014-01-24 16:18:13 +04:00
CAPTURE_1_FROM_SPDIF ,
. dac_channels_pcm = 6 ,
. dac_channels_mixer = 0 ,
. function_flags = OXYGEN_FUNCTION_SPI ,
. dac_mclks = OXYGEN_MCLKS ( 256 , 128 , 128 ) ,
. adc_mclks = OXYGEN_MCLKS ( 256 , 128 , 128 ) ,
. dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST ,
. adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST ,
} ;