// SPDX-License-Identifier: GPL-2.0-or-later /* * HD audio interface patch for Senary HDA audio codec * * Initially based on sound/pci/hda/patch_conexant.c */ #include #include #include #include #include #include #include #include "hda_local.h" #include "hda_auto_parser.h" #include "hda_beep.h" #include "hda_jack.h" #include "hda_generic.h" struct senary_spec { struct hda_gen_spec gen; /* extra EAPD pins */ unsigned int num_eapds; hda_nid_t eapds[4]; hda_nid_t mute_led_eapd; unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */ int mute_led_polarity; unsigned int gpio_led; unsigned int gpio_mute_led_mask; unsigned int gpio_mic_led_mask; }; #ifdef CONFIG_SND_HDA_INPUT_BEEP /* additional beep mixers; private_value will be overwritten */ static const struct snd_kcontrol_new senary_beep_mixer[] = { HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT), HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT), }; static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, int idx, int dir) { struct snd_kcontrol_new *knew; unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); int i; spec->gen.beep_nid = nid; for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) { knew = snd_hda_gen_add_kctl(&spec->gen, NULL, &senary_beep_mixer[i]); if (!knew) return -ENOMEM; knew->private_value = beep_amp; } return 0; } static int senary_auto_parse_beep(struct hda_codec *codec) { struct senary_spec *spec = codec->spec; hda_nid_t nid; for_each_hda_codec_node(nid, codec) if ((get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) && (get_wcaps(codec, nid) & (AC_WCAP_OUT_AMP | AC_WCAP_AMP_OVRD))) return set_beep_amp(spec, nid, 0, HDA_OUTPUT); return 0; } #else #define senary_auto_parse_beep(codec) 0 #endif /* parse EAPDs */ static void senary_auto_parse_eapd(struct hda_codec *codec) { struct senary_spec *spec = codec->spec; hda_nid_t nid; for_each_hda_codec_node(nid, codec) { if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) continue; if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) continue; spec->eapds[spec->num_eapds++] = nid; if (spec->num_eapds >= ARRAY_SIZE(spec->eapds)) break; } } static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, bool on) { int i; for (i = 0; i < num_pins; i++) { if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD) snd_hda_codec_write(codec, pins[i], 0, AC_VERB_SET_EAPD_BTLENABLE, on ? 0x02 : 0); } } /* turn on/off EAPD according to Master switch */ static void senary_auto_vmaster_hook(void *private_data, int enabled) { struct hda_codec *codec = private_data; struct senary_spec *spec = codec->spec; senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled); } static void senary_init_gpio_led(struct hda_codec *codec) { struct senary_spec *spec = codec->spec; unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; if (mask) { snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, mask); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, mask); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_led); } } static int senary_auto_init(struct hda_codec *codec) { snd_hda_gen_init(codec); senary_init_gpio_led(codec); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); return 0; } static void senary_auto_shutdown(struct hda_codec *codec) { struct senary_spec *spec = codec->spec; /* Turn the problematic codec into D3 to avoid spurious noises * from the internal speaker during (and after) reboot */ senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, false); } static void senary_auto_free(struct hda_codec *codec) { senary_auto_shutdown(codec); snd_hda_gen_free(codec); } static int senary_auto_suspend(struct hda_codec *codec) { senary_auto_shutdown(codec); return 0; } static const struct hda_codec_ops senary_auto_patch_ops = { .build_controls = snd_hda_gen_build_controls, .build_pcms = snd_hda_gen_build_pcms, .init = senary_auto_init, .free = senary_auto_free, .unsol_event = snd_hda_jack_unsol_event, .suspend = senary_auto_suspend, .check_power_status = snd_hda_gen_check_power_status, }; static int patch_senary_auto(struct hda_codec *codec) { struct senary_spec *spec; int err; codec_info(codec, "%s: BIOS auto-probing.\n", codec->core.chip_name); spec = kzalloc(sizeof(*spec), GFP_KERNEL); if (!spec) return -ENOMEM; snd_hda_gen_spec_init(&spec->gen); codec->spec = spec; codec->patch_ops = senary_auto_patch_ops; senary_auto_parse_eapd(codec); spec->gen.own_eapd_ctl = 1; if (!spec->gen.vmaster_mute.hook) spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, spec->parse_flags); if (err < 0) goto error; err = senary_auto_parse_beep(codec); if (err < 0) goto error; err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); if (err < 0) goto error; /* Some laptops with Senary chips show stalls in S3 resume, * which falls into the single-cmd mode. * Better to make reset, then. */ if (!codec->bus->core.sync_write) { codec_info(codec, "Enable sync_write for stable communication\n"); codec->bus->core.sync_write = 1; codec->bus->allow_bus_reset = 1; } snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); return 0; error: senary_auto_free(codec); return err; } /* */ static const struct hda_device_id snd_hda_id_senary[] = { HDA_CODEC_ENTRY(0x1fa86186, "SN6186", patch_senary_auto), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_senary); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Senarytech HD-audio codec"); static struct hda_codec_driver senary_driver = { .id = snd_hda_id_senary, }; module_hda_codec_driver(senary_driver);