Commit 461e2c78 authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela

[ALSA] hda-codec - Add Conexant 5051 codec support

Added the support for Conexant 5051 audio codec.
Right now there are two preset models, laptop and hp.
The whole patch is based on the information from the base patch by
Linuxant.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@perex.cz>
parent c626026d
......@@ -961,6 +961,10 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
can be adjusted. Appearing only when compiled with
$CONFIG_SND_DEBUG=y
Conexant 5051
laptop Basic Laptop config (default)
hp HP Spartan laptop
STAC9200
ref Reference board
dell-d21 Dell (unknown)
......
......@@ -64,6 +64,11 @@ struct conexant_spec {
hda_nid_t *adc_nids;
hda_nid_t dig_in_nid; /* digital-in NID; optional */
unsigned int cur_adc_idx;
hda_nid_t cur_adc;
unsigned int cur_adc_stream_tag;
unsigned int cur_adc_format;
/* capture source */
const struct hda_input_mux *input_mux;
hda_nid_t *capsrc_nids;
......@@ -217,6 +222,41 @@ static struct hda_pcm_stream conexant_pcm_digital_capture = {
/* NID is set in alc_build_pcms */
};
static int cx5051_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;
spec->cur_adc = spec->adc_nids[spec->cur_adc_idx];
spec->cur_adc_stream_tag = stream_tag;
spec->cur_adc_format = format;
snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
return 0;
}
static int cx5051_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->cur_adc, 0, 0, 0);
spec->cur_adc = 0;
return 0;
}
static struct hda_pcm_stream cx5051_pcm_analog_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
.nid = 0, /* fill later */
.ops = {
.prepare = cx5051_capture_pcm_prepare,
.cleanup = cx5051_capture_pcm_cleanup
},
};
static int conexant_build_pcms(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
......@@ -231,7 +271,12 @@ static int conexant_build_pcms(struct hda_codec *codec)
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;
if (codec->vendor_id == 0x14f15051)
info->stream[SNDRV_PCM_STREAM_CAPTURE] =
cx5051_pcm_analog_capture;
else
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];
......@@ -1421,10 +1466,260 @@ static int patch_cxt5047(struct hda_codec *codec)
return 0;
}
/* Conexant 5051 specific */
static hda_nid_t cxt5051_dac_nids[1] = { 0x10 };
static hda_nid_t cxt5051_adc_nids[2] = { 0x14, 0x15 };
#define CXT5051_SPDIF_OUT 0x1C
#define CXT5051_PORTB_EVENT 0x38
#define CXT5051_PORTC_EVENT 0x39
static struct hda_channel_mode cxt5051_modes[1] = {
{ 2, NULL },
};
static void cxt5051_update_speaker(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
unsigned int pinctl;
pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0;
snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
pinctl);
}
/* turn on/off EAPD (+ mute HP) as a master switch */
static int cxt5051_hp_master_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
if (!cxt_eapd_put(kcontrol, ucontrol))
return 0;
cxt5051_update_speaker(codec);
return 1;
}
/* toggle input of built-in and mic jack appropriately */
static void cxt5051_portb_automic(struct hda_codec *codec)
{
unsigned int present;
present = snd_hda_codec_read(codec, 0x17, 0,
AC_VERB_GET_PIN_SENSE, 0) &
AC_PINSENSE_PRESENCE;
snd_hda_codec_write(codec, 0x14, 0,
AC_VERB_SET_CONNECT_SEL,
present ? 0x01 : 0x00);
}
/* switch the current ADC according to the jack state */
static void cxt5051_portc_automic(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
unsigned int present;
hda_nid_t new_adc;
present = snd_hda_codec_read(codec, 0x18, 0,
AC_VERB_GET_PIN_SENSE, 0) &
AC_PINSENSE_PRESENCE;
if (present)
spec->cur_adc_idx = 1;
else
spec->cur_adc_idx = 0;
new_adc = spec->adc_nids[spec->cur_adc_idx];
if (spec->cur_adc && spec->cur_adc != new_adc) {
/* stream is running, let's swap the current ADC */
snd_hda_codec_setup_stream(codec, spec->cur_adc, 0, 0, 0);
spec->cur_adc = new_adc;
snd_hda_codec_setup_stream(codec, new_adc,
spec->cur_adc_stream_tag, 0,
spec->cur_adc_format);
}
}
/* mute internal speaker if HP is plugged */
static void cxt5051_hp_automute(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
spec->hp_present = snd_hda_codec_read(codec, 0x16, 0,
AC_VERB_GET_PIN_SENSE, 0) &
AC_PINSENSE_PRESENCE;
cxt5051_update_speaker(codec);
}
/* unsolicited event for HP jack sensing */
static void cxt5051_hp_unsol_event(struct hda_codec *codec,
unsigned int res)
{
switch (res >> 26) {
case CONEXANT_HP_EVENT:
cxt5051_hp_automute(codec);
break;
case CXT5051_PORTB_EVENT:
cxt5051_portb_automic(codec);
break;
case CXT5051_PORTC_EVENT:
cxt5051_portc_automic(codec);
break;
}
}
static struct snd_kcontrol_new cxt5051_mixers[] = {
HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT),
HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Switch",
.info = cxt_eapd_info,
.get = cxt_eapd_get,
.put = cxt5051_hp_master_sw_put,
.private_value = 0x1a,
},
{}
};
static struct snd_kcontrol_new cxt5051_hp_mixers[] = {
HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("External Mic Volume", 0x15, 0x00, HDA_INPUT),
HDA_CODEC_MUTE("External Mic Switch", 0x15, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Switch",
.info = cxt_eapd_info,
.get = cxt_eapd_get,
.put = cxt5051_hp_master_sw_put,
.private_value = 0x1a,
},
{}
};
static struct hda_verb cxt5051_init_verbs[] = {
/* Line in, Mic */
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
/* SPK */
{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
/* HP, Amp */
{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
/* DAC1 */
{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
/* Record selector: Int mic */
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
/* SPDIF route: PCM */
{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
/* EAPD */
{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},
{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT},
{ } /* end */
};
/* initialize jack-sensing, too */
static int cxt5051_init(struct hda_codec *codec)
{
conexant_init(codec);
if (codec->patch_ops.unsol_event) {
cxt5051_hp_automute(codec);
cxt5051_portb_automic(codec);
cxt5051_portc_automic(codec);
}
return 0;
}
enum {
CXT5051_LAPTOP, /* Laptops w/ EAPD support */
CXT5051_HP, /* no docking */
CXT5051_MODELS
};
static const char *cxt5051_models[CXT5051_MODELS] = {
[CXT5051_LAPTOP] = "laptop",
[CXT5051_HP] = "hp",
};
static struct snd_pci_quirk cxt5051_cfg_tbl[] = {
SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",
CXT5051_LAPTOP),
SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP),
{}
};
static int patch_cxt5051(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;
codec->patch_ops = conexant_patch_ops;
codec->patch_ops.init = cxt5051_init;
spec->multiout.max_channels = 2;
spec->multiout.num_dacs = ARRAY_SIZE(cxt5051_dac_nids);
spec->multiout.dac_nids = cxt5051_dac_nids;
spec->multiout.dig_out_nid = CXT5051_SPDIF_OUT;
spec->num_adc_nids = 1; /* not 2; via auto-mic switch */
spec->adc_nids = cxt5051_adc_nids;
spec->num_mixers = 1;
spec->mixers[0] = cxt5051_mixers;
spec->num_init_verbs = 1;
spec->init_verbs[0] = cxt5051_init_verbs;
spec->spdif_route = 0;
spec->num_channel_mode = ARRAY_SIZE(cxt5051_modes);
spec->channel_mode = cxt5051_modes;
spec->cur_adc = 0;
spec->cur_adc_idx = 0;
board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,
cxt5051_models,
cxt5051_cfg_tbl);
switch (board_config) {
case CXT5051_HP:
codec->patch_ops.unsol_event = cxt5051_hp_unsol_event;
spec->mixers[0] = cxt5051_hp_mixers;
break;
default:
case CXT5051_LAPTOP:
codec->patch_ops.unsol_event = cxt5051_hp_unsol_event;
break;
}
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 },
{ .id = 0x14f15051, .name = "CX20561 (Hermosa)",
.patch = patch_cxt5051 },
{} /* terminator */
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment