diff options
Diffstat (limited to 'sound/pci/hda/patch_conexant.c')
-rw-r--r-- | sound/pci/hda/patch_conexant.c | 122 |
1 files changed, 102 insertions, 20 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index d29d6d377904..e6eafb18c8f5 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c | |||
@@ -70,6 +70,8 @@ struct conexant_spec { | |||
70 | const struct snd_kcontrol_new *mixers[5]; | 70 | const struct snd_kcontrol_new *mixers[5]; |
71 | int num_mixers; | 71 | int num_mixers; |
72 | hda_nid_t vmaster_nid; | 72 | hda_nid_t vmaster_nid; |
73 | struct hda_vmaster_mute_hook vmaster_mute; | ||
74 | bool vmaster_mute_led; | ||
73 | 75 | ||
74 | const struct hda_verb *init_verbs[5]; /* initialization verbs | 76 | const struct hda_verb *init_verbs[5]; /* initialization verbs |
75 | * don't forget NULL | 77 | * don't forget NULL |
@@ -465,21 +467,8 @@ static const struct snd_kcontrol_new cxt_beep_mixer[] = { | |||
465 | }; | 467 | }; |
466 | #endif | 468 | #endif |
467 | 469 | ||
468 | static const char * const slave_vols[] = { | 470 | static const char * const slave_pfxs[] = { |
469 | "Headphone Playback Volume", | 471 | "Headphone", "Speaker", "Front", "Surround", "CLFE", |
470 | "Speaker Playback Volume", | ||
471 | "Front Playback Volume", | ||
472 | "Surround Playback Volume", | ||
473 | "CLFE Playback Volume", | ||
474 | NULL | ||
475 | }; | ||
476 | |||
477 | static const char * const slave_sws[] = { | ||
478 | "Headphone Playback Switch", | ||
479 | "Speaker Playback Switch", | ||
480 | "Front Playback Switch", | ||
481 | "Surround Playback Switch", | ||
482 | "CLFE Playback Switch", | ||
483 | NULL | 472 | NULL |
484 | }; | 473 | }; |
485 | 474 | ||
@@ -519,14 +508,17 @@ static int conexant_build_controls(struct hda_codec *codec) | |||
519 | snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, | 508 | snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, |
520 | HDA_OUTPUT, vmaster_tlv); | 509 | HDA_OUTPUT, vmaster_tlv); |
521 | err = snd_hda_add_vmaster(codec, "Master Playback Volume", | 510 | err = snd_hda_add_vmaster(codec, "Master Playback Volume", |
522 | vmaster_tlv, slave_vols); | 511 | vmaster_tlv, slave_pfxs, |
512 | "Playback Volume"); | ||
523 | if (err < 0) | 513 | if (err < 0) |
524 | return err; | 514 | return err; |
525 | } | 515 | } |
526 | if (spec->vmaster_nid && | 516 | if (spec->vmaster_nid && |
527 | !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { | 517 | !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { |
528 | err = snd_hda_add_vmaster(codec, "Master Playback Switch", | 518 | err = __snd_hda_add_vmaster(codec, "Master Playback Switch", |
529 | NULL, slave_sws); | 519 | NULL, slave_pfxs, |
520 | "Playback Switch", true, | ||
521 | &spec->vmaster_mute.sw_kctl); | ||
530 | if (err < 0) | 522 | if (err < 0) |
531 | return err; | 523 | return err; |
532 | } | 524 | } |
@@ -3034,7 +3026,6 @@ static const struct snd_pci_quirk cxt5066_cfg_tbl[] = { | |||
3034 | SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo U350", CXT5066_ASUS), | 3026 | SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo U350", CXT5066_ASUS), |
3035 | SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo G560", CXT5066_ASUS), | 3027 | SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo G560", CXT5066_ASUS), |
3036 | SND_PCI_QUIRK(0x17aa, 0x3938, "Lenovo G565", CXT5066_AUTO), | 3028 | SND_PCI_QUIRK(0x17aa, 0x3938, "Lenovo G565", CXT5066_AUTO), |
3037 | SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", CXT5066_IDEAPAD), /* Fallback for Lenovos without dock mic */ | ||
3038 | SND_PCI_QUIRK(0x1b0a, 0x2092, "CyberpowerPC Gamer Xplorer N57001", CXT5066_AUTO), | 3029 | SND_PCI_QUIRK(0x1b0a, 0x2092, "CyberpowerPC Gamer Xplorer N57001", CXT5066_AUTO), |
3039 | {} | 3030 | {} |
3040 | }; | 3031 | }; |
@@ -3943,6 +3934,63 @@ static void enable_unsol_pins(struct hda_codec *codec, int num_pins, | |||
3943 | snd_hda_jack_detect_enable(codec, pins[i], action); | 3934 | snd_hda_jack_detect_enable(codec, pins[i], action); |
3944 | } | 3935 | } |
3945 | 3936 | ||
3937 | static bool found_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) | ||
3938 | { | ||
3939 | int i; | ||
3940 | for (i = 0; i < nums; i++) | ||
3941 | if (list[i] == nid) | ||
3942 | return true; | ||
3943 | return false; | ||
3944 | } | ||
3945 | |||
3946 | /* is the given NID found in any of autocfg items? */ | ||
3947 | static bool found_in_autocfg(struct auto_pin_cfg *cfg, hda_nid_t nid) | ||
3948 | { | ||
3949 | int i; | ||
3950 | |||
3951 | if (found_in_nid_list(nid, cfg->line_out_pins, cfg->line_outs) || | ||
3952 | found_in_nid_list(nid, cfg->hp_pins, cfg->hp_outs) || | ||
3953 | found_in_nid_list(nid, cfg->speaker_pins, cfg->speaker_outs) || | ||
3954 | found_in_nid_list(nid, cfg->dig_out_pins, cfg->dig_outs)) | ||
3955 | return true; | ||
3956 | for (i = 0; i < cfg->num_inputs; i++) | ||
3957 | if (cfg->inputs[i].pin == nid) | ||
3958 | return true; | ||
3959 | if (cfg->dig_in_pin == nid) | ||
3960 | return true; | ||
3961 | return false; | ||
3962 | } | ||
3963 | |||
3964 | /* clear unsol-event tags on unused pins; Conexant codecs seem to leave | ||
3965 | * invalid unsol tags by some reason | ||
3966 | */ | ||
3967 | static void clear_unsol_on_unused_pins(struct hda_codec *codec) | ||
3968 | { | ||
3969 | struct conexant_spec *spec = codec->spec; | ||
3970 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
3971 | int i; | ||
3972 | |||
3973 | for (i = 0; i < codec->init_pins.used; i++) { | ||
3974 | struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i); | ||
3975 | if (!found_in_autocfg(cfg, pin->nid)) | ||
3976 | snd_hda_codec_write(codec, pin->nid, 0, | ||
3977 | AC_VERB_SET_UNSOLICITED_ENABLE, 0); | ||
3978 | } | ||
3979 | } | ||
3980 | |||
3981 | /* turn on/off EAPD according to Master switch */ | ||
3982 | static void cx_auto_vmaster_hook(void *private_data, int enabled) | ||
3983 | { | ||
3984 | struct hda_codec *codec = private_data; | ||
3985 | struct conexant_spec *spec = codec->spec; | ||
3986 | |||
3987 | if (enabled && spec->pin_eapd_ctrls) { | ||
3988 | cx_auto_update_speakers(codec); | ||
3989 | return; | ||
3990 | } | ||
3991 | cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled); | ||
3992 | } | ||
3993 | |||
3946 | static void cx_auto_init_output(struct hda_codec *codec) | 3994 | static void cx_auto_init_output(struct hda_codec *codec) |
3947 | { | 3995 | { |
3948 | struct conexant_spec *spec = codec->spec; | 3996 | struct conexant_spec *spec = codec->spec; |
@@ -3983,6 +4031,7 @@ static void cx_auto_init_output(struct hda_codec *codec) | |||
3983 | /* turn on all EAPDs if no individual EAPD control is available */ | 4031 | /* turn on all EAPDs if no individual EAPD control is available */ |
3984 | if (!spec->pin_eapd_ctrls) | 4032 | if (!spec->pin_eapd_ctrls) |
3985 | cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); | 4033 | cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); |
4034 | clear_unsol_on_unused_pins(codec); | ||
3986 | } | 4035 | } |
3987 | 4036 | ||
3988 | static void cx_auto_init_input(struct hda_codec *codec) | 4037 | static void cx_auto_init_input(struct hda_codec *codec) |
@@ -4046,11 +4095,13 @@ static void cx_auto_init_digital(struct hda_codec *codec) | |||
4046 | 4095 | ||
4047 | static int cx_auto_init(struct hda_codec *codec) | 4096 | static int cx_auto_init(struct hda_codec *codec) |
4048 | { | 4097 | { |
4098 | struct conexant_spec *spec = codec->spec; | ||
4049 | /*snd_hda_sequence_write(codec, cx_auto_init_verbs);*/ | 4099 | /*snd_hda_sequence_write(codec, cx_auto_init_verbs);*/ |
4050 | cx_auto_init_output(codec); | 4100 | cx_auto_init_output(codec); |
4051 | cx_auto_init_input(codec); | 4101 | cx_auto_init_input(codec); |
4052 | cx_auto_init_digital(codec); | 4102 | cx_auto_init_digital(codec); |
4053 | snd_hda_jack_report_sync(codec); | 4103 | snd_hda_jack_report_sync(codec); |
4104 | snd_hda_sync_vmaster_hook(&spec->vmaster_mute); | ||
4054 | return 0; | 4105 | return 0; |
4055 | } | 4106 | } |
4056 | 4107 | ||
@@ -4296,6 +4347,13 @@ static int cx_auto_build_controls(struct hda_codec *codec) | |||
4296 | err = snd_hda_jack_add_kctls(codec, &spec->autocfg); | 4347 | err = snd_hda_jack_add_kctls(codec, &spec->autocfg); |
4297 | if (err < 0) | 4348 | if (err < 0) |
4298 | return err; | 4349 | return err; |
4350 | if (spec->vmaster_mute.sw_kctl) { | ||
4351 | spec->vmaster_mute.hook = cx_auto_vmaster_hook; | ||
4352 | err = snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute, | ||
4353 | spec->vmaster_mute_led); | ||
4354 | if (err < 0) | ||
4355 | return err; | ||
4356 | } | ||
4299 | return 0; | 4357 | return 0; |
4300 | } | 4358 | } |
4301 | 4359 | ||
@@ -4320,7 +4378,6 @@ static int cx_auto_search_adcs(struct hda_codec *codec) | |||
4320 | return 0; | 4378 | return 0; |
4321 | } | 4379 | } |
4322 | 4380 | ||
4323 | |||
4324 | static const struct hda_codec_ops cx_auto_patch_ops = { | 4381 | static const struct hda_codec_ops cx_auto_patch_ops = { |
4325 | .build_controls = cx_auto_build_controls, | 4382 | .build_controls = cx_auto_build_controls, |
4326 | .build_pcms = conexant_build_pcms, | 4383 | .build_pcms = conexant_build_pcms, |
@@ -4368,6 +4425,7 @@ static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = { | |||
4368 | { 0x16, 0x042140ff }, /* HP (seq# overridden) */ | 4425 | { 0x16, 0x042140ff }, /* HP (seq# overridden) */ |
4369 | { 0x17, 0x21a11000 }, /* dock-mic */ | 4426 | { 0x17, 0x21a11000 }, /* dock-mic */ |
4370 | { 0x19, 0x2121103f }, /* dock-HP */ | 4427 | { 0x19, 0x2121103f }, /* dock-HP */ |
4428 | { 0x1c, 0x21440100 }, /* dock SPDIF out */ | ||
4371 | {} | 4429 | {} |
4372 | }; | 4430 | }; |
4373 | 4431 | ||
@@ -4421,6 +4479,18 @@ static int patch_conexant_auto(struct hda_codec *codec) | |||
4421 | 4479 | ||
4422 | apply_pin_fixup(codec, cxt_fixups, cxt_pincfg_tbl); | 4480 | apply_pin_fixup(codec, cxt_fixups, cxt_pincfg_tbl); |
4423 | 4481 | ||
4482 | /* Show mute-led control only on HP laptops | ||
4483 | * This is a sort of white-list: on HP laptops, EAPD corresponds | ||
4484 | * only to the mute-LED without actualy amp function. Meanwhile, | ||
4485 | * others may use EAPD really as an amp switch, so it might be | ||
4486 | * not good to expose it blindly. | ||
4487 | */ | ||
4488 | switch (codec->subsystem_id >> 16) { | ||
4489 | case 0x103c: | ||
4490 | spec->vmaster_mute_led = 1; | ||
4491 | break; | ||
4492 | } | ||
4493 | |||
4424 | err = cx_auto_search_adcs(codec); | 4494 | err = cx_auto_search_adcs(codec); |
4425 | if (err < 0) | 4495 | if (err < 0) |
4426 | return err; | 4496 | return err; |
@@ -4434,6 +4504,18 @@ static int patch_conexant_auto(struct hda_codec *codec) | |||
4434 | codec->patch_ops = cx_auto_patch_ops; | 4504 | codec->patch_ops = cx_auto_patch_ops; |
4435 | if (spec->beep_amp) | 4505 | if (spec->beep_amp) |
4436 | snd_hda_attach_beep_device(codec, spec->beep_amp); | 4506 | snd_hda_attach_beep_device(codec, spec->beep_amp); |
4507 | |||
4508 | /* Some laptops with Conexant chips show stalls in S3 resume, | ||
4509 | * which falls into the single-cmd mode. | ||
4510 | * Better to make reset, then. | ||
4511 | */ | ||
4512 | if (!codec->bus->sync_write) { | ||
4513 | snd_printd("hda_codec: " | ||
4514 | "Enable sync_write for stable communication\n"); | ||
4515 | codec->bus->sync_write = 1; | ||
4516 | codec->bus->allow_bus_reset = 1; | ||
4517 | } | ||
4518 | |||
4437 | return 0; | 4519 | return 0; |
4438 | } | 4520 | } |
4439 | 4521 | ||