82beb8fd36
So far, the driver looked the table of snd_kcontrol_new used for creating mixer elements and forces to call each of its put callbacks in PM resume code. This is too ugly and hackish. Now, the resume is simplified using the codec amp and command register caches. The driver simply restores the values that have been written in the cache table. With this simplification, most codec support codes don't require any special resume callback. Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
1360 lines
42 KiB
C
1360 lines
42 KiB
C
/*
|
|
* HD audio interface patch for Conexant HDA audio codec
|
|
*
|
|
* Copyright (c) 2006 Pototskiy Akex <alex.pototskiy@gmail.com>
|
|
* Takashi Iwai <tiwai@suse.de>
|
|
* Tobin Davis <tdavis@dsl-only.net>
|
|
*
|
|
* 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 "hda_codec.h"
|
|
#include "hda_local.h"
|
|
|
|
#define CXT_PIN_DIR_IN 0x00
|
|
#define CXT_PIN_DIR_OUT 0x01
|
|
#define CXT_PIN_DIR_INOUT 0x02
|
|
#define CXT_PIN_DIR_IN_NOMICBIAS 0x03
|
|
#define CXT_PIN_DIR_INOUT_NOMICBIAS 0x04
|
|
|
|
#define CONEXANT_HP_EVENT 0x37
|
|
#define CONEXANT_MIC_EVENT 0x38
|
|
|
|
|
|
|
|
struct conexant_spec {
|
|
|
|
struct snd_kcontrol_new *mixers[5];
|
|
int num_mixers;
|
|
|
|
const struct hda_verb *init_verbs[5]; /* initialization verbs
|
|
* don't forget NULL
|
|
* termination!
|
|
*/
|
|
unsigned int num_init_verbs;
|
|
|
|
/* playback */
|
|
struct hda_multi_out multiout; /* playback set-up
|
|
* max_channels, dacs must be set
|
|
* dig_out_nid and hp_nid are optional
|
|
*/
|
|
unsigned int cur_eapd;
|
|
unsigned int hp_present;
|
|
unsigned int need_dac_fix;
|
|
|
|
/* capture */
|
|
unsigned int num_adc_nids;
|
|
hda_nid_t *adc_nids;
|
|
hda_nid_t dig_in_nid; /* digital-in NID; optional */
|
|
|
|
/* capture source */
|
|
const struct hda_input_mux *input_mux;
|
|
hda_nid_t *capsrc_nids;
|
|
unsigned int cur_mux[3];
|
|
|
|
/* channel model */
|
|
const struct hda_channel_mode *channel_mode;
|
|
int num_channel_mode;
|
|
|
|
/* PCM information */
|
|
struct hda_pcm pcm_rec[2]; /* used in build_pcms() */
|
|
|
|
struct mutex amp_mutex; /* PCM volume/mute control mutex */
|
|
unsigned int spdif_route;
|
|
|
|
/* dynamic controls, init_verbs 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_imux;
|
|
hda_nid_t private_dac_nids[4];
|
|
|
|
};
|
|
|
|
static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
|
|
}
|
|
|
|
static int conexant_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 conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
|
|
stream_tag,
|
|
format, substream);
|
|
}
|
|
|
|
static int conexant_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
|
|
}
|
|
|
|
/*
|
|
* Digital out
|
|
*/
|
|
static int conexant_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
|
|
}
|
|
|
|
static int conexant_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
|
|
}
|
|
|
|
static int conexant_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
unsigned int stream_tag,
|
|
unsigned int format,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
|
|
stream_tag,
|
|
format, substream);
|
|
}
|
|
|
|
/*
|
|
* Analog capture
|
|
*/
|
|
static int conexant_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 conexant_spec *spec = codec->spec;
|
|
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
|
|
stream_tag, 0, format);
|
|
return 0;
|
|
}
|
|
|
|
static int conexant_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct conexant_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 conexant_pcm_analog_playback = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.nid = 0, /* fill later */
|
|
.ops = {
|
|
.open = conexant_playback_pcm_open,
|
|
.prepare = conexant_playback_pcm_prepare,
|
|
.cleanup = conexant_playback_pcm_cleanup
|
|
},
|
|
};
|
|
|
|
static struct hda_pcm_stream conexant_pcm_analog_capture = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.nid = 0, /* fill later */
|
|
.ops = {
|
|
.prepare = conexant_capture_pcm_prepare,
|
|
.cleanup = conexant_capture_pcm_cleanup
|
|
},
|
|
};
|
|
|
|
|
|
static struct hda_pcm_stream conexant_pcm_digital_playback = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.nid = 0, /* fill later */
|
|
.ops = {
|
|
.open = conexant_dig_playback_pcm_open,
|
|
.close = conexant_dig_playback_pcm_close,
|
|
.prepare = conexant_dig_playback_pcm_prepare
|
|
},
|
|
};
|
|
|
|
static struct hda_pcm_stream conexant_pcm_digital_capture = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
/* NID is set in alc_build_pcms */
|
|
};
|
|
|
|
static int conexant_build_pcms(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
struct hda_pcm *info = spec->pcm_rec;
|
|
|
|
codec->num_pcms = 1;
|
|
codec->pcm_info = info;
|
|
|
|
info->name = "CONEXANT Analog";
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = conexant_pcm_analog_playback;
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
|
|
spec->multiout.max_channels;
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
|
|
spec->multiout.dac_nids[0];
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE] = conexant_pcm_analog_capture;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
|
|
|
|
if (spec->multiout.dig_out_nid) {
|
|
info++;
|
|
codec->num_pcms++;
|
|
info->name = "Conexant Digital";
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
|
|
conexant_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] =
|
|
conexant_pcm_digital_capture;
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
|
|
spec->dig_in_nid;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int conexant_mux_enum_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
|
|
return snd_hda_input_mux_info(spec->input_mux, uinfo);
|
|
}
|
|
|
|
static int conexant_mux_enum_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_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 conexant_mux_enum_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_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->capsrc_nids[adc_idx],
|
|
&spec->cur_mux[adc_idx]);
|
|
}
|
|
|
|
static int conexant_init(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
for (i = 0; i < spec->num_init_verbs; i++)
|
|
snd_hda_sequence_write(codec, spec->init_verbs[i]);
|
|
return 0;
|
|
}
|
|
|
|
static void conexant_free(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int i;
|
|
|
|
if (spec->kctl_alloc) {
|
|
for (i = 0; i < spec->num_kctl_used; i++)
|
|
kfree(spec->kctl_alloc[i].name);
|
|
kfree(spec->kctl_alloc);
|
|
}
|
|
|
|
kfree(codec->spec);
|
|
}
|
|
|
|
static int conexant_build_controls(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int i;
|
|
int 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 struct hda_codec_ops conexant_patch_ops = {
|
|
.build_controls = conexant_build_controls,
|
|
.build_pcms = conexant_build_pcms,
|
|
.init = conexant_init,
|
|
.free = conexant_free,
|
|
};
|
|
|
|
/*
|
|
* EAPD control
|
|
* the private value = nid | (invert << 8)
|
|
*/
|
|
|
|
#define cxt_eapd_info snd_ctl_boolean_mono_info
|
|
|
|
static int cxt_eapd_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
int invert = (kcontrol->private_value >> 8) & 1;
|
|
if (invert)
|
|
ucontrol->value.integer.value[0] = !spec->cur_eapd;
|
|
else
|
|
ucontrol->value.integer.value[0] = spec->cur_eapd;
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int cxt_eapd_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
int invert = (kcontrol->private_value >> 8) & 1;
|
|
hda_nid_t nid = kcontrol->private_value & 0xff;
|
|
unsigned int eapd;
|
|
|
|
eapd = ucontrol->value.integer.value[0];
|
|
if (invert)
|
|
eapd = !eapd;
|
|
if (eapd == spec->cur_eapd)
|
|
return 0;
|
|
|
|
spec->cur_eapd = eapd;
|
|
snd_hda_codec_write_cache(codec, nid,
|
|
0, AC_VERB_SET_EAPD_BTLENABLE,
|
|
eapd ? 0x02 : 0x00);
|
|
return 1;
|
|
}
|
|
|
|
/* controls for test mode */
|
|
#ifdef CONFIG_SND_DEBUG
|
|
|
|
#define CXT_EAPD_SWITCH(xname, nid, mask) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
|
|
.info = cxt_eapd_info, \
|
|
.get = cxt_eapd_get, \
|
|
.put = cxt_eapd_put, \
|
|
.private_value = nid | (mask<<16) }
|
|
|
|
|
|
|
|
static int conexant_ch_mode_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode,
|
|
spec->num_channel_mode);
|
|
}
|
|
|
|
static int conexant_ch_mode_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode,
|
|
spec->num_channel_mode,
|
|
spec->multiout.max_channels);
|
|
}
|
|
|
|
static int conexant_ch_mode_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode,
|
|
spec->num_channel_mode,
|
|
&spec->multiout.max_channels);
|
|
if (err >= 0 && spec->need_dac_fix)
|
|
spec->multiout.num_dacs = spec->multiout.max_channels / 2;
|
|
return err;
|
|
}
|
|
|
|
#define CXT_PIN_MODE(xname, nid, dir) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
|
|
.info = conexant_ch_mode_info, \
|
|
.get = conexant_ch_mode_get, \
|
|
.put = conexant_ch_mode_put, \
|
|
.private_value = nid | (dir<<16) }
|
|
|
|
#endif /* CONFIG_SND_DEBUG */
|
|
|
|
/* Conexant 5045 specific */
|
|
|
|
static hda_nid_t cxt5045_dac_nids[1] = { 0x19 };
|
|
static hda_nid_t cxt5045_adc_nids[1] = { 0x1a };
|
|
static hda_nid_t cxt5045_capsrc_nids[1] = { 0x1a };
|
|
#define CXT5045_SPDIF_OUT 0x13
|
|
|
|
static struct hda_channel_mode cxt5045_modes[1] = {
|
|
{ 2, NULL },
|
|
};
|
|
|
|
static struct hda_input_mux cxt5045_capture_source = {
|
|
.num_items = 2,
|
|
.items = {
|
|
{ "IntMic", 0x1 },
|
|
{ "LineIn", 0x2 },
|
|
}
|
|
};
|
|
|
|
/* turn on/off EAPD (+ mute HP) as a master switch */
|
|
static int cxt5045_hp_master_sw_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int bits;
|
|
|
|
if (!cxt_eapd_put(kcontrol, ucontrol))
|
|
return 0;
|
|
|
|
/* toggle internal speakers mute depending of presence of
|
|
* the headphone jack
|
|
*/
|
|
bits = (!spec->hp_present && spec->cur_eapd) ? 0 : 0x80;
|
|
snd_hda_codec_amp_update(codec, 0x10, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x10, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
|
|
bits = spec->cur_eapd ? 0 : 0x80;
|
|
snd_hda_codec_amp_update(codec, 0x11, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x11, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
return 1;
|
|
}
|
|
|
|
/* bind volumes of both NID 0x10 and 0x11 */
|
|
static int cxt5045_hp_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, 0x10, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
change |= snd_hda_codec_amp_update(codec, 0x10, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x11, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x11, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
return change;
|
|
}
|
|
|
|
/* toggle input of built-in and mic jack appropriately */
|
|
static void cxt5045_hp_automic(struct hda_codec *codec)
|
|
{
|
|
static struct hda_verb mic_jack_on[] = {
|
|
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
|
|
{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
|
|
{}
|
|
};
|
|
static struct hda_verb mic_jack_off[] = {
|
|
{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
|
|
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
|
|
{}
|
|
};
|
|
unsigned int present;
|
|
|
|
present = snd_hda_codec_read(codec, 0x12, 0,
|
|
AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
|
|
if (present)
|
|
snd_hda_sequence_write(codec, mic_jack_on);
|
|
else
|
|
snd_hda_sequence_write(codec, mic_jack_off);
|
|
}
|
|
|
|
|
|
/* mute internal speaker if HP is plugged */
|
|
static void cxt5045_hp_automute(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int bits;
|
|
|
|
spec->hp_present = snd_hda_codec_read(codec, 0x11, 0,
|
|
AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
|
|
|
|
bits = (spec->hp_present || !spec->cur_eapd) ? 0x80 : 0;
|
|
snd_hda_codec_amp_update(codec, 0x10, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x10, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
}
|
|
|
|
/* unsolicited event for HP jack sensing */
|
|
static void cxt5045_hp_unsol_event(struct hda_codec *codec,
|
|
unsigned int res)
|
|
{
|
|
res >>= 26;
|
|
switch (res) {
|
|
case CONEXANT_HP_EVENT:
|
|
cxt5045_hp_automute(codec);
|
|
break;
|
|
case CONEXANT_MIC_EVENT:
|
|
cxt5045_hp_automic(codec);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
static struct snd_kcontrol_new cxt5045_mixers[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.info = conexant_mux_enum_info,
|
|
.get = conexant_mux_enum_get,
|
|
.put = conexant_mux_enum_put
|
|
},
|
|
HDA_CODEC_VOLUME("Int Mic Volume", 0x1a, 0x01, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Int Mic Switch", 0x1a, 0x01, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Ext Mic Volume", 0x1a, 0x02, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Ext Mic Switch", 0x1a, 0x02, HDA_INPUT),
|
|
{
|
|
.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 = cxt5045_hp_master_vol_put,
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x10, 3, 0, HDA_OUTPUT),
|
|
},
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.info = cxt_eapd_info,
|
|
.get = cxt_eapd_get,
|
|
.put = cxt5045_hp_master_sw_put,
|
|
.private_value = 0x10,
|
|
},
|
|
|
|
{}
|
|
};
|
|
|
|
static struct hda_verb cxt5045_init_verbs[] = {
|
|
/* Line in, Mic */
|
|
{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
|
|
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 },
|
|
/* HP, Amp */
|
|
{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
|
|
{0x17, AC_VERB_SET_CONNECT_SEL,0x01},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x01},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x02},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x03},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x04},
|
|
/* Record selector: Int mic */
|
|
{0x1a, AC_VERB_SET_CONNECT_SEL,0x1},
|
|
{0x1a, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
|
|
/* SPDIF route: PCM */
|
|
{ 0x13, AC_VERB_SET_CONNECT_SEL, 0x0 },
|
|
/* EAPD */
|
|
{0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x2 }, /* default on */
|
|
{ } /* end */
|
|
};
|
|
|
|
|
|
static struct hda_verb cxt5045_hp_sense_init_verbs[] = {
|
|
/* pin sensing on HP jack */
|
|
{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct hda_verb cxt5045_mic_sense_init_verbs[] = {
|
|
/* pin sensing on HP jack */
|
|
{0x12, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
|
|
{ } /* end */
|
|
};
|
|
|
|
#ifdef CONFIG_SND_DEBUG
|
|
/* Test configuration for debugging, modelled after the ALC260 test
|
|
* configuration.
|
|
*/
|
|
static struct hda_input_mux cxt5045_test_capture_source = {
|
|
.num_items = 5,
|
|
.items = {
|
|
{ "MIXER", 0x0 },
|
|
{ "MIC1 pin", 0x1 },
|
|
{ "LINE1 pin", 0x2 },
|
|
{ "HP-OUT pin", 0x3 },
|
|
{ "CD pin", 0x4 },
|
|
},
|
|
};
|
|
|
|
static struct snd_kcontrol_new cxt5045_test_mixer[] = {
|
|
|
|
/* Output controls */
|
|
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x10, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Speaker Playback Switch", 0x10, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Node 11 Playback Volume", 0x11, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Node 11 Playback Switch", 0x11, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Node 12 Playback Volume", 0x12, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Node 12 Playback Switch", 0x12, 0x0, HDA_OUTPUT),
|
|
|
|
/* Modes for retasking pin widgets */
|
|
CXT_PIN_MODE("HP-OUT pin mode", 0x11, CXT_PIN_DIR_INOUT),
|
|
CXT_PIN_MODE("LINE1 pin mode", 0x12, CXT_PIN_DIR_INOUT),
|
|
|
|
/* EAPD Switch Control */
|
|
CXT_EAPD_SWITCH("External Amplifier", 0x10, 0x0),
|
|
|
|
/* Loopback mixer controls */
|
|
|
|
HDA_CODEC_VOLUME("Mixer-1 Volume", 0x17, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mixer-1 Switch", 0x17, 0x0, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mixer-2 Volume", 0x17, 0x1, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mixer-2 Switch", 0x17, 0x1, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mixer-3 Volume", 0x17, 0x2, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mixer-3 Switch", 0x17, 0x2, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mixer-4 Volume", 0x17, 0x3, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mixer-4 Switch", 0x17, 0x3, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mixer-5 Volume", 0x17, 0x4, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mixer-5 Switch", 0x17, 0x4, HDA_INPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.info = conexant_mux_enum_info,
|
|
.get = conexant_mux_enum_get,
|
|
.put = conexant_mux_enum_put,
|
|
},
|
|
/* Audio input controls */
|
|
HDA_CODEC_VOLUME("Input-1 Volume", 0x1a, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Input-1 Switch", 0x1a, 0x0, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Input-2 Volume", 0x1a, 0x1, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Input-2 Switch", 0x1a, 0x1, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Input-3 Volume", 0x1a, 0x2, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Input-3 Switch", 0x1a, 0x2, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Input-4 Volume", 0x1a, 0x3, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Input-4 Switch", 0x1a, 0x3, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Input-5 Volume", 0x1a, 0x4, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Input-5 Switch", 0x1a, 0x4, HDA_INPUT),
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct hda_verb cxt5045_test_init_verbs[] = {
|
|
/* Set connections */
|
|
{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 },
|
|
{ 0x11, AC_VERB_SET_CONNECT_SEL, 0x0 },
|
|
{ 0x12, AC_VERB_SET_CONNECT_SEL, 0x0 },
|
|
/* Enable retasking pins as output, initially without power amp */
|
|
{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
|
|
{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
|
|
|
|
/* Disable digital (SPDIF) pins initially, but users can enable
|
|
* them via a mixer switch. In the case of SPDIF-out, this initverb
|
|
* payload also sets the generation to 0, output to be in "consumer"
|
|
* PCM format, copyright asserted, no pre-emphasis and no validity
|
|
* control.
|
|
*/
|
|
{0x13, AC_VERB_SET_DIGI_CONVERT_1, 0},
|
|
|
|
/* Start with output sum widgets muted and their output gains at min */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
|
|
|
|
/* Unmute retasking pin widget output buffers since the default
|
|
* state appears to be output. As the pin mode is changed by the
|
|
* user the pin mode control will take care of enabling the pin's
|
|
* input/output buffers as needed.
|
|
*/
|
|
{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
|
|
{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
|
|
|
|
/* Mute capture amp left and right */
|
|
{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
|
|
|
|
/* Set ADC connection select to match default mixer setting (mic1
|
|
* pin)
|
|
*/
|
|
{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
|
|
{0x17, AC_VERB_SET_CONNECT_SEL, 0x00},
|
|
|
|
/* Mute all inputs to mixer widget (even unconnected ones) */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* Mixer pin */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* Mic1 pin */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* Line pin */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* HP pin */
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
|
|
|
|
{ }
|
|
};
|
|
#endif
|
|
|
|
|
|
/* initialize jack-sensing, too */
|
|
static int cxt5045_init(struct hda_codec *codec)
|
|
{
|
|
conexant_init(codec);
|
|
cxt5045_hp_automute(codec);
|
|
return 0;
|
|
}
|
|
|
|
|
|
enum {
|
|
CXT5045_LAPTOP, /* Laptops w/ EAPD support */
|
|
CXT5045_FUJITSU, /* Laptops w/ EAPD support */
|
|
#ifdef CONFIG_SND_DEBUG
|
|
CXT5045_TEST,
|
|
#endif
|
|
CXT5045_MODELS
|
|
};
|
|
|
|
static const char *cxt5045_models[CXT5045_MODELS] = {
|
|
[CXT5045_LAPTOP] = "laptop",
|
|
[CXT5045_FUJITSU] = "fujitsu",
|
|
#ifdef CONFIG_SND_DEBUG
|
|
[CXT5045_TEST] = "test",
|
|
#endif
|
|
};
|
|
|
|
static struct snd_pci_quirk cxt5045_cfg_tbl[] = {
|
|
SND_PCI_QUIRK(0x103c, 0x30b7, "HP DV6000Z", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30bb, "HP DV8000", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30b2, "HP DV Series", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30b5, "HP DV2120", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30cd, "HP DV Series", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30d9, "HP Spartan", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x1734, 0x10ad, "Fujitsu Si1520", CXT5045_FUJITSU),
|
|
SND_PCI_QUIRK(0x1734, 0x10cb, "Fujitsu Si3515", CXT5045_LAPTOP),
|
|
SND_PCI_QUIRK(0x8086, 0x2111, "Conexant Reference board", CXT5045_LAPTOP),
|
|
{}
|
|
};
|
|
|
|
static int patch_cxt5045(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec;
|
|
int board_config;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (!spec)
|
|
return -ENOMEM;
|
|
mutex_init(&spec->amp_mutex);
|
|
codec->spec = spec;
|
|
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = ARRAY_SIZE(cxt5045_dac_nids);
|
|
spec->multiout.dac_nids = cxt5045_dac_nids;
|
|
spec->multiout.dig_out_nid = CXT5045_SPDIF_OUT;
|
|
spec->num_adc_nids = 1;
|
|
spec->adc_nids = cxt5045_adc_nids;
|
|
spec->capsrc_nids = cxt5045_capsrc_nids;
|
|
spec->input_mux = &cxt5045_capture_source;
|
|
spec->num_mixers = 1;
|
|
spec->mixers[0] = cxt5045_mixers;
|
|
spec->num_init_verbs = 1;
|
|
spec->init_verbs[0] = cxt5045_init_verbs;
|
|
spec->spdif_route = 0;
|
|
spec->num_channel_mode = ARRAY_SIZE(cxt5045_modes),
|
|
spec->channel_mode = cxt5045_modes,
|
|
|
|
|
|
codec->patch_ops = conexant_patch_ops;
|
|
|
|
board_config = snd_hda_check_board_config(codec, CXT5045_MODELS,
|
|
cxt5045_models,
|
|
cxt5045_cfg_tbl);
|
|
switch (board_config) {
|
|
case CXT5045_LAPTOP:
|
|
codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
|
|
spec->input_mux = &cxt5045_capture_source;
|
|
spec->num_init_verbs = 2;
|
|
spec->init_verbs[1] = cxt5045_hp_sense_init_verbs;
|
|
spec->mixers[0] = cxt5045_mixers;
|
|
codec->patch_ops.init = cxt5045_init;
|
|
break;
|
|
case CXT5045_FUJITSU:
|
|
spec->input_mux = &cxt5045_capture_source;
|
|
spec->num_init_verbs = 2;
|
|
spec->init_verbs[1] = cxt5045_mic_sense_init_verbs;
|
|
spec->mixers[0] = cxt5045_mixers;
|
|
codec->patch_ops.init = cxt5045_init;
|
|
break;
|
|
#ifdef CONFIG_SND_DEBUG
|
|
case CXT5045_TEST:
|
|
spec->input_mux = &cxt5045_test_capture_source;
|
|
spec->mixers[0] = cxt5045_test_mixer;
|
|
spec->init_verbs[0] = cxt5045_test_init_verbs;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Conexant 5047 specific */
|
|
#define CXT5047_SPDIF_OUT 0x11
|
|
|
|
static hda_nid_t cxt5047_dac_nids[2] = { 0x10, 0x1c };
|
|
static hda_nid_t cxt5047_adc_nids[1] = { 0x12 };
|
|
static hda_nid_t cxt5047_capsrc_nids[1] = { 0x1a };
|
|
|
|
static struct hda_channel_mode cxt5047_modes[1] = {
|
|
{ 2, NULL },
|
|
};
|
|
|
|
static struct hda_input_mux cxt5047_capture_source = {
|
|
.num_items = 1,
|
|
.items = {
|
|
{ "Mic", 0x2 },
|
|
}
|
|
};
|
|
|
|
static struct hda_input_mux cxt5047_hp_capture_source = {
|
|
.num_items = 1,
|
|
.items = {
|
|
{ "ExtMic", 0x2 },
|
|
}
|
|
};
|
|
|
|
static struct hda_input_mux cxt5047_toshiba_capture_source = {
|
|
.num_items = 2,
|
|
.items = {
|
|
{ "ExtMic", 0x2 },
|
|
{ "Line-In", 0x1 },
|
|
}
|
|
};
|
|
|
|
/* turn on/off EAPD (+ mute HP) as a master switch */
|
|
static int cxt5047_hp_master_sw_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int bits;
|
|
|
|
if (!cxt_eapd_put(kcontrol, ucontrol))
|
|
return 0;
|
|
|
|
/* toggle internal speakers mute depending of presence of
|
|
* the headphone jack
|
|
*/
|
|
bits = (!spec->hp_present && spec->cur_eapd) ? 0 : 0x80;
|
|
snd_hda_codec_amp_update(codec, 0x1d, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x1d, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
bits = spec->cur_eapd ? 0 : 0x80;
|
|
snd_hda_codec_amp_update(codec, 0x13, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x13, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
return 1;
|
|
}
|
|
|
|
/* bind volumes of both NID 0x13 (Headphones) and 0x1d (Speakers) */
|
|
static int cxt5047_hp_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, 0x1d, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
change |= snd_hda_codec_amp_update(codec, 0x1d, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x13, 0, HDA_OUTPUT, 0,
|
|
0x7f, valp[0] & 0x7f);
|
|
snd_hda_codec_amp_update(codec, 0x13, 1, HDA_OUTPUT, 0,
|
|
0x7f, valp[1] & 0x7f);
|
|
return change;
|
|
}
|
|
|
|
/* mute internal speaker if HP is plugged */
|
|
static void cxt5047_hp_automute(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int bits;
|
|
|
|
spec->hp_present = snd_hda_codec_read(codec, 0x13, 0,
|
|
AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
|
|
|
|
bits = (spec->hp_present || !spec->cur_eapd) ? 0x80 : 0;
|
|
snd_hda_codec_amp_update(codec, 0x1d, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x1d, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
/* Mute/Unmute PCM 2 for good measure - some systems need this */
|
|
snd_hda_codec_amp_update(codec, 0x1c, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
}
|
|
|
|
/* mute internal speaker if HP is plugged */
|
|
static void cxt5047_hp2_automute(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec = codec->spec;
|
|
unsigned int bits;
|
|
|
|
spec->hp_present = snd_hda_codec_read(codec, 0x13, 0,
|
|
AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
|
|
|
|
bits = spec->hp_present ? 0x80 : 0;
|
|
snd_hda_codec_amp_update(codec, 0x1d, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x1d, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
/* Mute/Unmute PCM 2 for good measure - some systems need this */
|
|
snd_hda_codec_amp_update(codec, 0x1c, 0, HDA_OUTPUT, 0, 0x80, bits);
|
|
snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, 0x80, bits);
|
|
}
|
|
|
|
/* toggle input of built-in and mic jack appropriately */
|
|
static void cxt5047_hp_automic(struct hda_codec *codec)
|
|
{
|
|
static struct hda_verb mic_jack_on[] = {
|
|
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
|
|
{}
|
|
};
|
|
static struct hda_verb mic_jack_off[] = {
|
|
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
|
|
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
|
|
{}
|
|
};
|
|
unsigned int present;
|
|
|
|
present = snd_hda_codec_read(codec, 0x15, 0,
|
|
AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
|
|
if (present)
|
|
snd_hda_sequence_write(codec, mic_jack_on);
|
|
else
|
|
snd_hda_sequence_write(codec, mic_jack_off);
|
|
}
|
|
|
|
/* unsolicited event for HP jack sensing */
|
|
static void cxt5047_hp_unsol_event(struct hda_codec *codec,
|
|
unsigned int res)
|
|
{
|
|
res >>= 26;
|
|
switch (res) {
|
|
case CONEXANT_HP_EVENT:
|
|
cxt5047_hp_automute(codec);
|
|
break;
|
|
case CONEXANT_MIC_EVENT:
|
|
cxt5047_hp_automic(codec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* unsolicited event for HP jack sensing - non-EAPD systems */
|
|
static void cxt5047_hp2_unsol_event(struct hda_codec *codec,
|
|
unsigned int res)
|
|
{
|
|
res >>= 26;
|
|
switch (res) {
|
|
case CONEXANT_HP_EVENT:
|
|
cxt5047_hp2_automute(codec);
|
|
break;
|
|
case CONEXANT_MIC_EVENT:
|
|
cxt5047_hp_automic(codec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct snd_kcontrol_new cxt5047_mixers[] = {
|
|
HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19, 0x02, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Mic Gain Volume", 0x1a, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Mic Gain Switch", 0x1a, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("PCM-2 Volume", 0x1c, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("PCM-2 Switch", 0x1c, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Speaker Playback Switch", 0x1d, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x13, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Headphone Playback Switch", 0x13, 0x00, HDA_OUTPUT),
|
|
|
|
{}
|
|
};
|
|
|
|
static struct snd_kcontrol_new cxt5047_toshiba_mixers[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.info = conexant_mux_enum_info,
|
|
.get = conexant_mux_enum_get,
|
|
.put = conexant_mux_enum_put
|
|
},
|
|
HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19, 0x02, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT),
|
|
{
|
|
.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 = cxt5047_hp_master_vol_put,
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0x13, 3, 0, HDA_OUTPUT),
|
|
},
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.info = cxt_eapd_info,
|
|
.get = cxt_eapd_get,
|
|
.put = cxt5047_hp_master_sw_put,
|
|
.private_value = 0x13,
|
|
},
|
|
|
|
{}
|
|
};
|
|
|
|
static struct snd_kcontrol_new cxt5047_hp_mixers[] = {
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.info = conexant_mux_enum_info,
|
|
.get = conexant_mux_enum_get,
|
|
.put = conexant_mux_enum_put
|
|
},
|
|
HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19,0x02,HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Master Playback Volume", 0x13, 0x00, HDA_OUTPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.info = cxt_eapd_info,
|
|
.get = cxt_eapd_get,
|
|
.put = cxt5047_hp_master_sw_put,
|
|
.private_value = 0x13,
|
|
},
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct hda_verb cxt5047_init_verbs[] = {
|
|
/* Line in, Mic, Built-in Mic */
|
|
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
|
|
{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 },
|
|
{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 },
|
|
/* HP, Speaker */
|
|
{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
|
|
{0x13, AC_VERB_SET_CONNECT_SEL,0x1},
|
|
{0x1d, AC_VERB_SET_CONNECT_SEL,0x0},
|
|
/* Record selector: Mic */
|
|
{0x12, AC_VERB_SET_CONNECT_SEL,0x03},
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
|
|
{0x1A, AC_VERB_SET_CONNECT_SEL,0x02},
|
|
{0x1A, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x00},
|
|
{0x1A, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x03},
|
|
/* SPDIF route: PCM */
|
|
{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x0 },
|
|
/* Enable unsolicited events */
|
|
{0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
|
|
{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
|
|
{ } /* end */
|
|
};
|
|
|
|
/* configuration for Toshiba Laptops */
|
|
static struct hda_verb cxt5047_toshiba_init_verbs[] = {
|
|
{0x13, AC_VERB_SET_EAPD_BTLENABLE, 0x0 }, /* default on */
|
|
/* pin sensing on HP and Mic jacks */
|
|
{0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
|
|
{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
|
|
/* Speaker routing */
|
|
{0x1d, AC_VERB_SET_CONNECT_SEL,0x1},
|
|
{}
|
|
};
|
|
|
|
/* configuration for HP Laptops */
|
|
static struct hda_verb cxt5047_hp_init_verbs[] = {
|
|
/* pin sensing on HP jack */
|
|
{0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
|
|
/* Record selector: Ext Mic */
|
|
{0x12, AC_VERB_SET_CONNECT_SEL,0x03},
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE,
|
|
AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
|
|
/* Speaker routing */
|
|
{0x1d, AC_VERB_SET_CONNECT_SEL,0x1},
|
|
{}
|
|
};
|
|
|
|
/* Test configuration for debugging, modelled after the ALC260 test
|
|
* configuration.
|
|
*/
|
|
#ifdef CONFIG_SND_DEBUG
|
|
static struct hda_input_mux cxt5047_test_capture_source = {
|
|
.num_items = 4,
|
|
.items = {
|
|
{ "LINE1 pin", 0x0 },
|
|
{ "MIC1 pin", 0x1 },
|
|
{ "MIC2 pin", 0x2 },
|
|
{ "CD pin", 0x3 },
|
|
},
|
|
};
|
|
|
|
static struct snd_kcontrol_new cxt5047_test_mixer[] = {
|
|
|
|
/* Output only controls */
|
|
HDA_CODEC_VOLUME("OutAmp-1 Volume", 0x10, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("OutAmp-1 Switch", 0x10,0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("OutAmp-2 Volume", 0x1c, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("OutAmp-2 Switch", 0x1c, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Speaker Playback Switch", 0x1d, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("HeadPhone Playback Volume", 0x13, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("HeadPhone Playback Switch", 0x13, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Line1-Out Playback Volume", 0x14, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Line1-Out Playback Switch", 0x14, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_VOLUME("Line2-Out Playback Volume", 0x15, 0x0, HDA_OUTPUT),
|
|
HDA_CODEC_MUTE("Line2-Out Playback Switch", 0x15, 0x0, HDA_OUTPUT),
|
|
|
|
/* Modes for retasking pin widgets */
|
|
CXT_PIN_MODE("LINE1 pin mode", 0x14, CXT_PIN_DIR_INOUT),
|
|
CXT_PIN_MODE("MIC1 pin mode", 0x15, CXT_PIN_DIR_INOUT),
|
|
|
|
/* EAPD Switch Control */
|
|
CXT_EAPD_SWITCH("External Amplifier", 0x13, 0x0),
|
|
|
|
/* Loopback mixer controls */
|
|
HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x12, 0x01, HDA_INPUT),
|
|
HDA_CODEC_MUTE("MIC1 Playback Switch", 0x12, 0x01, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x12, 0x02, HDA_INPUT),
|
|
HDA_CODEC_MUTE("MIC2 Playback Switch", 0x12, 0x02, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("LINE Playback Volume", 0x12, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("LINE Playback Switch", 0x12, 0x0, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("CD Playback Volume", 0x12, 0x04, HDA_INPUT),
|
|
HDA_CODEC_MUTE("CD Playback Switch", 0x12, 0x04, HDA_INPUT),
|
|
|
|
HDA_CODEC_VOLUME("Capture-1 Volume", 0x19, 0x0, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture-1 Switch", 0x19, 0x0, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Capture-2 Volume", 0x19, 0x1, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture-2 Switch", 0x19, 0x1, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Capture-3 Volume", 0x19, 0x2, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture-3 Switch", 0x19, 0x2, HDA_INPUT),
|
|
HDA_CODEC_VOLUME("Capture-4 Volume", 0x19, 0x3, HDA_INPUT),
|
|
HDA_CODEC_MUTE("Capture-4 Switch", 0x19, 0x3, HDA_INPUT),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Input Source",
|
|
.info = conexant_mux_enum_info,
|
|
.get = conexant_mux_enum_get,
|
|
.put = conexant_mux_enum_put,
|
|
},
|
|
{ } /* end */
|
|
};
|
|
|
|
static struct hda_verb cxt5047_test_init_verbs[] = {
|
|
/* Enable retasking pins as output, initially without power amp */
|
|
{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
|
|
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
|
|
{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
|
|
|
|
/* Disable digital (SPDIF) pins initially, but users can enable
|
|
* them via a mixer switch. In the case of SPDIF-out, this initverb
|
|
* payload also sets the generation to 0, output to be in "consumer"
|
|
* PCM format, copyright asserted, no pre-emphasis and no validity
|
|
* control.
|
|
*/
|
|
{0x18, AC_VERB_SET_DIGI_CONVERT_1, 0},
|
|
|
|
/* Ensure mic1, mic2, line1 pin widgets take input from the
|
|
* OUT1 sum bus when acting as an output.
|
|
*/
|
|
{0x1a, AC_VERB_SET_CONNECT_SEL, 0},
|
|
{0x1b, AC_VERB_SET_CONNECT_SEL, 0},
|
|
|
|
/* Start with output sum widgets muted and their output gains at min */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
|
|
|
|
/* Unmute retasking pin widget output buffers since the default
|
|
* state appears to be output. As the pin mode is changed by the
|
|
* user the pin mode control will take care of enabling the pin's
|
|
* input/output buffers as needed.
|
|
*/
|
|
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
|
|
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
|
|
{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
|
|
|
|
/* Mute capture amp left and right */
|
|
{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
|
|
|
|
/* Set ADC connection select to match default mixer setting (mic1
|
|
* pin)
|
|
*/
|
|
{0x12, AC_VERB_SET_CONNECT_SEL, 0x00},
|
|
|
|
/* Mute all inputs to mixer widget (even unconnected ones) */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
|
|
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
|
|
|
|
{ }
|
|
};
|
|
#endif
|
|
|
|
|
|
/* initialize jack-sensing, too */
|
|
static int cxt5047_hp_init(struct hda_codec *codec)
|
|
{
|
|
conexant_init(codec);
|
|
cxt5047_hp_automute(codec);
|
|
return 0;
|
|
}
|
|
|
|
|
|
enum {
|
|
CXT5047_LAPTOP, /* Laptops w/o EAPD support */
|
|
CXT5047_LAPTOP_HP, /* Some HP laptops */
|
|
CXT5047_LAPTOP_EAPD, /* Laptops with EAPD support */
|
|
#ifdef CONFIG_SND_DEBUG
|
|
CXT5047_TEST,
|
|
#endif
|
|
CXT5047_MODELS
|
|
};
|
|
|
|
static const char *cxt5047_models[CXT5047_MODELS] = {
|
|
[CXT5047_LAPTOP] = "laptop",
|
|
[CXT5047_LAPTOP_HP] = "laptop-hp",
|
|
[CXT5047_LAPTOP_EAPD] = "laptop-eapd",
|
|
#ifdef CONFIG_SND_DEBUG
|
|
[CXT5047_TEST] = "test",
|
|
#endif
|
|
};
|
|
|
|
static struct snd_pci_quirk cxt5047_cfg_tbl[] = {
|
|
SND_PCI_QUIRK(0x103c, 0x30a0, "HP DV1000", CXT5047_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30b2, "HP DV2000T/DV3000T", CXT5047_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30b5, "HP DV2000Z", CXT5047_LAPTOP),
|
|
SND_PCI_QUIRK(0x103c, 0x30a5, "HP DV5200T/DV8000T", CXT5047_LAPTOP_HP),
|
|
SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P100", CXT5047_LAPTOP_EAPD),
|
|
{}
|
|
};
|
|
|
|
static int patch_cxt5047(struct hda_codec *codec)
|
|
{
|
|
struct conexant_spec *spec;
|
|
int board_config;
|
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
|
if (!spec)
|
|
return -ENOMEM;
|
|
mutex_init(&spec->amp_mutex);
|
|
codec->spec = spec;
|
|
|
|
spec->multiout.max_channels = 2;
|
|
spec->multiout.num_dacs = ARRAY_SIZE(cxt5047_dac_nids);
|
|
spec->multiout.dac_nids = cxt5047_dac_nids;
|
|
spec->multiout.dig_out_nid = CXT5047_SPDIF_OUT;
|
|
spec->num_adc_nids = 1;
|
|
spec->adc_nids = cxt5047_adc_nids;
|
|
spec->capsrc_nids = cxt5047_capsrc_nids;
|
|
spec->input_mux = &cxt5047_capture_source;
|
|
spec->num_mixers = 1;
|
|
spec->mixers[0] = cxt5047_mixers;
|
|
spec->num_init_verbs = 1;
|
|
spec->init_verbs[0] = cxt5047_init_verbs;
|
|
spec->spdif_route = 0;
|
|
spec->num_channel_mode = ARRAY_SIZE(cxt5047_modes),
|
|
spec->channel_mode = cxt5047_modes,
|
|
|
|
codec->patch_ops = conexant_patch_ops;
|
|
|
|
board_config = snd_hda_check_board_config(codec, CXT5047_MODELS,
|
|
cxt5047_models,
|
|
cxt5047_cfg_tbl);
|
|
switch (board_config) {
|
|
case CXT5047_LAPTOP:
|
|
codec->patch_ops.unsol_event = cxt5047_hp2_unsol_event;
|
|
break;
|
|
case CXT5047_LAPTOP_HP:
|
|
spec->input_mux = &cxt5047_hp_capture_source;
|
|
spec->num_init_verbs = 2;
|
|
spec->init_verbs[1] = cxt5047_hp_init_verbs;
|
|
spec->mixers[0] = cxt5047_hp_mixers;
|
|
codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
|
|
codec->patch_ops.init = cxt5047_hp_init;
|
|
break;
|
|
case CXT5047_LAPTOP_EAPD:
|
|
spec->input_mux = &cxt5047_toshiba_capture_source;
|
|
spec->num_init_verbs = 2;
|
|
spec->init_verbs[1] = cxt5047_toshiba_init_verbs;
|
|
spec->mixers[0] = cxt5047_toshiba_mixers;
|
|
codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
|
|
break;
|
|
#ifdef CONFIG_SND_DEBUG
|
|
case CXT5047_TEST:
|
|
spec->input_mux = &cxt5047_test_capture_source;
|
|
spec->mixers[0] = cxt5047_test_mixer;
|
|
spec->init_verbs[0] = cxt5047_test_init_verbs;
|
|
codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct hda_codec_preset snd_hda_preset_conexant[] = {
|
|
{ .id = 0x14f15045, .name = "CX20549 (Venice)",
|
|
.patch = patch_cxt5045 },
|
|
{ .id = 0x14f15047, .name = "CX20551 (Waikiki)",
|
|
.patch = patch_cxt5047 },
|
|
{} /* terminator */
|
|
};
|