2009-07-01 18:11:44 +02:00
/*
* HD audio interface patch for Cirrus Logic CS420x chip
*
* Copyright ( c ) 2009 Takashi Iwai < tiwai @ suse . de >
*
* This driver is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/pci.h>
2011-07-15 12:38:28 -04:00
# include <linux/module.h>
2009-07-01 18:11:44 +02:00
# include <sound/core.h>
# include "hda_codec.h"
# include "hda_local.h"
2011-07-22 16:41:00 -05:00
# include <sound/tlv.h>
2009-07-01 18:11:44 +02:00
/*
*/
struct cs_spec {
2009-07-06 15:15:22 +02:00
int board_config ;
2009-07-01 18:11:44 +02:00
struct auto_pin_cfg autocfg ;
struct hda_multi_out multiout ;
struct snd_kcontrol * vmaster_sw ;
struct snd_kcontrol * vmaster_vol ;
hda_nid_t dac_nid [ AUTO_CFG_MAX_OUTS ] ;
hda_nid_t slave_dig_outs [ 2 ] ;
unsigned int input_idx [ AUTO_PIN_LAST ] ;
unsigned int capsrc_idx [ AUTO_PIN_LAST ] ;
hda_nid_t adc_nid [ AUTO_PIN_LAST ] ;
unsigned int adc_idx [ AUTO_PIN_LAST ] ;
unsigned int num_inputs ;
unsigned int cur_input ;
unsigned int automic_idx ;
hda_nid_t cur_adc ;
unsigned int cur_adc_stream_tag ;
unsigned int cur_adc_format ;
hda_nid_t dig_in ;
2011-05-02 11:36:09 +02:00
const struct hda_bind_ctls * capture_bind [ 2 ] ;
2009-07-01 18:11:44 +02:00
2009-07-07 09:04:26 +02:00
unsigned int gpio_mask ;
unsigned int gpio_dir ;
unsigned int gpio_data ;
2009-07-01 18:11:44 +02:00
struct hda_pcm pcm_rec [ 2 ] ; /* PCM information */
unsigned int hp_detect : 1 ;
unsigned int mic_detect : 1 ;
2011-07-22 16:41:00 -05:00
/* CS421x */
unsigned int spdif_detect : 1 ;
unsigned int sense_b : 1 ;
hda_nid_t vendor_nid ;
struct hda_input_mux input_mux ;
unsigned int last_input ;
2009-07-01 18:11:44 +02:00
} ;
2011-07-22 16:41:00 -05:00
/* available models with CS420x */
2009-07-06 15:15:22 +02:00
enum {
2010-09-22 17:31:37 -04:00
CS420X_MBP53 ,
2009-07-06 15:15:22 +02:00
CS420X_MBP55 ,
2009-12-22 07:59:37 +01:00
CS420X_IMAC27 ,
2009-07-06 15:15:22 +02:00
CS420X_AUTO ,
CS420X_MODELS
} ;
2011-07-22 16:41:00 -05:00
/* CS421x boards */
enum {
CS421X_CDB4210 ,
CS421X_MODELS
} ;
2009-07-06 13:00:57 +02:00
/* Vendor-specific processing widget */
# define CS420X_VENDOR_NID 0x11
# define CS_DIG_OUT1_PIN_NID 0x10
# define CS_DIG_OUT2_PIN_NID 0x15
# define CS_DMIC1_PIN_NID 0x12
# define CS_DMIC2_PIN_NID 0x0e
/* coef indices */
# define IDX_SPDIF_STAT 0x0000
# define IDX_SPDIF_CTL 0x0001
# define IDX_ADC_CFG 0x0002
/* SZC bitmask, 4 modes below:
* 0 = immediate ,
* 1 = digital immediate , analog zero - cross
* 2 = digtail & analog soft - ramp
* 3 = digital soft - ramp , analog zero - cross
*/
# define CS_COEF_ADC_SZC_MASK (3 << 0)
# define CS_COEF_ADC_MIC_SZC_MODE (3 << 0) /* SZC setup for mic */
# define CS_COEF_ADC_LI_SZC_MODE (3 << 0) /* SZC setup for line-in */
/* PGA mode: 0 = differential, 1 = signle-ended */
# define CS_COEF_ADC_MIC_PGA_MODE (1 << 5) /* PGA setup for mic */
# define CS_COEF_ADC_LI_PGA_MODE (1 << 6) /* PGA setup for line-in */
# define IDX_DAC_CFG 0x0003
/* SZC bitmask, 4 modes below:
* 0 = Immediate
* 1 = zero - cross
* 2 = soft - ramp
* 3 = soft - ramp on zero - cross
*/
# define CS_COEF_DAC_HP_SZC_MODE (3 << 0) /* nid 0x02 */
# define CS_COEF_DAC_LO_SZC_MODE (3 << 2) /* nid 0x03 */
# define CS_COEF_DAC_SPK_SZC_MODE (3 << 4) /* nid 0x04 */
# define IDX_BEEP_CFG 0x0004
/* 0x0008 - test reg key */
/* 0x0009 - 0x0014 -> 12 test regs */
/* 0x0015 - visibility reg */
2011-07-22 16:41:00 -05:00
/*
* Cirrus Logic CS4210
*
* 1 DAC = > HP ( sense ) / Speakers ,
* 1 ADC < = LineIn ( sense ) / MicIn / DMicIn ,
* 1 SPDIF OUT = > SPDIF Trasmitter ( sense )
*/
# define CS4210_DAC_NID 0x02
# define CS4210_ADC_NID 0x03
# define CS421X_VENDOR_NID 0x0B
# define CS421X_DMIC_PIN_NID 0x09 /* Port E */
# define CS421X_SPDIF_PIN_NID 0x0A /* Port H */
# define CS421X_IDX_DEV_CFG 0x01
# define CS421X_IDX_ADC_CFG 0x02
# define CS421X_IDX_DAC_CFG 0x03
# define CS421X_IDX_SPK_CTL 0x04
# define SPDIF_EVENT 0x04
2009-07-06 13:00:57 +02:00
2009-07-08 12:42:08 +02:00
static inline int cs_vendor_coef_get ( struct hda_codec * codec , unsigned int idx )
2009-07-06 13:00:57 +02:00
{
2011-07-22 16:41:00 -05:00
struct cs_spec * spec = codec - > spec ;
snd_hda_codec_write ( codec , spec - > vendor_nid , 0 ,
2009-07-06 13:00:57 +02:00
AC_VERB_SET_COEF_INDEX , idx ) ;
2011-07-22 16:41:00 -05:00
return snd_hda_codec_read ( codec , spec - > vendor_nid , 0 ,
2009-07-06 13:00:57 +02:00
AC_VERB_GET_PROC_COEF , 0 ) ;
}
2009-07-08 12:42:08 +02:00
static inline void cs_vendor_coef_set ( struct hda_codec * codec , unsigned int idx ,
unsigned int coef )
2009-07-06 13:00:57 +02:00
{
2011-07-22 16:41:00 -05:00
struct cs_spec * spec = codec - > spec ;
snd_hda_codec_write ( codec , spec - > vendor_nid , 0 ,
2009-07-06 13:00:57 +02:00
AC_VERB_SET_COEF_INDEX , idx ) ;
2011-07-22 16:41:00 -05:00
snd_hda_codec_write ( codec , spec - > vendor_nid , 0 ,
2009-07-06 13:00:57 +02:00
AC_VERB_SET_PROC_COEF , coef ) ;
}
2009-07-01 18:11:44 +02:00
# define HP_EVENT 1
# define MIC_EVENT 2
/*
* PCM callbacks
*/
static int cs_playback_pcm_open ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_analog_open ( codec , & spec - > multiout , substream ,
hinfo ) ;
}
static int cs_playback_pcm_prepare ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
unsigned int stream_tag ,
unsigned int format ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_analog_prepare ( codec , & spec - > multiout ,
stream_tag , format , substream ) ;
}
static int cs_playback_pcm_cleanup ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_analog_cleanup ( codec , & spec - > multiout ) ;
}
/*
* Digital out
*/
static int cs_dig_playback_pcm_open ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_dig_open ( codec , & spec - > multiout ) ;
}
static int cs_dig_playback_pcm_close ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_dig_close ( codec , & spec - > multiout ) ;
}
static int cs_dig_playback_pcm_prepare ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
unsigned int stream_tag ,
unsigned int format ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_dig_prepare ( codec , & spec - > multiout , stream_tag ,
format , substream ) ;
}
static int cs_dig_playback_pcm_cleanup ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
return snd_hda_multi_out_dig_cleanup ( codec , & spec - > multiout ) ;
}
2011-11-16 18:05:11 +01:00
static void cs_update_input_select ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
if ( spec - > cur_adc )
snd_hda_codec_write ( codec , spec - > cur_adc , 0 ,
AC_VERB_SET_CONNECT_SEL ,
spec - > adc_idx [ spec - > cur_input ] ) ;
}
2009-07-01 18:11:44 +02:00
/*
* Analog capture
*/
static int cs_capture_pcm_prepare ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
unsigned int stream_tag ,
unsigned int format ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
spec - > cur_adc = spec - > adc_nid [ spec - > cur_input ] ;
spec - > cur_adc_stream_tag = stream_tag ;
spec - > cur_adc_format = format ;
2011-11-16 18:05:11 +01:00
cs_update_input_select ( codec ) ;
2009-07-01 18:11:44 +02:00
snd_hda_codec_setup_stream ( codec , spec - > cur_adc , stream_tag , 0 , format ) ;
return 0 ;
}
static int cs_capture_pcm_cleanup ( struct hda_pcm_stream * hinfo ,
struct hda_codec * codec ,
struct snd_pcm_substream * substream )
{
struct cs_spec * spec = codec - > spec ;
snd_hda_codec_cleanup_stream ( codec , spec - > cur_adc ) ;
spec - > cur_adc = 0 ;
return 0 ;
}
/*
*/
2011-05-02 11:36:09 +02:00
static const struct hda_pcm_stream cs_pcm_analog_playback = {
2009-07-01 18:11:44 +02:00
. substreams = 1 ,
. channels_min = 2 ,
. channels_max = 2 ,
. ops = {
. open = cs_playback_pcm_open ,
. prepare = cs_playback_pcm_prepare ,
. cleanup = cs_playback_pcm_cleanup
} ,
} ;
2011-05-02 11:36:09 +02:00
static const struct hda_pcm_stream cs_pcm_analog_capture = {
2009-07-01 18:11:44 +02:00
. substreams = 1 ,
. channels_min = 2 ,
. channels_max = 2 ,
. ops = {
. prepare = cs_capture_pcm_prepare ,
. cleanup = cs_capture_pcm_cleanup
} ,
} ;
2011-05-02 11:36:09 +02:00
static const struct hda_pcm_stream cs_pcm_digital_playback = {
2009-07-01 18:11:44 +02:00
. substreams = 1 ,
. channels_min = 2 ,
. channels_max = 2 ,
. ops = {
. open = cs_dig_playback_pcm_open ,
. close = cs_dig_playback_pcm_close ,
. prepare = cs_dig_playback_pcm_prepare ,
. cleanup = cs_dig_playback_pcm_cleanup
} ,
} ;
2011-05-02 11:36:09 +02:00
static const struct hda_pcm_stream cs_pcm_digital_capture = {
2009-07-01 18:11:44 +02:00
. substreams = 1 ,
. channels_min = 2 ,
. channels_max = 2 ,
} ;
static int cs_build_pcms ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct hda_pcm * info = spec - > pcm_rec ;
codec - > pcm_info = info ;
codec - > num_pcms = 0 ;
info - > name = " Cirrus Analog " ;
info - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] = cs_pcm_analog_playback ;
info - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] . nid = spec - > dac_nid [ 0 ] ;
info - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] . channels_max =
spec - > multiout . max_channels ;
info - > stream [ SNDRV_PCM_STREAM_CAPTURE ] = cs_pcm_analog_capture ;
info - > stream [ SNDRV_PCM_STREAM_CAPTURE ] . nid =
spec - > adc_nid [ spec - > cur_input ] ;
codec - > num_pcms + + ;
if ( ! spec - > multiout . dig_out_nid & & ! spec - > dig_in )
return 0 ;
info + + ;
info - > name = " Cirrus Digital " ;
info - > pcm_type = spec - > autocfg . dig_out_type [ 0 ] ;
if ( ! info - > pcm_type )
info - > pcm_type = HDA_PCM_TYPE_SPDIF ;
if ( spec - > multiout . dig_out_nid ) {
info - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] =
cs_pcm_digital_playback ;
info - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] . nid =
spec - > multiout . dig_out_nid ;
}
if ( spec - > dig_in ) {
info - > stream [ SNDRV_PCM_STREAM_CAPTURE ] =
cs_pcm_digital_capture ;
info - > stream [ SNDRV_PCM_STREAM_CAPTURE ] . nid = spec - > dig_in ;
}
codec - > num_pcms + + ;
return 0 ;
}
2009-07-06 12:55:46 +02:00
/*
* parse codec topology
*/
2009-07-01 18:11:44 +02:00
static hda_nid_t get_dac ( struct hda_codec * codec , hda_nid_t pin )
{
hda_nid_t dac ;
if ( ! pin )
return 0 ;
if ( snd_hda_get_connections ( codec , pin , & dac , 1 ) ! = 1 )
return 0 ;
return dac ;
}
2009-07-06 12:55:46 +02:00
static int is_ext_mic ( struct hda_codec * codec , unsigned int idx )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2010-08-30 13:05:30 +02:00
hda_nid_t pin = cfg - > inputs [ idx ] . pin ;
2011-05-17 10:00:16 +02:00
unsigned int val ;
if ( ! is_jack_detectable ( codec , pin ) )
2009-07-06 12:55:46 +02:00
return 0 ;
val = snd_hda_codec_get_pincfg ( codec , pin ) ;
2010-09-17 14:42:34 +02:00
return ( snd_hda_get_input_pin_attr ( val ) ! = INPUT_PIN_ATTR_INT ) ;
2009-07-06 12:55:46 +02:00
}
static hda_nid_t get_adc ( struct hda_codec * codec , hda_nid_t pin ,
unsigned int * idxp )
{
2011-08-20 09:19:59 +02:00
int i , idx ;
2009-07-06 12:55:46 +02:00
hda_nid_t nid ;
nid = codec - > start_nid ;
for ( i = 0 ; i < codec - > num_nodes ; i + + , nid + + ) {
unsigned int type ;
2011-05-17 10:02:27 +02:00
type = get_wcaps_type ( get_wcaps ( codec , nid ) ) ;
2009-07-06 12:55:46 +02:00
if ( type ! = AC_WID_AUD_IN )
continue ;
2011-08-20 09:19:59 +02:00
idx = snd_hda_get_conn_index ( codec , nid , pin , false ) ;
if ( idx > = 0 ) {
* idxp = idx ;
2011-06-28 12:45:47 +02:00
return nid ;
2011-08-20 09:19:59 +02:00
}
2009-07-06 12:55:46 +02:00
}
return 0 ;
}
2009-07-06 13:00:57 +02:00
static int is_active_pin ( struct hda_codec * codec , hda_nid_t nid )
{
unsigned int val ;
val = snd_hda_codec_get_pincfg ( codec , nid ) ;
return ( get_defcfg_connect ( val ) ! = AC_JACK_PORT_NONE ) ;
}
2009-07-06 12:55:46 +02:00
static int parse_output ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2009-07-08 12:42:08 +02:00
int i , extra_nids ;
2009-07-06 12:55:46 +02:00
hda_nid_t dac ;
for ( i = 0 ; i < cfg - > line_outs ; i + + ) {
dac = get_dac ( codec , cfg - > line_out_pins [ i ] ) ;
if ( ! dac )
break ;
spec - > dac_nid [ i ] = dac ;
}
spec - > multiout . num_dacs = i ;
spec - > multiout . dac_nids = spec - > dac_nid ;
spec - > multiout . max_channels = i * 2 ;
/* add HP and speakers */
extra_nids = 0 ;
for ( i = 0 ; i < cfg - > hp_outs ; i + + ) {
dac = get_dac ( codec , cfg - > hp_pins [ i ] ) ;
if ( ! dac )
break ;
if ( ! i )
spec - > multiout . hp_nid = dac ;
else
spec - > multiout . extra_out_nid [ extra_nids + + ] = dac ;
}
for ( i = 0 ; i < cfg - > speaker_outs ; i + + ) {
dac = get_dac ( codec , cfg - > speaker_pins [ i ] ) ;
if ( ! dac )
break ;
spec - > multiout . extra_out_nid [ extra_nids + + ] = dac ;
}
if ( cfg - > line_out_type = = AUTO_PIN_SPEAKER_OUT ) {
cfg - > speaker_outs = cfg - > line_outs ;
memcpy ( cfg - > speaker_pins , cfg - > line_out_pins ,
sizeof ( cfg - > speaker_pins ) ) ;
cfg - > line_outs = 0 ;
}
return 0 ;
}
static int parse_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2009-07-08 12:42:08 +02:00
int i ;
2009-07-06 12:55:46 +02:00
2010-08-30 13:05:30 +02:00
for ( i = 0 ; i < cfg - > num_inputs ; i + + ) {
hda_nid_t pin = cfg - > inputs [ i ] . pin ;
2009-07-06 12:55:46 +02:00
spec - > input_idx [ spec - > num_inputs ] = i ;
spec - > capsrc_idx [ i ] = spec - > num_inputs + + ;
spec - > cur_input = i ;
spec - > adc_nid [ i ] = get_adc ( codec , pin , & spec - > adc_idx [ i ] ) ;
}
if ( ! spec - > num_inputs )
return 0 ;
/* check whether the automatic mic switch is available */
if ( spec - > num_inputs = = 2 & &
2010-09-09 14:50:17 +02:00
cfg - > inputs [ 0 ] . type = = AUTO_PIN_MIC & &
cfg - > inputs [ 1 ] . type = = AUTO_PIN_MIC ) {
2010-08-30 13:05:30 +02:00
if ( is_ext_mic ( codec , cfg - > inputs [ 0 ] . pin ) ) {
if ( ! is_ext_mic ( codec , cfg - > inputs [ 1 ] . pin ) ) {
2009-07-06 12:55:46 +02:00
spec - > mic_detect = 1 ;
2010-08-30 13:05:30 +02:00
spec - > automic_idx = 0 ;
2009-07-06 12:55:46 +02:00
}
} else {
2010-08-30 13:05:30 +02:00
if ( is_ext_mic ( codec , cfg - > inputs [ 1 ] . pin ) ) {
2009-07-06 12:55:46 +02:00
spec - > mic_detect = 1 ;
2010-08-30 13:05:30 +02:00
spec - > automic_idx = 1 ;
2009-07-06 12:55:46 +02:00
}
}
}
return 0 ;
}
static int parse_digital_output ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
hda_nid_t nid ;
if ( ! cfg - > dig_outs )
return 0 ;
if ( snd_hda_get_connections ( codec , cfg - > dig_out_pins [ 0 ] , & nid , 1 ) < 1 )
return 0 ;
spec - > multiout . dig_out_nid = nid ;
spec - > multiout . share_spdif = 1 ;
if ( cfg - > dig_outs > 1 & &
snd_hda_get_connections ( codec , cfg - > dig_out_pins [ 1 ] , & nid , 1 ) > 0 ) {
spec - > slave_dig_outs [ 0 ] = nid ;
codec - > slave_dig_outs = spec - > slave_dig_outs ;
}
return 0 ;
}
static int parse_digital_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
int idx ;
2009-07-06 15:01:09 +02:00
if ( cfg - > dig_in_pin )
spec - > dig_in = get_adc ( codec , cfg - > dig_in_pin , & idx ) ;
return 0 ;
2009-07-06 12:55:46 +02:00
}
/*
* create mixer controls
*/
2011-01-17 11:29:34 +01:00
static const char * const dir_sfx [ 2 ] = { " Playback " , " Capture " } ;
2009-07-01 18:11:44 +02:00
static int add_mute ( struct hda_codec * codec , const char * name , int index ,
unsigned int pval , int dir , struct snd_kcontrol * * kctlp )
{
2009-07-07 09:05:07 +02:00
char tmp [ 44 ] ;
2009-07-01 18:11:44 +02:00
struct snd_kcontrol_new knew =
HDA_CODEC_MUTE_IDX ( tmp , index , 0 , 0 , HDA_OUTPUT ) ;
knew . private_value = pval ;
snprintf ( tmp , sizeof ( tmp ) , " %s %s Switch " , name , dir_sfx [ dir ] ) ;
* kctlp = snd_ctl_new1 ( & knew , codec ) ;
ALSA: hda - introduce HDA_SUBDEV_AMP_FLAG (ControlAmp in proc)
The purpose of this changeset is to show information about amplifier
setting in the codec proc file. Something like:
Control: name="Front Playback Volume", index=0, device=0
ControlAmp: chs=3, dir=Out, idx=0, ofs=0
Control: name="Front Playback Switch", index=0, device=0
ControlAmp: chs=3, dir=In, idx=2, ofs=0
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2009-12-08 17:45:25 +01:00
( * kctlp ) - > id . subdevice = HDA_SUBDEV_AMP_FLAG ;
2009-12-10 13:57:01 +01:00
return snd_hda_ctl_add ( codec , 0 , * kctlp ) ;
2009-07-01 18:11:44 +02:00
}
static int add_volume ( struct hda_codec * codec , const char * name ,
int index , unsigned int pval , int dir ,
struct snd_kcontrol * * kctlp )
{
2011-09-14 13:22:54 +02:00
char tmp [ 44 ] ;
2009-07-01 18:11:44 +02:00
struct snd_kcontrol_new knew =
HDA_CODEC_VOLUME_IDX ( tmp , index , 0 , 0 , HDA_OUTPUT ) ;
knew . private_value = pval ;
snprintf ( tmp , sizeof ( tmp ) , " %s %s Volume " , name , dir_sfx [ dir ] ) ;
* kctlp = snd_ctl_new1 ( & knew , codec ) ;
ALSA: hda - introduce HDA_SUBDEV_AMP_FLAG (ControlAmp in proc)
The purpose of this changeset is to show information about amplifier
setting in the codec proc file. Something like:
Control: name="Front Playback Volume", index=0, device=0
ControlAmp: chs=3, dir=Out, idx=0, ofs=0
Control: name="Front Playback Switch", index=0, device=0
ControlAmp: chs=3, dir=In, idx=2, ofs=0
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2009-12-08 17:45:25 +01:00
( * kctlp ) - > id . subdevice = HDA_SUBDEV_AMP_FLAG ;
2009-12-10 13:57:01 +01:00
return snd_hda_ctl_add ( codec , 0 , * kctlp ) ;
2009-07-01 18:11:44 +02:00
}
static void fix_volume_caps ( struct hda_codec * codec , hda_nid_t dac )
{
unsigned int caps ;
/* set the upper-limit for mixer amp to 0dB */
caps = query_amp_caps ( codec , dac , HDA_OUTPUT ) ;
caps & = ~ ( 0x7f < < AC_AMPCAP_NUM_STEPS_SHIFT ) ;
caps | = ( ( caps > > AC_AMPCAP_OFFSET_SHIFT ) & 0x7f )
< < AC_AMPCAP_NUM_STEPS_SHIFT ;
snd_hda_override_amp_caps ( codec , dac , HDA_OUTPUT , caps ) ;
}
static int add_vmaster ( struct hda_codec * codec , hda_nid_t dac )
{
struct cs_spec * spec = codec - > spec ;
unsigned int tlv [ 4 ] ;
int err ;
spec - > vmaster_sw =
snd_ctl_make_virtual_master ( " Master Playback Switch " , NULL ) ;
2009-11-11 13:43:01 +01:00
err = snd_hda_ctl_add ( codec , dac , spec - > vmaster_sw ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
snd_hda_set_vmaster_tlv ( codec , dac , HDA_OUTPUT , tlv ) ;
spec - > vmaster_vol =
snd_ctl_make_virtual_master ( " Master Playback Volume " , tlv ) ;
2009-11-11 13:43:01 +01:00
err = snd_hda_ctl_add ( codec , dac , spec - > vmaster_vol ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
return 0 ;
}
static int add_output ( struct hda_codec * codec , hda_nid_t dac , int idx ,
int num_ctls , int type )
{
struct cs_spec * spec = codec - > spec ;
const char * name ;
int err , index ;
struct snd_kcontrol * kctl ;
2011-05-02 11:36:09 +02:00
static const char * const speakers [ ] = {
2009-07-01 18:11:44 +02:00
" Front Speaker " , " Surround Speaker " , " Bass Speaker "
} ;
2011-05-02 11:36:09 +02:00
static const char * const line_outs [ ] = {
2009-07-01 18:11:44 +02:00
" Front Line-Out " , " Surround Line-Out " , " Bass Line-Out "
} ;
fix_volume_caps ( codec , dac ) ;
if ( ! spec - > vmaster_sw ) {
err = add_vmaster ( codec , dac ) ;
if ( err < 0 )
return err ;
}
index = 0 ;
switch ( type ) {
case AUTO_PIN_HP_OUT :
name = " Headphone " ;
index = idx ;
break ;
case AUTO_PIN_SPEAKER_OUT :
if ( num_ctls > 1 )
name = speakers [ idx ] ;
else
name = " Speaker " ;
break ;
default :
if ( num_ctls > 1 )
name = line_outs [ idx ] ;
else
name = " Line-Out " ;
break ;
}
err = add_mute ( codec , name , index ,
HDA_COMPOSE_AMP_VAL ( dac , 3 , 0 , HDA_OUTPUT ) , 0 , & kctl ) ;
if ( err < 0 )
return err ;
err = snd_ctl_add_slave ( spec - > vmaster_sw , kctl ) ;
if ( err < 0 )
return err ;
err = add_volume ( codec , name , index ,
HDA_COMPOSE_AMP_VAL ( dac , 3 , 0 , HDA_OUTPUT ) , 0 , & kctl ) ;
if ( err < 0 )
return err ;
err = snd_ctl_add_slave ( spec - > vmaster_vol , kctl ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int build_output ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2009-07-06 12:55:46 +02:00
int i , err ;
2009-07-01 18:11:44 +02:00
for ( i = 0 ; i < cfg - > line_outs ; i + + ) {
2009-07-06 12:55:46 +02:00
err = add_output ( codec , get_dac ( codec , cfg - > line_out_pins [ i ] ) ,
i , cfg - > line_outs , cfg - > line_out_type ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
}
for ( i = 0 ; i < cfg - > hp_outs ; i + + ) {
2009-07-06 12:55:46 +02:00
err = add_output ( codec , get_dac ( codec , cfg - > hp_pins [ i ] ) ,
i , cfg - > hp_outs , AUTO_PIN_HP_OUT ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
}
for ( i = 0 ; i < cfg - > speaker_outs ; i + + ) {
2009-07-06 12:55:46 +02:00
err = add_output ( codec , get_dac ( codec , cfg - > speaker_pins [ i ] ) ,
i , cfg - > speaker_outs , AUTO_PIN_SPEAKER_OUT ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
}
return 0 ;
}
/*
*/
2011-05-02 11:36:09 +02:00
static const struct snd_kcontrol_new cs_capture_ctls [ ] = {
2009-07-01 18:11:44 +02:00
HDA_BIND_SW ( " Capture Switch " , 0 ) ,
HDA_BIND_VOL ( " Capture Volume " , 0 ) ,
} ;
2009-07-06 12:58:59 +02:00
static int change_cur_input ( struct hda_codec * codec , unsigned int idx ,
int force )
2009-07-01 18:11:44 +02:00
{
struct cs_spec * spec = codec - > spec ;
2009-07-06 12:58:59 +02:00
if ( spec - > cur_input = = idx & & ! force )
2009-07-01 18:11:44 +02:00
return 0 ;
if ( spec - > cur_adc & & spec - > cur_adc ! = spec - > adc_nid [ idx ] ) {
/* stream is running, let's swap the current ADC */
2010-08-13 11:56:53 +02:00
__snd_hda_codec_cleanup_stream ( codec , spec - > cur_adc , 1 ) ;
2009-07-01 18:11:44 +02:00
spec - > cur_adc = spec - > adc_nid [ idx ] ;
snd_hda_codec_setup_stream ( codec , spec - > cur_adc ,
spec - > cur_adc_stream_tag , 0 ,
spec - > cur_adc_format ) ;
}
spec - > cur_input = idx ;
2011-11-16 18:05:11 +01:00
cs_update_input_select ( codec ) ;
2009-07-01 18:11:44 +02:00
return 1 ;
}
static int cs_capture_source_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
2010-09-09 16:28:02 +02:00
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2009-07-01 18:11:44 +02:00
unsigned int idx ;
uinfo - > type = SNDRV_CTL_ELEM_TYPE_ENUMERATED ;
uinfo - > count = 1 ;
uinfo - > value . enumerated . items = spec - > num_inputs ;
if ( uinfo - > value . enumerated . item > = spec - > num_inputs )
uinfo - > value . enumerated . item = spec - > num_inputs - 1 ;
idx = spec - > input_idx [ uinfo - > value . enumerated . item ] ;
2010-09-09 16:28:02 +02:00
strcpy ( uinfo - > value . enumerated . name ,
hda_get_input_pin_label ( codec , cfg - > inputs [ idx ] . pin , 1 ) ) ;
2009-07-01 18:11:44 +02:00
return 0 ;
}
static int cs_capture_source_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
ucontrol - > value . enumerated . item [ 0 ] = spec - > capsrc_idx [ spec - > cur_input ] ;
return 0 ;
}
static int cs_capture_source_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
unsigned int idx = ucontrol - > value . enumerated . item [ 0 ] ;
if ( idx > = spec - > num_inputs )
return - EINVAL ;
idx = spec - > input_idx [ idx ] ;
2009-07-06 12:58:59 +02:00
return change_cur_input ( codec , idx , 0 ) ;
2009-07-01 18:11:44 +02:00
}
2011-05-02 11:36:09 +02:00
static const struct snd_kcontrol_new cs_capture_source = {
2009-07-01 18:11:44 +02:00
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Capture Source " ,
. access = SNDRV_CTL_ELEM_ACCESS_READWRITE ,
. info = cs_capture_source_info ,
. get = cs_capture_source_get ,
. put = cs_capture_source_put ,
} ;
2011-05-02 11:36:09 +02:00
static const struct hda_bind_ctls * make_bind_capture ( struct hda_codec * codec ,
2009-07-01 18:11:44 +02:00
struct hda_ctl_ops * ops )
{
struct cs_spec * spec = codec - > spec ;
struct hda_bind_ctls * bind ;
int i , n ;
bind = kzalloc ( sizeof ( * bind ) + sizeof ( long ) * ( spec - > num_inputs + 1 ) ,
GFP_KERNEL ) ;
if ( ! bind )
return NULL ;
bind - > ops = ops ;
n = 0 ;
for ( i = 0 ; i < AUTO_PIN_LAST ; i + + ) {
if ( ! spec - > adc_nid [ i ] )
continue ;
bind - > values [ n + + ] =
HDA_COMPOSE_AMP_VAL ( spec - > adc_nid [ i ] , 3 ,
spec - > adc_idx [ i ] , HDA_INPUT ) ;
}
return bind ;
}
2010-10-11 15:16:20 +02:00
/* add a (input-boost) volume control to the given input pin */
static int add_input_volume_control ( struct hda_codec * codec ,
struct auto_pin_cfg * cfg ,
int item )
{
hda_nid_t pin = cfg - > inputs [ item ] . pin ;
u32 caps ;
const char * label ;
struct snd_kcontrol * kctl ;
if ( ! ( get_wcaps ( codec , pin ) & AC_WCAP_IN_AMP ) )
return 0 ;
caps = query_amp_caps ( codec , pin , HDA_INPUT ) ;
caps = ( caps & AC_AMPCAP_NUM_STEPS ) > > AC_AMPCAP_NUM_STEPS_SHIFT ;
if ( caps < = 1 )
return 0 ;
label = hda_get_autocfg_input_label ( codec , cfg , item ) ;
return add_volume ( codec , label , 0 ,
HDA_COMPOSE_AMP_VAL ( pin , 3 , 0 , HDA_INPUT ) , 1 , & kctl ) ;
}
2009-07-01 18:11:44 +02:00
static int build_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
2009-07-06 12:55:46 +02:00
int i , err ;
2009-07-01 18:11:44 +02:00
if ( ! spec - > num_inputs )
return 0 ;
/* make bind-capture */
spec - > capture_bind [ 0 ] = make_bind_capture ( codec , & snd_hda_bind_sw ) ;
spec - > capture_bind [ 1 ] = make_bind_capture ( codec , & snd_hda_bind_vol ) ;
for ( i = 0 ; i < 2 ; i + + ) {
struct snd_kcontrol * kctl ;
2009-12-23 08:31:59 +01:00
int n ;
2009-07-01 18:11:44 +02:00
if ( ! spec - > capture_bind [ i ] )
return - ENOMEM ;
kctl = snd_ctl_new1 ( & cs_capture_ctls [ i ] , codec ) ;
if ( ! kctl )
return - ENOMEM ;
kctl - > private_value = ( long ) spec - > capture_bind [ i ] ;
2009-11-11 13:43:01 +01:00
err = snd_hda_ctl_add ( codec , 0 , kctl ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
2009-12-23 08:31:59 +01:00
for ( n = 0 ; n < AUTO_PIN_LAST ; n + + ) {
if ( ! spec - > adc_nid [ n ] )
continue ;
2010-05-02 17:32:10 -07:00
err = snd_hda_add_nid ( codec , kctl , 0 , spec - > adc_nid [ n ] ) ;
2009-12-23 08:31:59 +01:00
if ( err < 0 )
return err ;
}
2009-07-01 18:11:44 +02:00
}
if ( spec - > num_inputs > 1 & & ! spec - > mic_detect ) {
2009-11-11 13:43:01 +01:00
err = snd_hda_ctl_add ( codec , 0 ,
2009-07-01 18:11:44 +02:00
snd_ctl_new1 ( & cs_capture_source , codec ) ) ;
if ( err < 0 )
return err ;
}
2010-10-11 15:16:20 +02:00
for ( i = 0 ; i < spec - > num_inputs ; i + + ) {
err = add_input_volume_control ( codec , & spec - > autocfg , i ) ;
if ( err < 0 )
return err ;
}
2009-07-01 18:11:44 +02:00
return 0 ;
}
2009-07-06 12:55:46 +02:00
/*
*/
2009-07-01 18:11:44 +02:00
static int build_digital_output ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
int err ;
2009-07-09 11:45:59 +02:00
if ( ! spec - > multiout . dig_out_nid )
return 0 ;
2011-06-01 11:14:18 -06:00
err = snd_hda_create_spdif_out_ctls ( codec , spec - > multiout . dig_out_nid ,
spec - > multiout . dig_out_nid ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
err = snd_hda_create_spdif_share_sw ( codec , & spec - > multiout ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int build_digital_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
2009-07-06 12:55:46 +02:00
if ( spec - > dig_in )
return snd_hda_create_spdif_in_ctls ( codec , spec - > dig_in ) ;
return 0 ;
2009-07-01 18:11:44 +02:00
}
2009-07-06 12:55:46 +02:00
/*
* auto - mute and auto - mic switching
2011-07-22 16:41:00 -05:00
* CS421x auto - output redirecting
* HP / SPK / SPDIF
2009-07-06 12:55:46 +02:00
*/
2009-07-01 18:11:44 +02:00
static void cs_automute ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2011-05-17 10:00:16 +02:00
unsigned int hp_present ;
2011-07-22 16:41:00 -05:00
unsigned int spdif_present ;
2009-07-01 18:11:44 +02:00
hda_nid_t nid ;
int i ;
2011-07-22 16:41:00 -05:00
spdif_present = 0 ;
if ( cfg - > dig_outs ) {
nid = cfg - > dig_out_pins [ 0 ] ;
if ( is_jack_detectable ( codec , nid ) ) {
/*
TODO : SPDIF output redirect when SENSE_B is enabled .
Shared ( SENSE_A ) jack ( e . g HP / mini - TOSLINK )
assumed .
*/
if ( snd_hda_jack_detect ( codec , nid )
/* && spec->sense_b */ )
spdif_present = 1 ;
}
}
2009-07-01 18:11:44 +02:00
hp_present = 0 ;
for ( i = 0 ; i < cfg - > hp_outs ; i + + ) {
nid = cfg - > hp_pins [ i ] ;
2011-05-17 10:00:16 +02:00
if ( ! is_jack_detectable ( codec , nid ) )
2009-07-01 18:11:44 +02:00
continue ;
2009-11-18 12:38:02 +08:00
hp_present = snd_hda_jack_detect ( codec , nid ) ;
2009-07-01 18:11:44 +02:00
if ( hp_present )
break ;
}
2011-07-22 16:41:00 -05:00
/* mute speakers if spdif or hp jack is plugged in */
2009-07-01 18:11:44 +02:00
for ( i = 0 ; i < cfg - > speaker_outs ; i + + ) {
nid = cfg - > speaker_pins [ i ] ;
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL ,
hp_present ? 0 : PIN_OUT ) ;
2011-07-22 16:41:00 -05:00
/* detect on spdif is specific to CS421x */
if ( spec - > vendor_nid = = CS421X_VENDOR_NID ) {
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL ,
spdif_present ? 0 : PIN_OUT ) ;
}
2009-07-01 18:11:44 +02:00
}
2010-09-22 17:31:37 -04:00
if ( spec - > board_config = = CS420X_MBP53 | |
spec - > board_config = = CS420X_MBP55 | |
2009-12-22 07:59:37 +01:00
spec - > board_config = = CS420X_IMAC27 ) {
2009-07-30 14:44:27 +02:00
unsigned int gpio = hp_present ? 0x02 : 0x08 ;
snd_hda_codec_write ( codec , 0x01 , 0 ,
AC_VERB_SET_GPIO_DATA , gpio ) ;
}
2011-07-22 16:41:00 -05:00
/* specific to CS421x */
if ( spec - > vendor_nid = = CS421X_VENDOR_NID ) {
/* mute HPs if spdif jack (SENSE_B) is present */
for ( i = 0 ; i < cfg - > hp_outs ; i + + ) {
nid = cfg - > hp_pins [ i ] ;
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL ,
( spdif_present & & spec - > sense_b ) ? 0 : PIN_HP ) ;
}
/* SPDIF TX on/off */
if ( cfg - > dig_outs ) {
nid = cfg - > dig_out_pins [ 0 ] ;
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL ,
spdif_present ? PIN_OUT : 0 ) ;
}
/* Update board GPIOs if neccessary ... */
}
2009-07-01 18:11:44 +02:00
}
2011-07-22 16:41:00 -05:00
/*
* Auto - input redirect for CS421x
* Switch max 3 inputs of a single ADC ( nid 3 )
*/
2009-07-01 18:11:44 +02:00
static void cs_automic ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
hda_nid_t nid ;
2009-11-18 12:38:02 +08:00
unsigned int present ;
2011-07-22 16:41:00 -05:00
2010-08-30 13:05:30 +02:00
nid = cfg - > inputs [ spec - > automic_idx ] . pin ;
2009-11-18 12:38:02 +08:00
present = snd_hda_jack_detect ( codec , nid ) ;
2011-07-22 16:41:00 -05:00
/* specific to CS421x, single ADC */
if ( spec - > vendor_nid = = CS421X_VENDOR_NID ) {
if ( present ) {
spec - > last_input = spec - > cur_input ;
spec - > cur_input = spec - > automic_idx ;
} else {
spec - > cur_input = spec - > last_input ;
}
2011-11-16 18:05:11 +01:00
cs_update_input_select ( codec ) ;
2011-07-22 16:41:00 -05:00
} else {
if ( present )
change_cur_input ( codec , spec - > automic_idx , 0 ) ;
else
change_cur_input ( codec , ! spec - > automic_idx , 0 ) ;
}
2009-07-01 18:11:44 +02:00
}
/*
*/
static void init_output ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
int i ;
/* mute first */
for ( i = 0 ; i < spec - > multiout . num_dacs ; i + + )
snd_hda_codec_write ( codec , spec - > multiout . dac_nids [ i ] , 0 ,
AC_VERB_SET_AMP_GAIN_MUTE , AMP_OUT_MUTE ) ;
if ( spec - > multiout . hp_nid )
snd_hda_codec_write ( codec , spec - > multiout . hp_nid , 0 ,
AC_VERB_SET_AMP_GAIN_MUTE , AMP_OUT_MUTE ) ;
for ( i = 0 ; i < ARRAY_SIZE ( spec - > multiout . extra_out_nid ) ; i + + ) {
if ( ! spec - > multiout . extra_out_nid [ i ] )
break ;
snd_hda_codec_write ( codec , spec - > multiout . extra_out_nid [ i ] , 0 ,
AC_VERB_SET_AMP_GAIN_MUTE , AMP_OUT_MUTE ) ;
}
/* set appropriate pin controls */
for ( i = 0 ; i < cfg - > line_outs ; i + + )
snd_hda_codec_write ( codec , cfg - > line_out_pins [ i ] , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL , PIN_OUT ) ;
2011-07-22 16:41:00 -05:00
/* HP */
2009-07-01 18:11:44 +02:00
for ( i = 0 ; i < cfg - > hp_outs ; i + + ) {
hda_nid_t nid = cfg - > hp_pins [ i ] ;
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL , PIN_HP ) ;
if ( ! cfg - > speaker_outs )
continue ;
2011-07-22 16:41:00 -05:00
if ( get_wcaps ( codec , nid ) & AC_WCAP_UNSOL_CAP ) {
2009-07-01 18:11:44 +02:00
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_UNSOLICITED_ENABLE ,
AC_USRSP_EN | HP_EVENT ) ;
spec - > hp_detect = 1 ;
}
}
2011-07-22 16:41:00 -05:00
/* Speaker */
2009-07-01 18:11:44 +02:00
for ( i = 0 ; i < cfg - > speaker_outs ; i + + )
snd_hda_codec_write ( codec , cfg - > speaker_pins [ i ] , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL , PIN_OUT ) ;
2011-07-22 16:41:00 -05:00
/* SPDIF is enabled on presence detect for CS421x */
if ( spec - > hp_detect | | spec - > spdif_detect )
2009-07-01 18:11:44 +02:00
cs_automute ( codec ) ;
}
static void init_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
2009-07-06 13:00:57 +02:00
unsigned int coef ;
2009-07-01 18:11:44 +02:00
int i ;
2010-08-30 13:05:30 +02:00
for ( i = 0 ; i < cfg - > num_inputs ; i + + ) {
2009-07-01 18:11:44 +02:00
unsigned int ctl ;
2010-08-30 13:05:30 +02:00
hda_nid_t pin = cfg - > inputs [ i ] . pin ;
if ( ! spec - > adc_nid [ i ] )
2009-07-01 18:11:44 +02:00
continue ;
/* set appropriate pin control and mute first */
ctl = PIN_IN ;
2010-09-09 14:50:17 +02:00
if ( cfg - > inputs [ i ] . type = = AUTO_PIN_MIC ) {
2009-07-01 18:11:44 +02:00
unsigned int caps = snd_hda_query_pin_caps ( codec , pin ) ;
caps > > = AC_PINCAP_VREF_SHIFT ;
if ( caps & AC_PINCAP_VREF_80 )
ctl = PIN_VREF80 ;
}
snd_hda_codec_write ( codec , pin , 0 ,
AC_VERB_SET_PIN_WIDGET_CONTROL , ctl ) ;
snd_hda_codec_write ( codec , spec - > adc_nid [ i ] , 0 ,
AC_VERB_SET_AMP_GAIN_MUTE ,
AMP_IN_MUTE ( spec - > adc_idx [ i ] ) ) ;
if ( spec - > mic_detect & & spec - > automic_idx = = i )
snd_hda_codec_write ( codec , pin , 0 ,
AC_VERB_SET_UNSOLICITED_ENABLE ,
AC_USRSP_EN | MIC_EVENT ) ;
}
2011-07-22 16:41:00 -05:00
/* specific to CS421x */
if ( spec - > vendor_nid = = CS421X_VENDOR_NID ) {
if ( spec - > mic_detect )
cs_automic ( codec ) ;
else {
spec - > cur_adc = spec - > adc_nid [ spec - > cur_input ] ;
2011-11-16 18:05:11 +01:00
cs_update_input_select ( codec ) ;
2011-07-22 16:41:00 -05:00
}
} else {
change_cur_input ( codec , spec - > cur_input , 1 ) ;
if ( spec - > mic_detect )
cs_automic ( codec ) ;
coef = 0x000a ; /* ADC1/2 - Digital and Analog Soft Ramp */
if ( is_active_pin ( codec , CS_DMIC2_PIN_NID ) )
coef | = 0x0500 ; /* DMIC2 2 chan on, GPIO1 off */
if ( is_active_pin ( codec , CS_DMIC1_PIN_NID ) )
coef | = 0x1800 ; /* DMIC1 2 chan on, GPIO0 off
* No effect if SPDIF_OUT2 is
* selected in IDX_SPDIF_CTL .
*/
cs_vendor_coef_set ( codec , IDX_ADC_CFG , coef ) ;
}
2009-07-06 13:00:57 +02:00
}
2011-05-02 11:36:09 +02:00
static const struct hda_verb cs_coef_init_verbs [ ] = {
2009-07-06 13:00:57 +02:00
{ 0x11 , AC_VERB_SET_PROC_STATE , 1 } ,
{ 0x11 , AC_VERB_SET_COEF_INDEX , IDX_DAC_CFG } ,
{ 0x11 , AC_VERB_SET_PROC_COEF ,
( 0x002a /* DAC1/2/3 SZCMode Soft Ramp */
| 0x0040 /* Mute DACs on FIFO error */
| 0x1000 /* Enable DACs High Pass Filter */
| 0x0400 /* Disable Coefficient Auto increment */
) } ,
/* Beep */
{ 0x11 , AC_VERB_SET_COEF_INDEX , IDX_DAC_CFG } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x0007 } , /* Enable Beep thru DAC1/2/3 */
{ } /* terminator */
} ;
2010-09-07 14:36:22 -05:00
/* Errata: CS4207 rev C0/C1/C2 Silicon
*
* http : //www.cirrus.com/en/pubs/errata/ER880C3.pdf
*
* 6. At high temperature ( TA > + 85 ° C ) , the digital supply current ( IVD )
* may be excessive ( up to an additional 200 μ A ) , which is most easily
* observed while the part is being held in reset ( RESET # active low ) .
*
* Root Cause : At initial powerup of the device , the logic that drives
* the clock and write enable to the S / PDIF SRC RAMs is not properly
* initialized .
* Certain random patterns will cause a steady leakage current in those
* RAM cells . The issue will resolve once the SRCs are used ( turned on ) .
*
* Workaround : The following verb sequence briefly turns on the S / PDIF SRC
* blocks , which will alleviate the issue .
*/
2011-05-02 11:36:09 +02:00
static const struct hda_verb cs_errata_init_verbs [ ] = {
2010-09-07 14:36:22 -05:00
{ 0x01 , AC_VERB_SET_POWER_STATE , 0x00 } , /* AFG: D0 */
{ 0x11 , AC_VERB_SET_PROC_STATE , 0x01 } , /* VPW: processing on */
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0008 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x9999 } ,
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0017 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0xa412 } ,
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0001 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x0009 } ,
{ 0x07 , AC_VERB_SET_POWER_STATE , 0x00 } , /* S/PDIF Rx: D0 */
{ 0x08 , AC_VERB_SET_POWER_STATE , 0x00 } , /* S/PDIF Tx: D0 */
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0017 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x2412 } ,
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0008 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x0000 } ,
{ 0x11 , AC_VERB_SET_COEF_INDEX , 0x0001 } ,
{ 0x11 , AC_VERB_SET_PROC_COEF , 0x0008 } ,
{ 0x11 , AC_VERB_SET_PROC_STATE , 0x00 } ,
2011-03-03 14:54:19 +01:00
#if 0 /* Don't to set to D3 as we are in power-up sequence */
2010-09-07 14:36:22 -05:00
{ 0x07 , AC_VERB_SET_POWER_STATE , 0x03 } , /* S/PDIF Rx: D3 */
{ 0x08 , AC_VERB_SET_POWER_STATE , 0x03 } , /* S/PDIF Tx: D3 */
/*{0x01, AC_VERB_SET_POWER_STATE, 0x03},*/ /* AFG: D3 This is already handled */
2011-03-03 14:54:19 +01:00
# endif
2010-09-07 14:36:22 -05:00
{ } /* terminator */
} ;
2009-07-06 13:00:57 +02:00
/* SPDIF setup */
static void init_digital ( struct hda_codec * codec )
{
unsigned int coef ;
coef = 0x0002 ; /* SRC_MUTE soft-mute on SPDIF (if no lock) */
coef | = 0x0008 ; /* Replace with mute on error */
if ( is_active_pin ( codec , CS_DIG_OUT2_PIN_NID ) )
coef | = 0x4000 ; /* RX to TX1 or TX2 Loopthru / SPDIF2
* SPDIF_OUT2 is shared with GPIO1 and
* DMIC_SDA2 .
*/
cs_vendor_coef_set ( codec , IDX_SPDIF_CTL , coef ) ;
2009-07-01 18:11:44 +02:00
}
static int cs_init ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
2010-09-07 14:36:22 -05:00
/* init_verb sequence for C0/C1/C2 errata*/
snd_hda_sequence_write ( codec , cs_errata_init_verbs ) ;
2009-07-06 13:00:57 +02:00
snd_hda_sequence_write ( codec , cs_coef_init_verbs ) ;
2009-07-07 09:04:26 +02:00
if ( spec - > gpio_mask ) {
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_MASK ,
spec - > gpio_mask ) ;
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_DIRECTION ,
spec - > gpio_dir ) ;
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_DATA ,
spec - > gpio_data ) ;
}
2009-07-01 18:11:44 +02:00
init_output ( codec ) ;
init_input ( codec ) ;
2009-07-06 13:00:57 +02:00
init_digital ( codec ) ;
2009-07-01 18:11:44 +02:00
return 0 ;
}
static int cs_build_controls ( struct hda_codec * codec )
{
int err ;
err = build_output ( codec ) ;
if ( err < 0 )
return err ;
err = build_input ( codec ) ;
if ( err < 0 )
return err ;
err = build_digital_output ( codec ) ;
if ( err < 0 )
return err ;
err = build_digital_input ( codec ) ;
if ( err < 0 )
return err ;
return cs_init ( codec ) ;
}
static void cs_free ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
kfree ( spec - > capture_bind [ 0 ] ) ;
kfree ( spec - > capture_bind [ 1 ] ) ;
kfree ( codec - > spec ) ;
}
static void cs_unsol_event ( struct hda_codec * codec , unsigned int res )
{
switch ( ( res > > 26 ) & 0x7f ) {
case HP_EVENT :
cs_automute ( codec ) ;
break ;
case MIC_EVENT :
cs_automic ( codec ) ;
break ;
}
}
2011-05-02 11:36:09 +02:00
static const struct hda_codec_ops cs_patch_ops = {
2009-07-01 18:11:44 +02:00
. build_controls = cs_build_controls ,
. build_pcms = cs_build_pcms ,
. init = cs_init ,
. free = cs_free ,
. unsol_event = cs_unsol_event ,
} ;
static int cs_parse_auto_config ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
int err ;
err = snd_hda_parse_pin_def_config ( codec , & spec - > autocfg , NULL ) ;
2009-07-07 09:04:26 +02:00
if ( err < 0 )
return err ;
err = parse_output ( codec ) ;
if ( err < 0 )
return err ;
err = parse_input ( codec ) ;
if ( err < 0 )
return err ;
err = parse_digital_output ( codec ) ;
if ( err < 0 )
return err ;
err = parse_digital_input ( codec ) ;
2009-07-01 18:11:44 +02:00
if ( err < 0 )
return err ;
return 0 ;
}
2011-01-17 11:29:34 +01:00
static const char * const cs420x_models [ CS420X_MODELS ] = {
2010-09-22 17:31:37 -04:00
[ CS420X_MBP53 ] = " mbp53 " ,
2009-07-06 15:15:22 +02:00
[ CS420X_MBP55 ] = " mbp55 " ,
2009-12-22 07:59:37 +01:00
[ CS420X_IMAC27 ] = " imac27 " ,
2009-07-06 15:15:22 +02:00
[ CS420X_AUTO ] = " auto " ,
} ;
2011-05-02 11:36:09 +02:00
static const struct snd_pci_quirk cs420x_cfg_tbl [ ] = {
2010-09-22 17:31:37 -04:00
SND_PCI_QUIRK ( 0x10de , 0x0ac0 , " MacBookPro 5,3 " , CS420X_MBP53 ) ,
2010-11-03 08:14:10 +01:00
SND_PCI_QUIRK ( 0x10de , 0x0d94 , " MacBookAir 3,1(2) " , CS420X_MBP55 ) ,
2009-07-06 15:15:22 +02:00
SND_PCI_QUIRK ( 0x10de , 0xcb79 , " MacBookPro 5,5 " , CS420X_MBP55 ) ,
2010-10-11 14:46:35 +02:00
SND_PCI_QUIRK ( 0x10de , 0xcb89 , " MacBookPro 7,1 " , CS420X_MBP55 ) ,
2009-12-22 07:59:37 +01:00
SND_PCI_QUIRK ( 0x8086 , 0x7270 , " IMac 27 Inch " , CS420X_IMAC27 ) ,
2009-07-06 15:15:22 +02:00
{ } /* terminator */
} ;
struct cs_pincfg {
hda_nid_t nid ;
u32 val ;
} ;
2011-05-02 11:36:09 +02:00
static const struct cs_pincfg mbp53_pincfgs [ ] = {
2010-09-22 17:31:37 -04:00
{ 0x09 , 0x012b4050 } ,
{ 0x0a , 0x90100141 } ,
{ 0x0b , 0x90100140 } ,
{ 0x0c , 0x018b3020 } ,
{ 0x0d , 0x90a00110 } ,
{ 0x0e , 0x400000f0 } ,
{ 0x0f , 0x01cbe030 } ,
{ 0x10 , 0x014be060 } ,
{ 0x12 , 0x400000f0 } ,
{ 0x15 , 0x400000f0 } ,
{ } /* terminator */
} ;
2011-05-02 11:36:09 +02:00
static const struct cs_pincfg mbp55_pincfgs [ ] = {
2009-07-06 15:15:22 +02:00
{ 0x09 , 0x012b4030 } ,
{ 0x0a , 0x90100121 } ,
{ 0x0b , 0x90100120 } ,
{ 0x0c , 0x400000f0 } ,
{ 0x0d , 0x90a00110 } ,
{ 0x0e , 0x400000f0 } ,
{ 0x0f , 0x400000f0 } ,
{ 0x10 , 0x014be040 } ,
{ 0x12 , 0x400000f0 } ,
{ 0x15 , 0x400000f0 } ,
{ } /* terminator */
} ;
2011-05-02 11:36:09 +02:00
static const struct cs_pincfg imac27_pincfgs [ ] = {
2009-12-22 07:59:37 +01:00
{ 0x09 , 0x012b4050 } ,
{ 0x0a , 0x90100140 } ,
{ 0x0b , 0x90100142 } ,
{ 0x0c , 0x018b3020 } ,
{ 0x0d , 0x90a00110 } ,
{ 0x0e , 0x400000f0 } ,
{ 0x0f , 0x01cbe030 } ,
{ 0x10 , 0x014be060 } ,
{ 0x12 , 0x01ab9070 } ,
{ 0x15 , 0x400000f0 } ,
{ } /* terminator */
} ;
2011-05-02 11:36:09 +02:00
static const struct cs_pincfg * cs_pincfgs [ CS420X_MODELS ] = {
2010-09-22 17:31:37 -04:00
[ CS420X_MBP53 ] = mbp53_pincfgs ,
2009-07-06 15:15:22 +02:00
[ CS420X_MBP55 ] = mbp55_pincfgs ,
2009-12-22 07:59:37 +01:00
[ CS420X_IMAC27 ] = imac27_pincfgs ,
2009-07-06 15:15:22 +02:00
} ;
2011-07-22 16:41:00 -05:00
static void fix_pincfg ( struct hda_codec * codec , int model ,
const struct cs_pincfg * * pin_configs )
2009-07-06 15:15:22 +02:00
{
2011-07-22 16:41:00 -05:00
const struct cs_pincfg * cfg = pin_configs [ model ] ;
2009-07-06 15:15:22 +02:00
if ( ! cfg )
return ;
for ( ; cfg - > nid ; cfg + + )
snd_hda_codec_set_pincfg ( codec , cfg - > nid , cfg - > val ) ;
}
2009-07-01 18:11:44 +02:00
static int patch_cs420x ( struct hda_codec * codec )
{
struct cs_spec * spec ;
int err ;
spec = kzalloc ( sizeof ( * spec ) , GFP_KERNEL ) ;
if ( ! spec )
return - ENOMEM ;
codec - > spec = spec ;
2011-07-22 16:41:00 -05:00
spec - > vendor_nid = CS420X_VENDOR_NID ;
2009-07-06 15:15:22 +02:00
spec - > board_config =
snd_hda_check_board_config ( codec , CS420X_MODELS ,
cs420x_models , cs420x_cfg_tbl ) ;
if ( spec - > board_config > = 0 )
2011-07-22 16:41:00 -05:00
fix_pincfg ( codec , spec - > board_config , cs_pincfgs ) ;
2009-07-06 15:15:22 +02:00
2009-07-07 09:04:26 +02:00
switch ( spec - > board_config ) {
2009-12-22 07:59:37 +01:00
case CS420X_IMAC27 :
2010-09-22 17:31:37 -04:00
case CS420X_MBP53 :
2009-07-07 09:04:26 +02:00
case CS420X_MBP55 :
2009-07-30 14:44:27 +02:00
/* GPIO1 = headphones */
/* GPIO3 = speakers */
spec - > gpio_mask = 0x0a ;
spec - > gpio_dir = 0x0a ;
2009-07-07 09:04:26 +02:00
break ;
}
2009-07-01 18:11:44 +02:00
2009-07-07 09:04:26 +02:00
err = cs_parse_auto_config ( codec ) ;
2009-07-06 12:55:46 +02:00
if ( err < 0 )
goto error ;
2009-07-01 18:11:44 +02:00
codec - > patch_ops = cs_patch_ops ;
return 0 ;
error :
kfree ( codec - > spec ) ;
codec - > spec = NULL ;
return err ;
}
2011-07-22 16:41:00 -05:00
/*
* Cirrus Logic CS4210
*
* 1 DAC = > HP ( sense ) / Speakers ,
* 1 ADC < = LineIn ( sense ) / MicIn / DMicIn ,
* 1 SPDIF OUT = > SPDIF Trasmitter ( sense )
*/
/* CS4210 board names */
static const char * cs421x_models [ CS421X_MODELS ] = {
[ CS421X_CDB4210 ] = " cdb4210 " ,
} ;
static const struct snd_pci_quirk cs421x_cfg_tbl [ ] = {
/* Test Intel board + CDB2410 */
SND_PCI_QUIRK ( 0x8086 , 0x5001 , " DP45SG/CDB4210 " , CS421X_CDB4210 ) ,
{ } /* terminator */
} ;
/* CS4210 board pinconfigs */
/* Default CS4210 (CDB4210)*/
static const struct cs_pincfg cdb4210_pincfgs [ ] = {
{ 0x05 , 0x0321401f } ,
{ 0x06 , 0x90170010 } ,
{ 0x07 , 0x03813031 } ,
{ 0x08 , 0xb7a70037 } ,
{ 0x09 , 0xb7a6003e } ,
{ 0x0a , 0x034510f0 } ,
{ } /* terminator */
} ;
static const struct cs_pincfg * cs421x_pincfgs [ CS421X_MODELS ] = {
[ CS421X_CDB4210 ] = cdb4210_pincfgs ,
} ;
static const struct hda_verb cs421x_coef_init_verbs [ ] = {
{ 0x0B , AC_VERB_SET_PROC_STATE , 1 } ,
{ 0x0B , AC_VERB_SET_COEF_INDEX , CS421X_IDX_DEV_CFG } ,
/*
Disable Coefficient Index Auto - Increment ( DAI ) = 1 ,
PDREF = 0
*/
{ 0x0B , AC_VERB_SET_PROC_COEF , 0x0001 } ,
{ 0x0B , AC_VERB_SET_COEF_INDEX , CS421X_IDX_ADC_CFG } ,
/* ADC SZCMode = Digital Soft Ramp */
{ 0x0B , AC_VERB_SET_PROC_COEF , 0x0002 } ,
{ 0x0B , AC_VERB_SET_COEF_INDEX , CS421X_IDX_DAC_CFG } ,
{ 0x0B , AC_VERB_SET_PROC_COEF ,
( 0x0002 /* DAC SZCMode = Digital Soft Ramp */
| 0x0004 /* Mute DAC on FIFO error */
| 0x0008 /* Enable DAC High Pass Filter */
) } ,
{ } /* terminator */
} ;
/* Errata: CS4210 rev A1 Silicon
*
* http : //www.cirrus.com/en/pubs/errata/
*
* Description :
* 1. Performance degredation is present in the ADC .
* 2. Speaker output is not completely muted upon HP detect .
* 3. Noise is present when clipping occurs on the amplified
* speaker outputs .
*
* Workaround :
* The following verb sequence written to the registers during
* initialization will correct the issues listed above .
*/
static const struct hda_verb cs421x_coef_init_verbs_A1_silicon_fixes [ ] = {
{ 0x0B , AC_VERB_SET_PROC_STATE , 0x01 } , /* VPW: processing on */
{ 0x0B , AC_VERB_SET_COEF_INDEX , 0x0006 } ,
{ 0x0B , AC_VERB_SET_PROC_COEF , 0x9999 } , /* Test mode: on */
{ 0x0B , AC_VERB_SET_COEF_INDEX , 0x000A } ,
{ 0x0B , AC_VERB_SET_PROC_COEF , 0x14CB } , /* Chop double */
{ 0x0B , AC_VERB_SET_COEF_INDEX , 0x0011 } ,
{ 0x0B , AC_VERB_SET_PROC_COEF , 0xA2D0 } , /* Increase ADC current */
{ 0x0B , AC_VERB_SET_COEF_INDEX , 0x001A } ,
{ 0x0B , AC_VERB_SET_PROC_COEF , 0x02A9 } , /* Mute speaker */
{ 0x0B , AC_VERB_SET_COEF_INDEX , 0x001B } ,
{ 0x0B , AC_VERB_SET_PROC_COEF , 0 X1006 } , /* Remove noise */
{ } /* terminator */
} ;
/* Speaker Amp Gain is controlled by the vendor widget's coef 4 */
static const DECLARE_TLV_DB_SCALE ( cs421x_speaker_boost_db_scale , 900 , 300 , 0 ) ;
static int cs421x_boost_vol_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
uinfo - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
uinfo - > count = 1 ;
uinfo - > value . integer . min = 0 ;
uinfo - > value . integer . max = 3 ;
return 0 ;
}
static int cs421x_boost_vol_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
ucontrol - > value . integer . value [ 0 ] =
cs_vendor_coef_get ( codec , CS421X_IDX_SPK_CTL ) & 0x0003 ;
return 0 ;
}
static int cs421x_boost_vol_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
unsigned int vol = ucontrol - > value . integer . value [ 0 ] ;
unsigned int coef =
cs_vendor_coef_get ( codec , CS421X_IDX_SPK_CTL ) ;
unsigned int original_coef = coef ;
coef & = ~ 0x0003 ;
coef | = ( vol & 0x0003 ) ;
if ( original_coef = = coef )
return 0 ;
else {
cs_vendor_coef_set ( codec , CS421X_IDX_SPK_CTL , coef ) ;
return 1 ;
}
}
static const struct snd_kcontrol_new cs421x_speaker_bost_ctl = {
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. access = ( SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ ) ,
. name = " Speaker Boost Playback Volume " ,
. info = cs421x_boost_vol_info ,
. get = cs421x_boost_vol_get ,
. put = cs421x_boost_vol_put ,
. tlv = { . p = cs421x_speaker_boost_db_scale } ,
} ;
static void cs421x_pinmux_init ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
unsigned int def_conf , coef ;
/* GPIO, DMIC_SCL, DMIC_SDA and SENSE_B are multiplexed */
coef = cs_vendor_coef_get ( codec , CS421X_IDX_DEV_CFG ) ;
if ( spec - > gpio_mask )
coef | = 0x0008 ; /* B1,B2 are GPIOs */
else
coef & = ~ 0x0008 ;
if ( spec - > sense_b )
coef | = 0x0010 ; /* B2 is SENSE_B, not inverted */
else
coef & = ~ 0x0010 ;
cs_vendor_coef_set ( codec , CS421X_IDX_DEV_CFG , coef ) ;
if ( ( spec - > gpio_mask | | spec - > sense_b ) & &
is_active_pin ( codec , CS421X_DMIC_PIN_NID ) ) {
/*
GPIO or SENSE_B forced - disconnect the DMIC pin .
*/
def_conf = snd_hda_codec_get_pincfg ( codec , CS421X_DMIC_PIN_NID ) ;
def_conf & = ~ AC_DEFCFG_PORT_CONN ;
def_conf | = ( AC_JACK_PORT_NONE < < AC_DEFCFG_PORT_CONN_SHIFT ) ;
snd_hda_codec_set_pincfg ( codec , CS421X_DMIC_PIN_NID , def_conf ) ;
}
}
static void init_cs421x_digital ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
int i ;
for ( i = 0 ; i < cfg - > dig_outs ; i + + ) {
hda_nid_t nid = cfg - > dig_out_pins [ i ] ;
if ( ! cfg - > speaker_outs )
continue ;
if ( get_wcaps ( codec , nid ) & AC_WCAP_UNSOL_CAP ) {
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_UNSOLICITED_ENABLE ,
AC_USRSP_EN | SPDIF_EVENT ) ;
spec - > spdif_detect = 1 ;
}
}
}
static int cs421x_init ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
snd_hda_sequence_write ( codec , cs421x_coef_init_verbs ) ;
snd_hda_sequence_write ( codec , cs421x_coef_init_verbs_A1_silicon_fixes ) ;
cs421x_pinmux_init ( codec ) ;
if ( spec - > gpio_mask ) {
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_MASK ,
spec - > gpio_mask ) ;
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_DIRECTION ,
spec - > gpio_dir ) ;
snd_hda_codec_write ( codec , 0x01 , 0 , AC_VERB_SET_GPIO_DATA ,
spec - > gpio_data ) ;
}
init_output ( codec ) ;
init_input ( codec ) ;
init_cs421x_digital ( codec ) ;
return 0 ;
}
/*
* CS4210 Input MUX ( 1 ADC )
*/
static int cs421x_mux_enum_info ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
return snd_hda_input_mux_info ( & spec - > input_mux , uinfo ) ;
}
static int cs421x_mux_enum_get ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
ucontrol - > value . enumerated . item [ 0 ] = spec - > cur_input ;
return 0 ;
}
static int cs421x_mux_enum_put ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct hda_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct cs_spec * spec = codec - > spec ;
return snd_hda_input_mux_put ( codec , & spec - > input_mux , ucontrol ,
spec - > adc_nid [ 0 ] , & spec - > cur_input ) ;
}
static struct snd_kcontrol_new cs421x_capture_source = {
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Capture Source " ,
. access = SNDRV_CTL_ELEM_ACCESS_READWRITE ,
. info = cs421x_mux_enum_info ,
. get = cs421x_mux_enum_get ,
. put = cs421x_mux_enum_put ,
} ;
static int cs421x_add_input_volume_control ( struct hda_codec * codec , int item )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
const struct hda_input_mux * imux = & spec - > input_mux ;
hda_nid_t pin = cfg - > inputs [ item ] . pin ;
struct snd_kcontrol * kctl ;
u32 caps ;
if ( ! ( get_wcaps ( codec , pin ) & AC_WCAP_IN_AMP ) )
return 0 ;
caps = query_amp_caps ( codec , pin , HDA_INPUT ) ;
caps = ( caps & AC_AMPCAP_NUM_STEPS ) > > AC_AMPCAP_NUM_STEPS_SHIFT ;
if ( caps < = 1 )
return 0 ;
return add_volume ( codec , imux - > items [ item ] . label , 0 ,
HDA_COMPOSE_AMP_VAL ( pin , 3 , 0 , HDA_INPUT ) , 1 , & kctl ) ;
}
/* add a (input-boost) volume control to the given input pin */
static int build_cs421x_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
struct hda_input_mux * imux = & spec - > input_mux ;
int i , err , type_idx ;
const char * label ;
if ( ! spec - > num_inputs )
return 0 ;
/* make bind-capture */
spec - > capture_bind [ 0 ] = make_bind_capture ( codec , & snd_hda_bind_sw ) ;
spec - > capture_bind [ 1 ] = make_bind_capture ( codec , & snd_hda_bind_vol ) ;
for ( i = 0 ; i < 2 ; i + + ) {
struct snd_kcontrol * kctl ;
int n ;
if ( ! spec - > capture_bind [ i ] )
return - ENOMEM ;
kctl = snd_ctl_new1 ( & cs_capture_ctls [ i ] , codec ) ;
if ( ! kctl )
return - ENOMEM ;
kctl - > private_value = ( long ) spec - > capture_bind [ i ] ;
err = snd_hda_ctl_add ( codec , 0 , kctl ) ;
if ( err < 0 )
return err ;
for ( n = 0 ; n < AUTO_PIN_LAST ; n + + ) {
if ( ! spec - > adc_nid [ n ] )
continue ;
err = snd_hda_add_nid ( codec , kctl , 0 , spec - > adc_nid [ n ] ) ;
if ( err < 0 )
return err ;
}
}
/* Add Input MUX Items + Capture Volume/Switch */
for ( i = 0 ; i < spec - > num_inputs ; i + + ) {
label = hda_get_autocfg_input_label ( codec , cfg , i ) ;
snd_hda_add_imux_item ( imux , label , spec - > adc_idx [ i ] , & type_idx ) ;
err = cs421x_add_input_volume_control ( codec , i ) ;
if ( err < 0 )
return err ;
}
/*
Add ' Capture Source ' Switch if
* 2 inputs and no mic detec
* 3 inputs
*/
if ( ( spec - > num_inputs = = 2 & & ! spec - > mic_detect ) | |
( spec - > num_inputs = = 3 ) ) {
err = snd_hda_ctl_add ( codec , spec - > adc_nid [ 0 ] ,
snd_ctl_new1 ( & cs421x_capture_source , codec ) ) ;
if ( err < 0 )
return err ;
}
return 0 ;
}
/* Single DAC (Mute/Gain) */
static int build_cs421x_output ( struct hda_codec * codec )
{
hda_nid_t dac = CS4210_DAC_NID ;
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
struct snd_kcontrol * kctl ;
int err ;
char * name = " HP/Speakers " ;
fix_volume_caps ( codec , dac ) ;
if ( ! spec - > vmaster_sw ) {
err = add_vmaster ( codec , dac ) ;
if ( err < 0 )
return err ;
}
err = add_mute ( codec , name , 0 ,
HDA_COMPOSE_AMP_VAL ( dac , 3 , 0 , HDA_OUTPUT ) , 0 , & kctl ) ;
if ( err < 0 )
return err ;
err = snd_ctl_add_slave ( spec - > vmaster_sw , kctl ) ;
if ( err < 0 )
return err ;
err = add_volume ( codec , name , 0 ,
HDA_COMPOSE_AMP_VAL ( dac , 3 , 0 , HDA_OUTPUT ) , 0 , & kctl ) ;
if ( err < 0 )
return err ;
err = snd_ctl_add_slave ( spec - > vmaster_vol , kctl ) ;
if ( err < 0 )
return err ;
if ( cfg - > speaker_outs ) {
err = snd_hda_ctl_add ( codec , 0 ,
snd_ctl_new1 ( & cs421x_speaker_bost_ctl , codec ) ) ;
if ( err < 0 )
return err ;
}
return err ;
}
static int cs421x_build_controls ( struct hda_codec * codec )
{
int err ;
err = build_cs421x_output ( codec ) ;
if ( err < 0 )
return err ;
err = build_cs421x_input ( codec ) ;
if ( err < 0 )
return err ;
err = build_digital_output ( codec ) ;
if ( err < 0 )
return err ;
return cs421x_init ( codec ) ;
}
static void cs421x_unsol_event ( struct hda_codec * codec , unsigned int res )
{
switch ( ( res > > 26 ) & 0x3f ) {
case HP_EVENT :
case SPDIF_EVENT :
cs_automute ( codec ) ;
break ;
case MIC_EVENT :
cs_automic ( codec ) ;
break ;
}
}
static int parse_cs421x_input ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
struct auto_pin_cfg * cfg = & spec - > autocfg ;
int i ;
for ( i = 0 ; i < cfg - > num_inputs ; i + + ) {
hda_nid_t pin = cfg - > inputs [ i ] . pin ;
spec - > adc_nid [ i ] = get_adc ( codec , pin , & spec - > adc_idx [ i ] ) ;
spec - > cur_input = spec - > last_input = i ;
spec - > num_inputs + + ;
/* check whether the automatic mic switch is available */
if ( is_ext_mic ( codec , i ) & & cfg - > num_inputs > = 2 ) {
spec - > mic_detect = 1 ;
spec - > automic_idx = i ;
}
}
return 0 ;
}
static int cs421x_parse_auto_config ( struct hda_codec * codec )
{
struct cs_spec * spec = codec - > spec ;
int err ;
err = snd_hda_parse_pin_def_config ( codec , & spec - > autocfg , NULL ) ;
if ( err < 0 )
return err ;
err = parse_output ( codec ) ;
if ( err < 0 )
return err ;
err = parse_cs421x_input ( codec ) ;
if ( err < 0 )
return err ;
err = parse_digital_output ( codec ) ;
if ( err < 0 )
return err ;
return 0 ;
}
# ifdef CONFIG_PM
/*
Manage PDREF , when transitioning to D3hot
( DAC , ADC ) - > D3 , PDREF = 1 , AFG - > D3
*/
static int cs421x_suspend ( struct hda_codec * codec , pm_message_t state )
{
unsigned int coef ;
snd_hda_shutup_pins ( codec ) ;
snd_hda_codec_write ( codec , CS4210_DAC_NID , 0 ,
AC_VERB_SET_POWER_STATE , AC_PWRST_D3 ) ;
snd_hda_codec_write ( codec , CS4210_ADC_NID , 0 ,
AC_VERB_SET_POWER_STATE , AC_PWRST_D3 ) ;
coef = cs_vendor_coef_get ( codec , CS421X_IDX_DEV_CFG ) ;
coef | = 0x0004 ; /* PDREF */
cs_vendor_coef_set ( codec , CS421X_IDX_DEV_CFG , coef ) ;
return 0 ;
}
# endif
static struct hda_codec_ops cs4210_patch_ops = {
. build_controls = cs421x_build_controls ,
. build_pcms = cs_build_pcms ,
. init = cs421x_init ,
. free = cs_free ,
. unsol_event = cs421x_unsol_event ,
# ifdef CONFIG_PM
. suspend = cs421x_suspend ,
# endif
} ;
static int patch_cs421x ( struct hda_codec * codec )
{
struct cs_spec * spec ;
int err ;
spec = kzalloc ( sizeof ( * spec ) , GFP_KERNEL ) ;
if ( ! spec )
return - ENOMEM ;
codec - > spec = spec ;
spec - > vendor_nid = CS421X_VENDOR_NID ;
spec - > board_config =
snd_hda_check_board_config ( codec , CS421X_MODELS ,
cs421x_models , cs421x_cfg_tbl ) ;
if ( spec - > board_config > = 0 )
fix_pincfg ( codec , spec - > board_config , cs421x_pincfgs ) ;
/*
Setup GPIO / SENSE for each board ( if used )
*/
switch ( spec - > board_config ) {
case CS421X_CDB4210 :
snd_printd ( " CS4210 board: %s \n " ,
cs421x_models [ spec - > board_config ] ) ;
/* spec->gpio_mask = 3;
spec - > gpio_dir = 3 ;
spec - > gpio_data = 3 ;
*/
spec - > sense_b = 1 ;
break ;
}
/*
Update the GPIO / DMIC / SENSE_B pinmux before the configuration
is auto - parsed . If GPIO or SENSE_B is forced , DMIC input
is disabled .
*/
cs421x_pinmux_init ( codec ) ;
err = cs421x_parse_auto_config ( codec ) ;
if ( err < 0 )
goto error ;
codec - > patch_ops = cs4210_patch_ops ;
return 0 ;
error :
kfree ( codec - > spec ) ;
codec - > spec = NULL ;
return err ;
}
2009-07-01 18:11:44 +02:00
/*
* patch entries
*/
2011-05-02 11:36:09 +02:00
static const struct hda_codec_preset snd_hda_preset_cirrus [ ] = {
2009-07-01 18:11:44 +02:00
{ . id = 0x10134206 , . name = " CS4206 " , . patch = patch_cs420x } ,
{ . id = 0x10134207 , . name = " CS4207 " , . patch = patch_cs420x } ,
2011-07-22 16:41:00 -05:00
{ . id = 0x10134210 , . name = " CS4210 " , . patch = patch_cs421x } ,
2009-07-01 18:11:44 +02:00
{ } /* terminator */
} ;
MODULE_ALIAS ( " snd-hda-codec-id:10134206 " ) ;
MODULE_ALIAS ( " snd-hda-codec-id:10134207 " ) ;
2011-07-22 16:41:00 -05:00
MODULE_ALIAS ( " snd-hda-codec-id:10134210 " ) ;
2009-07-01 18:11:44 +02:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Cirrus Logic HD-audio codec " ) ;
static struct hda_codec_preset_list cirrus_list = {
. preset = snd_hda_preset_cirrus ,
. owner = THIS_MODULE ,
} ;
static int __init patch_cirrus_init ( void )
{
return snd_hda_add_codec_preset ( & cirrus_list ) ;
}
static void __exit patch_cirrus_exit ( void )
{
snd_hda_delete_codec_preset ( & cirrus_list ) ;
}
module_init ( patch_cirrus_init )
module_exit ( patch_cirrus_exit )