2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* Universal Interface for Intel High Definition Audio Codec
|
|
|
|
*
|
2009-10-10 19:08:52 +08:00
|
|
|
* HD audio interface patch for VIA VT17xx/VT18xx/VT20xx codec
|
2006-11-29 22:29:40 +08:00
|
|
|
*
|
2009-10-10 19:08:52 +08:00
|
|
|
* (C) 2006-2009 VIA Technology, Inc.
|
|
|
|
* (C) 2006-2008 Takashi Iwai <tiwai@suse.de>
|
2006-11-29 22:29:40 +08:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * Release History * * * * * * * * * * * * * * * * */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* */
|
2006-11-29 22:29:40 +08:00
|
|
|
/* 2006-03-03 Lydia Wang Create the basic patch to support VT1708 codec */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* 2006-03-14 Lydia Wang Modify hard code for some pin widget nid */
|
|
|
|
/* 2006-08-02 Lydia Wang Add support to VT1709 codec */
|
2006-11-29 22:29:40 +08:00
|
|
|
/* 2006-09-08 Lydia Wang Fix internal loopback recording source select bug */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* 2007-09-12 Lydia Wang Add EAPD enable during driver initialization */
|
|
|
|
/* 2007-09-17 Lydia Wang Add VT1708B codec support */
|
2008-09-09 15:50:37 +08:00
|
|
|
/* 2007-11-14 Lydia Wang Add VT1708A codec HP and CD pin connect config */
|
2008-09-09 15:53:36 +08:00
|
|
|
/* 2008-02-03 Lydia Wang Fix Rear channels and Back channels inverse issue */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* 2008-03-06 Lydia Wang Add VT1702 codec and VT1708S codec support */
|
|
|
|
/* 2008-04-09 Lydia Wang Add mute front speaker when HP plugin */
|
|
|
|
/* 2008-04-09 Lydia Wang Add Independent HP feature */
|
2008-09-09 16:02:09 +08:00
|
|
|
/* 2008-05-28 Lydia Wang Add second S/PDIF Out support for VT1702 */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* 2008-09-15 Logan Li Add VT1708S Mic Boost workaround/backdoor */
|
2009-10-10 19:08:52 +08:00
|
|
|
/* 2009-02-16 Logan Li Add support for VT1718S */
|
|
|
|
/* 2009-03-13 Logan Li Add support for VT1716S */
|
|
|
|
/* 2009-04-14 Lydai Wang Add support for VT1828S and VT2020 */
|
|
|
|
/* 2009-07-08 Lydia Wang Add support for VT2002P */
|
|
|
|
/* 2009-07-21 Lydia Wang Add support for VT1812 */
|
2009-10-20 13:18:04 +08:00
|
|
|
/* 2009-09-19 Lydia Wang Add support for VT1818S */
|
2009-10-10 19:08:55 +08:00
|
|
|
/* */
|
2006-11-29 22:29:40 +08:00
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <sound/core.h>
|
2008-09-09 15:58:27 +08:00
|
|
|
#include <sound/asoundef.h>
|
2006-11-29 22:29:40 +08:00
|
|
|
#include "hda_codec.h"
|
|
|
|
#include "hda_local.h"
|
|
|
|
|
|
|
|
/* Pin Widget NID */
|
2008-09-09 15:56:01 +08:00
|
|
|
#define VT1708_HP_PIN_NID 0x20
|
|
|
|
#define VT1708_CD_PIN_NID 0x24
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2008-09-15 22:43:23 +08:00
|
|
|
enum VIA_HDA_CODEC {
|
|
|
|
UNKNOWN = -1,
|
|
|
|
VT1708,
|
|
|
|
VT1709_10CH,
|
|
|
|
VT1709_6CH,
|
|
|
|
VT1708B_8CH,
|
|
|
|
VT1708B_4CH,
|
|
|
|
VT1708S,
|
2009-10-10 19:07:29 +08:00
|
|
|
VT1708BCE,
|
2008-09-15 22:43:23 +08:00
|
|
|
VT1702,
|
2009-10-10 19:08:34 +08:00
|
|
|
VT1718S,
|
2009-10-10 19:08:41 +08:00
|
|
|
VT1716S,
|
2009-10-10 19:08:43 +08:00
|
|
|
VT2002P,
|
2009-10-10 19:08:46 +08:00
|
|
|
VT1812,
|
2011-03-23 17:57:34 +08:00
|
|
|
VT1802,
|
2008-09-15 22:43:23 +08:00
|
|
|
CODEC_TYPES,
|
|
|
|
};
|
|
|
|
|
2011-03-23 17:57:34 +08:00
|
|
|
#define VT2002P_COMPATIBLE(spec) \
|
|
|
|
((spec)->codec_type == VT2002P ||\
|
|
|
|
(spec)->codec_type == VT1812 ||\
|
|
|
|
(spec)->codec_type == VT1802)
|
|
|
|
|
2011-06-21 15:01:36 +08:00
|
|
|
#define MAX_NID_PATH_DEPTH 5
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
struct nid_path {
|
|
|
|
int depth;
|
2011-06-21 15:01:36 +08:00
|
|
|
hda_nid_t path[MAX_NID_PATH_DEPTH];
|
|
|
|
short idx[MAX_NID_PATH_DEPTH];
|
2011-06-17 23:53:38 +08:00
|
|
|
};
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
struct via_spec {
|
|
|
|
/* codec parameterization */
|
2011-05-02 18:38:19 +08:00
|
|
|
const struct snd_kcontrol_new *mixers[6];
|
2009-10-10 19:08:17 +08:00
|
|
|
unsigned int num_mixers;
|
|
|
|
|
2011-05-02 18:38:19 +08:00
|
|
|
const struct hda_verb *init_verbs[5];
|
2009-10-10 19:08:17 +08:00
|
|
|
unsigned int num_iverbs;
|
|
|
|
|
2011-06-17 22:24:21 +08:00
|
|
|
char stream_name_analog[32];
|
2011-06-18 22:40:14 +08:00
|
|
|
char stream_name_hp[32];
|
2011-05-02 18:38:19 +08:00
|
|
|
const struct hda_pcm_stream *stream_analog_playback;
|
|
|
|
const struct hda_pcm_stream *stream_analog_capture;
|
2009-10-10 19:08:17 +08:00
|
|
|
|
2011-06-17 22:24:21 +08:00
|
|
|
char stream_name_digital[32];
|
2011-05-02 18:38:19 +08:00
|
|
|
const struct hda_pcm_stream *stream_digital_playback;
|
|
|
|
const struct hda_pcm_stream *stream_digital_capture;
|
2009-10-10 19:08:17 +08:00
|
|
|
|
|
|
|
/* playback */
|
|
|
|
struct hda_multi_out multiout;
|
|
|
|
hda_nid_t slave_dig_outs[2];
|
2011-06-19 22:24:21 +08:00
|
|
|
hda_nid_t hp_dac_nid;
|
2011-06-20 21:40:19 +08:00
|
|
|
int num_active_streams;
|
2009-10-10 19:08:17 +08:00
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
struct nid_path out_path[4];
|
|
|
|
struct nid_path hp_path;
|
|
|
|
struct nid_path hp_dep_path;
|
2011-06-20 18:39:26 +08:00
|
|
|
struct nid_path speaker_path;
|
2011-06-17 23:53:38 +08:00
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
/* capture */
|
|
|
|
unsigned int num_adc_nids;
|
2011-06-17 15:01:29 +08:00
|
|
|
hda_nid_t adc_nids[3];
|
2009-10-10 19:08:17 +08:00
|
|
|
hda_nid_t mux_nids[3];
|
2011-06-17 23:19:19 +08:00
|
|
|
hda_nid_t aa_mix_nid;
|
2009-10-10 19:08:17 +08:00
|
|
|
hda_nid_t dig_in_nid;
|
|
|
|
|
|
|
|
/* capture source */
|
|
|
|
const struct hda_input_mux *input_mux;
|
|
|
|
unsigned int cur_mux[3];
|
|
|
|
|
|
|
|
/* PCM information */
|
|
|
|
struct hda_pcm pcm_rec[3];
|
|
|
|
|
|
|
|
/* dynamic controls, init_verbs and input_mux */
|
|
|
|
struct auto_pin_cfg autocfg;
|
|
|
|
struct snd_array kctls;
|
|
|
|
struct hda_input_mux private_imux[2];
|
|
|
|
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
|
|
|
|
|
|
|
|
/* HP mode source */
|
|
|
|
unsigned int hp_independent_mode;
|
2009-10-10 19:08:41 +08:00
|
|
|
unsigned int dmic_enabled;
|
2011-06-17 22:59:21 +08:00
|
|
|
unsigned int no_pin_power_ctl;
|
2009-10-10 19:08:17 +08:00
|
|
|
enum VIA_HDA_CODEC codec_type;
|
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
/* smart51 setup */
|
|
|
|
unsigned int smart51_nums;
|
|
|
|
hda_nid_t smart51_pins[2];
|
|
|
|
int smart51_idxs[2];
|
|
|
|
const char *smart51_labels[2];
|
|
|
|
unsigned int smart51_enabled;
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
/* work to check hp jack state */
|
|
|
|
struct hda_codec *codec;
|
|
|
|
struct delayed_work vt1708_hp_work;
|
2011-06-17 21:46:13 +08:00
|
|
|
int vt1708_jack_detect;
|
2009-10-10 19:08:17 +08:00
|
|
|
int vt1708_hp_present;
|
2011-03-23 15:13:28 +08:00
|
|
|
|
|
|
|
void (*set_widgets_power_state)(struct hda_codec *codec);
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
struct hda_loopback_check loopback;
|
2011-06-20 20:05:46 +08:00
|
|
|
int num_loopbacks;
|
|
|
|
struct hda_amp_list loopback_list[8];
|
2009-10-10 19:08:17 +08:00
|
|
|
};
|
|
|
|
|
2011-03-22 16:25:03 +08:00
|
|
|
static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec);
|
2009-12-08 23:13:32 +08:00
|
|
|
static struct via_spec * via_new_spec(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
|
|
if (spec == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
codec->spec = spec;
|
|
|
|
spec->codec = codec;
|
2011-03-22 16:25:03 +08:00
|
|
|
spec->codec_type = get_codec_type(codec);
|
|
|
|
/* VT1708BCE & VT1708S are almost same */
|
|
|
|
if (spec->codec_type == VT1708BCE)
|
|
|
|
spec->codec_type = VT1708S;
|
2009-12-08 23:13:32 +08:00
|
|
|
return spec;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:07:26 +08:00
|
|
|
static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
|
2008-09-15 22:43:23 +08:00
|
|
|
{
|
2009-10-10 19:07:26 +08:00
|
|
|
u32 vendor_id = codec->vendor_id;
|
2008-09-15 22:43:23 +08:00
|
|
|
u16 ven_id = vendor_id >> 16;
|
|
|
|
u16 dev_id = vendor_id & 0xffff;
|
|
|
|
enum VIA_HDA_CODEC codec_type;
|
|
|
|
|
|
|
|
/* get codec type */
|
|
|
|
if (ven_id != 0x1106)
|
|
|
|
codec_type = UNKNOWN;
|
|
|
|
else if (dev_id >= 0x1708 && dev_id <= 0x170b)
|
|
|
|
codec_type = VT1708;
|
|
|
|
else if (dev_id >= 0xe710 && dev_id <= 0xe713)
|
|
|
|
codec_type = VT1709_10CH;
|
|
|
|
else if (dev_id >= 0xe714 && dev_id <= 0xe717)
|
|
|
|
codec_type = VT1709_6CH;
|
2009-10-10 19:07:29 +08:00
|
|
|
else if (dev_id >= 0xe720 && dev_id <= 0xe723) {
|
2008-09-15 22:43:23 +08:00
|
|
|
codec_type = VT1708B_8CH;
|
2009-10-10 19:07:29 +08:00
|
|
|
if (snd_hda_param_read(codec, 0x16, AC_PAR_CONNLIST_LEN) == 0x7)
|
|
|
|
codec_type = VT1708BCE;
|
|
|
|
} else if (dev_id >= 0xe724 && dev_id <= 0xe727)
|
2008-09-15 22:43:23 +08:00
|
|
|
codec_type = VT1708B_4CH;
|
|
|
|
else if ((dev_id & 0xfff) == 0x397
|
|
|
|
&& (dev_id >> 12) < 8)
|
|
|
|
codec_type = VT1708S;
|
|
|
|
else if ((dev_id & 0xfff) == 0x398
|
|
|
|
&& (dev_id >> 12) < 8)
|
|
|
|
codec_type = VT1702;
|
2009-10-10 19:08:34 +08:00
|
|
|
else if ((dev_id & 0xfff) == 0x428
|
|
|
|
&& (dev_id >> 12) < 8)
|
|
|
|
codec_type = VT1718S;
|
2009-10-10 19:08:41 +08:00
|
|
|
else if (dev_id == 0x0433 || dev_id == 0xa721)
|
|
|
|
codec_type = VT1716S;
|
2009-10-10 19:08:39 +08:00
|
|
|
else if (dev_id == 0x0441 || dev_id == 0x4441)
|
|
|
|
codec_type = VT1718S;
|
2009-10-10 19:08:43 +08:00
|
|
|
else if (dev_id == 0x0438 || dev_id == 0x4438)
|
|
|
|
codec_type = VT2002P;
|
2009-10-10 19:08:46 +08:00
|
|
|
else if (dev_id == 0x0448)
|
|
|
|
codec_type = VT1812;
|
2009-10-20 13:18:04 +08:00
|
|
|
else if (dev_id == 0x0440)
|
|
|
|
codec_type = VT1708S;
|
2011-03-23 17:57:34 +08:00
|
|
|
else if ((dev_id & 0xfff) == 0x446)
|
|
|
|
codec_type = VT1802;
|
2008-09-15 22:43:23 +08:00
|
|
|
else
|
|
|
|
codec_type = UNKNOWN;
|
|
|
|
return codec_type;
|
|
|
|
};
|
|
|
|
|
2011-03-24 12:43:44 +08:00
|
|
|
#define VIA_JACK_EVENT 0x20
|
2008-09-09 15:57:32 +08:00
|
|
|
#define VIA_HP_EVENT 0x01
|
|
|
|
#define VIA_GPIO_EVENT 0x02
|
2011-06-20 18:39:26 +08:00
|
|
|
#define VIA_LINE_EVENT 0x03
|
2008-09-09 15:57:32 +08:00
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
enum {
|
|
|
|
VIA_CTL_WIDGET_VOL,
|
|
|
|
VIA_CTL_WIDGET_MUTE,
|
2009-10-10 19:07:35 +08:00
|
|
|
VIA_CTL_WIDGET_ANALOG_MUTE,
|
2006-11-29 22:29:40 +08:00
|
|
|
};
|
|
|
|
|
2011-06-20 21:40:19 +08:00
|
|
|
static void analog_low_current_mode(struct hda_codec *codec);
|
|
|
|
static bool is_aa_path_mute(struct hda_codec *codec);
|
2009-10-10 19:08:17 +08:00
|
|
|
|
|
|
|
static void vt1708_start_hp_work(struct via_spec *spec)
|
|
|
|
{
|
|
|
|
if (spec->codec_type != VT1708 || spec->autocfg.hp_pins[0] == 0)
|
|
|
|
return;
|
|
|
|
snd_hda_codec_write(spec->codec, 0x1, 0, 0xf81,
|
2011-06-17 21:46:13 +08:00
|
|
|
!spec->vt1708_jack_detect);
|
2009-10-10 19:08:17 +08:00
|
|
|
if (!delayed_work_pending(&spec->vt1708_hp_work))
|
|
|
|
schedule_delayed_work(&spec->vt1708_hp_work,
|
|
|
|
msecs_to_jiffies(100));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vt1708_stop_hp_work(struct via_spec *spec)
|
|
|
|
{
|
|
|
|
if (spec->codec_type != VT1708 || spec->autocfg.hp_pins[0] == 0)
|
|
|
|
return;
|
|
|
|
if (snd_hda_get_bool_hint(spec->codec, "analog_loopback_hp_detect") == 1
|
|
|
|
&& !is_aa_path_mute(spec->codec))
|
|
|
|
return;
|
|
|
|
snd_hda_codec_write(spec->codec, 0x1, 0, 0xf81,
|
2011-06-17 21:46:13 +08:00
|
|
|
!spec->vt1708_jack_detect);
|
2010-12-12 00:51:26 +08:00
|
|
|
cancel_delayed_work_sync(&spec->vt1708_hp_work);
|
2009-10-10 19:08:17 +08:00
|
|
|
}
|
2009-10-10 19:07:35 +08:00
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
if (spec->set_widgets_power_state)
|
|
|
|
spec->set_widgets_power_state(codec);
|
|
|
|
}
|
2009-10-10 19:08:43 +08:00
|
|
|
|
2009-10-10 19:07:35 +08:00
|
|
|
static int analog_input_switch_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
int change = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2011-06-20 21:40:19 +08:00
|
|
|
analog_low_current_mode(snd_kcontrol_chip(kcontrol));
|
2009-10-10 19:08:17 +08:00
|
|
|
if (snd_hda_get_bool_hint(codec, "analog_loopback_hp_detect") == 1) {
|
|
|
|
if (is_aa_path_mute(codec))
|
|
|
|
vt1708_start_hp_work(codec->spec);
|
|
|
|
else
|
|
|
|
vt1708_stop_hp_work(codec->spec);
|
|
|
|
}
|
2009-10-10 19:07:35 +08:00
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* modify .put = snd_hda_mixer_amp_switch_put */
|
|
|
|
#define ANALOG_INPUT_MUTE \
|
|
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
|
|
|
|
.name = NULL, \
|
|
|
|
.index = 0, \
|
|
|
|
.info = snd_hda_mixer_amp_switch_info, \
|
|
|
|
.get = snd_hda_mixer_amp_switch_get, \
|
|
|
|
.put = analog_input_switch_put, \
|
|
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0) }
|
|
|
|
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct snd_kcontrol_new via_control_templates[] = {
|
2006-11-29 22:29:40 +08:00
|
|
|
HDA_CODEC_VOLUME(NULL, 0, 0, 0),
|
|
|
|
HDA_CODEC_MUTE(NULL, 0, 0, 0),
|
2009-10-10 19:07:35 +08:00
|
|
|
ANALOG_INPUT_MUTE,
|
2006-11-29 22:29:40 +08:00
|
|
|
};
|
|
|
|
|
2009-10-10 19:08:46 +08:00
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/* add dynamic controls */
|
2011-06-17 22:15:26 +08:00
|
|
|
static struct snd_kcontrol_new *__via_clone_ctl(struct via_spec *spec,
|
|
|
|
const struct snd_kcontrol_new *tmpl,
|
|
|
|
const char *name)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
|
2008-07-30 21:01:44 +08:00
|
|
|
snd_array_init(&spec->kctls, sizeof(*knew), 32);
|
|
|
|
knew = snd_array_new(&spec->kctls);
|
|
|
|
if (!knew)
|
2011-06-17 22:15:26 +08:00
|
|
|
return NULL;
|
|
|
|
*knew = *tmpl;
|
|
|
|
if (!name)
|
|
|
|
name = tmpl->name;
|
|
|
|
if (name) {
|
|
|
|
knew->name = kstrdup(name, GFP_KERNEL);
|
|
|
|
if (!knew->name)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return knew;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __via_add_control(struct via_spec *spec, int type, const char *name,
|
|
|
|
int idx, unsigned long val)
|
|
|
|
{
|
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
|
|
|
|
knew = __via_clone_ctl(spec, &via_control_templates[type], name);
|
|
|
|
if (!knew)
|
2006-11-29 22:29:40 +08:00
|
|
|
return -ENOMEM;
|
2011-06-18 23:24:46 +08:00
|
|
|
knew->index = idx;
|
2009-11-12 17:15:48 +08:00
|
|
|
if (get_amp_nid_(val))
|
2009-12-10 20:57:01 +08:00
|
|
|
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
|
2006-11-29 22:29:40 +08:00
|
|
|
knew->private_value = val;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-30 19:06:30 +08:00
|
|
|
#define via_add_control(spec, type, name, val) \
|
|
|
|
__via_add_control(spec, type, name, 0, val)
|
|
|
|
|
2011-06-17 22:15:26 +08:00
|
|
|
#define via_clone_control(spec, tmpl) __via_clone_ctl(spec, tmpl, NULL)
|
2009-12-08 23:13:32 +08:00
|
|
|
|
2008-07-30 21:01:44 +08:00
|
|
|
static void via_free_kctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->kctls.list) {
|
|
|
|
struct snd_kcontrol_new *kctl = spec->kctls.list;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < spec->kctls.used; i++)
|
|
|
|
kfree(kctl[i].name);
|
|
|
|
}
|
|
|
|
snd_array_free(&spec->kctls);
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/* create input playback/capture controls for the given pin */
|
2009-10-10 19:07:39 +08:00
|
|
|
static int via_new_analog_input(struct via_spec *spec, const char *ctlname,
|
2010-08-30 19:06:30 +08:00
|
|
|
int type_idx, int idx, int mix_nid)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
|
|
|
char name[32];
|
|
|
|
int err;
|
|
|
|
|
|
|
|
sprintf(name, "%s Playback Volume", ctlname);
|
2010-08-30 19:06:30 +08:00
|
|
|
err = __via_add_control(spec, VIA_CTL_WIDGET_VOL, name, type_idx,
|
2006-11-29 22:29:40 +08:00
|
|
|
HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
sprintf(name, "%s Playback Switch", ctlname);
|
2010-08-30 19:06:30 +08:00
|
|
|
err = __via_add_control(spec, VIA_CTL_WIDGET_ANALOG_MUTE, name, type_idx,
|
2006-11-29 22:29:40 +08:00
|
|
|
HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
/* return the index of the given widget nid as the source of mux;
|
|
|
|
* return -1 if not found;
|
|
|
|
* if num_conns is non-NULL, set the total number of connections
|
|
|
|
*/
|
|
|
|
static int __get_connection_index(struct hda_codec *codec, hda_nid_t mux,
|
|
|
|
hda_nid_t nid, int *num_conns)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2011-06-20 17:32:27 +08:00
|
|
|
hda_nid_t conn[HDA_MAX_NUM_INPUTS];
|
|
|
|
int i, nums;
|
|
|
|
|
|
|
|
nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
|
|
|
|
if (num_conns)
|
|
|
|
*num_conns = nums;
|
|
|
|
for (i = 0; i < nums; i++)
|
|
|
|
if (conn[i] == nid)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define get_connection_index(codec, mux, nid) \
|
|
|
|
__get_connection_index(codec, mux, nid, NULL)
|
|
|
|
|
2011-06-21 17:48:29 +08:00
|
|
|
static bool check_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
|
|
|
|
unsigned int mask)
|
|
|
|
{
|
|
|
|
unsigned int caps = get_wcaps(codec, nid);
|
|
|
|
if (dir == HDA_INPUT)
|
|
|
|
caps &= AC_WCAP_IN_AMP;
|
|
|
|
else
|
|
|
|
caps &= AC_WCAP_OUT_AMP;
|
|
|
|
if (!caps)
|
|
|
|
return false;
|
|
|
|
if (query_amp_caps(codec, nid, dir) & mask)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define have_vol_or_mute(codec, nid, dir) \
|
|
|
|
check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS | AC_AMPCAP_MUTE)
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
/* unmute input amp and select the specificed source */
|
|
|
|
static void unmute_and_select(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
hda_nid_t src, hda_nid_t mix)
|
|
|
|
{
|
|
|
|
int idx, num_conns;
|
|
|
|
|
|
|
|
idx = __get_connection_index(codec, nid, src, &num_conns);
|
|
|
|
if (idx < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* select the route explicitly when multiple connections exist */
|
2011-06-21 15:01:36 +08:00
|
|
|
if (num_conns > 1 &&
|
|
|
|
get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_MIX)
|
2011-06-20 17:32:27 +08:00
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
|
|
AC_VERB_SET_CONNECT_SEL, idx);
|
2011-06-21 15:01:36 +08:00
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
/* unmute if the input amp is present */
|
2011-06-21 17:48:29 +08:00
|
|
|
if (have_vol_or_mute(codec, nid, HDA_INPUT))
|
2011-06-21 15:01:36 +08:00
|
|
|
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_IN_UNMUTE(idx));
|
|
|
|
|
|
|
|
/* unmute the src output */
|
2011-06-21 17:48:29 +08:00
|
|
|
if (have_vol_or_mute(codec, src, HDA_OUTPUT))
|
2011-06-21 15:01:36 +08:00
|
|
|
snd_hda_codec_write(codec, src, 0, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_OUT_UNMUTE);
|
2011-06-20 17:32:27 +08:00
|
|
|
|
|
|
|
/* unmute AA-path if present */
|
2011-06-21 17:48:29 +08:00
|
|
|
if (!mix || mix == src)
|
2011-06-20 17:32:27 +08:00
|
|
|
return;
|
|
|
|
idx = __get_connection_index(codec, nid, mix, NULL);
|
2011-06-21 17:48:29 +08:00
|
|
|
if (idx >= 0 && have_vol_or_mute(codec, nid, HDA_INPUT))
|
2009-10-10 19:08:55 +08:00
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
2011-06-20 17:32:27 +08:00
|
|
|
AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_IN_UNMUTE(idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set the given pin as output */
|
|
|
|
static void init_output_pin(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
int pin_type)
|
|
|
|
{
|
|
|
|
if (!pin)
|
|
|
|
return;
|
|
|
|
snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
|
pin_type);
|
|
|
|
if (snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD)
|
|
|
|
snd_hda_codec_write(codec, pin, 0,
|
2009-07-07 19:43:35 +08:00
|
|
|
AC_VERB_SET_EAPD_BTLENABLE, 0x02);
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
static void via_auto_init_output(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
int pin_type, struct nid_path *path)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
unsigned int caps;
|
|
|
|
hda_nid_t nid;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!pin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
init_output_pin(codec, pin, pin_type);
|
|
|
|
caps = query_amp_caps(codec, pin, HDA_OUTPUT);
|
|
|
|
if (caps & AC_AMPCAP_MUTE) {
|
|
|
|
unsigned int val;
|
|
|
|
val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
|
|
|
|
snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_OUT_MUTE | val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the output path */
|
2011-06-21 15:01:36 +08:00
|
|
|
for (i = path->depth - 1; i > 0; i--) {
|
|
|
|
nid = path->path[i - 1];
|
|
|
|
unmute_and_select(codec, path->path[i], nid, spec->aa_mix_nid);
|
2011-06-20 17:32:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
|
|
|
|
static void via_auto_init_multi_out(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++)
|
2011-06-20 17:32:27 +08:00
|
|
|
via_auto_init_output(codec, spec->autocfg.line_out_pins[i],
|
|
|
|
PIN_OUT, &spec->out_path[i]);
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void via_auto_init_hp_out(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
if (spec->hp_dac_nid)
|
|
|
|
via_auto_init_output(codec, spec->autocfg.hp_pins[0], PIN_HP,
|
|
|
|
&spec->hp_path);
|
|
|
|
else
|
|
|
|
via_auto_init_output(codec, spec->autocfg.hp_pins[0], PIN_HP,
|
|
|
|
&spec->hp_dep_path);
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
static void via_auto_init_speaker_out(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->autocfg.speaker_outs)
|
|
|
|
via_auto_init_output(codec, spec->autocfg.speaker_pins[0],
|
|
|
|
PIN_OUT, &spec->speaker_path);
|
|
|
|
}
|
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
static bool is_smart51_pins(struct hda_codec *codec, hda_nid_t pin);
|
2010-07-12 22:28:50 +08:00
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
static void via_auto_init_analog_input(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2010-08-30 19:06:30 +08:00
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
2011-06-20 18:09:02 +08:00
|
|
|
hda_nid_t conn[HDA_MAX_CONNECTIONS];
|
2010-07-12 22:28:50 +08:00
|
|
|
unsigned int ctl;
|
2011-06-20 18:09:02 +08:00
|
|
|
int i, num_conns;
|
|
|
|
|
|
|
|
/* init ADCs */
|
|
|
|
for (i = 0; i < spec->num_adc_nids; i++) {
|
|
|
|
snd_hda_codec_write(codec, spec->adc_nids[i], 0,
|
|
|
|
AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_IN_UNMUTE(0));
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
/* init pins */
|
2010-08-30 19:06:30 +08:00
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t nid = cfg->inputs[i].pin;
|
2011-06-18 00:46:48 +08:00
|
|
|
if (spec->smart51_enabled && is_smart51_pins(codec, nid))
|
2010-07-12 22:28:50 +08:00
|
|
|
ctl = PIN_OUT;
|
2011-02-21 17:23:18 +08:00
|
|
|
else if (cfg->inputs[i].type == AUTO_PIN_MIC)
|
2010-07-12 22:28:50 +08:00
|
|
|
ctl = PIN_VREF50;
|
|
|
|
else
|
|
|
|
ctl = PIN_IN;
|
2006-11-29 22:29:40 +08:00
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
2010-07-12 22:28:50 +08:00
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL, ctl);
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
2011-06-20 18:09:02 +08:00
|
|
|
|
|
|
|
/* init input-src */
|
|
|
|
for (i = 0; i < spec->num_adc_nids; i++) {
|
|
|
|
const struct hda_input_mux *imux = spec->input_mux;
|
|
|
|
if (!imux || !spec->mux_nids[i])
|
|
|
|
continue;
|
|
|
|
snd_hda_codec_write(codec, spec->mux_nids[i], 0,
|
|
|
|
AC_VERB_SET_CONNECT_SEL,
|
|
|
|
imux->items[spec->cur_mux[i]].index);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* init aa-mixer */
|
|
|
|
if (!spec->aa_mix_nid)
|
|
|
|
return;
|
|
|
|
num_conns = snd_hda_get_connections(codec, spec->aa_mix_nid, conn,
|
|
|
|
ARRAY_SIZE(conn));
|
|
|
|
for (i = 0; i < num_conns; i++) {
|
|
|
|
unsigned int caps = get_wcaps(codec, conn[i]);
|
|
|
|
if (get_wcaps_type(caps) == AC_WID_PIN)
|
|
|
|
snd_hda_codec_write(codec, spec->aa_mix_nid, 0,
|
|
|
|
AC_VERB_SET_AMP_GAIN_MUTE,
|
|
|
|
AMP_IN_MUTE(i));
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
2009-10-10 19:07:35 +08:00
|
|
|
|
|
|
|
static void set_pin_power_state(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
unsigned int *affected_parm)
|
|
|
|
{
|
|
|
|
unsigned parm;
|
|
|
|
unsigned def_conf = snd_hda_codec_get_pincfg(codec, nid);
|
|
|
|
unsigned no_presence = (def_conf & AC_DEFCFG_MISC)
|
|
|
|
>> AC_DEFCFG_MISC_SHIFT
|
|
|
|
& AC_DEFCFG_MISC_NO_PRESENCE; /* do not support pin sense */
|
2009-10-10 19:07:52 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-17 22:59:21 +08:00
|
|
|
unsigned present = 0;
|
|
|
|
|
|
|
|
no_presence |= spec->no_pin_power_ctl;
|
|
|
|
if (!no_presence)
|
|
|
|
present = snd_hda_jack_detect(codec, nid);
|
2011-06-18 00:46:48 +08:00
|
|
|
if ((spec->smart51_enabled && is_smart51_pins(codec, nid))
|
2009-10-10 19:07:52 +08:00
|
|
|
|| ((no_presence || present)
|
|
|
|
&& get_defcfg_connect(def_conf) != AC_JACK_PORT_NONE)) {
|
2009-10-10 19:07:35 +08:00
|
|
|
*affected_parm = AC_PWRST_D0; /* if it's connected */
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
} else
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
|
|
|
|
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
|
|
|
|
2011-06-17 22:59:21 +08:00
|
|
|
static int via_pin_power_ctl_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
static const char * const texts[] = {
|
|
|
|
"Disabled", "Enabled"
|
|
|
|
};
|
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.enumerated.items = 2;
|
|
|
|
if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
|
|
|
|
uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
|
|
|
|
strcpy(uinfo->value.enumerated.name,
|
|
|
|
texts[uinfo->value.enumerated.item]);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_pin_power_ctl_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
ucontrol->value.enumerated.item[0] = !spec->no_pin_power_ctl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_pin_power_ctl_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
unsigned int val = !ucontrol->value.enumerated.item[0];
|
|
|
|
|
|
|
|
if (val == spec->no_pin_power_ctl)
|
|
|
|
return 0;
|
|
|
|
spec->no_pin_power_ctl = val;
|
|
|
|
set_widgets_power_state(codec);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new via_pin_power_ctl_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Dynamic Power-Control",
|
|
|
|
.info = via_pin_power_ctl_info,
|
|
|
|
.get = via_pin_power_ctl_get,
|
|
|
|
.put = via_pin_power_ctl_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* input MUX handling
|
|
|
|
*/
|
|
|
|
static int via_mux_enum_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
return snd_hda_input_mux_info(spec->input_mux, uinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_mux_enum_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_mux_enum_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
2011-03-22 16:21:38 +08:00
|
|
|
int ret;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2009-07-08 00:18:59 +08:00
|
|
|
if (!spec->mux_nids[adc_idx])
|
|
|
|
return -EINVAL;
|
2009-10-10 19:07:55 +08:00
|
|
|
/* switch to D0 beofre change index */
|
|
|
|
if (snd_hda_codec_read(codec, spec->mux_nids[adc_idx], 0,
|
|
|
|
AC_VERB_GET_POWER_STATE, 0x00) != AC_PWRST_D0)
|
|
|
|
snd_hda_codec_write(codec, spec->mux_nids[adc_idx], 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
|
2011-03-22 16:21:38 +08:00
|
|
|
ret = snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
|
2009-07-08 00:18:59 +08:00
|
|
|
spec->mux_nids[adc_idx],
|
|
|
|
&spec->cur_mux[adc_idx]);
|
2011-03-22 16:21:38 +08:00
|
|
|
/* update jack power state */
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2011-03-22 16:21:38 +08:00
|
|
|
|
|
|
|
return ret;
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
2008-09-09 15:58:27 +08:00
|
|
|
static int via_independent_hp_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
2011-06-21 17:48:29 +08:00
|
|
|
static const char * const texts[] = { "OFF", "ON" };
|
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.enumerated.items = 2;
|
|
|
|
if (uinfo->value.enumerated.item >= 2)
|
|
|
|
uinfo->value.enumerated.item = 1;
|
|
|
|
strcpy(uinfo->value.enumerated.name,
|
|
|
|
texts[uinfo->value.enumerated.item]);
|
|
|
|
return 0;
|
2008-09-09 15:58:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int via_independent_hp_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
2009-10-10 19:07:47 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
ucontrol->value.enumerated.item[0] = spec->hp_independent_mode;
|
2009-10-10 19:07:47 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-09 15:58:27 +08:00
|
|
|
static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-21 17:48:29 +08:00
|
|
|
hda_nid_t nid, src;
|
|
|
|
int i, idx, num_conns;
|
|
|
|
struct nid_path *path;
|
|
|
|
|
|
|
|
spec->hp_independent_mode = !!ucontrol->value.enumerated.item[0];
|
|
|
|
if (spec->hp_independent_mode)
|
|
|
|
path = &spec->hp_path;
|
|
|
|
else
|
|
|
|
path = &spec->hp_dep_path;
|
|
|
|
|
|
|
|
/* re-route the output path */
|
|
|
|
for (i = path->depth - 1; i > 0; i--) {
|
|
|
|
nid = path->path[i];
|
|
|
|
src = path->path[i - 1];
|
|
|
|
idx = __get_connection_index(codec, nid, src, &num_conns);
|
|
|
|
if (idx < 0)
|
|
|
|
continue;
|
|
|
|
if (num_conns > 1 &&
|
|
|
|
get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_MIX)
|
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
|
|
AC_VERB_SET_CONNECT_SEL, idx);
|
|
|
|
}
|
2011-06-19 22:24:21 +08:00
|
|
|
|
2011-03-22 16:22:37 +08:00
|
|
|
/* update jack power state */
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2008-09-09 15:58:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
static const struct snd_kcontrol_new via_hp_mixer = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Independent HP",
|
|
|
|
.info = via_independent_hp_info,
|
|
|
|
.get = via_independent_hp_get,
|
|
|
|
.put = via_independent_hp_put,
|
2008-09-09 15:58:27 +08:00
|
|
|
};
|
|
|
|
|
2010-04-14 20:36:23 +08:00
|
|
|
static int via_hp_build(struct hda_codec *codec)
|
2009-12-08 23:13:32 +08:00
|
|
|
{
|
2010-04-14 20:36:23 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2009-12-08 23:13:32 +08:00
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
hda_nid_t nid;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
nid = spec->autocfg.hp_pins[0];
|
|
|
|
knew = via_clone_control(spec, &via_hp_mixer);
|
2010-04-14 20:36:23 +08:00
|
|
|
if (knew == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2009-12-08 23:13:32 +08:00
|
|
|
knew->subdevice = HDA_SUBDEV_NID_FLAG | nid;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:07:52 +08:00
|
|
|
static void notify_aa_path_ctls(struct hda_codec *codec)
|
|
|
|
{
|
2011-06-20 19:52:33 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2009-10-10 19:07:52 +08:00
|
|
|
int i;
|
2011-06-20 19:52:33 +08:00
|
|
|
|
|
|
|
for (i = 0; i < spec->smart51_nums; i++) {
|
|
|
|
struct snd_kcontrol *ctl;
|
|
|
|
struct snd_ctl_elem_id id;
|
|
|
|
memset(&id, 0, sizeof(id));
|
|
|
|
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
|
|
sprintf(id.name, "%s Playback Volume", spec->smart51_labels[i]);
|
2011-04-28 16:03:39 +08:00
|
|
|
ctl = snd_hda_find_mixer_ctl(codec, id.name);
|
|
|
|
if (ctl)
|
|
|
|
snd_ctl_notify(codec->bus->card,
|
|
|
|
SNDRV_CTL_EVENT_MASK_VALUE,
|
|
|
|
&ctl->id);
|
2009-10-10 19:07:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mute_aa_path(struct hda_codec *codec, int mute)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-20 19:52:33 +08:00
|
|
|
int val = mute ? HDA_AMP_MUTE : HDA_AMP_UNMUTE;
|
2009-10-10 19:07:52 +08:00
|
|
|
int i;
|
2011-06-20 19:52:33 +08:00
|
|
|
|
2009-10-10 19:07:52 +08:00
|
|
|
/* check AA path's mute status */
|
2011-06-20 19:52:33 +08:00
|
|
|
for (i = 0; i < spec->smart51_nums; i++) {
|
|
|
|
if (spec->smart51_idxs[i] < 0)
|
|
|
|
continue;
|
|
|
|
snd_hda_codec_amp_stereo(codec, spec->aa_mix_nid,
|
|
|
|
HDA_INPUT, spec->smart51_idxs[i],
|
2009-10-10 19:07:52 +08:00
|
|
|
HDA_AMP_MUTE, val);
|
|
|
|
}
|
|
|
|
}
|
2011-06-18 00:46:48 +08:00
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
static bool is_smart51_candidate(struct hda_codec *codec, hda_nid_t pin)
|
2009-10-10 19:07:52 +08:00
|
|
|
{
|
2011-06-18 00:46:48 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2010-08-30 19:06:30 +08:00
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
2011-06-18 00:46:48 +08:00
|
|
|
unsigned int defcfg;
|
|
|
|
if (pin != cfg->inputs[i].pin)
|
|
|
|
continue;
|
|
|
|
if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
|
|
|
|
return false;
|
|
|
|
defcfg = snd_hda_codec_get_pincfg(codec, pin);
|
|
|
|
if (snd_hda_get_input_pin_attr(defcfg) < INPUT_PIN_ATTR_NORMAL)
|
|
|
|
return false;
|
|
|
|
return true;
|
2009-10-10 19:07:52 +08:00
|
|
|
}
|
2011-06-18 00:46:48 +08:00
|
|
|
return false;
|
2009-10-10 19:07:52 +08:00
|
|
|
}
|
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
static bool is_smart51_pins(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->smart51_nums; i++)
|
|
|
|
if (spec->smart51_pins[i] == pin)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:07:52 +08:00
|
|
|
static int via_smart51_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_smart51_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int on = 1;
|
|
|
|
int i;
|
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
for (i = 0; i < spec->smart51_nums; i++) {
|
|
|
|
hda_nid_t nid = spec->smart51_pins[i];
|
2011-06-18 00:46:48 +08:00
|
|
|
unsigned int ctl;
|
|
|
|
ctl = snd_hda_codec_read(codec, nid, 0,
|
|
|
|
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
|
2010-08-30 19:06:30 +08:00
|
|
|
if ((ctl & AC_PINCTL_IN_EN) && !(ctl & AC_PINCTL_OUT_EN))
|
|
|
|
on = 0;
|
2009-10-10 19:07:52 +08:00
|
|
|
}
|
|
|
|
*ucontrol->value.integer.value = on;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_smart51_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int out_in = *ucontrol->value.integer.value
|
|
|
|
? AC_PINCTL_OUT_EN : AC_PINCTL_IN_EN;
|
|
|
|
int i;
|
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
for (i = 0; i < spec->smart51_nums; i++) {
|
|
|
|
hda_nid_t nid = spec->smart51_pins[i];
|
2010-08-30 19:06:30 +08:00
|
|
|
unsigned int parm;
|
|
|
|
|
|
|
|
parm = snd_hda_codec_read(codec, nid, 0,
|
|
|
|
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
|
|
|
|
parm &= ~(AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN);
|
|
|
|
parm |= out_in;
|
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
|
parm);
|
|
|
|
if (out_in == AC_PINCTL_OUT_EN) {
|
|
|
|
mute_aa_path(codec, 1);
|
|
|
|
notify_aa_path_ctls(codec);
|
|
|
|
}
|
2009-10-10 19:07:52 +08:00
|
|
|
}
|
|
|
|
spec->smart51_enabled = *ucontrol->value.integer.value;
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2009-10-10 19:07:52 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-06-17 20:55:02 +08:00
|
|
|
static const struct snd_kcontrol_new via_smart51_mixer = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Smart 5.1",
|
|
|
|
.count = 1,
|
|
|
|
.info = via_smart51_info,
|
|
|
|
.get = via_smart51_get,
|
|
|
|
.put = via_smart51_put,
|
2009-10-10 19:07:52 +08:00
|
|
|
};
|
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
static int via_smart51_build(struct hda_codec *codec)
|
2009-12-08 23:13:32 +08:00
|
|
|
{
|
2011-06-18 00:46:48 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2009-12-08 23:13:32 +08:00
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
if (!spec->smart51_nums)
|
2011-04-27 17:44:16 +08:00
|
|
|
return 0;
|
2011-06-20 19:52:33 +08:00
|
|
|
if (!via_clone_control(spec, &via_smart51_mixer))
|
2009-12-08 23:13:32 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-20 21:40:19 +08:00
|
|
|
/* check AA path's mute status */
|
|
|
|
static bool is_aa_path_mute(struct hda_codec *codec)
|
2009-10-10 19:07:35 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-20 21:40:19 +08:00
|
|
|
const struct hda_amp_list *p;
|
|
|
|
int i, ch, v;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->num_loopbacks; i++) {
|
|
|
|
p = &spec->loopback_list[i];
|
|
|
|
for (ch = 0; ch < 2; ch++) {
|
|
|
|
v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
|
|
|
|
p->idx);
|
|
|
|
if (!(v & HDA_AMP_MUTE) && v > 0)
|
|
|
|
return false;
|
2009-10-10 19:07:35 +08:00
|
|
|
}
|
|
|
|
}
|
2011-06-20 21:40:19 +08:00
|
|
|
return true;
|
2009-10-10 19:07:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* enter/exit analog low-current mode */
|
2011-06-20 21:40:19 +08:00
|
|
|
static void analog_low_current_mode(struct hda_codec *codec)
|
2009-10-10 19:07:35 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-20 21:40:19 +08:00
|
|
|
bool enable;
|
|
|
|
unsigned int verb, parm;
|
|
|
|
|
|
|
|
enable = is_aa_path_mute(codec) && (spec->num_active_streams > 0);
|
2009-10-10 19:07:35 +08:00
|
|
|
|
|
|
|
/* decide low current mode's verb & parameter */
|
|
|
|
switch (spec->codec_type) {
|
|
|
|
case VT1708B_8CH:
|
|
|
|
case VT1708B_4CH:
|
|
|
|
verb = 0xf70;
|
|
|
|
parm = enable ? 0x02 : 0x00; /* 0x02: 2/3x, 0x00: 1x */
|
|
|
|
break;
|
|
|
|
case VT1708S:
|
2009-10-10 19:08:34 +08:00
|
|
|
case VT1718S:
|
2009-10-10 19:08:41 +08:00
|
|
|
case VT1716S:
|
2009-10-10 19:07:35 +08:00
|
|
|
verb = 0xf73;
|
|
|
|
parm = enable ? 0x51 : 0xe1; /* 0x51: 4/28x, 0xe1: 1x */
|
|
|
|
break;
|
|
|
|
case VT1702:
|
|
|
|
verb = 0xf73;
|
|
|
|
parm = enable ? 0x01 : 0x1d; /* 0x01: 4/40x, 0x1d: 1x */
|
|
|
|
break;
|
2009-10-10 19:08:43 +08:00
|
|
|
case VT2002P:
|
2009-10-10 19:08:46 +08:00
|
|
|
case VT1812:
|
2011-03-23 17:57:34 +08:00
|
|
|
case VT1802:
|
2009-10-10 19:08:43 +08:00
|
|
|
verb = 0xf93;
|
|
|
|
parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */
|
|
|
|
break;
|
2009-10-10 19:07:35 +08:00
|
|
|
default:
|
|
|
|
return; /* other codecs are not supported */
|
|
|
|
}
|
|
|
|
/* send verb */
|
|
|
|
snd_hda_codec_write(codec, codec->afg, 0, verb, parm);
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* generic initialization of ADC, input mixers and output mixers
|
|
|
|
*/
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1708_init_verbs[] = {
|
2011-03-24 12:41:01 +08:00
|
|
|
/* power down jack detect function */
|
|
|
|
{0x1, 0xf81, 0x1},
|
2007-12-13 23:40:40 +08:00
|
|
|
{ }
|
2006-11-29 22:29:40 +08:00
|
|
|
};
|
|
|
|
|
2011-06-20 21:40:19 +08:00
|
|
|
static void set_stream_active(struct hda_codec *codec, bool active)
|
2011-06-18 22:40:14 +08:00
|
|
|
{
|
2011-06-20 21:40:19 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (active)
|
|
|
|
spec->num_active_streams++;
|
|
|
|
else
|
|
|
|
spec->num_active_streams--;
|
|
|
|
analog_low_current_mode(codec);
|
2011-06-18 22:40:14 +08:00
|
|
|
}
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
static int via_playback_multi_pcm_open(struct hda_pcm_stream *hinfo,
|
2006-11-29 22:29:40 +08:00
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-20 21:40:19 +08:00
|
|
|
int err;
|
2011-06-19 22:24:21 +08:00
|
|
|
|
|
|
|
if (!spec->hp_independent_mode)
|
|
|
|
spec->multiout.hp_nid = spec->hp_dac_nid;
|
2011-06-20 21:40:19 +08:00
|
|
|
set_stream_active(codec, true);
|
|
|
|
err = snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
|
|
|
|
hinfo);
|
|
|
|
if (err < 0) {
|
|
|
|
spec->multiout.hp_nid = 0;
|
|
|
|
set_stream_active(codec, false);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
static int via_playback_multi_pcm_close(struct hda_pcm_stream *hinfo,
|
2011-06-18 22:17:45 +08:00
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
2011-06-19 22:24:21 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
spec->multiout.hp_nid = 0;
|
2011-06-20 21:40:19 +08:00
|
|
|
set_stream_active(codec, false);
|
2011-06-18 22:40:14 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2011-06-18 22:17:45 +08:00
|
|
|
|
2011-06-18 22:40:14 +08:00
|
|
|
static int via_playback_hp_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
if (snd_BUG_ON(!spec->hp_dac_nid))
|
2011-06-18 22:40:14 +08:00
|
|
|
return -EINVAL;
|
2011-06-19 22:24:21 +08:00
|
|
|
if (!spec->hp_independent_mode || spec->multiout.hp_nid)
|
|
|
|
return -EBUSY;
|
2011-06-20 21:40:19 +08:00
|
|
|
set_stream_active(codec, true);
|
2011-06-19 22:24:21 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_playback_hp_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
2011-06-20 21:40:19 +08:00
|
|
|
set_stream_active(codec, false);
|
2011-06-18 22:17:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-18 22:40:14 +08:00
|
|
|
static int via_playback_multi_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
2008-09-09 15:58:27 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-19 22:24:21 +08:00
|
|
|
|
|
|
|
snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
|
|
|
|
format, substream);
|
2011-06-18 22:40:14 +08:00
|
|
|
vt1708_start_hp_work(spec);
|
|
|
|
return 0;
|
2008-09-09 15:58:27 +08:00
|
|
|
}
|
|
|
|
|
2011-06-18 22:40:14 +08:00
|
|
|
static int via_playback_hp_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
2008-09-09 15:58:27 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
snd_hda_codec_setup_stream(codec, spec->hp_dac_nid,
|
|
|
|
stream_tag, 0, format);
|
2009-10-10 19:08:17 +08:00
|
|
|
vt1708_start_hp_work(spec);
|
2008-09-09 15:58:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_playback_multi_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
|
2011-06-18 22:40:14 +08:00
|
|
|
vt1708_stop_hp_work(spec);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_playback_hp_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, 0, 0, 0);
|
2009-10-10 19:08:17 +08:00
|
|
|
vt1708_stop_hp_work(spec);
|
2008-09-09 15:58:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* Digital out
|
|
|
|
*/
|
|
|
|
static int via_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
|
|
|
|
}
|
|
|
|
|
2008-09-15 22:42:26 +08:00
|
|
|
static int via_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
2008-09-09 16:02:09 +08:00
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2009-05-07 22:31:14 +08:00
|
|
|
return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
|
|
|
|
stream_tag, format, substream);
|
|
|
|
}
|
2008-09-09 16:02:09 +08:00
|
|
|
|
2009-05-07 22:31:14 +08:00
|
|
|
static int via_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
|
2008-09-09 16:02:09 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* Analog capture
|
|
|
|
*/
|
|
|
|
static int via_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 via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
|
|
|
|
stream_tag, 0, format);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2008-03-18 16:57:50 +08:00
|
|
|
snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-18 22:17:45 +08:00
|
|
|
static const struct hda_pcm_stream via_pcm_analog_playback = {
|
2011-06-18 22:40:14 +08:00
|
|
|
.substreams = 1,
|
2006-11-29 22:29:40 +08:00
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 8,
|
2011-06-18 22:17:45 +08:00
|
|
|
/* NID is set in via_build_pcms */
|
2006-11-29 22:29:40 +08:00
|
|
|
.ops = {
|
2011-06-19 22:24:21 +08:00
|
|
|
.open = via_playback_multi_pcm_open,
|
|
|
|
.close = via_playback_multi_pcm_close,
|
2008-09-09 15:58:27 +08:00
|
|
|
.prepare = via_playback_multi_pcm_prepare,
|
|
|
|
.cleanup = via_playback_multi_pcm_cleanup
|
2006-11-29 22:29:40 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-06-18 22:40:14 +08:00
|
|
|
static const struct hda_pcm_stream via_pcm_hp_playback = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in via_build_pcms */
|
|
|
|
.ops = {
|
|
|
|
.open = via_playback_hp_pcm_open,
|
2011-06-19 22:24:21 +08:00
|
|
|
.close = via_playback_hp_pcm_close,
|
2011-06-18 22:40:14 +08:00
|
|
|
.prepare = via_playback_hp_pcm_prepare,
|
|
|
|
.cleanup = via_playback_hp_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct hda_pcm_stream vt1708_pcm_analog_s16_playback = {
|
2011-06-18 22:40:14 +08:00
|
|
|
.substreams = 1,
|
2008-05-23 23:50:27 +08:00
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 8,
|
2011-06-18 22:17:45 +08:00
|
|
|
/* NID is set in via_build_pcms */
|
2008-05-23 23:50:27 +08:00
|
|
|
/* We got noisy outputs on the right channel on VT1708 when
|
|
|
|
* 24bit samples are used. Until any workaround is found,
|
|
|
|
* disable the 24bit format, so far.
|
|
|
|
*/
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
|
|
.ops = {
|
2011-06-19 22:24:21 +08:00
|
|
|
.open = via_playback_multi_pcm_open,
|
|
|
|
.close = via_playback_multi_pcm_close,
|
2009-10-10 19:08:21 +08:00
|
|
|
.prepare = via_playback_multi_pcm_prepare,
|
|
|
|
.cleanup = via_playback_multi_pcm_cleanup
|
2008-05-23 23:50:27 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-06-18 22:17:45 +08:00
|
|
|
static const struct hda_pcm_stream via_pcm_analog_capture = {
|
2011-06-18 22:40:14 +08:00
|
|
|
.substreams = 1, /* will be changed in via_build_pcms() */
|
2006-11-29 22:29:40 +08:00
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
2011-06-18 22:17:45 +08:00
|
|
|
/* NID is set in via_build_pcms */
|
2006-11-29 22:29:40 +08:00
|
|
|
.ops = {
|
|
|
|
.prepare = via_capture_pcm_prepare,
|
|
|
|
.cleanup = via_capture_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-06-18 22:17:45 +08:00
|
|
|
static const struct hda_pcm_stream via_pcm_digital_playback = {
|
2006-11-29 22:29:40 +08:00
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in via_build_pcms */
|
|
|
|
.ops = {
|
|
|
|
.open = via_dig_playback_pcm_open,
|
2007-04-05 20:51:48 +08:00
|
|
|
.close = via_dig_playback_pcm_close,
|
2009-05-07 22:31:14 +08:00
|
|
|
.prepare = via_dig_playback_pcm_prepare,
|
|
|
|
.cleanup = via_dig_playback_pcm_cleanup
|
2006-11-29 22:29:40 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-06-18 22:17:45 +08:00
|
|
|
static const struct hda_pcm_stream via_pcm_digital_capture = {
|
2006-11-29 22:29:40 +08:00
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
};
|
|
|
|
|
2011-06-20 18:47:45 +08:00
|
|
|
/*
|
|
|
|
* slave controls for virtual master
|
|
|
|
*/
|
|
|
|
static const char * const via_slave_vols[] = {
|
|
|
|
"Front Playback Volume",
|
|
|
|
"Surround Playback Volume",
|
|
|
|
"Center Playback Volume",
|
|
|
|
"LFE Playback Volume",
|
|
|
|
"Side Playback Volume",
|
|
|
|
"Headphone Playback Volume",
|
|
|
|
"Speaker Playback Volume",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const via_slave_sws[] = {
|
|
|
|
"Front Playback Switch",
|
|
|
|
"Surround Playback Switch",
|
|
|
|
"Center Playback Switch",
|
|
|
|
"LFE Playback Switch",
|
|
|
|
"Side Playback Switch",
|
|
|
|
"Headphone Playback Switch",
|
|
|
|
"Speaker Playback Switch",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
static int via_build_controls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2009-12-08 23:13:32 +08:00
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
int err, i;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-17 22:59:21 +08:00
|
|
|
if (spec->set_widgets_power_state)
|
|
|
|
if (!via_clone_control(spec, &via_pin_power_ctl_enum))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
for (i = 0; i < spec->num_mixers; i++) {
|
|
|
|
err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spec->multiout.dig_out_nid) {
|
|
|
|
err = snd_hda_create_spdif_out_ctls(codec,
|
2011-06-02 01:14:18 +08:00
|
|
|
spec->multiout.dig_out_nid,
|
2006-11-29 22:29:40 +08:00
|
|
|
spec->multiout.dig_out_nid);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2008-02-13 01:37:26 +08:00
|
|
|
err = snd_hda_create_spdif_share_sw(codec,
|
|
|
|
&spec->multiout);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
spec->multiout.share_spdif = 1;
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
if (spec->dig_in_nid) {
|
|
|
|
err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2009-10-10 19:07:37 +08:00
|
|
|
|
2011-06-20 18:47:45 +08:00
|
|
|
/* if we have no master control, let's create it */
|
|
|
|
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
|
|
|
|
unsigned int vmaster_tlv[4];
|
|
|
|
snd_hda_set_vmaster_tlv(codec, spec->multiout.dac_nids[0],
|
|
|
|
HDA_OUTPUT, vmaster_tlv);
|
|
|
|
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
|
|
|
|
vmaster_tlv, via_slave_vols);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
|
|
|
|
err = snd_hda_add_vmaster(codec, "Master Playback Switch",
|
|
|
|
NULL, via_slave_sws);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-12-08 23:13:32 +08:00
|
|
|
/* assign Capture Source enums to NID */
|
|
|
|
kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
|
|
|
|
for (i = 0; kctl && i < kctl->count; i++) {
|
2009-12-23 15:31:59 +08:00
|
|
|
err = snd_hda_add_nid(codec, kctl, i, spec->mux_nids[i]);
|
2009-12-08 23:13:32 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:07:37 +08:00
|
|
|
/* init power states */
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2011-06-20 21:40:19 +08:00
|
|
|
analog_low_current_mode(codec);
|
2009-10-10 19:07:37 +08:00
|
|
|
|
2008-07-30 21:01:44 +08:00
|
|
|
via_free_kctls(codec); /* no longer needed */
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int via_build_pcms(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
struct hda_pcm *info = spec->pcm_rec;
|
|
|
|
|
|
|
|
codec->num_pcms = 1;
|
|
|
|
codec->pcm_info = info;
|
|
|
|
|
2011-06-17 22:24:21 +08:00
|
|
|
snprintf(spec->stream_name_analog, sizeof(spec->stream_name_analog),
|
|
|
|
"%s Analog", codec->chip_name);
|
2006-11-29 22:29:40 +08:00
|
|
|
info->name = spec->stream_name_analog;
|
2011-06-18 22:17:45 +08:00
|
|
|
|
|
|
|
if (!spec->stream_analog_playback)
|
|
|
|
spec->stream_analog_playback = &via_pcm_analog_playback;
|
2009-10-10 19:08:55 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
|
2011-06-18 22:17:45 +08:00
|
|
|
*spec->stream_analog_playback;
|
2009-10-10 19:08:55 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
|
|
|
|
spec->multiout.dac_nids[0];
|
2011-06-18 22:17:45 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
|
|
|
|
spec->multiout.max_channels;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-18 22:17:45 +08:00
|
|
|
if (!spec->stream_analog_capture)
|
|
|
|
spec->stream_analog_capture = &via_pcm_analog_capture;
|
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE] =
|
|
|
|
*spec->stream_analog_capture;
|
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
|
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
|
|
|
|
spec->num_adc_nids;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
|
|
|
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
|
|
|
|
codec->num_pcms++;
|
|
|
|
info++;
|
2011-06-17 22:24:21 +08:00
|
|
|
snprintf(spec->stream_name_digital,
|
|
|
|
sizeof(spec->stream_name_digital),
|
|
|
|
"%s Digital", codec->chip_name);
|
2006-11-29 22:29:40 +08:00
|
|
|
info->name = spec->stream_name_digital;
|
[ALSA] hda-intel - Fix PCM device number assignment
In the current scheme, PCM device numbers are assigned incrementally
in the order of codecs. This causes problems when the codec number
is irregular, e.g. codec #0 for HDMI and codec #1 for analog. Then
the HDMI becomes the first PCM, which is picked up as the default
output device. Unfortuantely this doesn't work well with normal
setups.
This patch introduced the fixed device numbers for the PCM types,
namely, analog, SPDIF, HDMI and modem. The PCM devices are assigned
according to the corresponding PCM type. After this patch, HDMI will
be always assigned to PCM #3, SPDIF to PCM #1, and the first analog
to PCM #0, etc.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2008-02-06 21:03:20 +08:00
|
|
|
info->pcm_type = HDA_PCM_TYPE_SPDIF;
|
2006-11-29 22:29:40 +08:00
|
|
|
if (spec->multiout.dig_out_nid) {
|
2011-06-18 22:17:45 +08:00
|
|
|
if (!spec->stream_digital_playback)
|
|
|
|
spec->stream_digital_playback =
|
|
|
|
&via_pcm_digital_playback;
|
2006-11-29 22:29:40 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
|
2011-06-18 22:17:45 +08:00
|
|
|
*spec->stream_digital_playback;
|
2006-11-29 22:29:40 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
|
|
|
|
spec->multiout.dig_out_nid;
|
|
|
|
}
|
|
|
|
if (spec->dig_in_nid) {
|
2011-06-18 22:17:45 +08:00
|
|
|
if (!spec->stream_digital_capture)
|
|
|
|
spec->stream_digital_capture =
|
|
|
|
&via_pcm_digital_capture;
|
2006-11-29 22:29:40 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE] =
|
2011-06-18 22:17:45 +08:00
|
|
|
*spec->stream_digital_capture;
|
2006-11-29 22:29:40 +08:00
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
|
|
|
|
spec->dig_in_nid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
if (spec->hp_dac_nid) {
|
2011-06-18 22:40:14 +08:00
|
|
|
codec->num_pcms++;
|
|
|
|
info++;
|
|
|
|
snprintf(spec->stream_name_hp, sizeof(spec->stream_name_hp),
|
|
|
|
"%s HP", codec->chip_name);
|
|
|
|
info->name = spec->stream_name_hp;
|
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = via_pcm_hp_playback;
|
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
|
2011-06-19 22:24:21 +08:00
|
|
|
spec->hp_dac_nid;
|
2011-06-18 22:40:14 +08:00
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void via_free(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (!spec)
|
|
|
|
return;
|
|
|
|
|
2008-07-30 21:01:44 +08:00
|
|
|
via_free_kctls(codec);
|
2009-10-10 19:08:17 +08:00
|
|
|
vt1708_stop_hp_work(spec);
|
2006-11-29 22:29:40 +08:00
|
|
|
kfree(codec->spec);
|
|
|
|
}
|
|
|
|
|
2011-06-17 22:51:39 +08:00
|
|
|
/* mute/unmute outputs */
|
|
|
|
static void toggle_output_mutes(struct hda_codec *codec, int num_pins,
|
|
|
|
hda_nid_t *pins, bool mute)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < num_pins; i++)
|
|
|
|
snd_hda_codec_write(codec, pins[i], 0,
|
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
|
mute ? 0 : PIN_OUT);
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
/* mute internal speaker if line-out is plugged */
|
|
|
|
static void via_line_automute(struct hda_codec *codec, int present)
|
2008-09-09 15:57:32 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
if (!spec->autocfg.speaker_outs)
|
|
|
|
return;
|
|
|
|
if (!present)
|
|
|
|
present = snd_hda_jack_detect(codec,
|
|
|
|
spec->autocfg.line_out_pins[0]);
|
|
|
|
toggle_output_mutes(codec, spec->autocfg.speaker_outs,
|
|
|
|
spec->autocfg.speaker_pins,
|
|
|
|
present);
|
2008-09-09 15:57:32 +08:00
|
|
|
}
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
/* mute internal speaker if HP is plugged */
|
|
|
|
static void via_hp_automute(struct hda_codec *codec)
|
2009-10-10 19:08:41 +08:00
|
|
|
{
|
2011-06-20 18:39:26 +08:00
|
|
|
int present = 0;
|
2009-10-10 19:08:41 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
if (!spec->hp_independent_mode && spec->autocfg.hp_pins[0]) {
|
|
|
|
present = snd_hda_jack_detect(codec, spec->autocfg.hp_pins[0]);
|
|
|
|
toggle_output_mutes(codec, spec->autocfg.line_outs,
|
|
|
|
spec->autocfg.line_out_pins,
|
|
|
|
present);
|
2009-10-10 19:08:41 +08:00
|
|
|
}
|
2011-06-20 18:39:26 +08:00
|
|
|
via_line_automute(codec, present);
|
2009-10-10 19:08:41 +08:00
|
|
|
}
|
|
|
|
|
2008-09-09 15:57:32 +08:00
|
|
|
static void via_gpio_control(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
unsigned int gpio_data;
|
|
|
|
unsigned int vol_counter;
|
|
|
|
unsigned int vol;
|
|
|
|
unsigned int master_vol;
|
|
|
|
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
gpio_data = snd_hda_codec_read(codec, codec->afg, 0,
|
|
|
|
AC_VERB_GET_GPIO_DATA, 0) & 0x03;
|
|
|
|
|
|
|
|
vol_counter = (snd_hda_codec_read(codec, codec->afg, 0,
|
|
|
|
0xF84, 0) & 0x3F0000) >> 16;
|
|
|
|
|
|
|
|
vol = vol_counter & 0x1F;
|
|
|
|
master_vol = snd_hda_codec_read(codec, 0x1A, 0,
|
|
|
|
AC_VERB_GET_AMP_GAIN_MUTE,
|
|
|
|
AC_AMP_GET_INPUT);
|
|
|
|
|
|
|
|
if (gpio_data == 0x02) {
|
|
|
|
/* unmute line out */
|
2011-06-17 22:37:45 +08:00
|
|
|
snd_hda_codec_write(codec, spec->autocfg.line_out_pins[0], 0,
|
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
|
PIN_OUT);
|
2008-09-09 15:57:32 +08:00
|
|
|
if (vol_counter & 0x20) {
|
|
|
|
/* decrease volume */
|
|
|
|
if (vol > master_vol)
|
|
|
|
vol = master_vol;
|
|
|
|
snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT,
|
|
|
|
0, HDA_AMP_VOLMASK,
|
|
|
|
master_vol-vol);
|
|
|
|
} else {
|
|
|
|
/* increase volume */
|
|
|
|
snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT, 0,
|
|
|
|
HDA_AMP_VOLMASK,
|
|
|
|
((master_vol+vol) > 0x2A) ? 0x2A :
|
|
|
|
(master_vol+vol));
|
|
|
|
}
|
|
|
|
} else if (!(gpio_data & 0x02)) {
|
|
|
|
/* mute line out */
|
2011-06-17 22:37:45 +08:00
|
|
|
snd_hda_codec_write(codec, spec->autocfg.line_out_pins[0], 0,
|
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
|
0);
|
2008-09-09 15:57:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* unsolicited event for jack sensing */
|
|
|
|
static void via_unsol_event(struct hda_codec *codec,
|
|
|
|
unsigned int res)
|
|
|
|
{
|
|
|
|
res >>= 26;
|
2011-03-24 12:43:44 +08:00
|
|
|
|
2009-10-10 19:08:01 +08:00
|
|
|
if (res & VIA_JACK_EVENT)
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2011-03-24 12:43:44 +08:00
|
|
|
|
|
|
|
res &= ~VIA_JACK_EVENT;
|
|
|
|
|
|
|
|
if (res == VIA_HP_EVENT)
|
|
|
|
via_hp_automute(codec);
|
|
|
|
else if (res == VIA_GPIO_EVENT)
|
|
|
|
via_gpio_control(codec);
|
2011-06-20 18:39:26 +08:00
|
|
|
else if (res == VIA_LINE_EVENT)
|
|
|
|
via_line_automute(codec, false);
|
2008-09-09 15:57:32 +08:00
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
#ifdef SND_HDA_NEEDS_RESUME
|
|
|
|
static int via_suspend(struct hda_codec *codec, pm_message_t state)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
vt1708_stop_hp_work(spec);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2007-08-10 23:21:45 +08:00
|
|
|
#ifdef CONFIG_SND_HDA_POWER_SAVE
|
|
|
|
static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
*/
|
2011-06-20 17:32:27 +08:00
|
|
|
|
|
|
|
static int via_init(struct hda_codec *codec);
|
|
|
|
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct hda_codec_ops via_patch_ops = {
|
2006-11-29 22:29:40 +08:00
|
|
|
.build_controls = via_build_controls,
|
|
|
|
.build_pcms = via_build_pcms,
|
|
|
|
.init = via_init,
|
|
|
|
.free = via_free,
|
2011-06-20 18:39:26 +08:00
|
|
|
.unsol_event = via_unsol_event,
|
2009-10-10 19:08:17 +08:00
|
|
|
#ifdef SND_HDA_NEEDS_RESUME
|
|
|
|
.suspend = via_suspend,
|
|
|
|
#endif
|
2007-08-10 23:21:45 +08:00
|
|
|
#ifdef CONFIG_SND_HDA_POWER_SAVE
|
|
|
|
.check_power_status = via_check_power_status,
|
|
|
|
#endif
|
2006-11-29 22:29:40 +08:00
|
|
|
};
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
static bool is_empty_dac(struct hda_codec *codec, hda_nid_t dac)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->multiout.num_dacs; i++) {
|
|
|
|
if (spec->multiout.dac_nids[i] == dac)
|
|
|
|
return false;
|
|
|
|
}
|
2011-06-19 22:24:21 +08:00
|
|
|
if (spec->hp_dac_nid == dac)
|
2011-06-17 23:53:38 +08:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-06-21 15:01:36 +08:00
|
|
|
static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid,
|
2011-06-17 23:53:38 +08:00
|
|
|
hda_nid_t target_dac, struct nid_path *path,
|
|
|
|
int depth, int wid_type)
|
|
|
|
{
|
|
|
|
hda_nid_t conn[8];
|
|
|
|
int i, nums;
|
|
|
|
|
|
|
|
nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn));
|
|
|
|
for (i = 0; i < nums; i++) {
|
|
|
|
if (get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT)
|
|
|
|
continue;
|
|
|
|
if (conn[i] == target_dac || is_empty_dac(codec, conn[i])) {
|
2011-06-21 15:01:36 +08:00
|
|
|
path->path[0] = conn[i];
|
|
|
|
path->idx[0] = i;
|
|
|
|
path->depth = 1;
|
2011-06-17 23:53:38 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2011-06-21 15:01:36 +08:00
|
|
|
if (depth >= MAX_NID_PATH_DEPTH)
|
2011-06-17 23:53:38 +08:00
|
|
|
return false;
|
|
|
|
for (i = 0; i < nums; i++) {
|
|
|
|
unsigned int type;
|
|
|
|
type = get_wcaps_type(get_wcaps(codec, conn[i]));
|
|
|
|
if (type == AC_WID_AUD_OUT ||
|
|
|
|
(wid_type != -1 && type != wid_type))
|
|
|
|
continue;
|
2011-06-21 15:01:36 +08:00
|
|
|
if (__parse_output_path(codec, conn[i], target_dac,
|
2011-06-17 23:53:38 +08:00
|
|
|
path, depth + 1, AC_WID_AUD_SEL)) {
|
2011-06-21 15:01:36 +08:00
|
|
|
path->path[path->depth] = conn[i];
|
|
|
|
path->idx[path->depth] = i;
|
|
|
|
path->depth++;
|
2011-06-17 23:53:38 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-06-21 15:01:36 +08:00
|
|
|
static bool parse_output_path(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
hda_nid_t target_dac, struct nid_path *path)
|
|
|
|
{
|
|
|
|
if (__parse_output_path(codec, nid, target_dac, path, 1, -1)) {
|
|
|
|
path->path[path->depth] = nid;
|
|
|
|
path->depth++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
static int via_auto_fill_dac_nids(struct hda_codec *codec)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2011-06-17 23:53:38 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
2006-11-29 22:29:40 +08:00
|
|
|
int i;
|
|
|
|
hda_nid_t nid;
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
spec->multiout.dac_nids = spec->private_dac_nids;
|
2006-11-29 22:29:40 +08:00
|
|
|
spec->multiout.num_dacs = cfg->line_outs;
|
2011-06-17 23:53:38 +08:00
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
nid = cfg->line_out_pins[i];
|
|
|
|
if (!nid)
|
|
|
|
continue;
|
2011-06-21 15:01:36 +08:00
|
|
|
if (parse_output_path(codec, nid, 0, &spec->out_path[i]))
|
|
|
|
spec->private_dac_nids[i] = spec->out_path[i].path[0];
|
2011-06-17 23:53:38 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
static int create_ch_ctls(struct hda_codec *codec, const char *pfx,
|
|
|
|
hda_nid_t pin, hda_nid_t dac, int chs)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
char name[32];
|
|
|
|
hda_nid_t nid;
|
|
|
|
int err;
|
2009-10-10 19:08:55 +08:00
|
|
|
|
2011-06-21 17:48:29 +08:00
|
|
|
if (dac && check_amp_caps(codec, dac, HDA_OUTPUT, AC_AMPCAP_NUM_STEPS))
|
2011-06-17 23:53:38 +08:00
|
|
|
nid = dac;
|
2011-06-21 17:48:29 +08:00
|
|
|
else if (check_amp_caps(codec, pin, HDA_OUTPUT, AC_AMPCAP_NUM_STEPS))
|
2011-06-17 23:53:38 +08:00
|
|
|
nid = pin;
|
|
|
|
else
|
|
|
|
nid = 0;
|
|
|
|
if (nid) {
|
|
|
|
sprintf(name, "%s Playback Volume", pfx);
|
|
|
|
err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
|
2011-06-21 16:11:11 +08:00
|
|
|
HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
|
2011-06-17 23:53:38 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
|
|
|
|
2011-06-21 17:48:29 +08:00
|
|
|
if (dac && check_amp_caps(codec, dac, HDA_OUTPUT, AC_AMPCAP_MUTE))
|
2011-06-17 23:53:38 +08:00
|
|
|
nid = dac;
|
2011-06-21 17:48:29 +08:00
|
|
|
else if (check_amp_caps(codec, pin, HDA_OUTPUT, AC_AMPCAP_MUTE))
|
2011-06-17 23:53:38 +08:00
|
|
|
nid = pin;
|
|
|
|
else
|
|
|
|
nid = 0;
|
|
|
|
if (nid) {
|
|
|
|
sprintf(name, "%s Playback Switch", pfx);
|
|
|
|
err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
|
|
|
|
HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
static void mangle_smart51(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
2011-06-20 19:52:33 +08:00
|
|
|
int i, nums = 0;
|
2011-06-18 00:46:48 +08:00
|
|
|
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
2011-06-20 19:52:33 +08:00
|
|
|
if (is_smart51_candidate(codec, cfg->inputs[i].pin))
|
|
|
|
nums++;
|
|
|
|
}
|
|
|
|
if (cfg->line_outs + nums < 3)
|
|
|
|
return;
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
if (!is_smart51_candidate(codec, cfg->inputs[i].pin))
|
2011-06-18 00:46:48 +08:00
|
|
|
continue;
|
2011-06-20 19:52:33 +08:00
|
|
|
spec->smart51_pins[spec->smart51_nums++] = cfg->inputs[i].pin;
|
2011-06-18 00:46:48 +08:00
|
|
|
cfg->line_out_pins[cfg->line_outs++] = cfg->inputs[i].pin;
|
|
|
|
if (cfg->line_outs == 3)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/* add playback controls from the parsed DAC table */
|
2011-06-17 23:53:38 +08:00
|
|
|
static int via_auto_create_multi_out_ctls(struct hda_codec *codec)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2011-06-17 23:53:38 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-18 00:46:48 +08:00
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
2011-01-17 18:29:34 +08:00
|
|
|
static const char * const chname[4] = {
|
|
|
|
"Front", "Surround", "C/LFE", "Side"
|
|
|
|
};
|
2011-06-17 23:53:38 +08:00
|
|
|
int i, idx, err;
|
2011-06-18 00:46:48 +08:00
|
|
|
int old_line_outs;
|
|
|
|
|
|
|
|
/* check smart51 */
|
|
|
|
old_line_outs = cfg->line_outs;
|
|
|
|
if (cfg->line_outs == 1)
|
|
|
|
mangle_smart51(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-20 19:52:33 +08:00
|
|
|
err = via_auto_fill_dac_nids(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
hda_nid_t pin, dac;
|
|
|
|
pin = cfg->line_out_pins[i];
|
|
|
|
dac = spec->multiout.dac_nids[i];
|
|
|
|
if (!pin || !dac)
|
2006-11-29 22:29:40 +08:00
|
|
|
continue;
|
2011-06-19 22:27:53 +08:00
|
|
|
if (i == HDA_CLFE) {
|
2011-06-17 23:53:38 +08:00
|
|
|
err = create_ch_ctls(codec, "Center", pin, dac, 1);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2011-06-17 23:53:38 +08:00
|
|
|
err = create_ch_ctls(codec, "LFE", pin, dac, 2);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
} else {
|
2011-06-20 20:09:02 +08:00
|
|
|
const char *pfx = chname[i];
|
|
|
|
if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
|
|
|
|
cfg->line_outs == 1)
|
|
|
|
pfx = "Speaker";
|
|
|
|
err = create_ch_ctls(codec, pfx, pin, dac, 3);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
idx = get_connection_index(codec, spec->aa_mix_nid,
|
|
|
|
spec->multiout.dac_nids[0]);
|
|
|
|
if (idx >= 0) {
|
|
|
|
/* add control to mixer */
|
|
|
|
err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
|
|
|
|
"PCM Playback Volume",
|
|
|
|
HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3,
|
|
|
|
idx, HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
|
|
|
|
"PCM Playback Switch",
|
|
|
|
HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3,
|
|
|
|
idx, HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
cfg->line_outs = old_line_outs;
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
static int via_auto_create_hp_ctls(struct hda_codec *codec, hda_nid_t pin)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2011-06-17 23:53:38 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2006-11-29 22:29:40 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!pin)
|
|
|
|
return 0;
|
|
|
|
|
2011-06-21 17:48:29 +08:00
|
|
|
if (parse_output_path(codec, pin, 0, &spec->hp_path))
|
2011-06-21 15:01:36 +08:00
|
|
|
spec->hp_dac_nid = spec->hp_path.path[0];
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-19 22:24:21 +08:00
|
|
|
if (!parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
|
2011-06-21 15:01:36 +08:00
|
|
|
&spec->hp_dep_path) &&
|
2011-06-19 22:24:21 +08:00
|
|
|
!spec->hp_dac_nid)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err = create_ch_ctls(codec, "Headphone", pin, spec->hp_dac_nid, 3);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
static int via_auto_create_speaker_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
hda_nid_t pin, dac;
|
|
|
|
|
|
|
|
pin = spec->autocfg.speaker_pins[0];
|
|
|
|
if (!spec->autocfg.speaker_outs || !pin)
|
|
|
|
return 0;
|
|
|
|
|
2011-06-21 15:01:36 +08:00
|
|
|
if (parse_output_path(codec, pin, 0, &spec->speaker_path)) {
|
|
|
|
dac = spec->speaker_path.path[0];
|
2011-06-20 18:39:26 +08:00
|
|
|
spec->multiout.extra_out_nid[0] = dac;
|
|
|
|
return create_ch_ctls(codec, "Speaker", pin, dac, 3);
|
|
|
|
}
|
|
|
|
if (parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT],
|
2011-06-21 15:01:36 +08:00
|
|
|
&spec->speaker_path))
|
|
|
|
return create_ch_ctls(codec, "Speaker", pin, 0, 3);
|
2011-06-20 18:39:26 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-17 15:01:29 +08:00
|
|
|
/* look for ADCs */
|
|
|
|
static int via_fill_adcs(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
hda_nid_t nid = codec->start_nid;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < codec->num_nodes; i++, nid++) {
|
|
|
|
unsigned int wcaps = get_wcaps(codec, nid);
|
|
|
|
if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
|
|
|
|
continue;
|
|
|
|
if (wcaps & AC_WCAP_DIGITAL)
|
|
|
|
continue;
|
|
|
|
if (!(wcaps & AC_WCAP_CONN_LIST))
|
|
|
|
continue;
|
|
|
|
if (spec->num_adc_nids >= ARRAY_SIZE(spec->adc_nids))
|
|
|
|
return -ENOMEM;
|
|
|
|
spec->adc_nids[spec->num_adc_nids++] = nid;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_mux_nids(struct hda_codec *codec);
|
|
|
|
|
2011-06-18 23:24:46 +08:00
|
|
|
static const struct snd_kcontrol_new via_input_src_ctl = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
/* The multiple "Capture Source" controls confuse alsamixer
|
|
|
|
* So call somewhat different..
|
|
|
|
*/
|
|
|
|
/* .name = "Capture Source", */
|
|
|
|
.name = "Input Source",
|
|
|
|
.info = via_mux_enum_info,
|
|
|
|
.get = via_mux_enum_get,
|
|
|
|
.put = via_mux_enum_put,
|
|
|
|
};
|
|
|
|
|
2011-06-20 20:05:46 +08:00
|
|
|
static void add_loopback_list(struct via_spec *spec, hda_nid_t mix, int idx)
|
|
|
|
{
|
|
|
|
struct hda_amp_list *list;
|
|
|
|
|
|
|
|
if (spec->num_loopbacks >= ARRAY_SIZE(spec->loopback_list) - 1)
|
|
|
|
return;
|
|
|
|
list = spec->loopback_list + spec->num_loopbacks;
|
|
|
|
list->nid = mix;
|
|
|
|
list->dir = HDA_INPUT;
|
|
|
|
list->idx = idx;
|
|
|
|
spec->num_loopbacks++;
|
|
|
|
spec->loopback.amplist = spec->loopback_list;
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/* create playback/capture controls for input pins */
|
2011-06-17 23:19:19 +08:00
|
|
|
static int via_auto_create_analog_input_ctls(struct hda_codec *codec,
|
|
|
|
const struct auto_pin_cfg *cfg)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2010-09-09 22:28:02 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2008-09-09 15:58:27 +08:00
|
|
|
struct hda_input_mux *imux = &spec->private_imux[0];
|
2011-06-20 19:52:33 +08:00
|
|
|
int i, j, err, idx, idx2, type, type_idx = 0;
|
2011-06-17 15:01:29 +08:00
|
|
|
hda_nid_t cap_nid;
|
|
|
|
hda_nid_t pin_idxs[8];
|
|
|
|
int num_idxs;
|
|
|
|
|
|
|
|
err = via_fill_adcs(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = get_mux_nids(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
cap_nid = spec->mux_nids[0];
|
|
|
|
|
|
|
|
num_idxs = snd_hda_get_connections(codec, cap_nid, pin_idxs,
|
|
|
|
ARRAY_SIZE(pin_idxs));
|
|
|
|
if (num_idxs <= 0)
|
|
|
|
return 0;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
|
|
|
/* for internal loopback recording select */
|
2010-08-30 17:00:19 +08:00
|
|
|
for (idx = 0; idx < num_idxs; idx++) {
|
2011-06-17 23:19:19 +08:00
|
|
|
if (pin_idxs[idx] == spec->aa_mix_nid) {
|
2010-09-09 22:28:02 +08:00
|
|
|
snd_hda_add_imux_item(imux, "Stereo Mixer", idx, NULL);
|
2010-08-30 17:00:19 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2010-08-30 19:06:30 +08:00
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
2010-09-09 22:28:02 +08:00
|
|
|
const char *label;
|
2010-08-30 19:06:30 +08:00
|
|
|
type = cfg->inputs[i].type;
|
2010-08-30 17:00:19 +08:00
|
|
|
for (idx = 0; idx < num_idxs; idx++)
|
2010-08-30 19:06:30 +08:00
|
|
|
if (pin_idxs[idx] == cfg->inputs[i].pin)
|
2010-08-30 17:00:19 +08:00
|
|
|
break;
|
|
|
|
if (idx >= num_idxs)
|
|
|
|
continue;
|
2010-08-30 19:06:30 +08:00
|
|
|
if (i > 0 && type == cfg->inputs[i - 1].type)
|
|
|
|
type_idx++;
|
|
|
|
else
|
|
|
|
type_idx = 0;
|
2010-09-09 22:28:02 +08:00
|
|
|
label = hda_get_autocfg_input_label(codec, cfg, i);
|
2011-06-17 23:19:19 +08:00
|
|
|
idx2 = get_connection_index(codec, spec->aa_mix_nid,
|
|
|
|
pin_idxs[idx]);
|
2011-06-20 20:05:46 +08:00
|
|
|
if (idx2 >= 0) {
|
2011-03-22 16:24:10 +08:00
|
|
|
err = via_new_analog_input(spec, label, type_idx,
|
2011-06-17 23:19:19 +08:00
|
|
|
idx2, spec->aa_mix_nid);
|
2011-06-20 20:05:46 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
add_loopback_list(spec, spec->aa_mix_nid, idx2);
|
|
|
|
}
|
2010-09-09 22:28:02 +08:00
|
|
|
snd_hda_add_imux_item(imux, label, idx, NULL);
|
2011-06-20 19:52:33 +08:00
|
|
|
|
|
|
|
/* remember the label for smart51 control */
|
|
|
|
for (j = 0; j < spec->smart51_nums; j++) {
|
|
|
|
if (spec->smart51_pins[j] == cfg->inputs[i].pin) {
|
|
|
|
spec->smart51_idxs[j] = idx;
|
|
|
|
spec->smart51_labels[j] = label;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
}
|
2011-06-18 23:24:46 +08:00
|
|
|
|
|
|
|
/* create capture mixer elements */
|
|
|
|
for (i = 0; i < spec->num_adc_nids; i++) {
|
|
|
|
hda_nid_t adc = spec->adc_nids[i];
|
|
|
|
err = __via_add_control(spec, VIA_CTL_WIDGET_VOL,
|
|
|
|
"Capture Volume", i,
|
|
|
|
HDA_COMPOSE_AMP_VAL(adc, 3, 0,
|
|
|
|
HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = __via_add_control(spec, VIA_CTL_WIDGET_MUTE,
|
|
|
|
"Capture Switch", i,
|
|
|
|
HDA_COMPOSE_AMP_VAL(adc, 3, 0,
|
|
|
|
HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* input-source control */
|
|
|
|
for (i = 0; i < spec->num_adc_nids; i++)
|
|
|
|
if (!spec->mux_nids[i])
|
|
|
|
break;
|
|
|
|
if (i) {
|
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
knew = via_clone_control(spec, &via_input_src_ctl);
|
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->count = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mic-boosts */
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t pin = cfg->inputs[i].pin;
|
|
|
|
unsigned int caps;
|
|
|
|
const char *label;
|
|
|
|
char name[32];
|
|
|
|
|
|
|
|
if (cfg->inputs[i].type != AUTO_PIN_MIC)
|
|
|
|
continue;
|
|
|
|
caps = query_amp_caps(codec, pin, HDA_INPUT);
|
|
|
|
if (caps == -1 || !(caps & AC_AMPCAP_NUM_STEPS))
|
|
|
|
continue;
|
|
|
|
label = hda_get_autocfg_input_label(codec, cfg, i);
|
2011-06-21 14:37:41 +08:00
|
|
|
snprintf(name, sizeof(name), "%s Boost Volume", label);
|
2011-06-18 23:24:46 +08:00
|
|
|
err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
|
|
|
|
HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_INPUT));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-09 15:50:37 +08:00
|
|
|
static void vt1708_set_pinconfig_connect(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
unsigned int def_conf;
|
|
|
|
unsigned char seqassoc;
|
|
|
|
|
2009-02-20 21:37:42 +08:00
|
|
|
def_conf = snd_hda_codec_get_pincfg(codec, nid);
|
2008-09-09 15:50:37 +08:00
|
|
|
seqassoc = (unsigned char) get_defcfg_association(def_conf);
|
|
|
|
seqassoc = (seqassoc << 4) | get_defcfg_sequence(def_conf);
|
2009-10-10 19:08:19 +08:00
|
|
|
if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE
|
|
|
|
&& (seqassoc == 0xf0 || seqassoc == 0xff)) {
|
|
|
|
def_conf = def_conf & (~(AC_JACK_PORT_BOTH << 30));
|
|
|
|
snd_hda_codec_set_pincfg(codec, nid, def_conf);
|
2008-09-09 15:50:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-17 21:46:13 +08:00
|
|
|
static int vt1708_jack_detect_get(struct snd_kcontrol *kcontrol,
|
2009-10-10 19:08:17 +08:00
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->codec_type != VT1708)
|
|
|
|
return 0;
|
2011-06-17 21:46:13 +08:00
|
|
|
spec->vt1708_jack_detect =
|
2009-10-10 19:08:17 +08:00
|
|
|
!((snd_hda_codec_read(codec, 0x1, 0, 0xf84, 0) >> 8) & 0x1);
|
2011-06-17 21:46:13 +08:00
|
|
|
ucontrol->value.integer.value[0] = spec->vt1708_jack_detect;
|
2009-10-10 19:08:17 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-17 21:46:13 +08:00
|
|
|
static int vt1708_jack_detect_put(struct snd_kcontrol *kcontrol,
|
2009-10-10 19:08:17 +08:00
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int change;
|
|
|
|
|
|
|
|
if (spec->codec_type != VT1708)
|
|
|
|
return 0;
|
2011-06-17 21:46:13 +08:00
|
|
|
spec->vt1708_jack_detect = ucontrol->value.integer.value[0];
|
2009-10-10 19:08:17 +08:00
|
|
|
change = (0x1 & (snd_hda_codec_read(codec, 0x1, 0, 0xf84, 0) >> 8))
|
2011-06-17 21:46:13 +08:00
|
|
|
== !spec->vt1708_jack_detect;
|
|
|
|
if (spec->vt1708_jack_detect) {
|
2009-10-10 19:08:17 +08:00
|
|
|
mute_aa_path(codec, 1);
|
|
|
|
notify_aa_path_ctls(codec);
|
|
|
|
}
|
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
2011-06-17 21:46:13 +08:00
|
|
|
static const struct snd_kcontrol_new vt1708_jack_detect_ctl = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Jack Detect",
|
|
|
|
.count = 1,
|
|
|
|
.info = snd_ctl_boolean_mono_info,
|
|
|
|
.get = vt1708_jack_detect_get,
|
|
|
|
.put = vt1708_jack_detect_put,
|
2009-10-10 19:08:17 +08:00
|
|
|
};
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
static void fill_dig_outs(struct hda_codec *codec);
|
|
|
|
static void fill_dig_in(struct hda_codec *codec);
|
|
|
|
|
|
|
|
static int via_parse_auto_config(struct hda_codec *codec)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
|
2011-06-18 23:33:34 +08:00
|
|
|
return -EINVAL;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
err = via_auto_create_multi_out_ctls(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2011-06-17 23:53:38 +08:00
|
|
|
err = via_auto_create_hp_ctls(codec, spec->autocfg.hp_pins[0]);
|
2011-06-20 18:39:26 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = via_auto_create_speaker_ctls(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2011-06-17 23:19:19 +08:00
|
|
|
err = via_auto_create_analog_input_ctls(codec, &spec->autocfg);
|
2009-10-10 19:08:17 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
|
|
|
spec->multiout.max_channels = spec->multiout.num_dacs * 2;
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
fill_dig_outs(codec);
|
|
|
|
fill_dig_in(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2008-07-30 21:01:44 +08:00
|
|
|
if (spec->kctls.list)
|
|
|
|
spec->mixers[spec->num_mixers++] = spec->kctls.list;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1708_init_verbs;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2008-09-09 15:58:27 +08:00
|
|
|
spec->input_mux = &spec->private_imux[0];
|
|
|
|
|
2011-06-21 17:48:29 +08:00
|
|
|
if (spec->hp_dac_nid && spec->hp_dep_path.depth) {
|
2011-06-19 22:24:21 +08:00
|
|
|
err = via_hp_build(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2006-11-29 22:29:40 +08:00
|
|
|
|
2011-06-18 00:46:48 +08:00
|
|
|
err = via_smart51_build(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
/* assign slave outs */
|
|
|
|
if (spec->slave_dig_outs[0])
|
|
|
|
codec->slave_dig_outs = spec->slave_dig_outs;
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
static void via_auto_init_dig_outs(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
if (spec->multiout.dig_out_nid)
|
|
|
|
init_output_pin(codec, spec->autocfg.dig_out_pins[0], PIN_OUT);
|
|
|
|
if (spec->slave_dig_outs[0])
|
|
|
|
init_output_pin(codec, spec->autocfg.dig_out_pins[1], PIN_OUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void via_auto_init_dig_in(struct hda_codec *codec)
|
2006-11-29 22:29:40 +08:00
|
|
|
{
|
2009-10-10 19:08:43 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-20 17:32:27 +08:00
|
|
|
if (!spec->dig_in_nid)
|
|
|
|
return;
|
|
|
|
snd_hda_codec_write(codec, spec->autocfg.dig_in_pin, 0,
|
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN);
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
/* initialize the unsolicited events */
|
|
|
|
static void via_auto_init_unsol_event(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
unsigned int ev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (cfg->hp_pins[0] && is_jack_detectable(codec, cfg->hp_pins[0]))
|
|
|
|
snd_hda_codec_write(codec, cfg->hp_pins[0], 0,
|
|
|
|
AC_VERB_SET_UNSOLICITED_ENABLE,
|
|
|
|
AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT);
|
|
|
|
|
|
|
|
if (cfg->speaker_pins[0])
|
|
|
|
ev = VIA_LINE_EVENT;
|
|
|
|
else
|
|
|
|
ev = 0;
|
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
if (cfg->line_out_pins[i] &&
|
|
|
|
is_jack_detectable(codec, cfg->line_out_pins[i]))
|
|
|
|
snd_hda_codec_write(codec, cfg->line_out_pins[0], 0,
|
|
|
|
AC_VERB_SET_UNSOLICITED_ENABLE,
|
|
|
|
AC_USRSP_EN | ev | VIA_JACK_EVENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
if (is_jack_detectable(codec, cfg->inputs[i].pin))
|
|
|
|
snd_hda_codec_write(codec, cfg->inputs[i].pin, 0,
|
|
|
|
AC_VERB_SET_UNSOLICITED_ENABLE,
|
|
|
|
AC_USRSP_EN | VIA_JACK_EVENT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-20 17:32:27 +08:00
|
|
|
static int via_init(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->num_iverbs; i++)
|
|
|
|
snd_hda_sequence_write(codec, spec->init_verbs[i]);
|
2009-10-10 19:08:43 +08:00
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
via_auto_init_multi_out(codec);
|
|
|
|
via_auto_init_hp_out(codec);
|
2011-06-20 18:39:26 +08:00
|
|
|
via_auto_init_speaker_out(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
via_auto_init_analog_input(codec);
|
2011-06-20 17:32:27 +08:00
|
|
|
via_auto_init_dig_outs(codec);
|
|
|
|
via_auto_init_dig_in(codec);
|
2011-03-23 17:57:34 +08:00
|
|
|
|
2011-06-20 18:39:26 +08:00
|
|
|
via_auto_init_unsol_event(codec);
|
|
|
|
|
|
|
|
via_hp_automute(codec);
|
|
|
|
via_line_automute(codec, false);
|
2009-10-10 19:08:43 +08:00
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
static void vt1708_update_hp_jack_state(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = container_of(work, struct via_spec,
|
|
|
|
vt1708_hp_work.work);
|
|
|
|
if (spec->codec_type != VT1708)
|
|
|
|
return;
|
|
|
|
/* if jack state toggled */
|
|
|
|
if (spec->vt1708_hp_present
|
2009-11-18 15:00:14 +08:00
|
|
|
!= snd_hda_jack_detect(spec->codec, spec->autocfg.hp_pins[0])) {
|
2009-10-10 19:08:17 +08:00
|
|
|
spec->vt1708_hp_present ^= 1;
|
|
|
|
via_hp_automute(spec->codec);
|
|
|
|
}
|
|
|
|
vt1708_start_hp_work(spec);
|
|
|
|
}
|
|
|
|
|
2009-07-08 00:18:59 +08:00
|
|
|
static int get_mux_nids(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
hda_nid_t nid, conn[8];
|
|
|
|
unsigned int type;
|
|
|
|
int i, n;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->num_adc_nids; i++) {
|
|
|
|
nid = spec->adc_nids[i];
|
|
|
|
while (nid) {
|
2009-07-27 18:54:26 +08:00
|
|
|
type = get_wcaps_type(get_wcaps(codec, nid));
|
2009-07-08 13:45:46 +08:00
|
|
|
if (type == AC_WID_PIN)
|
|
|
|
break;
|
2009-07-08 00:18:59 +08:00
|
|
|
n = snd_hda_get_connections(codec, nid, conn,
|
|
|
|
ARRAY_SIZE(conn));
|
|
|
|
if (n <= 0)
|
|
|
|
break;
|
|
|
|
if (n > 1) {
|
|
|
|
spec->mux_nids[i] = nid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nid = conn[0];
|
|
|
|
}
|
|
|
|
}
|
2009-07-08 13:45:46 +08:00
|
|
|
return 0;
|
2009-07-08 00:18:59 +08:00
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
static int patch_vt1708(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x17;
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
/* Add HP and CD pin config connect bit re-config action */
|
|
|
|
vt1708_set_pinconfig_connect(codec, VT1708_HP_PIN_NID);
|
|
|
|
vt1708_set_pinconfig_connect(codec, VT1708_CD_PIN_NID);
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
/* add jack detect on/off control */
|
|
|
|
if (!via_clone_control(spec, &vt1708_jack_detect_ctl))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2008-05-23 23:50:27 +08:00
|
|
|
/* disable 32bit format on VT1708 */
|
|
|
|
if (codec->vendor_id == 0x11061708)
|
|
|
|
spec->stream_analog_playback = &vt1708_pcm_analog_s16_playback;
|
2006-11-29 22:29:40 +08:00
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2009-10-10 19:08:17 +08:00
|
|
|
INIT_DELAYED_WORK(&spec->vt1708_hp_work, vt1708_update_hp_jack_state);
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int patch_vt1709_10ch(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x18;
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* generic initialization of ADC, input mixers and output mixers
|
|
|
|
*/
|
|
|
|
static int patch_vt1709_6ch(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x18;
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2006-11-29 22:29:40 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2007-12-13 23:40:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt1708B(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int imux_is_smixer;
|
|
|
|
unsigned int parm;
|
|
|
|
int is_8ch = 0;
|
2011-03-23 17:56:05 +08:00
|
|
|
if ((spec->codec_type != VT1708B_4CH) &&
|
|
|
|
(codec->vendor_id != 0x11064397))
|
2011-03-23 15:13:28 +08:00
|
|
|
is_8ch = 1;
|
|
|
|
|
|
|
|
/* SW0 (17h) = stereo mixer */
|
|
|
|
imux_is_smixer =
|
|
|
|
(snd_hda_codec_read(codec, 0x17, 0, AC_VERB_GET_CONNECT_SEL, 0x00)
|
|
|
|
== ((spec->codec_type == VT1708S) ? 5 : 0));
|
|
|
|
/* inputs */
|
|
|
|
/* PW 1/2/5 (1ah/1bh/1eh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x1a, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1b, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1e, &parm);
|
|
|
|
if (imux_is_smixer)
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
/* SW0 (17h), AIW 0/1 (13h/14h) */
|
|
|
|
snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* PW0 (19h), SW1 (18h), AOW1 (11h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x19, &parm);
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1b, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW6 (22h), SW2 (26h), AOW2 (24h) */
|
|
|
|
if (is_8ch) {
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x22, &parm);
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1a, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x26, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x24, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
2011-03-23 17:56:05 +08:00
|
|
|
} else if (codec->vendor_id == 0x11064397) {
|
|
|
|
/* PW7(23h), SW2(27h), AOW2(25h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x23, &parm);
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1a, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x27, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x25, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
2011-03-23 15:13:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* PW 3/4/7 (1ch/1dh/23h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
/* force to D0 for internal Speaker */
|
|
|
|
set_pin_power_state(codec, 0x1c, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1d, &parm);
|
|
|
|
if (is_8ch)
|
|
|
|
set_pin_power_state(codec, 0x23, &parm);
|
|
|
|
|
|
|
|
/* MW0 (16h), Sw3 (27h), AOW 0/3 (10h/25h) */
|
|
|
|
snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_POWER_STATE,
|
|
|
|
imux_is_smixer ? AC_PWRST_D0 : parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
if (is_8ch) {
|
|
|
|
snd_hda_codec_write(codec, 0x25, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x27, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
2011-03-23 17:56:05 +08:00
|
|
|
} else if (codec->vendor_id == 0x11064397 && spec->hp_independent_mode)
|
|
|
|
snd_hda_codec_write(codec, 0x25, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
2011-03-23 15:13:28 +08:00
|
|
|
}
|
|
|
|
|
2009-10-10 19:07:29 +08:00
|
|
|
static int patch_vt1708S(struct hda_codec *codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
static int patch_vt1708B_8ch(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
2009-10-10 19:07:29 +08:00
|
|
|
if (get_codec_type(codec) == VT1708BCE)
|
|
|
|
return patch_vt1708S(codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x16;
|
|
|
|
|
2007-12-13 23:40:40 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1708B;
|
|
|
|
|
2007-12-13 23:40:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int patch_vt1708B_4ch(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2007-12-13 23:40:40 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1708B;
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-09 15:56:01 +08:00
|
|
|
/* Patch for VT1708S */
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1708S_init_verbs[] = {
|
2008-09-15 22:43:23 +08:00
|
|
|
/* Enable Mic Boost Volume backdoor */
|
|
|
|
{0x1, 0xf98, 0x1},
|
2009-10-10 19:08:32 +08:00
|
|
|
/* don't bybass mixer */
|
|
|
|
{0x1, 0xf88, 0xc0},
|
2008-09-09 15:56:01 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
/* fill out digital output widgets; one for master and one for slave outputs */
|
|
|
|
static void fill_dig_outs(struct hda_codec *codec)
|
2008-09-09 15:56:01 +08:00
|
|
|
{
|
2011-06-17 23:53:38 +08:00
|
|
|
struct via_spec *spec = codec->spec;
|
2008-09-09 15:56:01 +08:00
|
|
|
int i;
|
|
|
|
|
2011-06-17 23:53:38 +08:00
|
|
|
for (i = 0; i < spec->autocfg.dig_outs; i++) {
|
|
|
|
hda_nid_t nid;
|
|
|
|
int conn;
|
2009-05-07 22:31:14 +08:00
|
|
|
|
|
|
|
nid = spec->autocfg.dig_out_pins[i];
|
|
|
|
if (!nid)
|
|
|
|
continue;
|
|
|
|
conn = snd_hda_get_connections(codec, nid, &nid, 1);
|
|
|
|
if (conn < 1)
|
|
|
|
continue;
|
|
|
|
if (!spec->multiout.dig_out_nid)
|
|
|
|
spec->multiout.dig_out_nid = nid;
|
|
|
|
else {
|
|
|
|
spec->slave_dig_outs[0] = nid;
|
|
|
|
break; /* at most two dig outs */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
static void fill_dig_in(struct hda_codec *codec)
|
2008-09-09 15:56:01 +08:00
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
2011-06-18 23:45:49 +08:00
|
|
|
hda_nid_t dig_nid;
|
|
|
|
int i, err;
|
2008-09-09 15:56:01 +08:00
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
if (!spec->autocfg.dig_in_pin)
|
|
|
|
return;
|
2011-06-18 00:46:48 +08:00
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
dig_nid = codec->start_nid;
|
|
|
|
for (i = 0; i < codec->num_nodes; i++, dig_nid++) {
|
|
|
|
unsigned int wcaps = get_wcaps(codec, dig_nid);
|
|
|
|
if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
|
|
|
|
continue;
|
|
|
|
if (!(wcaps & AC_WCAP_DIGITAL))
|
|
|
|
continue;
|
|
|
|
if (!(wcaps & AC_WCAP_CONN_LIST))
|
|
|
|
continue;
|
|
|
|
err = get_connection_index(codec, dig_nid,
|
|
|
|
spec->autocfg.dig_in_pin);
|
|
|
|
if (err >= 0) {
|
|
|
|
spec->dig_in_nid = dig_nid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-09-09 15:56:01 +08:00
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:31 +08:00
|
|
|
static void override_mic_boost(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
int offset, int num_steps, int step_size)
|
|
|
|
{
|
|
|
|
snd_hda_override_amp_caps(codec, pin, HDA_INPUT,
|
|
|
|
(offset << AC_AMPCAP_OFFSET_SHIFT) |
|
|
|
|
(num_steps << AC_AMPCAP_NUM_STEPS_SHIFT) |
|
|
|
|
(step_size << AC_AMPCAP_STEP_SIZE_SHIFT) |
|
|
|
|
(0 << AC_AMPCAP_MUTE_SHIFT));
|
|
|
|
}
|
|
|
|
|
2008-09-09 15:56:01 +08:00
|
|
|
static int patch_vt1708S(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2008-09-09 15:56:01 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x16;
|
2011-06-18 23:24:46 +08:00
|
|
|
override_mic_boost(codec, 0x1a, 0, 3, 40);
|
|
|
|
override_mic_boost(codec, 0x1e, 0, 3, 40);
|
2011-06-17 23:19:19 +08:00
|
|
|
|
2008-09-09 15:56:01 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2008-09-09 15:56:01 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1708S_init_verbs;
|
2008-09-09 15:56:01 +08:00
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2009-10-10 19:07:29 +08:00
|
|
|
/* correct names for VT1708BCE */
|
|
|
|
if (get_codec_type(codec) == VT1708BCE) {
|
|
|
|
kfree(codec->chip_name);
|
|
|
|
codec->chip_name = kstrdup("VT1708BCE", GFP_KERNEL);
|
|
|
|
snprintf(codec->bus->card->mixername,
|
|
|
|
sizeof(codec->bus->card->mixername),
|
|
|
|
"%s %s", codec->vendor_name, codec->chip_name);
|
2011-03-22 16:25:56 +08:00
|
|
|
}
|
2011-03-23 17:56:05 +08:00
|
|
|
/* correct names for VT1705 */
|
|
|
|
if (codec->vendor_id == 0x11064397) {
|
|
|
|
kfree(codec->chip_name);
|
|
|
|
codec->chip_name = kstrdup("VT1705", GFP_KERNEL);
|
|
|
|
snprintf(codec->bus->card->mixername,
|
|
|
|
sizeof(codec->bus->card->mixername),
|
|
|
|
"%s %s", codec->vendor_name, codec->chip_name);
|
|
|
|
}
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1708B;
|
2008-09-09 15:56:01 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Patch for VT1702 */
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1702_init_verbs[] = {
|
2009-10-10 19:08:32 +08:00
|
|
|
/* mixer enable */
|
|
|
|
{0x1, 0xF88, 0x3},
|
|
|
|
/* GPIO 0~2 */
|
|
|
|
{0x1, 0xF82, 0x3F},
|
2008-09-09 15:56:01 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt1702(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
int imux_is_smixer =
|
|
|
|
snd_hda_codec_read(codec, 0x13, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
|
|
|
|
unsigned int parm;
|
|
|
|
/* inputs */
|
|
|
|
/* PW 1/2/5 (14h/15h/18h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x14, &parm);
|
|
|
|
set_pin_power_state(codec, 0x15, &parm);
|
|
|
|
set_pin_power_state(codec, 0x18, &parm);
|
|
|
|
if (imux_is_smixer)
|
|
|
|
parm = AC_PWRST_D0; /* SW0 (13h) = stereo mixer (idx 3) */
|
|
|
|
/* SW0 (13h), AIW 0/1/2 (12h/1fh/20h) */
|
|
|
|
snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x12, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* PW 3/4 (16h/17h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x17, &parm);
|
|
|
|
set_pin_power_state(codec, 0x16, &parm);
|
|
|
|
/* MW0 (1ah), AOW 0/1 (10h/1dh) */
|
|
|
|
snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_POWER_STATE,
|
|
|
|
imux_is_smixer ? AC_PWRST_D0 : parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1d, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
|
|
|
|
2008-09-09 15:56:01 +08:00
|
|
|
static int patch_vt1702(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2008-09-09 15:56:01 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x1a;
|
|
|
|
|
2011-06-18 23:45:49 +08:00
|
|
|
/* limit AA path volume to 0 dB */
|
|
|
|
snd_hda_override_amp_caps(codec, 0x1A, HDA_INPUT,
|
|
|
|
(0x17 << AC_AMPCAP_OFFSET_SHIFT) |
|
|
|
|
(0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
|
|
|
|
(0x5 << AC_AMPCAP_STEP_SIZE_SHIFT) |
|
|
|
|
(1 << AC_AMPCAP_MUTE_SHIFT));
|
|
|
|
|
2008-09-09 15:56:01 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2008-09-09 15:56:01 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1702_init_verbs;
|
2008-09-09 15:56:01 +08:00
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1702;
|
2008-09-09 15:56:01 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:34 +08:00
|
|
|
/* Patch for VT1718S */
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1718S_init_verbs[] = {
|
2011-03-24 12:42:03 +08:00
|
|
|
/* Enable MW0 adjust Gain 5 */
|
|
|
|
{0x1, 0xfb2, 0x10},
|
2009-10-10 19:08:34 +08:00
|
|
|
/* Enable Boost Volume backdoor */
|
|
|
|
{0x1, 0xf88, 0x8},
|
2011-06-20 17:32:27 +08:00
|
|
|
|
2009-10-10 19:08:34 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt1718S(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int imux_is_smixer;
|
|
|
|
unsigned int parm;
|
|
|
|
/* MUX6 (1eh) = stereo mixer */
|
|
|
|
imux_is_smixer =
|
|
|
|
snd_hda_codec_read(codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 5;
|
|
|
|
/* inputs */
|
|
|
|
/* PW 5/6/7 (29h/2ah/2bh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x29, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2a, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2b, &parm);
|
|
|
|
if (imux_is_smixer)
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
/* MUX6/7 (1eh/1fh), AIW 0/1 (10h/11h) */
|
|
|
|
snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* PW3 (27h), MW2 (1ah), AOW3 (bh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x27, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0xb, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW2 (26h), AOW2 (ah) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x26, &parm);
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x2b, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0xa, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW0 (24h), AOW0 (8h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x24, &parm);
|
|
|
|
if (!spec->hp_independent_mode) /* check for redirected HP */
|
|
|
|
set_pin_power_state(codec, 0x28, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x8, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
/* MW9 (21h), Mw2 (1ah), AOW0 (8h) */
|
|
|
|
snd_hda_codec_write(codec, 0x21, 0, AC_VERB_SET_POWER_STATE,
|
|
|
|
imux_is_smixer ? AC_PWRST_D0 : parm);
|
|
|
|
|
|
|
|
/* PW1 (25h), AOW1 (9h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x25, &parm);
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x2a, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x9, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
if (spec->hp_independent_mode) {
|
|
|
|
/* PW4 (28h), MW3 (1bh), MUX1(34h), AOW4 (ch) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x28, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1b, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x34, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0xc, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:34 +08:00
|
|
|
static int patch_vt1718S(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2009-10-10 19:08:34 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x21;
|
2011-06-18 23:24:46 +08:00
|
|
|
override_mic_boost(codec, 0x2b, 0, 3, 40);
|
|
|
|
override_mic_boost(codec, 0x29, 0, 3, 40);
|
2011-06-17 23:19:19 +08:00
|
|
|
|
2009-10-10 19:08:34 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2009-10-10 19:08:34 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1718S_init_verbs;
|
2009-10-10 19:08:34 +08:00
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1718S;
|
|
|
|
|
2009-10-10 19:08:34 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2009-10-10 19:08:41 +08:00
|
|
|
|
|
|
|
/* Patch for VT1716S */
|
|
|
|
|
|
|
|
static int vt1716s_dmic_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vt1716s_dmic_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
index = snd_hda_codec_read(codec, 0x26, 0,
|
|
|
|
AC_VERB_GET_CONNECT_SEL, 0);
|
|
|
|
if (index != -1)
|
|
|
|
*ucontrol->value.integer.value = index;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vt1716s_dmic_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int index = *ucontrol->value.integer.value;
|
|
|
|
|
|
|
|
snd_hda_codec_write(codec, 0x26, 0,
|
|
|
|
AC_VERB_SET_CONNECT_SEL, index);
|
|
|
|
spec->dmic_enabled = index;
|
2011-03-23 15:13:28 +08:00
|
|
|
set_widgets_power_state(codec);
|
2009-10-10 19:08:41 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct snd_kcontrol_new vt1716s_dmic_mixer[] = {
|
2009-10-10 19:08:41 +08:00
|
|
|
HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x22, 0x0, HDA_INPUT),
|
|
|
|
{
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Digital Mic Capture Switch",
|
2009-12-08 23:13:32 +08:00
|
|
|
.subdevice = HDA_SUBDEV_NID_FLAG | 0x26,
|
2009-10-10 19:08:41 +08:00
|
|
|
.count = 1,
|
|
|
|
.info = vt1716s_dmic_info,
|
|
|
|
.get = vt1716s_dmic_get,
|
|
|
|
.put = vt1716s_dmic_put,
|
|
|
|
},
|
|
|
|
{} /* end */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* mono-out mixer elements */
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct snd_kcontrol_new vt1716S_mono_out_mixer[] = {
|
2009-10-10 19:08:41 +08:00
|
|
|
HDA_CODEC_MUTE("Mono Playback Switch", 0x2a, 0x0, HDA_OUTPUT),
|
|
|
|
{ } /* end */
|
|
|
|
};
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1716S_init_verbs[] = {
|
2009-10-10 19:08:41 +08:00
|
|
|
/* Enable Boost Volume backdoor */
|
|
|
|
{0x1, 0xf8a, 0x80},
|
|
|
|
/* don't bybass mixer */
|
|
|
|
{0x1, 0xf88, 0xc0},
|
|
|
|
/* Enable mono output */
|
|
|
|
{0x1, 0xf90, 0x08},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt1716S(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int imux_is_smixer;
|
|
|
|
unsigned int parm;
|
|
|
|
unsigned int mono_out, present;
|
|
|
|
/* SW0 (17h) = stereo mixer */
|
|
|
|
imux_is_smixer =
|
|
|
|
(snd_hda_codec_read(codec, 0x17, 0,
|
|
|
|
AC_VERB_GET_CONNECT_SEL, 0x00) == 5);
|
|
|
|
/* inputs */
|
|
|
|
/* PW 1/2/5 (1ah/1bh/1eh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x1a, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1b, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1e, &parm);
|
|
|
|
if (imux_is_smixer)
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
/* SW0 (17h), AIW0(13h) */
|
|
|
|
snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x1e, &parm);
|
|
|
|
/* PW11 (22h) */
|
|
|
|
if (spec->dmic_enabled)
|
|
|
|
set_pin_power_state(codec, 0x22, &parm);
|
|
|
|
else
|
|
|
|
snd_hda_codec_write(codec, 0x22, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
|
|
|
|
/* SW2(26h), AIW1(14h) */
|
|
|
|
snd_hda_codec_write(codec, 0x26, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* PW0 (19h), SW1 (18h), AOW1 (11h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x19, &parm);
|
|
|
|
/* Smart 5.1 PW2(1bh) */
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1b, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW7 (23h), SW3 (27h), AOW3 (25h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x23, &parm);
|
|
|
|
/* Smart 5.1 PW1(1ah) */
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1a, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x27, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* Smart 5.1 PW5(1eh) */
|
|
|
|
if (spec->smart51_enabled)
|
|
|
|
set_pin_power_state(codec, 0x1e, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x25, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* Mono out */
|
|
|
|
/* SW4(28h)->MW1(29h)-> PW12 (2ah)*/
|
|
|
|
present = snd_hda_jack_detect(codec, 0x1c);
|
|
|
|
|
|
|
|
if (present)
|
|
|
|
mono_out = 0;
|
|
|
|
else {
|
|
|
|
present = snd_hda_jack_detect(codec, 0x1d);
|
|
|
|
if (!spec->hp_independent_mode && present)
|
|
|
|
mono_out = 0;
|
|
|
|
else
|
|
|
|
mono_out = 1;
|
|
|
|
}
|
|
|
|
parm = mono_out ? AC_PWRST_D0 : AC_PWRST_D3;
|
|
|
|
snd_hda_codec_write(codec, 0x28, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x29, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x2a, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW 3/4 (1ch/1dh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x1c, &parm);
|
|
|
|
set_pin_power_state(codec, 0x1d, &parm);
|
|
|
|
/* HP Independent Mode, power on AOW3 */
|
|
|
|
if (spec->hp_independent_mode)
|
|
|
|
snd_hda_codec_write(codec, 0x25, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* force to D0 for internal Speaker */
|
|
|
|
/* MW0 (16h), AOW0 (10h) */
|
|
|
|
snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_POWER_STATE,
|
|
|
|
imux_is_smixer ? AC_PWRST_D0 : parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
|
|
|
|
mono_out ? AC_PWRST_D0 : parm);
|
|
|
|
}
|
|
|
|
|
2009-10-10 19:08:41 +08:00
|
|
|
static int patch_vt1716S(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2009-10-10 19:08:41 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x16;
|
2011-06-18 23:24:46 +08:00
|
|
|
override_mic_boost(codec, 0x1a, 0, 3, 40);
|
|
|
|
override_mic_boost(codec, 0x1e, 0, 3, 40);
|
2011-06-17 23:19:19 +08:00
|
|
|
|
2009-10-10 19:08:41 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2009-10-10 19:08:41 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1716S_init_verbs;
|
2009-10-10 19:08:41 +08:00
|
|
|
|
|
|
|
spec->mixers[spec->num_mixers] = vt1716s_dmic_mixer;
|
|
|
|
spec->num_mixers++;
|
|
|
|
|
|
|
|
spec->mixers[spec->num_mixers++] = vt1716S_mono_out_mixer;
|
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1716S;
|
2009-10-10 19:08:41 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2009-10-10 19:08:43 +08:00
|
|
|
|
|
|
|
/* for vt2002P */
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt2002P_init_verbs[] = {
|
2011-03-24 12:43:02 +08:00
|
|
|
/* Class-D speaker related verbs */
|
|
|
|
{0x1, 0xfe0, 0x4},
|
|
|
|
{0x1, 0xfe9, 0x80},
|
|
|
|
{0x1, 0xfe2, 0x22},
|
2009-10-10 19:08:43 +08:00
|
|
|
/* Enable Boost Volume backdoor */
|
|
|
|
{0x1, 0xfb9, 0x24},
|
|
|
|
/* Enable AOW0 to MW9 */
|
|
|
|
{0x1, 0xfb8, 0x88},
|
|
|
|
{ }
|
|
|
|
};
|
2011-06-20 18:39:26 +08:00
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1802_init_verbs[] = {
|
2011-03-23 17:57:34 +08:00
|
|
|
/* Enable Boost Volume backdoor */
|
|
|
|
{0x1, 0xfb9, 0x24},
|
|
|
|
/* Enable AOW0 to MW9 */
|
|
|
|
{0x1, 0xfb8, 0x88},
|
|
|
|
{ }
|
|
|
|
};
|
2009-10-10 19:08:43 +08:00
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt2002P(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int imux_is_smixer;
|
|
|
|
unsigned int parm;
|
|
|
|
unsigned int present;
|
|
|
|
/* MUX9 (1eh) = stereo mixer */
|
|
|
|
imux_is_smixer =
|
|
|
|
snd_hda_codec_read(codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
|
|
|
|
/* inputs */
|
|
|
|
/* PW 5/6/7 (29h/2ah/2bh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x29, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2a, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2b, &parm);
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
/* MUX9/10 (1eh/1fh), AIW 0/1 (10h/11h) */
|
|
|
|
snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* AOW0 (8h)*/
|
|
|
|
snd_hda_codec_write(codec, 0x8, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
2011-03-23 17:57:34 +08:00
|
|
|
if (spec->codec_type == VT1802) {
|
|
|
|
/* PW4 (28h), MW4 (18h), MUX4(38h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x28, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x18, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x38, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
} else {
|
|
|
|
/* PW4 (26h), MW4 (1ch), MUX4(37h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x26, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x37, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
2011-03-23 15:13:28 +08:00
|
|
|
|
2011-03-23 17:57:34 +08:00
|
|
|
if (spec->codec_type == VT1802) {
|
|
|
|
/* PW1 (25h), MW1 (15h), MUX1(35h), AOW1 (9h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x25, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x15, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x35, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
} else {
|
|
|
|
/* PW1 (25h), MW1 (19h), MUX1(35h), AOW1 (9h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x25, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x19, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x35, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
2011-03-23 15:13:28 +08:00
|
|
|
|
|
|
|
if (spec->hp_independent_mode)
|
|
|
|
snd_hda_codec_write(codec, 0x9, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
|
|
|
|
/* Class-D */
|
|
|
|
/* PW0 (24h), MW0(18h/14h), MUX0(34h) */
|
|
|
|
present = snd_hda_jack_detect(codec, 0x25);
|
|
|
|
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x24, &parm);
|
|
|
|
parm = present ? AC_PWRST_D3 : AC_PWRST_D0;
|
2011-03-23 17:57:34 +08:00
|
|
|
if (spec->codec_type == VT1802)
|
|
|
|
snd_hda_codec_write(codec, 0x14, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
else
|
|
|
|
snd_hda_codec_write(codec, 0x18, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
2011-03-23 15:13:28 +08:00
|
|
|
snd_hda_codec_write(codec, 0x34, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* Mono Out */
|
|
|
|
present = snd_hda_jack_detect(codec, 0x26);
|
|
|
|
|
|
|
|
parm = present ? AC_PWRST_D3 : AC_PWRST_D0;
|
2011-03-23 17:57:34 +08:00
|
|
|
if (spec->codec_type == VT1802) {
|
|
|
|
/* PW15 (33h), MW8(1ch), MUX8(3ch) */
|
|
|
|
snd_hda_codec_write(codec, 0x33, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x3c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
} else {
|
|
|
|
/* PW15 (31h), MW8(17h), MUX8(3bh) */
|
|
|
|
snd_hda_codec_write(codec, 0x31, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x17, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x3b, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
}
|
2011-03-23 15:13:28 +08:00
|
|
|
/* MW9 (21h) */
|
|
|
|
if (imux_is_smixer || !is_aa_path_mute(codec))
|
|
|
|
snd_hda_codec_write(codec, 0x21, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
else
|
|
|
|
snd_hda_codec_write(codec, 0x21, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
}
|
2009-10-10 19:08:43 +08:00
|
|
|
|
|
|
|
/* patch for vt2002P */
|
|
|
|
static int patch_vt2002P(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2009-10-10 19:08:43 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x21;
|
2011-06-18 23:24:46 +08:00
|
|
|
override_mic_boost(codec, 0x2b, 0, 3, 40);
|
|
|
|
override_mic_boost(codec, 0x29, 0, 3, 40);
|
2011-06-17 23:19:19 +08:00
|
|
|
|
2009-10-10 19:08:43 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2009-10-10 19:08:43 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-03-23 17:57:34 +08:00
|
|
|
if (spec->codec_type == VT1802)
|
2011-06-20 18:39:26 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1802_init_verbs;
|
2011-03-23 17:57:34 +08:00
|
|
|
else
|
2011-06-20 18:39:26 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt2002P_init_verbs;
|
2011-03-23 17:57:34 +08:00
|
|
|
|
2009-10-10 19:08:43 +08:00
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt2002P;
|
2009-10-10 19:08:43 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2009-10-10 19:08:46 +08:00
|
|
|
|
|
|
|
/* for vt1812 */
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
static const struct hda_verb vt1812_init_verbs[] = {
|
2009-10-10 19:08:46 +08:00
|
|
|
/* Enable Boost Volume backdoor */
|
|
|
|
{0x1, 0xfb9, 0x24},
|
|
|
|
/* Enable AOW0 to MW9 */
|
|
|
|
{0x1, 0xfb8, 0xa8},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
static void set_widgets_power_state_vt1812(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec = codec->spec;
|
|
|
|
int imux_is_smixer =
|
|
|
|
snd_hda_codec_read(codec, 0x13, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
|
|
|
|
unsigned int parm;
|
|
|
|
unsigned int present;
|
|
|
|
/* MUX10 (1eh) = stereo mixer */
|
|
|
|
imux_is_smixer =
|
|
|
|
snd_hda_codec_read(codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 5;
|
|
|
|
/* inputs */
|
|
|
|
/* PW 5/6/7 (29h/2ah/2bh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x29, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2a, &parm);
|
|
|
|
set_pin_power_state(codec, 0x2b, &parm);
|
|
|
|
parm = AC_PWRST_D0;
|
|
|
|
/* MUX10/11 (1eh/1fh), AIW 0/1 (10h/11h) */
|
|
|
|
snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* outputs */
|
|
|
|
/* AOW0 (8h)*/
|
|
|
|
snd_hda_codec_write(codec, 0x8, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
|
|
|
|
/* PW4 (28h), MW4 (18h), MUX4(38h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x28, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x38, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
/* PW1 (25h), MW1 (15h), MUX1(35h), AOW1 (9h) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x25, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x15, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x35, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
if (spec->hp_independent_mode)
|
|
|
|
snd_hda_codec_write(codec, 0x9, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
|
|
|
|
/* Internal Speaker */
|
|
|
|
/* PW0 (24h), MW0(14h), MUX0(34h) */
|
|
|
|
present = snd_hda_jack_detect(codec, 0x25);
|
|
|
|
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x24, &parm);
|
|
|
|
if (present) {
|
|
|
|
snd_hda_codec_write(codec, 0x14, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
snd_hda_codec_write(codec, 0x34, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
} else {
|
|
|
|
snd_hda_codec_write(codec, 0x14, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
snd_hda_codec_write(codec, 0x34, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Mono Out */
|
|
|
|
/* PW13 (31h), MW13(1ch), MUX13(3ch), MW14(3eh) */
|
|
|
|
present = snd_hda_jack_detect(codec, 0x28);
|
|
|
|
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x31, &parm);
|
|
|
|
if (present) {
|
|
|
|
snd_hda_codec_write(codec, 0x1c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
snd_hda_codec_write(codec, 0x3c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
snd_hda_codec_write(codec, 0x3e, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
|
|
|
|
} else {
|
|
|
|
snd_hda_codec_write(codec, 0x1c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
snd_hda_codec_write(codec, 0x3c, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
snd_hda_codec_write(codec, 0x3e, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* PW15 (33h), MW15 (1dh), MUX15(3dh) */
|
|
|
|
parm = AC_PWRST_D3;
|
|
|
|
set_pin_power_state(codec, 0x33, &parm);
|
|
|
|
snd_hda_codec_write(codec, 0x1d, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
snd_hda_codec_write(codec, 0x3d, 0, AC_VERB_SET_POWER_STATE, parm);
|
|
|
|
|
|
|
|
}
|
2009-10-10 19:08:46 +08:00
|
|
|
|
|
|
|
/* patch for vt1812 */
|
|
|
|
static int patch_vt1812(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct via_spec *spec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* create a codec specific record */
|
2009-12-08 23:13:32 +08:00
|
|
|
spec = via_new_spec(codec);
|
2009-10-10 19:08:46 +08:00
|
|
|
if (spec == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-06-17 23:19:19 +08:00
|
|
|
spec->aa_mix_nid = 0x21;
|
2011-06-18 23:24:46 +08:00
|
|
|
override_mic_boost(codec, 0x2b, 0, 3, 40);
|
|
|
|
override_mic_boost(codec, 0x29, 0, 3, 40);
|
2011-06-17 23:19:19 +08:00
|
|
|
|
2009-10-10 19:08:46 +08:00
|
|
|
/* automatic parse from the BIOS config */
|
2011-06-18 23:45:49 +08:00
|
|
|
err = via_parse_auto_config(codec);
|
2009-10-10 19:08:46 +08:00
|
|
|
if (err < 0) {
|
|
|
|
via_free(codec);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-06-20 18:09:02 +08:00
|
|
|
spec->init_verbs[spec->num_iverbs++] = vt1812_init_verbs;
|
2009-10-10 19:08:46 +08:00
|
|
|
|
|
|
|
codec->patch_ops = via_patch_ops;
|
|
|
|
|
2011-03-23 15:13:28 +08:00
|
|
|
spec->set_widgets_power_state = set_widgets_power_state_vt1812;
|
2009-10-10 19:08:46 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-11-29 22:29:40 +08:00
|
|
|
/*
|
|
|
|
* patch entries
|
|
|
|
*/
|
2011-05-02 18:38:19 +08:00
|
|
|
static const struct hda_codec_preset snd_hda_preset_via[] = {
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11061708, .name = "VT1708", .patch = patch_vt1708},
|
|
|
|
{ .id = 0x11061709, .name = "VT1708", .patch = patch_vt1708},
|
|
|
|
{ .id = 0x1106170a, .name = "VT1708", .patch = patch_vt1708},
|
|
|
|
{ .id = 0x1106170b, .name = "VT1708", .patch = patch_vt1708},
|
|
|
|
{ .id = 0x1106e710, .name = "VT1709 10-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_10ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e711, .name = "VT1709 10-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_10ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e712, .name = "VT1709 10-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_10ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e713, .name = "VT1709 10-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_10ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e714, .name = "VT1709 6-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_6ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e715, .name = "VT1709 6-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_6ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e716, .name = "VT1709 6-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_6ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e717, .name = "VT1709 6-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1709_6ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e720, .name = "VT1708B 8-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_8ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e721, .name = "VT1708B 8-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_8ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e722, .name = "VT1708B 8-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_8ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e723, .name = "VT1708B 8-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_8ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e724, .name = "VT1708B 4-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_4ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e725, .name = "VT1708B 4-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_4ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e726, .name = "VT1708B 4-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_4ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x1106e727, .name = "VT1708B 4-Ch",
|
2007-12-13 23:40:40 +08:00
|
|
|
.patch = patch_vt1708B_4ch},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11060397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11061397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11062397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11063397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2011-03-23 17:56:05 +08:00
|
|
|
{ .id = 0x11064397, .name = "VT1705",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11065397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11066397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11067397, .name = "VT1708S",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1708S},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11060398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11061398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11062398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11063398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11064398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11065398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11066398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2008-12-18 16:17:56 +08:00
|
|
|
{ .id = 0x11067398, .name = "VT1702",
|
2008-09-09 15:56:01 +08:00
|
|
|
.patch = patch_vt1702},
|
2009-10-10 19:08:34 +08:00
|
|
|
{ .id = 0x11060428, .name = "VT1718S",
|
|
|
|
.patch = patch_vt1718S},
|
|
|
|
{ .id = 0x11064428, .name = "VT1718S",
|
|
|
|
.patch = patch_vt1718S},
|
2009-10-10 19:08:39 +08:00
|
|
|
{ .id = 0x11060441, .name = "VT2020",
|
|
|
|
.patch = patch_vt1718S},
|
|
|
|
{ .id = 0x11064441, .name = "VT1828S",
|
|
|
|
.patch = patch_vt1718S},
|
2009-10-10 19:08:41 +08:00
|
|
|
{ .id = 0x11060433, .name = "VT1716S",
|
|
|
|
.patch = patch_vt1716S},
|
|
|
|
{ .id = 0x1106a721, .name = "VT1716S",
|
|
|
|
.patch = patch_vt1716S},
|
2009-10-10 19:08:43 +08:00
|
|
|
{ .id = 0x11060438, .name = "VT2002P", .patch = patch_vt2002P},
|
|
|
|
{ .id = 0x11064438, .name = "VT2002P", .patch = patch_vt2002P},
|
2009-10-10 19:08:46 +08:00
|
|
|
{ .id = 0x11060448, .name = "VT1812", .patch = patch_vt1812},
|
2009-10-20 13:18:04 +08:00
|
|
|
{ .id = 0x11060440, .name = "VT1818S",
|
|
|
|
.patch = patch_vt1708S},
|
2011-03-23 17:57:34 +08:00
|
|
|
{ .id = 0x11060446, .name = "VT1802",
|
|
|
|
.patch = patch_vt2002P},
|
|
|
|
{ .id = 0x11068446, .name = "VT1802",
|
|
|
|
.patch = patch_vt2002P},
|
2006-11-29 22:29:40 +08:00
|
|
|
{} /* terminator */
|
|
|
|
};
|
2008-11-27 22:47:11 +08:00
|
|
|
|
|
|
|
MODULE_ALIAS("snd-hda-codec-id:1106*");
|
|
|
|
|
|
|
|
static struct hda_codec_preset_list via_list = {
|
|
|
|
.preset = snd_hda_preset_via,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("VIA HD-audio codec");
|
|
|
|
|
|
|
|
static int __init patch_via_init(void)
|
|
|
|
{
|
|
|
|
return snd_hda_add_codec_preset(&via_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit patch_via_exit(void)
|
|
|
|
{
|
|
|
|
snd_hda_delete_codec_preset(&via_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(patch_via_init)
|
|
|
|
module_exit(patch_via_exit)
|