| 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* |
| 3 | * HD audio codec driver for Senary HDA audio codec |
| 4 | * |
| 5 | * Initially based on conexant.c |
| 6 | */ |
| 7 | |
| 8 | #include <linux/init.h> |
| 9 | #include <linux/delay.h> |
| 10 | #include <linux/slab.h> |
| 11 | #include <linux/module.h> |
| 12 | #include <sound/core.h> |
| 13 | #include <sound/jack.h> |
| 14 | |
| 15 | #include <sound/hda_codec.h> |
| 16 | #include "hda_local.h" |
| 17 | #include "hda_auto_parser.h" |
| 18 | #include "hda_beep.h" |
| 19 | #include "hda_jack.h" |
| 20 | #include "generic.h" |
| 21 | |
| 22 | /* GPIO node ID */ |
| 23 | #define SENARY_GPIO_NODE 0x01 |
| 24 | |
| 25 | struct senary_spec { |
| 26 | struct hda_gen_spec gen; |
| 27 | |
| 28 | /* extra EAPD pins */ |
| 29 | unsigned int num_eapds; |
| 30 | hda_nid_t eapds[4]; |
| 31 | hda_nid_t mute_led_eapd; |
| 32 | |
| 33 | unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */ |
| 34 | |
| 35 | int mute_led_polarity; |
| 36 | unsigned int gpio_led; |
| 37 | unsigned int gpio_mute_led_mask; |
| 38 | unsigned int gpio_mic_led_mask; |
| 39 | }; |
| 40 | |
| 41 | #ifdef CONFIG_SND_HDA_INPUT_BEEP |
| 42 | /* additional beep mixers; private_value will be overwritten */ |
| 43 | static const struct snd_kcontrol_new senary_beep_mixer[] = { |
| 44 | HDA_CODEC_VOLUME_MONO("Beep Playback Volume" , 0, 1, 0, HDA_OUTPUT), |
| 45 | HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch" , 0, 1, 0, HDA_OUTPUT), |
| 46 | }; |
| 47 | |
| 48 | static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, |
| 49 | int idx, int dir) |
| 50 | { |
| 51 | struct snd_kcontrol_new *knew; |
| 52 | unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); |
| 53 | int i; |
| 54 | |
| 55 | spec->gen.beep_nid = nid; |
| 56 | for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) { |
| 57 | knew = snd_hda_gen_add_kctl(spec: &spec->gen, NULL, |
| 58 | temp: &senary_beep_mixer[i]); |
| 59 | if (!knew) |
| 60 | return -ENOMEM; |
| 61 | knew->private_value = beep_amp; |
| 62 | } |
| 63 | return 0; |
| 64 | } |
| 65 | |
| 66 | static int senary_auto_parse_beep(struct hda_codec *codec) |
| 67 | { |
| 68 | struct senary_spec *spec = codec->spec; |
| 69 | hda_nid_t nid; |
| 70 | |
| 71 | for_each_hda_codec_node(nid, codec) |
| 72 | if ((get_wcaps_type(wcaps: get_wcaps(codec, nid)) == AC_WID_BEEP) && |
| 73 | (get_wcaps(codec, nid) & (AC_WCAP_OUT_AMP | AC_WCAP_AMP_OVRD))) |
| 74 | return set_beep_amp(spec, nid, idx: 0, dir: HDA_OUTPUT); |
| 75 | return 0; |
| 76 | } |
| 77 | #else |
| 78 | #define senary_auto_parse_beep(codec) 0 |
| 79 | #endif |
| 80 | |
| 81 | /* parse EAPDs */ |
| 82 | static void senary_auto_parse_eapd(struct hda_codec *codec) |
| 83 | { |
| 84 | struct senary_spec *spec = codec->spec; |
| 85 | hda_nid_t nid; |
| 86 | |
| 87 | for_each_hda_codec_node(nid, codec) { |
| 88 | if (get_wcaps_type(wcaps: get_wcaps(codec, nid)) != AC_WID_PIN) |
| 89 | continue; |
| 90 | if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) |
| 91 | continue; |
| 92 | spec->eapds[spec->num_eapds++] = nid; |
| 93 | if (spec->num_eapds >= ARRAY_SIZE(spec->eapds)) |
| 94 | break; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, |
| 99 | const hda_nid_t *pins, bool on) |
| 100 | { |
| 101 | int i; |
| 102 | |
| 103 | for (i = 0; i < num_pins; i++) { |
| 104 | if (snd_hda_query_pin_caps(codec, nid: pins[i]) & AC_PINCAP_EAPD) |
| 105 | snd_hda_codec_write(codec, nid: pins[i], flags: 0, |
| 106 | AC_VERB_SET_EAPD_BTLENABLE, |
| 107 | parm: on ? 0x02 : 0); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /* turn on/off EAPD according to Master switch */ |
| 112 | static void senary_auto_vmaster_hook(void *private_data, int enabled) |
| 113 | { |
| 114 | struct hda_codec *codec = private_data; |
| 115 | struct senary_spec *spec = codec->spec; |
| 116 | |
| 117 | senary_auto_turn_eapd(codec, num_pins: spec->num_eapds, pins: spec->eapds, on: enabled); |
| 118 | } |
| 119 | |
| 120 | static void senary_init_gpio_led(struct hda_codec *codec) |
| 121 | { |
| 122 | struct senary_spec *spec = codec->spec; |
| 123 | unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; |
| 124 | |
| 125 | if (mask) { |
| 126 | snd_hda_codec_write(codec, SENARY_GPIO_NODE, flags: 0, AC_VERB_SET_GPIO_MASK, |
| 127 | parm: mask); |
| 128 | snd_hda_codec_write(codec, SENARY_GPIO_NODE, flags: 0, AC_VERB_SET_GPIO_DIRECTION, |
| 129 | parm: mask); |
| 130 | snd_hda_codec_write(codec, SENARY_GPIO_NODE, flags: 0, AC_VERB_SET_GPIO_DATA, |
| 131 | parm: spec->gpio_led); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | static int senary_init(struct hda_codec *codec) |
| 136 | { |
| 137 | snd_hda_gen_init(codec); |
| 138 | senary_init_gpio_led(codec); |
| 139 | snd_hda_apply_fixup(codec, action: HDA_FIXUP_ACT_INIT); |
| 140 | |
| 141 | return 0; |
| 142 | } |
| 143 | |
| 144 | static void senary_shutdown(struct hda_codec *codec) |
| 145 | { |
| 146 | struct senary_spec *spec = codec->spec; |
| 147 | |
| 148 | /* Turn the problematic codec into D3 to avoid spurious noises |
| 149 | * from the internal speaker during (and after) reboot |
| 150 | */ |
| 151 | senary_auto_turn_eapd(codec, num_pins: spec->num_eapds, pins: spec->eapds, on: false); |
| 152 | } |
| 153 | |
| 154 | static void senary_remove(struct hda_codec *codec) |
| 155 | { |
| 156 | senary_shutdown(codec); |
| 157 | snd_hda_gen_remove(codec); |
| 158 | } |
| 159 | |
| 160 | static int senary_suspend(struct hda_codec *codec) |
| 161 | { |
| 162 | senary_shutdown(codec); |
| 163 | return 0; |
| 164 | } |
| 165 | |
| 166 | static int senary_probe(struct hda_codec *codec, const struct hda_device_id *id) |
| 167 | { |
| 168 | struct senary_spec *spec; |
| 169 | int err; |
| 170 | |
| 171 | codec_info(codec, "%s: BIOS auto-probing.\n" , codec->core.chip_name); |
| 172 | |
| 173 | spec = kzalloc(sizeof(*spec), GFP_KERNEL); |
| 174 | if (!spec) |
| 175 | return -ENOMEM; |
| 176 | snd_hda_gen_spec_init(spec: &spec->gen); |
| 177 | codec->spec = spec; |
| 178 | |
| 179 | senary_auto_parse_eapd(codec); |
| 180 | spec->gen.own_eapd_ctl = 1; |
| 181 | |
| 182 | if (!spec->gen.vmaster_mute.hook) |
| 183 | spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; |
| 184 | |
| 185 | snd_hda_apply_fixup(codec, action: HDA_FIXUP_ACT_PRE_PROBE); |
| 186 | |
| 187 | err = snd_hda_parse_pin_defcfg(codec, cfg: &spec->gen.autocfg, NULL, |
| 188 | cond_flags: spec->parse_flags); |
| 189 | if (err < 0) |
| 190 | goto error; |
| 191 | |
| 192 | err = senary_auto_parse_beep(codec); |
| 193 | if (err < 0) |
| 194 | goto error; |
| 195 | |
| 196 | err = snd_hda_gen_parse_auto_config(codec, cfg: &spec->gen.autocfg); |
| 197 | if (err < 0) |
| 198 | goto error; |
| 199 | |
| 200 | /* Some laptops with Senary chips show stalls in S3 resume, |
| 201 | * which falls into the single-cmd mode. |
| 202 | * Better to make reset, then. |
| 203 | */ |
| 204 | if (!codec->bus->core.sync_write) { |
| 205 | codec_info(codec, |
| 206 | "Enable sync_write for stable communication\n" ); |
| 207 | codec->bus->core.sync_write = 1; |
| 208 | codec->bus->allow_bus_reset = 1; |
| 209 | } |
| 210 | |
| 211 | snd_hda_apply_fixup(codec, action: HDA_FIXUP_ACT_PROBE); |
| 212 | |
| 213 | return 0; |
| 214 | |
| 215 | error: |
| 216 | senary_remove(codec); |
| 217 | return err; |
| 218 | } |
| 219 | |
| 220 | static const struct hda_codec_ops senary_codec_ops = { |
| 221 | .probe = senary_probe, |
| 222 | .remove = senary_remove, |
| 223 | .build_controls = snd_hda_gen_build_controls, |
| 224 | .build_pcms = snd_hda_gen_build_pcms, |
| 225 | .init = senary_init, |
| 226 | .unsol_event = snd_hda_jack_unsol_event, |
| 227 | .suspend = senary_suspend, |
| 228 | .check_power_status = snd_hda_gen_check_power_status, |
| 229 | .stream_pm = snd_hda_gen_stream_pm, |
| 230 | }; |
| 231 | |
| 232 | /* |
| 233 | */ |
| 234 | |
| 235 | static const struct hda_device_id snd_hda_id_senary[] = { |
| 236 | HDA_CODEC_ID(0x1fa86186, "SN6186" ), |
| 237 | {} /* terminator */ |
| 238 | }; |
| 239 | MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_senary); |
| 240 | |
| 241 | MODULE_LICENSE("GPL" ); |
| 242 | MODULE_DESCRIPTION("Senarytech HD-audio codec" ); |
| 243 | |
| 244 | static struct hda_codec_driver senary_driver = { |
| 245 | .id = snd_hda_id_senary, |
| 246 | .ops = &senary_codec_ops, |
| 247 | }; |
| 248 | |
| 249 | module_hda_codec_driver(senary_driver); |
| 250 | |