forked from luck/tmp_suning_uos_patched
3fc24d8507
Define pin configs for MacBook and MacBook Pro with STAC92xx codecs. The latter is detected automatically by checking codec SSID now. Also, fixed the documentation regarding available modeliof sigmatel codec chips. Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2384 lines
67 KiB
C
2384 lines
67 KiB
C
/*
|
|
* Universal Interface for Intel High Definition Audio Codec
|
|
*
|
|
* HD audio interface patch for SigmaTel STAC92xx
|
|
*
|
|
* Copyright (c) 2005 Embedded Alley Solutions, Inc.
|
|
* Matt Porter <mporter@embeddedalley.com>
|
|
*
|
|
* Based on patch_cmedia.c and patch_realtek.c
|
|
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
|
*
|
|
* This driver is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This driver is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <sound/driver.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pci.h>
|
|
#include <sound/core.h>
|
|
#include <sound/asoundef.h>
|
|
#include "hda_codec.h"
|
|
#include "hda_local.h"
|
|
|
|
#define NUM_CONTROL_ALLOC 32
|
|
#define STAC_HP_EVENT 0x37
|
|
|
|
enum {
|
|
STAC_REF,
|
|
STAC_9200_MODELS
|
|
};
|
|
|
|
enum {
|
|
STAC_9205_REF,
|
|
STAC_9205_MODELS
|
|
};
|
|
|
|
enum {
|
|
STAC_925x_REF,
|
|
STAC_M2_2,
|
|
STAC_MA6,
|
|
STAC_925x_MODELS
|
|
};
|
|
|
|
enum {
|
|
STAC_D945_REF,
|
|
STAC_D945GTP3,
|
|
STAC_D945GTP5,
|
|
STAC_MACMINI,
|
|
STAC_MACBOOK,
|
|
STAC_MACBOOK_PRO,
|
|
STAC_922X_MODELS
|
|
};
|
|
|
|
enum {
|
|
STAC_D965_REF,
|
|
STAC_D965_3ST,
|
|
STAC_D965_5ST,
|
|
STAC_927X_MODELS
|
|
};
|
|
|
|
struct sigmatel_spec {
|
|
struct snd_kcontrol_new *mixers[4];
|
|
unsigned int num_mixers;
|
|
|
|
int board_config;
|
|
unsigned int surr_switch: 1;
|
|
unsigned int line_switch: 1;
|
|
unsigned int mic_switch: 1;
|
|
unsigned int alt_switch: 1;
|
|
unsigned int hp_detect: 1;
|
|
unsigned int gpio_mute: 1;
|
|
|
|
/* playback */
|
|
struct hda_multi_out multiout;
|
|
hda_nid_t dac_nids[5];
|
|
|
|
/* capture */
|
|
hda_nid_t *adc_nids;
|
|
unsigned int num_adcs;
|
|
hda_nid_t *mux_nids;
|
|
unsigned int num_muxes;
|
|
hda_nid_t *dmic_nids;
|
|
unsigned int num_dmics;
|
|
hda_nid_t dmux_nid;
|
|
hda_nid_t dig_in_nid;
|
|
|
|
/* pin widgets */
|
|
hda_nid_t *pin_nids;
|
|
unsigned int num_pins;
|
|
unsigned int *pin_configs;
|
|
unsigned int *bios_pin_configs;
|
|
|
|
/* codec specific stuff */
|
|
struct hda_verb *init;
|
|
struct snd_kcontrol_new *mixer;
|
|
|
|
/* capture source */
|
|
struct hda_input_mux *dinput_mux;
|
|
unsigned int cur_dmux;
|
|
struct hda_input_mux *input_mux;
|
|
unsigned int cur_mux[3];
|
|
|
|
/* i/o switches */
|
|
unsigned int io_switch[2];
|
|
|
|
struct hda_pcm pcm_rec[2]; /* PCM information */
|
|
|
|
/* dynamic controls and input_mux */
|
|
struct auto_pin_cfg autocfg;
|
|
unsigned int num_kctl_alloc, num_kctl_used;
|
|
struct snd_kcontrol_new *kctl_alloc;
|
|
struct hda_input_mux private_dimux;
|
|
struct hda_input_mux private_imux;
|
|
};
|
|
|
|
static hda_nid_t stac9200_adc_nids[1] = {
|
|
0x03,
|
|
};
|
|
|
|
static hda_nid_t stac9200_mux_nids[1] = {
|
|
0x0c,
|
|
};
|
|
|
|
static hda_nid_t stac9200_dac_nids[1] = {
|
|
0x02,
|
|
};
|
|
|
|
static hda_nid_t stac925x_adc_nids[1] = {
|
|
0x03,
|
|
};
|
|
|
|
static hda_nid_t stac925x_mux_nids[1] = {
|
|
0x0f,
|
|
};
|
|
|
|
static hda_nid_t stac925x_dac_nids[1] = {
|
|
0x02,
|
|
};
|
|
|
|
static hda_nid_t stac922x_adc_nids[2] = {
|
|
0x06, 0x07,
|
|
};
|
|
|
|
static hda_nid_t stac922x_mux_nids[2] = {
|
|
0x12, 0x13,
|
|
};
|
|
|
|
static hda_nid_t stac927x_adc_nids[3] = {
|
|
0x07, 0x08, 0x09
|
|
};
|
|
|
|
static hda_nid_t stac927x_mux_nids[3] = {
|
|
0x15, 0x16, 0x17
|
|
};
|
|
|
|
static hda_nid_t stac9205_adc_nids[2] = {
|
|
0x12, 0x13
|
|
};
|
|
|
|
static hda_nid_t stac9205_mux_nids[2] = {
|
|
0x19, 0x1a
|
|
};
|
|
|
|
static hda_nid_t stac9205_dmic_nids[3] = {
|
|
0x17, 0x18, 0
|
|
};
|
|
|
|
static hda_nid_t stac9200_pin_nids[8] = {
|
|
0x08, 0x09, 0x0d, 0x0e,
|
|
0x0f, 0x10, 0x11, 0x12,
|
|
};
|
|
|
|
static hda_nid_t stac925x_pin_nids[8] = {
|
|
0x07, 0x08, 0x0a, 0x0b,
|
|
0x0c, 0x0d, 0x10, 0x11,
|
|
};
|
|
|
|
static hda_nid_t stac922x_pin_nids[10] = {
|
|
0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
|
0x0f, 0x10, 0x11, 0x15, 0x1b,
|
|
};
|
|
|
|
static hda_nid_t stac927x_pin_nids[14] = {
|
|
0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
|
0x0f, 0x10, 0x11, 0x12, 0x13,
|
|
0x14, 0x21, 0x22, 0x23,
|
|
};
|
|
|
|
static hda_nid_t stac9205_pin_nids[12] = {
|
|
0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
|
0x0f, 0x14, 0x16, 0x17, 0x18,
|
|
0x21, 0x22,
|
|
|
|
};
|
|
|
|
static int stac92xx_dmux_enum_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_input_mux_info(spec->dinput_mux, uinfo);
|
|
}
|
|
|
|
static int stac92xx_dmux_enum_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->cur_dmux;
|
|
return 0;
|
|
}
|
|
|
|
static int stac92xx_dmux_enum_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
|
|
return snd_hda_input_mux_put(codec, spec->dinput_mux, ucontrol,
|
|
spec->dmux_nid, &spec->cur_dmux);
|
|
}
|
|
|
|
static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_input_mux_info(spec->input_mux, uinfo);
|
|
}
|
|
|
|
static int stac92xx_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_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 stac92xx_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
|
|
return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
|
|
spec->mux_nids[adc_idx], &spec->cur_mux[adc_idx]);
|
|
}
|
|
|
|
static struct hda_verb stac9200_core_init[] = {
|
|
/* set dac0mux for dac converter */
|
|
{ 0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb stac925x_core_init[] = {
|
|
/* set dac0mux for dac converter */
|
|
{ 0x06, AC_VERB_SET_CONNECT_SEL, 0x00},
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb stac922x_core_init[] = {
|
|
/* set master volume and direct control */
|
|
{ 0x16, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb d965_core_init[] = {
|
|
/* set master volume and direct control */
|
|
{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
|
|
/* unmute node 0x1b */
|
|
{ 0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
|
|
/* select node 0x03 as DAC */
|
|
{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x01},
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb stac927x_core_init[] = {
|
|
/* set master volume and direct control */
|
|
{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb stac9205_core_init[] = {
|
|
/* set master volume and direct control */
|
|
{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
|
|
{}
|
|
};
|
|
|
|
static struct snd_kcontrol_new stac9200_mixer[] = {
|
|
HDA_CODEC_VOLUME("Master Playback Volume", 0xb, 0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x0a, 0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x0a, 0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Capture Mux Volume", 0x0c, 0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct snd_kcontrol_new stac925x_mixer[] = {
|
|
HDA_CODEC_VOLUME("Master Playback Volume", 0xe, 0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Master Playback Switch", 0xe, 0, HDA_OUTPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Capture Mux Volume", 0x0f, 0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
/* This needs to be generated dynamically based on sequence */
|
|
static struct snd_kcontrol_new stac922x_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x17, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x17, 0x0, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mux Capture Volume", 0x12, 0x0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
/* This needs to be generated dynamically based on sequence */
|
|
static struct snd_kcontrol_new stac9227_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x1b, 0x0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct snd_kcontrol_new stac927x_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("InMux Capture Volume", 0x15, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("InVol Capture Volume", 0x18, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("ADCMux Capture Switch", 0x1b, 0x0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct snd_kcontrol_new stac9205_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Digital Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_dmux_enum_info,
|
|
.get = stac92xx_dmux_enum_get,
|
|
.put = stac92xx_dmux_enum_put,
|
|
},
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
HDA_CODEC_VOLUME("InMux Capture Volume", 0x19, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("InVol Capture Volume", 0x1b, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("ADCMux Capture Switch", 0x1d, 0x0, HDA_OUTPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
static int stac92xx_build_controls(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int err;
|
|
int i;
|
|
|
|
err = snd_hda_add_new_ctls(codec, spec->mixer);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
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, spec->multiout.dig_out_nid);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (spec->dig_in_nid) {
|
|
err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int ref9200_pin_configs[8] = {
|
|
0x01c47010, 0x01447010, 0x0221401f, 0x01114010,
|
|
0x02a19020, 0x01a19021, 0x90100140, 0x01813122,
|
|
};
|
|
|
|
static unsigned int *stac9200_brd_tbl[STAC_9200_MODELS] = {
|
|
[STAC_REF] = ref9200_pin_configs,
|
|
};
|
|
|
|
static const char *stac9200_models[STAC_9200_MODELS] = {
|
|
[STAC_REF] = "ref",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac9200_cfg_tbl[] = {
|
|
/* SigmaTel reference board */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
|
|
"DFI LanParty", STAC_REF),
|
|
/* Dell laptops have BIOS problem */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5,
|
|
"Dell Inspiron 630m", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2,
|
|
"Dell Latitude D620", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb,
|
|
"Dell Latitude 120L", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc,
|
|
"Dell Latitude D820", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cd,
|
|
"Dell Inspiron E1705/9400", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ce,
|
|
"Dell XPS M1710", STAC_REF),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf,
|
|
"Dell Precision M90", STAC_REF),
|
|
{} /* terminator */
|
|
};
|
|
|
|
static unsigned int ref925x_pin_configs[8] = {
|
|
0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021,
|
|
0x90a70320, 0x02214210, 0x400003f1, 0x9033032e,
|
|
};
|
|
|
|
static unsigned int stac925x_MA6_pin_configs[8] = {
|
|
0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021,
|
|
0x90a70320, 0x90100211, 0x400003f1, 0x9033032e,
|
|
};
|
|
|
|
static unsigned int stac925xM2_2_pin_configs[8] = {
|
|
0x40c003f3, 0x424503f2, 0x041800f4, 0x02a19020,
|
|
0x50a103F0, 0x90100210, 0x400003f1, 0x9033032e,
|
|
};
|
|
|
|
static unsigned int *stac925x_brd_tbl[STAC_925x_MODELS] = {
|
|
[STAC_REF] = ref925x_pin_configs,
|
|
[STAC_M2_2] = stac925xM2_2_pin_configs,
|
|
[STAC_MA6] = stac925x_MA6_pin_configs,
|
|
};
|
|
|
|
static const char *stac925x_models[STAC_925x_MODELS] = {
|
|
[STAC_REF] = "ref",
|
|
[STAC_M2_2] = "m2-2",
|
|
[STAC_MA6] = "m6",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac925x_cfg_tbl[] = {
|
|
/* SigmaTel reference board */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF),
|
|
SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_REF),
|
|
SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_REF),
|
|
SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_MA6),
|
|
SND_PCI_QUIRK(0x1002, 0x437b, "Gateway MX6453", STAC_M2_2),
|
|
{} /* terminator */
|
|
};
|
|
|
|
static unsigned int ref922x_pin_configs[10] = {
|
|
0x01014010, 0x01016011, 0x01012012, 0x0221401f,
|
|
0x01813122, 0x01011014, 0x01441030, 0x01c41030,
|
|
0x40000100, 0x40000100,
|
|
};
|
|
|
|
static unsigned int d945gtp3_pin_configs[10] = {
|
|
0x0221401f, 0x01a19022, 0x01813021, 0x01014010,
|
|
0x40000100, 0x40000100, 0x40000100, 0x40000100,
|
|
0x02a19120, 0x40000100,
|
|
};
|
|
|
|
static unsigned int d945gtp5_pin_configs[10] = {
|
|
0x0221401f, 0x01011012, 0x01813024, 0x01014010,
|
|
0x01a19021, 0x01016011, 0x01452130, 0x40000100,
|
|
0x02a19320, 0x40000100,
|
|
};
|
|
|
|
static unsigned int macbook_pin_configs[10] = {
|
|
0x0321e230, 0x03a1e020, 0x400000fd, 0x9017e110,
|
|
0x400000fe, 0x0381e021, 0x1345e240, 0x13c5e22e,
|
|
0x400000fc, 0x400000fb,
|
|
};
|
|
|
|
static unsigned int macbook_pro_pin_configs[10] = {
|
|
0x0221401f, 0x90a70120, 0x01813024, 0x01014010,
|
|
0x400000fd, 0x01016011, 0x1345e240, 0x13c5e22e,
|
|
0x400000fc, 0x400000fb,
|
|
};
|
|
|
|
static unsigned int *stac922x_brd_tbl[STAC_922X_MODELS] = {
|
|
[STAC_D945_REF] = ref922x_pin_configs,
|
|
[STAC_D945GTP3] = d945gtp3_pin_configs,
|
|
[STAC_D945GTP5] = d945gtp5_pin_configs,
|
|
[STAC_MACMINI] = d945gtp5_pin_configs,
|
|
[STAC_MACBOOK] = macbook_pin_configs,
|
|
[STAC_MACBOOK_PRO] = macbook_pro_pin_configs,
|
|
};
|
|
|
|
static const char *stac922x_models[STAC_922X_MODELS] = {
|
|
[STAC_D945_REF] = "ref",
|
|
[STAC_D945GTP5] = "5stack",
|
|
[STAC_D945GTP3] = "3stack",
|
|
[STAC_MACMINI] = "macmini",
|
|
[STAC_MACBOOK] = "macbook",
|
|
[STAC_MACBOOK_PRO] = "macbook-pro",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac922x_cfg_tbl[] = {
|
|
/* SigmaTel reference board */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
|
|
"DFI LanParty", STAC_D945_REF),
|
|
/* Intel 945G based systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0101,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0202,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0606,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0601,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0111,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1115,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1116,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1117,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1118,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1119,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x8826,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5049,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5055,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5048,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0110,
|
|
"Intel D945G", STAC_D945GTP3),
|
|
/* Intel D945G 5-stack systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0404,
|
|
"Intel D945G", STAC_D945GTP5),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0303,
|
|
"Intel D945G", STAC_D945GTP5),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0013,
|
|
"Intel D945G", STAC_D945GTP5),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0417,
|
|
"Intel D945G", STAC_D945GTP5),
|
|
/* Intel 945P based systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0b0b,
|
|
"Intel D945P", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0112,
|
|
"Intel D945P", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0d0d,
|
|
"Intel D945P", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0909,
|
|
"Intel D945P", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0505,
|
|
"Intel D945P", STAC_D945GTP3),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0707,
|
|
"Intel D945P", STAC_D945GTP5),
|
|
/* other systems */
|
|
/* Apple Mac Mini (early 2006) */
|
|
SND_PCI_QUIRK(0x8384, 0x7680,
|
|
"Mac Mini", STAC_MACMINI),
|
|
{} /* terminator */
|
|
};
|
|
|
|
static unsigned int ref927x_pin_configs[14] = {
|
|
0x02214020, 0x02a19080, 0x0181304e, 0x01014010,
|
|
0x01a19040, 0x01011012, 0x01016011, 0x0101201f,
|
|
0x183301f0, 0x18a001f0, 0x18a001f0, 0x01442070,
|
|
0x01c42190, 0x40000100,
|
|
};
|
|
|
|
static unsigned int d965_3st_pin_configs[14] = {
|
|
0x0221401f, 0x02a19120, 0x40000100, 0x01014011,
|
|
0x01a19021, 0x01813024, 0x40000100, 0x40000100,
|
|
0x40000100, 0x40000100, 0x40000100, 0x40000100,
|
|
0x40000100, 0x40000100
|
|
};
|
|
|
|
static unsigned int d965_5st_pin_configs[14] = {
|
|
0x02214020, 0x02a19080, 0x0181304e, 0x01014010,
|
|
0x01a19040, 0x01011012, 0x01016011, 0x40000100,
|
|
0x40000100, 0x40000100, 0x40000100, 0x01442070,
|
|
0x40000100, 0x40000100
|
|
};
|
|
|
|
static unsigned int *stac927x_brd_tbl[STAC_927X_MODELS] = {
|
|
[STAC_D965_REF] = ref927x_pin_configs,
|
|
[STAC_D965_3ST] = d965_3st_pin_configs,
|
|
[STAC_D965_5ST] = d965_5st_pin_configs,
|
|
};
|
|
|
|
static const char *stac927x_models[STAC_927X_MODELS] = {
|
|
[STAC_D965_REF] = "ref",
|
|
[STAC_D965_3ST] = "3stack",
|
|
[STAC_D965_5ST] = "5stack",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac927x_cfg_tbl[] = {
|
|
/* SigmaTel reference board */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
|
|
"DFI LanParty", STAC_D965_REF),
|
|
/* Intel 946 based systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x3d01, "Intel D946", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xa301, "Intel D946", STAC_D965_3ST),
|
|
/* 965 based 3 stack systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2116, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2115, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2114, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2113, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2112, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2111, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2110, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2009, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2008, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2007, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2006, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2005, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2004, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2003, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2002, "Intel D965", STAC_D965_3ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2001, "Intel D965", STAC_D965_3ST),
|
|
/* 965 based 5 stack systems */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2301, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2302, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2303, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2304, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2305, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2501, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2502, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2503, "Intel D965", STAC_D965_5ST),
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2504, "Intel D965", STAC_D965_5ST),
|
|
{} /* terminator */
|
|
};
|
|
|
|
static unsigned int ref9205_pin_configs[12] = {
|
|
0x40000100, 0x40000100, 0x01016011, 0x01014010,
|
|
0x01813122, 0x01a19021, 0x40000100, 0x40000100,
|
|
0x90a000f0, 0x90a000f0, 0x01441030, 0x01c41030
|
|
};
|
|
|
|
static unsigned int *stac9205_brd_tbl[STAC_9205_MODELS] = {
|
|
ref9205_pin_configs,
|
|
};
|
|
|
|
static const char *stac9205_models[STAC_9205_MODELS] = {
|
|
[STAC_9205_REF] = "ref",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac9205_cfg_tbl[] = {
|
|
/* SigmaTel reference board */
|
|
SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
|
|
"DFI LanParty", STAC_9205_REF),
|
|
{} /* terminator */
|
|
};
|
|
|
|
static int stac92xx_save_bios_config_regs(struct hda_codec *codec)
|
|
{
|
|
int i;
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
|
|
if (! spec->bios_pin_configs) {
|
|
spec->bios_pin_configs = kcalloc(spec->num_pins,
|
|
sizeof(*spec->bios_pin_configs), GFP_KERNEL);
|
|
if (! spec->bios_pin_configs)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < spec->num_pins; i++) {
|
|
hda_nid_t nid = spec->pin_nids[i];
|
|
unsigned int pin_cfg;
|
|
|
|
pin_cfg = snd_hda_codec_read(codec, nid, 0,
|
|
AC_VERB_GET_CONFIG_DEFAULT, 0x00);
|
|
snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x bios pin config %8.8x\n",
|
|
nid, pin_cfg);
|
|
spec->bios_pin_configs[i] = pin_cfg;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void stac92xx_set_config_regs(struct hda_codec *codec)
|
|
{
|
|
int i;
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
unsigned int pin_cfg;
|
|
|
|
if (! spec->pin_nids || ! spec->pin_configs)
|
|
return;
|
|
|
|
for (i = 0; i < spec->num_pins; i++) {
|
|
snd_hda_codec_write(codec, spec->pin_nids[i], 0,
|
|
AC_VERB_SET_CONFIG_DEFAULT_BYTES_0,
|
|
spec->pin_configs[i] & 0x000000ff);
|
|
snd_hda_codec_write(codec, spec->pin_nids[i], 0,
|
|
AC_VERB_SET_CONFIG_DEFAULT_BYTES_1,
|
|
(spec->pin_configs[i] & 0x0000ff00) >> 8);
|
|
snd_hda_codec_write(codec, spec->pin_nids[i], 0,
|
|
AC_VERB_SET_CONFIG_DEFAULT_BYTES_2,
|
|
(spec->pin_configs[i] & 0x00ff0000) >> 16);
|
|
snd_hda_codec_write(codec, spec->pin_nids[i], 0,
|
|
AC_VERB_SET_CONFIG_DEFAULT_BYTES_3,
|
|
spec->pin_configs[i] >> 24);
|
|
pin_cfg = snd_hda_codec_read(codec, spec->pin_nids[i], 0,
|
|
AC_VERB_GET_CONFIG_DEFAULT,
|
|
0x00);
|
|
snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x pin config %8.8x\n", spec->pin_nids[i], pin_cfg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Analog playback callbacks
|
|
*/
|
|
static int stac92xx_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
|
|
}
|
|
|
|
static int stac92xx_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
unsigned int stream_tag,
|
|
unsigned int format,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, format, substream);
|
|
}
|
|
|
|
static int stac92xx_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
|
|
}
|
|
|
|
/*
|
|
* Digital playback callbacks
|
|
*/
|
|
static int stac92xx_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
|
|
}
|
|
|
|
static int stac92xx_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
|
|
}
|
|
|
|
|
|
/*
|
|
* Analog capture callbacks
|
|
*/
|
|
static int stac92xx_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 sigmatel_spec *spec = codec->spec;
|
|
|
|
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
|
|
stream_tag, 0, format);
|
|
return 0;
|
|
}
|
|
|
|
static int stac92xx_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
|
|
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
|
|
return 0;
|
|
}
|
|
|
|
static struct hda_pcm_stream stac92xx_pcm_digital_playback = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
/* NID is set in stac92xx_build_pcms */
|
|
.ops = {
|
|
.open = stac92xx_dig_playback_pcm_open,
|
|
.close = stac92xx_dig_playback_pcm_close
|
|
},
|
|
};
|
|
|
|
static struct hda_pcm_stream stac92xx_pcm_digital_capture = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
/* NID is set in stac92xx_build_pcms */
|
|
};
|
|
|
|
static struct hda_pcm_stream stac92xx_pcm_analog_playback = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 8,
|
|
.nid = 0x02, /* NID to query formats and rates */
|
|
.ops = {
|
|
.open = stac92xx_playback_pcm_open,
|
|
.prepare = stac92xx_playback_pcm_prepare,
|
|
.cleanup = stac92xx_playback_pcm_cleanup
|
|
},
|
|
};
|
|
|
|
static struct hda_pcm_stream stac92xx_pcm_analog_alt_playback = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.nid = 0x06, /* NID to query formats and rates */
|
|
.ops = {
|
|
.open = stac92xx_playback_pcm_open,
|
|
.prepare = stac92xx_playback_pcm_prepare,
|
|
.cleanup = stac92xx_playback_pcm_cleanup
|
|
},
|
|
};
|
|
|
|
static struct hda_pcm_stream stac92xx_pcm_analog_capture = {
|
|
.substreams = 2,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
/* NID is set in stac92xx_build_pcms */
|
|
.ops = {
|
|
.prepare = stac92xx_capture_pcm_prepare,
|
|
.cleanup = stac92xx_capture_pcm_cleanup
|
|
},
|
|
};
|
|
|
|
static int stac92xx_build_pcms(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
struct hda_pcm *info = spec->pcm_rec;
|
|
|
|
codec->num_pcms = 1;
|
|
codec->pcm_info = info;
|
|
|
|
info->name = "STAC92xx Analog";
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_playback;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_analog_capture;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
|
|
|
|
if (spec->alt_switch) {
|
|
codec->num_pcms++;
|
|
info++;
|
|
info->name = "STAC92xx Analog Alt";
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_alt_playback;
|
|
}
|
|
|
|
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
|
|
codec->num_pcms++;
|
|
info++;
|
|
info->name = "STAC92xx Digital";
|
|
if (spec->multiout.dig_out_nid) {
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_digital_playback;
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
|
|
}
|
|
if (spec->dig_in_nid) {
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_digital_capture;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int stac92xx_get_vref(struct hda_codec *codec, hda_nid_t nid)
|
|
{
|
|
unsigned int pincap = snd_hda_param_read(codec, nid,
|
|
AC_PAR_PIN_CAP);
|
|
pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
|
|
if (pincap & AC_PINCAP_VREF_100)
|
|
return AC_PINCTL_VREF_100;
|
|
if (pincap & AC_PINCAP_VREF_80)
|
|
return AC_PINCTL_VREF_80;
|
|
if (pincap & AC_PINCAP_VREF_50)
|
|
return AC_PINCTL_VREF_50;
|
|
if (pincap & AC_PINCAP_VREF_GRD)
|
|
return AC_PINCTL_VREF_GRD;
|
|
return 0;
|
|
}
|
|
|
|
static void stac92xx_auto_set_pinctl(struct hda_codec *codec, hda_nid_t nid, int pin_type)
|
|
|
|
{
|
|
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pin_type);
|
|
}
|
|
|
|
static int stac92xx_io_switch_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 stac92xx_io_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int io_idx = kcontrol-> private_value & 0xff;
|
|
|
|
ucontrol->value.integer.value[0] = spec->io_switch[io_idx];
|
|
return 0;
|
|
}
|
|
|
|
static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
hda_nid_t nid = kcontrol->private_value >> 8;
|
|
int io_idx = kcontrol-> private_value & 0xff;
|
|
unsigned short val = ucontrol->value.integer.value[0];
|
|
|
|
spec->io_switch[io_idx] = val;
|
|
|
|
if (val)
|
|
stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN);
|
|
else {
|
|
unsigned int pinctl = AC_PINCTL_IN_EN;
|
|
if (io_idx) /* set VREF for mic */
|
|
pinctl |= stac92xx_get_vref(codec, nid);
|
|
stac92xx_auto_set_pinctl(codec, nid, pinctl);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#define STAC_CODEC_IO_SWITCH(xname, xpval) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
|
|
.name = xname, \
|
|
.index = 0, \
|
|
.info = stac92xx_io_switch_info, \
|
|
.get = stac92xx_io_switch_get, \
|
|
.put = stac92xx_io_switch_put, \
|
|
.private_value = xpval, \
|
|
}
|
|
|
|
|
|
enum {
|
|
STAC_CTL_WIDGET_VOL,
|
|
STAC_CTL_WIDGET_MUTE,
|
|
STAC_CTL_WIDGET_IO_SWITCH,
|
|
};
|
|
|
|
static struct snd_kcontrol_new stac92xx_control_templates[] = {
|
|
HDA_CODEC_VOLUME(NULL, 0, 0, 0),
|
|
HDA_CODEC_MUTE(NULL, 0, 0, 0),
|
|
STAC_CODEC_IO_SWITCH(NULL, 0),
|
|
};
|
|
|
|
/* add dynamic controls */
|
|
static int stac92xx_add_control(struct sigmatel_spec *spec, int type, const char *name, unsigned long val)
|
|
{
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
if (spec->num_kctl_used >= spec->num_kctl_alloc) {
|
|
int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC;
|
|
|
|
knew = kcalloc(num + 1, sizeof(*knew), GFP_KERNEL); /* array + terminator */
|
|
if (! knew)
|
|
return -ENOMEM;
|
|
if (spec->kctl_alloc) {
|
|
memcpy(knew, spec->kctl_alloc, sizeof(*knew) * spec->num_kctl_alloc);
|
|
kfree(spec->kctl_alloc);
|
|
}
|
|
spec->kctl_alloc = knew;
|
|
spec->num_kctl_alloc = num;
|
|
}
|
|
|
|
knew = &spec->kctl_alloc[spec->num_kctl_used];
|
|
*knew = stac92xx_control_templates[type];
|
|
knew->name = kstrdup(name, GFP_KERNEL);
|
|
if (! knew->name)
|
|
return -ENOMEM;
|
|
knew->private_value = val;
|
|
spec->num_kctl_used++;
|
|
return 0;
|
|
}
|
|
|
|
/* flag inputs as additional dynamic lineouts */
|
|
static int stac92xx_add_dyn_out_pins(struct hda_codec *codec, struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
|
|
switch (cfg->line_outs) {
|
|
case 3:
|
|
/* add line-in as side */
|
|
if (cfg->input_pins[AUTO_PIN_LINE]) {
|
|
cfg->line_out_pins[3] = cfg->input_pins[AUTO_PIN_LINE];
|
|
spec->line_switch = 1;
|
|
cfg->line_outs++;
|
|
}
|
|
break;
|
|
case 2:
|
|
/* add line-in as clfe and mic as side */
|
|
if (cfg->input_pins[AUTO_PIN_LINE]) {
|
|
cfg->line_out_pins[2] = cfg->input_pins[AUTO_PIN_LINE];
|
|
spec->line_switch = 1;
|
|
cfg->line_outs++;
|
|
}
|
|
if (cfg->input_pins[AUTO_PIN_MIC]) {
|
|
cfg->line_out_pins[3] = cfg->input_pins[AUTO_PIN_MIC];
|
|
spec->mic_switch = 1;
|
|
cfg->line_outs++;
|
|
}
|
|
break;
|
|
case 1:
|
|
/* add line-in as surr and mic as clfe */
|
|
if (cfg->input_pins[AUTO_PIN_LINE]) {
|
|
cfg->line_out_pins[1] = cfg->input_pins[AUTO_PIN_LINE];
|
|
spec->line_switch = 1;
|
|
cfg->line_outs++;
|
|
}
|
|
if (cfg->input_pins[AUTO_PIN_MIC]) {
|
|
cfg->line_out_pins[2] = cfg->input_pins[AUTO_PIN_MIC];
|
|
spec->mic_switch = 1;
|
|
cfg->line_outs++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* XXX The line_out pin widget connection list may not be set to the
|
|
* desired DAC nid. This is the case on 927x where ports A and B can
|
|
* be routed to several DACs.
|
|
*
|
|
* This requires an analysis of the line-out/hp pin configuration
|
|
* to provide a best fit for pin/DAC configurations that are routable.
|
|
* For now, 927x DAC4 is not supported and 927x DAC1 output to ports
|
|
* A and B is not supported.
|
|
*/
|
|
/* fill in the dac_nids table from the parsed pin configuration */
|
|
static int stac92xx_auto_fill_dac_nids(struct hda_codec *codec,
|
|
const struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
hda_nid_t nid;
|
|
int i;
|
|
|
|
/* check the pins hardwired to audio widget */
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
nid = cfg->line_out_pins[i];
|
|
spec->multiout.dac_nids[i] = snd_hda_codec_read(codec, nid, 0,
|
|
AC_VERB_GET_CONNECT_LIST, 0) & 0xff;
|
|
}
|
|
|
|
spec->multiout.num_dacs = cfg->line_outs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* create volume control/switch for the given prefx type */
|
|
static int create_controls(struct sigmatel_spec *spec, const char *pfx, hda_nid_t nid, int chs)
|
|
{
|
|
char name[32];
|
|
int err;
|
|
|
|
sprintf(name, "%s Playback Volume", pfx);
|
|
err = stac92xx_add_control(spec, STAC_CTL_WIDGET_VOL, name,
|
|
HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
|
|
if (err < 0)
|
|
return err;
|
|
sprintf(name, "%s Playback Switch", pfx);
|
|
err = stac92xx_add_control(spec, STAC_CTL_WIDGET_MUTE, name,
|
|
HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
/* add playback controls from the parsed DAC table */
|
|
static int stac92xx_auto_create_multi_out_ctls(struct sigmatel_spec *spec,
|
|
const struct auto_pin_cfg *cfg)
|
|
{
|
|
static const char *chname[4] = {
|
|
"Front", "Surround", NULL /*CLFE*/, "Side"
|
|
};
|
|
hda_nid_t nid;
|
|
int i, err;
|
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
if (!spec->multiout.dac_nids[i])
|
|
continue;
|
|
|
|
nid = spec->multiout.dac_nids[i];
|
|
|
|
if (i == 2) {
|
|
/* Center/LFE */
|
|
err = create_controls(spec, "Center", nid, 1);
|
|
if (err < 0)
|
|
return err;
|
|
err = create_controls(spec, "LFE", nid, 2);
|
|
if (err < 0)
|
|
return err;
|
|
} else {
|
|
err = create_controls(spec, chname[i], nid, 3);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (spec->line_switch)
|
|
if ((err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH, "Line In as Output Switch", cfg->input_pins[AUTO_PIN_LINE] << 8)) < 0)
|
|
return err;
|
|
|
|
if (spec->mic_switch)
|
|
if ((err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH, "Mic as Output Switch", (cfg->input_pins[AUTO_PIN_MIC] << 8) | 1)) < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_in_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < spec->multiout.num_dacs; i++) {
|
|
if (spec->multiout.dac_nids[i] == nid)
|
|
return 1;
|
|
}
|
|
if (spec->multiout.hp_nid == nid)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int add_spec_dacs(struct sigmatel_spec *spec, hda_nid_t nid)
|
|
{
|
|
if (!spec->multiout.hp_nid)
|
|
spec->multiout.hp_nid = nid;
|
|
else if (spec->multiout.num_dacs > 4) {
|
|
printk(KERN_WARNING "stac92xx: No space for DAC 0x%x\n", nid);
|
|
return 1;
|
|
} else {
|
|
spec->multiout.dac_nids[spec->multiout.num_dacs] = nid;
|
|
spec->multiout.num_dacs++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* add playback controls for Speaker and HP outputs */
|
|
static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec,
|
|
struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
hda_nid_t nid;
|
|
int i, old_num_dacs, err;
|
|
|
|
old_num_dacs = spec->multiout.num_dacs;
|
|
for (i = 0; i < cfg->hp_outs; i++) {
|
|
unsigned int wid_caps = get_wcaps(codec, cfg->hp_pins[i]);
|
|
if (wid_caps & AC_WCAP_UNSOL_CAP)
|
|
spec->hp_detect = 1;
|
|
nid = snd_hda_codec_read(codec, cfg->hp_pins[i], 0,
|
|
AC_VERB_GET_CONNECT_LIST, 0) & 0xff;
|
|
if (check_in_dac_nids(spec, nid))
|
|
nid = 0;
|
|
if (! nid)
|
|
continue;
|
|
add_spec_dacs(spec, nid);
|
|
}
|
|
for (i = 0; i < cfg->speaker_outs; i++) {
|
|
nid = snd_hda_codec_read(codec, cfg->speaker_pins[0], 0,
|
|
AC_VERB_GET_CONNECT_LIST, 0) & 0xff;
|
|
if (check_in_dac_nids(spec, nid))
|
|
nid = 0;
|
|
if (check_in_dac_nids(spec, nid))
|
|
nid = 0;
|
|
if (! nid)
|
|
continue;
|
|
add_spec_dacs(spec, nid);
|
|
}
|
|
|
|
for (i = old_num_dacs; i < spec->multiout.num_dacs; i++) {
|
|
static const char *pfxs[] = {
|
|
"Speaker", "External Speaker", "Speaker2",
|
|
};
|
|
err = create_controls(spec, pfxs[i - old_num_dacs],
|
|
spec->multiout.dac_nids[i], 3);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (spec->multiout.hp_nid) {
|
|
const char *pfx;
|
|
if (old_num_dacs == spec->multiout.num_dacs)
|
|
pfx = "Master";
|
|
else
|
|
pfx = "Headphone";
|
|
err = create_controls(spec, pfx, spec->multiout.hp_nid, 3);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* labels for dmic mux inputs */
|
|
static const char *stac92xx_dmic_labels[5] = {
|
|
"Analog Inputs", "Digital Mic 1", "Digital Mic 2",
|
|
"Digital Mic 3", "Digital Mic 4"
|
|
};
|
|
|
|
/* create playback/capture controls for input pins on dmic capable codecs */
|
|
static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec,
|
|
const struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
struct hda_input_mux *dimux = &spec->private_dimux;
|
|
hda_nid_t con_lst[HDA_MAX_NUM_INPUTS];
|
|
int i, j;
|
|
|
|
dimux->items[dimux->num_items].label = stac92xx_dmic_labels[0];
|
|
dimux->items[dimux->num_items].index = 0;
|
|
dimux->num_items++;
|
|
|
|
for (i = 0; i < spec->num_dmics; i++) {
|
|
int index;
|
|
int num_cons;
|
|
unsigned int def_conf;
|
|
|
|
def_conf = snd_hda_codec_read(codec,
|
|
spec->dmic_nids[i],
|
|
0,
|
|
AC_VERB_GET_CONFIG_DEFAULT,
|
|
0);
|
|
if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
|
|
continue;
|
|
|
|
num_cons = snd_hda_get_connections(codec,
|
|
spec->dmux_nid,
|
|
con_lst,
|
|
HDA_MAX_NUM_INPUTS);
|
|
for (j = 0; j < num_cons; j++)
|
|
if (con_lst[j] == spec->dmic_nids[i]) {
|
|
index = j;
|
|
goto found;
|
|
}
|
|
continue;
|
|
found:
|
|
dimux->items[dimux->num_items].label =
|
|
stac92xx_dmic_labels[dimux->num_items];
|
|
dimux->items[dimux->num_items].index = index;
|
|
dimux->num_items++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* create playback/capture controls for input pins */
|
|
static int stac92xx_auto_create_analog_input_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
struct hda_input_mux *imux = &spec->private_imux;
|
|
hda_nid_t con_lst[HDA_MAX_NUM_INPUTS];
|
|
int i, j, k;
|
|
|
|
for (i = 0; i < AUTO_PIN_LAST; i++) {
|
|
int index;
|
|
|
|
if (!cfg->input_pins[i])
|
|
continue;
|
|
index = -1;
|
|
for (j = 0; j < spec->num_muxes; j++) {
|
|
int num_cons;
|
|
num_cons = snd_hda_get_connections(codec,
|
|
spec->mux_nids[j],
|
|
con_lst,
|
|
HDA_MAX_NUM_INPUTS);
|
|
for (k = 0; k < num_cons; k++)
|
|
if (con_lst[k] == cfg->input_pins[i]) {
|
|
index = k;
|
|
goto found;
|
|
}
|
|
}
|
|
continue;
|
|
found:
|
|
imux->items[imux->num_items].label = auto_pin_cfg_labels[i];
|
|
imux->items[imux->num_items].index = index;
|
|
imux->num_items++;
|
|
}
|
|
|
|
if (imux->num_items == 1) {
|
|
/*
|
|
* Set the current input for the muxes.
|
|
* The STAC9221 has two input muxes with identical source
|
|
* NID lists. Hopefully this won't get confused.
|
|
*/
|
|
for (i = 0; i < spec->num_muxes; i++) {
|
|
snd_hda_codec_write(codec, spec->mux_nids[i], 0,
|
|
AC_VERB_SET_CONNECT_SEL,
|
|
imux->items[0].index);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void stac92xx_auto_init_multi_out(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
for (i = 0; i < spec->autocfg.line_outs; i++) {
|
|
hda_nid_t nid = spec->autocfg.line_out_pins[i];
|
|
stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN);
|
|
}
|
|
}
|
|
|
|
static void stac92xx_auto_init_hp_out(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
for (i = 0; i < spec->autocfg.hp_outs; i++) {
|
|
hda_nid_t pin;
|
|
pin = spec->autocfg.hp_pins[i];
|
|
if (pin) /* connect to front */
|
|
stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
|
|
}
|
|
for (i = 0; i < spec->autocfg.speaker_outs; i++) {
|
|
hda_nid_t pin;
|
|
pin = spec->autocfg.speaker_pins[i];
|
|
if (pin) /* connect to front */
|
|
stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN);
|
|
}
|
|
}
|
|
|
|
static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out, hda_nid_t dig_in)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int err;
|
|
|
|
if ((err = snd_hda_parse_pin_def_config(codec,
|
|
&spec->autocfg,
|
|
spec->dmic_nids)) < 0)
|
|
return err;
|
|
if (! spec->autocfg.line_outs)
|
|
return 0; /* can't find valid pin config */
|
|
|
|
if ((err = stac92xx_add_dyn_out_pins(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
if (spec->multiout.num_dacs == 0)
|
|
if ((err = stac92xx_auto_fill_dac_nids(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
if ((err = stac92xx_auto_create_multi_out_ctls(spec, &spec->autocfg)) < 0 ||
|
|
(err = stac92xx_auto_create_hp_ctls(codec, &spec->autocfg)) < 0 ||
|
|
(err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
if (spec->num_dmics > 0)
|
|
if ((err = stac92xx_auto_create_dmic_input_ctls(codec,
|
|
&spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
spec->multiout.max_channels = spec->multiout.num_dacs * 2;
|
|
if (spec->multiout.max_channels > 2)
|
|
spec->surr_switch = 1;
|
|
|
|
if (spec->autocfg.dig_out_pin)
|
|
spec->multiout.dig_out_nid = dig_out;
|
|
if (spec->autocfg.dig_in_pin)
|
|
spec->dig_in_nid = dig_in;
|
|
|
|
if (spec->kctl_alloc)
|
|
spec->mixers[spec->num_mixers++] = spec->kctl_alloc;
|
|
|
|
spec->input_mux = &spec->private_imux;
|
|
spec->dinput_mux = &spec->private_dimux;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* add playback controls for HP output */
|
|
static int stac9200_auto_create_hp_ctls(struct hda_codec *codec,
|
|
struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
hda_nid_t pin = cfg->hp_pins[0];
|
|
unsigned int wid_caps;
|
|
|
|
if (! pin)
|
|
return 0;
|
|
|
|
wid_caps = get_wcaps(codec, pin);
|
|
if (wid_caps & AC_WCAP_UNSOL_CAP)
|
|
spec->hp_detect = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* add playback controls for LFE output */
|
|
static int stac9200_auto_create_lfe_ctls(struct hda_codec *codec,
|
|
struct auto_pin_cfg *cfg)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int err;
|
|
hda_nid_t lfe_pin = 0x0;
|
|
int i;
|
|
|
|
/*
|
|
* search speaker outs and line outs for a mono speaker pin
|
|
* with an amp. If one is found, add LFE controls
|
|
* for it.
|
|
*/
|
|
for (i = 0; i < spec->autocfg.speaker_outs && lfe_pin == 0x0; i++) {
|
|
hda_nid_t pin = spec->autocfg.speaker_pins[i];
|
|
unsigned long wcaps = get_wcaps(codec, pin);
|
|
wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP);
|
|
if (wcaps == AC_WCAP_OUT_AMP)
|
|
/* found a mono speaker with an amp, must be lfe */
|
|
lfe_pin = pin;
|
|
}
|
|
|
|
/* if speaker_outs is 0, then speakers may be in line_outs */
|
|
if (lfe_pin == 0 && spec->autocfg.speaker_outs == 0) {
|
|
for (i = 0; i < spec->autocfg.line_outs && lfe_pin == 0x0; i++) {
|
|
hda_nid_t pin = spec->autocfg.line_out_pins[i];
|
|
unsigned long cfg;
|
|
cfg = snd_hda_codec_read(codec, pin, 0,
|
|
AC_VERB_GET_CONFIG_DEFAULT,
|
|
0x00);
|
|
if (get_defcfg_device(cfg) == AC_JACK_SPEAKER) {
|
|
unsigned long wcaps = get_wcaps(codec, pin);
|
|
wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP);
|
|
if (wcaps == AC_WCAP_OUT_AMP)
|
|
/* found a mono speaker with an amp,
|
|
must be lfe */
|
|
lfe_pin = pin;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lfe_pin) {
|
|
err = create_controls(spec, "LFE", lfe_pin, 1);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stac9200_parse_auto_config(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int err;
|
|
|
|
if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 0)
|
|
return err;
|
|
|
|
if ((err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
if ((err = stac9200_auto_create_hp_ctls(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
if ((err = stac9200_auto_create_lfe_ctls(codec, &spec->autocfg)) < 0)
|
|
return err;
|
|
|
|
if (spec->autocfg.dig_out_pin)
|
|
spec->multiout.dig_out_nid = 0x05;
|
|
if (spec->autocfg.dig_in_pin)
|
|
spec->dig_in_nid = 0x04;
|
|
|
|
if (spec->kctl_alloc)
|
|
spec->mixers[spec->num_mixers++] = spec->kctl_alloc;
|
|
|
|
spec->input_mux = &spec->private_imux;
|
|
spec->dinput_mux = &spec->private_dimux;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Early 2006 Intel Macintoshes with STAC9220X5 codecs seem to have a
|
|
* funky external mute control using GPIO pins.
|
|
*/
|
|
|
|
static void stac922x_gpio_mute(struct hda_codec *codec, int pin, int muted)
|
|
{
|
|
unsigned int gpiostate, gpiomask, gpiodir;
|
|
|
|
gpiostate = snd_hda_codec_read(codec, codec->afg, 0,
|
|
AC_VERB_GET_GPIO_DATA, 0);
|
|
|
|
if (!muted)
|
|
gpiostate |= (1 << pin);
|
|
else
|
|
gpiostate &= ~(1 << pin);
|
|
|
|
gpiomask = snd_hda_codec_read(codec, codec->afg, 0,
|
|
AC_VERB_GET_GPIO_MASK, 0);
|
|
gpiomask |= (1 << pin);
|
|
|
|
gpiodir = snd_hda_codec_read(codec, codec->afg, 0,
|
|
AC_VERB_GET_GPIO_DIRECTION, 0);
|
|
gpiodir |= (1 << pin);
|
|
|
|
/* AppleHDA seems to do this -- WTF is this verb?? */
|
|
snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0);
|
|
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_MASK, gpiomask);
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_DIRECTION, gpiodir);
|
|
|
|
msleep(1);
|
|
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_DATA, gpiostate);
|
|
}
|
|
|
|
static void enable_pin_detect(struct hda_codec *codec, hda_nid_t nid,
|
|
unsigned int event)
|
|
{
|
|
if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP)
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
AC_VERB_SET_UNSOLICITED_ENABLE,
|
|
(AC_USRSP_EN | event));
|
|
}
|
|
|
|
static int stac92xx_init(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
int i;
|
|
|
|
snd_hda_sequence_write(codec, spec->init);
|
|
|
|
/* set up pins */
|
|
if (spec->hp_detect) {
|
|
/* Enable unsolicited responses on the HP widget */
|
|
for (i = 0; i < cfg->hp_outs; i++)
|
|
enable_pin_detect(codec, cfg->hp_pins[i],
|
|
STAC_HP_EVENT);
|
|
stac92xx_auto_init_hp_out(codec);
|
|
/* fake event to set up pins */
|
|
codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26);
|
|
} else {
|
|
stac92xx_auto_init_multi_out(codec);
|
|
stac92xx_auto_init_hp_out(codec);
|
|
}
|
|
for (i = 0; i < AUTO_PIN_LAST; i++) {
|
|
hda_nid_t nid = cfg->input_pins[i];
|
|
if (nid) {
|
|
unsigned int pinctl = AC_PINCTL_IN_EN;
|
|
if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC)
|
|
pinctl |= stac92xx_get_vref(codec, nid);
|
|
stac92xx_auto_set_pinctl(codec, nid, pinctl);
|
|
}
|
|
}
|
|
if (spec->num_dmics > 0)
|
|
for (i = 0; i < spec->num_dmics; i++)
|
|
stac92xx_auto_set_pinctl(codec, spec->dmic_nids[i],
|
|
AC_PINCTL_IN_EN);
|
|
|
|
if (cfg->dig_out_pin)
|
|
stac92xx_auto_set_pinctl(codec, cfg->dig_out_pin,
|
|
AC_PINCTL_OUT_EN);
|
|
if (cfg->dig_in_pin)
|
|
stac92xx_auto_set_pinctl(codec, cfg->dig_in_pin,
|
|
AC_PINCTL_IN_EN);
|
|
|
|
if (spec->gpio_mute) {
|
|
stac922x_gpio_mute(codec, 0, 0);
|
|
stac922x_gpio_mute(codec, 1, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void stac92xx_free(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
if (! spec)
|
|
return;
|
|
|
|
if (spec->kctl_alloc) {
|
|
for (i = 0; i < spec->num_kctl_used; i++)
|
|
kfree(spec->kctl_alloc[i].name);
|
|
kfree(spec->kctl_alloc);
|
|
}
|
|
|
|
if (spec->bios_pin_configs)
|
|
kfree(spec->bios_pin_configs);
|
|
|
|
kfree(spec);
|
|
}
|
|
|
|
static void stac92xx_set_pinctl(struct hda_codec *codec, hda_nid_t nid,
|
|
unsigned int flag)
|
|
{
|
|
unsigned int pin_ctl = snd_hda_codec_read(codec, nid,
|
|
0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
|
|
if (flag == AC_PINCTL_OUT_EN && (pin_ctl & AC_PINCTL_IN_EN))
|
|
return;
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
pin_ctl | flag);
|
|
}
|
|
|
|
static void stac92xx_reset_pinctl(struct hda_codec *codec, hda_nid_t nid,
|
|
unsigned int flag)
|
|
{
|
|
unsigned int pin_ctl = snd_hda_codec_read(codec, nid,
|
|
0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
pin_ctl & ~flag);
|
|
}
|
|
|
|
static int get_pin_presence(struct hda_codec *codec, hda_nid_t nid)
|
|
{
|
|
if (!nid)
|
|
return 0;
|
|
if (snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_SENSE, 0x00)
|
|
& (1 << 31))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void stac92xx_hp_detect(struct hda_codec *codec, unsigned int res)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
int i, presence;
|
|
|
|
presence = 0;
|
|
for (i = 0; i < cfg->hp_outs; i++) {
|
|
presence = get_pin_presence(codec, cfg->hp_pins[i]);
|
|
if (presence)
|
|
break;
|
|
}
|
|
|
|
if (presence) {
|
|
/* disable lineouts, enable hp */
|
|
for (i = 0; i < cfg->line_outs; i++)
|
|
stac92xx_reset_pinctl(codec, cfg->line_out_pins[i],
|
|
AC_PINCTL_OUT_EN);
|
|
for (i = 0; i < cfg->speaker_outs; i++)
|
|
stac92xx_reset_pinctl(codec, cfg->speaker_pins[i],
|
|
AC_PINCTL_OUT_EN);
|
|
} else {
|
|
/* enable lineouts, disable hp */
|
|
for (i = 0; i < cfg->line_outs; i++)
|
|
stac92xx_set_pinctl(codec, cfg->line_out_pins[i],
|
|
AC_PINCTL_OUT_EN);
|
|
for (i = 0; i < cfg->speaker_outs; i++)
|
|
stac92xx_set_pinctl(codec, cfg->speaker_pins[i],
|
|
AC_PINCTL_OUT_EN);
|
|
}
|
|
}
|
|
|
|
static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
|
|
{
|
|
switch (res >> 26) {
|
|
case STAC_HP_EVENT:
|
|
stac92xx_hp_detect(codec, res);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int stac92xx_resume(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
stac92xx_init(codec);
|
|
stac92xx_set_config_regs(codec);
|
|
for (i = 0; i < spec->num_mixers; i++)
|
|
snd_hda_resume_ctls(codec, spec->mixers[i]);
|
|
if (spec->multiout.dig_out_nid)
|
|
snd_hda_resume_spdif_out(codec);
|
|
if (spec->dig_in_nid)
|
|
snd_hda_resume_spdif_in(codec);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static struct hda_codec_ops stac92xx_patch_ops = {
|
|
.build_controls = stac92xx_build_controls,
|
|
.build_pcms = stac92xx_build_pcms,
|
|
.init = stac92xx_init,
|
|
.free = stac92xx_free,
|
|
.unsol_event = stac92xx_unsol_event,
|
|
#ifdef CONFIG_PM
|
|
.resume = stac92xx_resume,
|
|
#endif
|
|
};
|
|
|
|
static int patch_stac9200(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int err;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
spec->num_pins = 8;
|
|
spec->pin_nids = stac9200_pin_nids;
|
|
spec->board_config = snd_hda_check_board_config(codec, STAC_9200_MODELS,
|
|
stac9200_models,
|
|
stac9200_cfg_tbl);
|
|
if (spec->board_config < 0) {
|
|
snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC9200, using BIOS defaults\n");
|
|
err = stac92xx_save_bios_config_regs(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
spec->pin_configs = spec->bios_pin_configs;
|
|
} else {
|
|
spec->pin_configs = stac9200_brd_tbl[spec->board_config];
|
|
stac92xx_set_config_regs(codec);
|
|
}
|
|
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = 1;
|
|
spec->multiout.dac_nids = stac9200_dac_nids;
|
|
spec->adc_nids = stac9200_adc_nids;
|
|
spec->mux_nids = stac9200_mux_nids;
|
|
spec->num_muxes = 1;
|
|
spec->num_dmics = 0;
|
|
|
|
spec->init = stac9200_core_init;
|
|
spec->mixer = stac9200_mixer;
|
|
|
|
err = stac9200_parse_auto_config(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
|
|
codec->patch_ops = stac92xx_patch_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int patch_stac925x(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int err;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
spec->num_pins = 8;
|
|
spec->pin_nids = stac925x_pin_nids;
|
|
spec->board_config = snd_hda_check_board_config(codec, STAC_925x_MODELS,
|
|
stac925x_models,
|
|
stac925x_cfg_tbl);
|
|
again:
|
|
if (spec->board_config < 0) {
|
|
snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC925x, using BIOS defaults\n");
|
|
err = stac92xx_save_bios_config_regs(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
spec->pin_configs = spec->bios_pin_configs;
|
|
} else if (stac925x_brd_tbl[spec->board_config] != NULL){
|
|
spec->pin_configs = stac925x_brd_tbl[spec->board_config];
|
|
stac92xx_set_config_regs(codec);
|
|
}
|
|
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = 1;
|
|
spec->multiout.dac_nids = stac925x_dac_nids;
|
|
spec->adc_nids = stac925x_adc_nids;
|
|
spec->mux_nids = stac925x_mux_nids;
|
|
spec->num_muxes = 1;
|
|
spec->num_dmics = 0;
|
|
|
|
spec->init = stac925x_core_init;
|
|
spec->mixer = stac925x_mixer;
|
|
|
|
err = stac92xx_parse_auto_config(codec, 0x8, 0x7);
|
|
if (!err) {
|
|
if (spec->board_config < 0) {
|
|
printk(KERN_WARNING "hda_codec: No auto-config is "
|
|
"available, default to model=ref\n");
|
|
spec->board_config = STAC_925x_REF;
|
|
goto again;
|
|
}
|
|
err = -EINVAL;
|
|
}
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
|
|
codec->patch_ops = stac92xx_patch_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int patch_stac922x(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int err;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
spec->num_pins = 10;
|
|
spec->pin_nids = stac922x_pin_nids;
|
|
spec->board_config = snd_hda_check_board_config(codec, STAC_922X_MODELS,
|
|
stac922x_models,
|
|
stac922x_cfg_tbl);
|
|
if (spec->board_config == STAC_MACMINI) {
|
|
spec->gpio_mute = 1;
|
|
/* Intel Macs have all same PCI SSID, so we need to check
|
|
* codec SSID to distinguish the exact models
|
|
*/
|
|
switch (codec->subsystem_id) {
|
|
case 0x106b1e00:
|
|
spec->board_config = STAC_MACBOOK_PRO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
again:
|
|
if (spec->board_config < 0) {
|
|
snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC922x, "
|
|
"using BIOS defaults\n");
|
|
err = stac92xx_save_bios_config_regs(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
spec->pin_configs = spec->bios_pin_configs;
|
|
} else if (stac922x_brd_tbl[spec->board_config] != NULL) {
|
|
spec->pin_configs = stac922x_brd_tbl[spec->board_config];
|
|
stac92xx_set_config_regs(codec);
|
|
}
|
|
|
|
spec->adc_nids = stac922x_adc_nids;
|
|
spec->mux_nids = stac922x_mux_nids;
|
|
spec->num_muxes = 2;
|
|
spec->num_dmics = 0;
|
|
|
|
spec->init = stac922x_core_init;
|
|
spec->mixer = stac922x_mixer;
|
|
|
|
spec->multiout.dac_nids = spec->dac_nids;
|
|
|
|
err = stac92xx_parse_auto_config(codec, 0x08, 0x09);
|
|
if (!err) {
|
|
if (spec->board_config < 0) {
|
|
printk(KERN_WARNING "hda_codec: No auto-config is "
|
|
"available, default to model=ref\n");
|
|
spec->board_config = STAC_D945_REF;
|
|
goto again;
|
|
}
|
|
err = -EINVAL;
|
|
}
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
|
|
codec->patch_ops = stac92xx_patch_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int patch_stac927x(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int err;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
spec->num_pins = 14;
|
|
spec->pin_nids = stac927x_pin_nids;
|
|
spec->board_config = snd_hda_check_board_config(codec, STAC_927X_MODELS,
|
|
stac927x_models,
|
|
stac927x_cfg_tbl);
|
|
again:
|
|
if (spec->board_config < 0) {
|
|
snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC927x, using BIOS defaults\n");
|
|
err = stac92xx_save_bios_config_regs(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
spec->pin_configs = spec->bios_pin_configs;
|
|
} else if (stac927x_brd_tbl[spec->board_config] != NULL) {
|
|
spec->pin_configs = stac927x_brd_tbl[spec->board_config];
|
|
stac92xx_set_config_regs(codec);
|
|
}
|
|
|
|
switch (spec->board_config) {
|
|
case STAC_D965_3ST:
|
|
spec->adc_nids = stac927x_adc_nids;
|
|
spec->mux_nids = stac927x_mux_nids;
|
|
spec->num_muxes = 3;
|
|
spec->num_dmics = 0;
|
|
spec->init = d965_core_init;
|
|
spec->mixer = stac9227_mixer;
|
|
break;
|
|
case STAC_D965_5ST:
|
|
spec->adc_nids = stac927x_adc_nids;
|
|
spec->mux_nids = stac927x_mux_nids;
|
|
spec->num_muxes = 3;
|
|
spec->num_dmics = 0;
|
|
spec->init = d965_core_init;
|
|
spec->mixer = stac9227_mixer;
|
|
break;
|
|
default:
|
|
spec->adc_nids = stac927x_adc_nids;
|
|
spec->mux_nids = stac927x_mux_nids;
|
|
spec->num_muxes = 3;
|
|
spec->num_dmics = 0;
|
|
spec->init = stac927x_core_init;
|
|
spec->mixer = stac927x_mixer;
|
|
}
|
|
|
|
spec->multiout.dac_nids = spec->dac_nids;
|
|
|
|
err = stac92xx_parse_auto_config(codec, 0x1e, 0x20);
|
|
if (!err) {
|
|
if (spec->board_config < 0) {
|
|
printk(KERN_WARNING "hda_codec: No auto-config is "
|
|
"available, default to model=ref\n");
|
|
spec->board_config = STAC_D965_REF;
|
|
goto again;
|
|
}
|
|
err = -EINVAL;
|
|
}
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
|
|
codec->patch_ops = stac92xx_patch_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int patch_stac9205(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int err;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
spec->num_pins = 14;
|
|
spec->pin_nids = stac9205_pin_nids;
|
|
spec->board_config = snd_hda_check_board_config(codec, STAC_9205_MODELS,
|
|
stac9205_models,
|
|
stac9205_cfg_tbl);
|
|
again:
|
|
if (spec->board_config < 0) {
|
|
snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC9205, using BIOS defaults\n");
|
|
err = stac92xx_save_bios_config_regs(codec);
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
spec->pin_configs = spec->bios_pin_configs;
|
|
} else {
|
|
spec->pin_configs = stac9205_brd_tbl[spec->board_config];
|
|
stac92xx_set_config_regs(codec);
|
|
}
|
|
|
|
spec->adc_nids = stac9205_adc_nids;
|
|
spec->mux_nids = stac9205_mux_nids;
|
|
spec->num_muxes = 2;
|
|
spec->dmic_nids = stac9205_dmic_nids;
|
|
spec->num_dmics = 2;
|
|
spec->dmux_nid = 0x1d;
|
|
|
|
spec->init = stac9205_core_init;
|
|
spec->mixer = stac9205_mixer;
|
|
|
|
spec->multiout.dac_nids = spec->dac_nids;
|
|
|
|
/* Configure GPIO0 as EAPD output */
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_DIRECTION, 0x00000001);
|
|
/* Configure GPIO0 as CMOS */
|
|
snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0x00000000);
|
|
/* Assert GPIO0 high */
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_DATA, 0x00000001);
|
|
/* Enable GPIO0 */
|
|
snd_hda_codec_write(codec, codec->afg, 0,
|
|
AC_VERB_SET_GPIO_MASK, 0x00000001);
|
|
|
|
err = stac92xx_parse_auto_config(codec, 0x1f, 0x20);
|
|
if (!err) {
|
|
if (spec->board_config < 0) {
|
|
printk(KERN_WARNING "hda_codec: No auto-config is "
|
|
"available, default to model=ref\n");
|
|
spec->board_config = STAC_9205_REF;
|
|
goto again;
|
|
}
|
|
err = -EINVAL;
|
|
}
|
|
if (err < 0) {
|
|
stac92xx_free(codec);
|
|
return err;
|
|
}
|
|
|
|
codec->patch_ops = stac92xx_patch_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* STAC9872 hack
|
|
*/
|
|
|
|
/* static config for Sony VAIO FE550G and Sony VAIO AR */
|
|
static hda_nid_t vaio_dacs[] = { 0x2 };
|
|
#define VAIO_HP_DAC 0x5
|
|
static hda_nid_t vaio_adcs[] = { 0x8 /*,0x6*/ };
|
|
static hda_nid_t vaio_mux_nids[] = { 0x15 };
|
|
|
|
static struct hda_input_mux vaio_mux = {
|
|
.num_items = 2,
|
|
.items = {
|
|
/* { "HP", 0x0 }, */
|
|
{ "Line", 0x1 },
|
|
{ "Mic", 0x2 },
|
|
{ "PCM", 0x3 },
|
|
}
|
|
};
|
|
|
|
static struct hda_verb vaio_init[] = {
|
|
{0x0a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP <- 0x2 */
|
|
{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Speaker <- 0x5 */
|
|
{0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? (<- 0x2) */
|
|
{0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */
|
|
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */
|
|
{0x15, AC_VERB_SET_CONNECT_SEL, 0x2}, /* mic-sel: 0a,0d,14,02 */
|
|
{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */
|
|
{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */
|
|
{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* capture sw/vol -> 0x8 */
|
|
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* CD-in -> 0x6 */
|
|
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb vaio_ar_init[] = {
|
|
{0x0a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP <- 0x2 */
|
|
{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Speaker <- 0x5 */
|
|
{0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? (<- 0x2) */
|
|
{0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */
|
|
/* {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },*/ /* Optical Out */
|
|
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */
|
|
{0x15, AC_VERB_SET_CONNECT_SEL, 0x2}, /* mic-sel: 0a,0d,14,02 */
|
|
{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */
|
|
{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */
|
|
/* {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},*/ /* Optical Out */
|
|
{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* capture sw/vol -> 0x8 */
|
|
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* CD-in -> 0x6 */
|
|
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */
|
|
{}
|
|
};
|
|
|
|
/* bind volumes of both NID 0x02 and 0x05 */
|
|
static int vaio_master_vol_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
long *valp = ucontrol->value.integer.value;
|
|
int change;
|
|
|
|
change = snd_hda_codec_amp_update(codec, 0x02, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
change |= snd_hda_codec_amp_update(codec, 0x02, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x05, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x05, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
return change;
|
|
}
|
|
|
|
/* bind volumes of both NID 0x02 and 0x05 */
|
|
static int vaio_master_sw_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
long *valp = ucontrol->value.integer.value;
|
|
int change;
|
|
|
|
change = snd_hda_codec_amp_update(codec, 0x02, 0, HDA_OUTPUT, 0,
|
|
0x80, (valp[0] ? 0 : 0x80));
|
|
change |= snd_hda_codec_amp_update(codec, 0x02, 1, HDA_OUTPUT, 0,
|
|
0x80, (valp[1] ? 0 : 0x80));
|
|
snd_hda_codec_amp_update(codec, 0x05, 0, HDA_OUTPUT, 0,
|
|
0x80, (valp[0] ? 0 : 0x80));
|
|
snd_hda_codec_amp_update(codec, 0x05, 1, HDA_OUTPUT, 0,
|
|
0x80, (valp[1] ? 0 : 0x80));
|
|
return change;
|
|
}
|
|
|
|
static struct snd_kcontrol_new vaio_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Volume",
|
|
.info = snd_hda_mixer_amp_volume_info,
|
|
.get = snd_hda_mixer_amp_volume_get,
|
|
.put = vaio_master_vol_put,
|
|
.tlv = { .c = snd_hda_mixer_amp_tlv },
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
|
|
},
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.info = snd_hda_mixer_amp_switch_info,
|
|
.get = snd_hda_mixer_amp_switch_get,
|
|
.put = vaio_master_sw_put,
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
|
|
},
|
|
/* HDA_CODEC_VOLUME("CD Capture Volume", 0x07, 0, HDA_INPUT), */
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_INPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct snd_kcontrol_new vaio_ar_mixer[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Volume",
|
|
.info = snd_hda_mixer_amp_volume_info,
|
|
.get = snd_hda_mixer_amp_volume_get,
|
|
.put = vaio_master_vol_put,
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
|
|
},
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.info = snd_hda_mixer_amp_switch_info,
|
|
.get = snd_hda_mixer_amp_switch_get,
|
|
.put = vaio_master_sw_put,
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
|
|
},
|
|
/* HDA_CODEC_VOLUME("CD Capture Volume", 0x07, 0, HDA_INPUT), */
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_INPUT),
|
|
/*HDA_CODEC_MUTE("Optical Out Switch", 0x10, 0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Optical Out Volume", 0x10, 0, HDA_OUTPUT),*/
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.count = 1,
|
|
.info = stac92xx_mux_enum_info,
|
|
.get = stac92xx_mux_enum_get,
|
|
.put = stac92xx_mux_enum_put,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct hda_codec_ops stac9872_patch_ops = {
|
|
.build_controls = stac92xx_build_controls,
|
|
.build_pcms = stac92xx_build_pcms,
|
|
.init = stac92xx_init,
|
|
.free = stac92xx_free,
|
|
#ifdef CONFIG_PM
|
|
.resume = stac92xx_resume,
|
|
#endif
|
|
};
|
|
|
|
enum { /* FE and SZ series. id=0x83847661 and subsys=0x104D0700 or 104D1000. */
|
|
CXD9872RD_VAIO,
|
|
/* Unknown. id=0x83847662 and subsys=0x104D1200 or 104D1000. */
|
|
STAC9872AK_VAIO,
|
|
/* Unknown. id=0x83847661 and subsys=0x104D1200. */
|
|
STAC9872K_VAIO,
|
|
/* AR Series. id=0x83847664 and subsys=104D1300 */
|
|
CXD9872AKD_VAIO,
|
|
STAC_9872_MODELS,
|
|
};
|
|
|
|
static const char *stac9872_models[STAC_9872_MODELS] = {
|
|
[CXD9872RD_VAIO] = "vaio",
|
|
[CXD9872AKD_VAIO] = "vaio-ar",
|
|
};
|
|
|
|
static struct snd_pci_quirk stac9872_cfg_tbl[] = {
|
|
SND_PCI_QUIRK(0x104d, 0x81e6, "Sony VAIO F/S", CXD9872RD_VAIO),
|
|
SND_PCI_QUIRK(0x104d, 0x81ef, "Sony VAIO F/S", CXD9872RD_VAIO),
|
|
SND_PCI_QUIRK(0x104d, 0x81fd, "Sony VAIO AR", CXD9872AKD_VAIO),
|
|
{}
|
|
};
|
|
|
|
static int patch_stac9872(struct hda_codec *codec)
|
|
{
|
|
struct sigmatel_spec *spec;
|
|
int board_config;
|
|
|
|
board_config = snd_hda_check_board_config(codec, STAC_9872_MODELS,
|
|
stac9872_models,
|
|
stac9872_cfg_tbl);
|
|
if (board_config < 0)
|
|
/* unknown config, let generic-parser do its job... */
|
|
return snd_hda_parse_generic_codec(codec);
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (spec == NULL)
|
|
return -ENOMEM;
|
|
|
|
codec->spec = spec;
|
|
switch (board_config) {
|
|
case CXD9872RD_VAIO:
|
|
case STAC9872AK_VAIO:
|
|
case STAC9872K_VAIO:
|
|
spec->mixer = vaio_mixer;
|
|
spec->init = vaio_init;
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = ARRAY_SIZE(vaio_dacs);
|
|
spec->multiout.dac_nids = vaio_dacs;
|
|
spec->multiout.hp_nid = VAIO_HP_DAC;
|
|
spec->num_adcs = ARRAY_SIZE(vaio_adcs);
|
|
spec->adc_nids = vaio_adcs;
|
|
spec->input_mux = &vaio_mux;
|
|
spec->mux_nids = vaio_mux_nids;
|
|
break;
|
|
|
|
case CXD9872AKD_VAIO:
|
|
spec->mixer = vaio_ar_mixer;
|
|
spec->init = vaio_ar_init;
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = ARRAY_SIZE(vaio_dacs);
|
|
spec->multiout.dac_nids = vaio_dacs;
|
|
spec->multiout.hp_nid = VAIO_HP_DAC;
|
|
spec->num_adcs = ARRAY_SIZE(vaio_adcs);
|
|
spec->adc_nids = vaio_adcs;
|
|
spec->input_mux = &vaio_mux;
|
|
spec->mux_nids = vaio_mux_nids;
|
|
break;
|
|
}
|
|
|
|
codec->patch_ops = stac9872_patch_ops;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* patch entries
|
|
*/
|
|
struct hda_codec_preset snd_hda_preset_sigmatel[] = {
|
|
{ .id = 0x83847690, .name = "STAC9200", .patch = patch_stac9200 },
|
|
{ .id = 0x83847882, .name = "STAC9220 A1", .patch = patch_stac922x },
|
|
{ .id = 0x83847680, .name = "STAC9221 A1", .patch = patch_stac922x },
|
|
{ .id = 0x83847880, .name = "STAC9220 A2", .patch = patch_stac922x },
|
|
{ .id = 0x83847681, .name = "STAC9220D/9223D A2", .patch = patch_stac922x },
|
|
{ .id = 0x83847682, .name = "STAC9221 A2", .patch = patch_stac922x },
|
|
{ .id = 0x83847683, .name = "STAC9221D A2", .patch = patch_stac922x },
|
|
{ .id = 0x83847618, .name = "STAC9227", .patch = patch_stac927x },
|
|
{ .id = 0x83847619, .name = "STAC9227", .patch = patch_stac927x },
|
|
{ .id = 0x83847616, .name = "STAC9228", .patch = patch_stac927x },
|
|
{ .id = 0x83847617, .name = "STAC9228", .patch = patch_stac927x },
|
|
{ .id = 0x83847614, .name = "STAC9229", .patch = patch_stac927x },
|
|
{ .id = 0x83847615, .name = "STAC9229", .patch = patch_stac927x },
|
|
{ .id = 0x83847620, .name = "STAC9274", .patch = patch_stac927x },
|
|
{ .id = 0x83847621, .name = "STAC9274D", .patch = patch_stac927x },
|
|
{ .id = 0x83847622, .name = "STAC9273X", .patch = patch_stac927x },
|
|
{ .id = 0x83847623, .name = "STAC9273D", .patch = patch_stac927x },
|
|
{ .id = 0x83847624, .name = "STAC9272X", .patch = patch_stac927x },
|
|
{ .id = 0x83847625, .name = "STAC9272D", .patch = patch_stac927x },
|
|
{ .id = 0x83847626, .name = "STAC9271X", .patch = patch_stac927x },
|
|
{ .id = 0x83847627, .name = "STAC9271D", .patch = patch_stac927x },
|
|
{ .id = 0x83847628, .name = "STAC9274X5NH", .patch = patch_stac927x },
|
|
{ .id = 0x83847629, .name = "STAC9274D5NH", .patch = patch_stac927x },
|
|
{ .id = 0x83847632, .name = "STAC9202", .patch = patch_stac925x },
|
|
{ .id = 0x83847633, .name = "STAC9202D", .patch = patch_stac925x },
|
|
{ .id = 0x83847634, .name = "STAC9250", .patch = patch_stac925x },
|
|
{ .id = 0x83847635, .name = "STAC9250D", .patch = patch_stac925x },
|
|
{ .id = 0x83847636, .name = "STAC9251", .patch = patch_stac925x },
|
|
{ .id = 0x83847637, .name = "STAC9250D", .patch = patch_stac925x },
|
|
/* The following does not take into account .id=0x83847661 when subsys =
|
|
* 104D0C00 which is STAC9225s. Because of this, some SZ Notebooks are
|
|
* currently not fully supported.
|
|
*/
|
|
{ .id = 0x83847661, .name = "CXD9872RD/K", .patch = patch_stac9872 },
|
|
{ .id = 0x83847662, .name = "STAC9872AK", .patch = patch_stac9872 },
|
|
{ .id = 0x83847664, .name = "CXD9872AKD", .patch = patch_stac9872 },
|
|
{ .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a1, .name = "STAC9205D", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a2, .name = "STAC9204", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a3, .name = "STAC9204D", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a4, .name = "STAC9255", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a5, .name = "STAC9255D", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a6, .name = "STAC9254", .patch = patch_stac9205 },
|
|
{ .id = 0x838476a7, .name = "STAC9254D", .patch = patch_stac9205 },
|
|
{} /* terminator */
|
|
};
|