summaryrefslogtreecommitdiff
path: root/sound/hda/common
diff options
context:
space:
mode:
Diffstat (limited to 'sound/hda/common')
-rw-r--r--sound/hda/common/Kconfig97
-rw-r--r--sound/hda/common/Makefile13
-rw-r--r--sound/hda/common/auto_parser.c1104
-rw-r--r--sound/hda/common/beep.c345
-rw-r--r--sound/hda/common/bind.c346
-rw-r--r--sound/hda/common/codec.c4060
-rw-r--r--sound/hda/common/controller.c1336
-rw-r--r--sound/hda/common/controller_trace.h99
-rw-r--r--sound/hda/common/hda_auto_parser.h118
-rw-r--r--sound/hda/common/hda_beep.h46
-rw-r--r--sound/hda/common/hda_controller.h215
-rw-r--r--sound/hda/common/hda_jack.h195
-rw-r--r--sound/hda/common/hda_local.h725
-rw-r--r--sound/hda/common/hwdep.c119
-rw-r--r--sound/hda/common/jack.c770
-rw-r--r--sound/hda/common/proc.c948
-rw-r--r--sound/hda/common/sysfs.c792
17 files changed, 11328 insertions, 0 deletions
diff --git a/sound/hda/common/Kconfig b/sound/hda/common/Kconfig
new file mode 100644
index 000000000000..f38e1947fb3e
--- /dev/null
+++ b/sound/hda/common/Kconfig
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SND_HDA
+ tristate
+ select SND_PCM
+ select SND_VMASTER
+ select SND_JACK
+ select SND_HDA_CORE
+
+if SND_HDA
+
+config SND_HDA_HWDEP
+ bool "Build hwdep interface for HD-audio driver"
+ select SND_HWDEP
+ help
+ Say Y here to build a hwdep interface for HD-audio driver.
+ This interface can be used for out-of-band communication
+ with codecs for debugging purposes.
+
+config SND_HDA_RECONFIG
+ bool "Allow dynamic codec reconfiguration"
+ help
+ Say Y here to enable the HD-audio codec re-configuration feature.
+ It allows user to clear the whole codec configuration, change the
+ codec setup, add extra verbs, and re-configure the codec dynamically.
+
+ Note that this item alone doesn't provide the sysfs interface, but
+ enables the feature just for the patch loader below.
+ If you need the traditional sysfs entries for the manual interaction,
+ turn on CONFIG_SND_HDA_HWDEP as well.
+
+config SND_HDA_INPUT_BEEP
+ bool "Support digital beep via input layer"
+ depends on INPUT=y || INPUT=SND_HDA
+ help
+ Say Y here to build a digital beep interface for HD-audio
+ driver. This interface is used to generate digital beeps.
+
+config SND_HDA_INPUT_BEEP_MODE
+ int "Digital beep registration mode (0=off, 1=on)"
+ depends on SND_HDA_INPUT_BEEP=y
+ default "1"
+ range 0 1
+ help
+ Set 0 to disable the digital beep interface for HD-audio by default.
+ Set 1 to always enable the digital beep interface for HD-audio by
+ default.
+
+config SND_HDA_PATCH_LOADER
+ bool "Support initialization patch loading for HD-audio"
+ select FW_LOADER
+ select SND_HDA_RECONFIG
+ help
+ Say Y here to allow the HD-audio driver to load a pseudo
+ firmware file ("patch") for overriding the BIOS setup at
+ start up. The "patch" file can be specified via patch module
+ option, such as patch=hda-init.
+
+config SND_HDA_POWER_SAVE_DEFAULT
+ int "Default time-out for HD-audio power-save mode"
+ depends on PM
+ default 0
+ help
+ The default time-out value in seconds for HD-audio automatic
+ power-save mode. 0 means to disable the power-save mode.
+
+config SND_HDA_CTL_DEV_ID
+ bool "Use the device identifier field for controls"
+ depends on SND_HDA_INTEL
+ help
+ Say Y to use the device identifier field for (mixer)
+ controls (old behaviour until this option is available).
+
+ When enabled, the multiple HDA codecs may set the device
+ field in control (mixer) element identifiers. The use
+ of this field is not recommended and defined for mixer controls.
+
+ The old behaviour (Y) is obsolete and will be removed. Consider
+ to not enable this option.
+
+config SND_HDA_PREALLOC_SIZE
+ int "Pre-allocated buffer size for HD-audio driver"
+ range 0 32768
+ default 0 if SND_DMA_SGBUF
+ default 64 if !SND_DMA_SGBUF
+ help
+ Specifies the default pre-allocated buffer-size in kB for the
+ HD-audio driver. A larger buffer (e.g. 2048) is preferred
+ for systems using PulseAudio. The default 64 is chosen just
+ for compatibility reasons.
+ On x86 systems, the default is zero as S/G allocation works
+ and no preallocation is needed in most cases.
+
+ Note that the pre-allocation size can be changed dynamically
+ via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too.
+
+endif
diff --git a/sound/hda/common/Makefile b/sound/hda/common/Makefile
new file mode 100644
index 000000000000..3344fa0efe75
--- /dev/null
+++ b/sound/hda/common/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-hda-codec-y := bind.o codec.o jack.o auto_parser.o sysfs.o
+snd-hda-codec-y += controller.o
+snd-hda-codec-$(CONFIG_SND_PROC_FS) += proc.o
+
+snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hwdep.o
+snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += beep.o
+
+# for trace-points
+CFLAGS_controller.o := -I$(src)
+
+# common driver
+obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c
new file mode 100644
index 000000000000..8923813ce424
--- /dev/null
+++ b/sound/hda/common/auto_parser.c
@@ -0,0 +1,1104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BIOS auto-parser helper functions for HD-audio
+ *
+ * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/sort.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+static int is_in_nid_list(hda_nid_t nid, const hda_nid_t *list)
+{
+ for (; *list; list++)
+ if (*list == nid)
+ return 1;
+ return 0;
+}
+
+/* a pair of input pin and its sequence */
+struct auto_out_pin {
+ hda_nid_t pin;
+ short seq;
+};
+
+static int compare_seq(const void *ap, const void *bp)
+{
+ const struct auto_out_pin *a = ap;
+ const struct auto_out_pin *b = bp;
+ return (int)(a->seq - b->seq);
+}
+
+/*
+ * Sort an associated group of pins according to their sequence numbers.
+ * then store it to a pin array.
+ */
+static void sort_pins_by_sequence(hda_nid_t *pins, struct auto_out_pin *list,
+ int num_pins)
+{
+ int i;
+ sort(list, num_pins, sizeof(list[0]), compare_seq, NULL);
+ for (i = 0; i < num_pins; i++)
+ pins[i] = list[i].pin;
+}
+
+
+/* add the found input-pin to the cfg->inputs[] table */
+static void add_auto_cfg_input_pin(struct hda_codec *codec, struct auto_pin_cfg *cfg,
+ hda_nid_t nid, int type)
+{
+ if (cfg->num_inputs < AUTO_CFG_MAX_INS) {
+ cfg->inputs[cfg->num_inputs].pin = nid;
+ cfg->inputs[cfg->num_inputs].type = type;
+ cfg->inputs[cfg->num_inputs].has_boost_on_pin =
+ nid_has_volume(codec, nid, HDA_INPUT);
+ cfg->num_inputs++;
+ }
+}
+
+static int compare_input_type(const void *ap, const void *bp)
+{
+ const struct auto_pin_cfg_item *a = ap;
+ const struct auto_pin_cfg_item *b = bp;
+ if (a->type != b->type)
+ return (int)(a->type - b->type);
+
+ /* If has both hs_mic and hp_mic, pick the hs_mic ahead of hp_mic. */
+ if (a->is_headset_mic && b->is_headphone_mic)
+ return -1; /* don't swap */
+ else if (a->is_headphone_mic && b->is_headset_mic)
+ return 1; /* swap */
+
+ /* In case one has boost and the other one has not,
+ pick the one with boost first. */
+ if (a->has_boost_on_pin != b->has_boost_on_pin)
+ return (int)(b->has_boost_on_pin - a->has_boost_on_pin);
+
+ /* Keep the original order */
+ return a->order - b->order;
+}
+
+/* Reorder the surround channels
+ * ALSA sequence is front/surr/clfe/side
+ * HDA sequence is:
+ * 4-ch: front/surr => OK as it is
+ * 6-ch: front/clfe/surr
+ * 8-ch: front/clfe/rear/side|fc
+ */
+static void reorder_outputs(unsigned int nums, hda_nid_t *pins)
+{
+ switch (nums) {
+ case 3:
+ case 4:
+ swap(pins[1], pins[2]);
+ break;
+ }
+}
+
+/* check whether the given pin has a proper pin I/O capability bit */
+static bool check_pincap_validity(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int dev)
+{
+ unsigned int pincap = snd_hda_query_pin_caps(codec, pin);
+
+ /* some old hardware don't return the proper pincaps */
+ if (!pincap)
+ return true;
+
+ switch (dev) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ case AC_JACK_HP_OUT:
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ return !!(pincap & AC_PINCAP_OUT);
+ default:
+ return !!(pincap & AC_PINCAP_IN);
+ }
+}
+
+static bool can_be_headset_mic(struct hda_codec *codec,
+ struct auto_pin_cfg_item *item,
+ int seq_number)
+{
+ int attr;
+ unsigned int def_conf;
+ if (item->type != AUTO_PIN_MIC)
+ return false;
+
+ if (item->is_headset_mic || item->is_headphone_mic)
+ return false; /* Already assigned */
+
+ def_conf = snd_hda_codec_get_pincfg(codec, item->pin);
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (attr <= INPUT_PIN_ATTR_DOCK)
+ return false;
+
+ if (seq_number >= 0) {
+ int seq = get_defcfg_sequence(def_conf);
+ if (seq != seq_number)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Parse all pin widgets and store the useful pin nids to cfg
+ *
+ * The number of line-outs or any primary output is stored in line_outs,
+ * and the corresponding output pins are assigned to line_out_pins[],
+ * in the order of front, rear, CLFE, side, ...
+ *
+ * If more extra outputs (speaker and headphone) are found, the pins are
+ * assisnged to hp_pins[] and speaker_pins[], respectively. If no line-out jack
+ * is detected, one of speaker of HP pins is assigned as the primary
+ * output, i.e. to line_out_pins[0]. So, line_outs is always positive
+ * if any analog output exists.
+ *
+ * The analog input pins are assigned to inputs array.
+ * The digital input/output pins are assigned to dig_in_pin and dig_out_pin,
+ * respectively.
+ */
+int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg,
+ const hda_nid_t *ignore_nids,
+ unsigned int cond_flags)
+{
+ hda_nid_t nid;
+ short seq, assoc_line_out;
+ struct auto_out_pin line_out[ARRAY_SIZE(cfg->line_out_pins)];
+ struct auto_out_pin speaker_out[ARRAY_SIZE(cfg->speaker_pins)];
+ struct auto_out_pin hp_out[ARRAY_SIZE(cfg->hp_pins)];
+ int i;
+
+ if (!snd_hda_get_int_hint(codec, "parser_flags", &i))
+ cond_flags = i;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ memset(line_out, 0, sizeof(line_out));
+ memset(speaker_out, 0, sizeof(speaker_out));
+ memset(hp_out, 0, sizeof(hp_out));
+ assoc_line_out = 0;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wid_caps = get_wcaps(codec, nid);
+ unsigned int wid_type = get_wcaps_type(wid_caps);
+ unsigned int def_conf;
+ short assoc, loc, conn, dev;
+
+ /* read all default configuration for pin complex */
+ if (wid_type != AC_WID_PIN)
+ continue;
+ /* ignore the given nids (e.g. pc-beep returns error) */
+ if (ignore_nids && is_in_nid_list(nid, ignore_nids))
+ continue;
+
+ def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ continue;
+ loc = get_defcfg_location(def_conf);
+ dev = get_defcfg_device(def_conf);
+
+ /* workaround for buggy BIOS setups */
+ if (dev == AC_JACK_LINE_OUT) {
+ if (conn == AC_JACK_PORT_FIXED ||
+ conn == AC_JACK_PORT_BOTH)
+ dev = AC_JACK_SPEAKER;
+ }
+
+ if (!check_pincap_validity(codec, nid, dev))
+ continue;
+
+ switch (dev) {
+ case AC_JACK_LINE_OUT:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+
+ if (!(wid_caps & AC_WCAP_STEREO))
+ if (!cfg->mono_out_pin)
+ cfg->mono_out_pin = nid;
+ if (!assoc)
+ continue;
+ if (!assoc_line_out)
+ assoc_line_out = assoc;
+ else if (assoc_line_out != assoc) {
+ codec_info(codec,
+ "ignore pin 0x%x with mismatching assoc# 0x%x vs 0x%x\n",
+ nid, assoc, assoc_line_out);
+ continue;
+ }
+ if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ line_out[cfg->line_outs].pin = nid;
+ line_out[cfg->line_outs].seq = seq;
+ cfg->line_outs++;
+ break;
+ case AC_JACK_SPEAKER:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+ if (cfg->speaker_outs >= ARRAY_SIZE(cfg->speaker_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ speaker_out[cfg->speaker_outs].pin = nid;
+ speaker_out[cfg->speaker_outs].seq = (assoc << 4) | seq;
+ cfg->speaker_outs++;
+ break;
+ case AC_JACK_HP_OUT:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+ if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ hp_out[cfg->hp_outs].pin = nid;
+ hp_out[cfg->hp_outs].seq = (assoc << 4) | seq;
+ cfg->hp_outs++;
+ break;
+ case AC_JACK_MIC_IN:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_MIC);
+ break;
+ case AC_JACK_LINE_IN:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_LINE_IN);
+ break;
+ case AC_JACK_CD:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_CD);
+ break;
+ case AC_JACK_AUX:
+ add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_AUX);
+ break;
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ if (cfg->dig_outs >= ARRAY_SIZE(cfg->dig_out_pins)) {
+ codec_info(codec,
+ "ignore pin 0x%x, too many assigned pins\n",
+ nid);
+ continue;
+ }
+ cfg->dig_out_pins[cfg->dig_outs] = nid;
+ cfg->dig_out_type[cfg->dig_outs] =
+ (loc == AC_JACK_LOC_HDMI) ?
+ HDA_PCM_TYPE_HDMI : HDA_PCM_TYPE_SPDIF;
+ cfg->dig_outs++;
+ break;
+ case AC_JACK_SPDIF_IN:
+ case AC_JACK_DIG_OTHER_IN:
+ cfg->dig_in_pin = nid;
+ if (loc == AC_JACK_LOC_HDMI)
+ cfg->dig_in_type = HDA_PCM_TYPE_HDMI;
+ else
+ cfg->dig_in_type = HDA_PCM_TYPE_SPDIF;
+ break;
+ }
+ }
+
+ /* Find a pin that could be a headset or headphone mic */
+ if (cond_flags & HDA_PINCFG_HEADSET_MIC || cond_flags & HDA_PINCFG_HEADPHONE_MIC) {
+ bool hsmic = !!(cond_flags & HDA_PINCFG_HEADSET_MIC);
+ bool hpmic = !!(cond_flags & HDA_PINCFG_HEADPHONE_MIC);
+ for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++)
+ if (hsmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xc)) {
+ cfg->inputs[i].is_headset_mic = 1;
+ hsmic = false;
+ } else if (hpmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xd)) {
+ cfg->inputs[i].is_headphone_mic = 1;
+ hpmic = false;
+ }
+
+ /* If we didn't find our sequence number mark, fall back to any sequence number */
+ for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++) {
+ if (!can_be_headset_mic(codec, &cfg->inputs[i], -1))
+ continue;
+ if (hsmic) {
+ cfg->inputs[i].is_headset_mic = 1;
+ hsmic = false;
+ } else if (hpmic) {
+ cfg->inputs[i].is_headphone_mic = 1;
+ hpmic = false;
+ }
+ }
+
+ if (hsmic)
+ codec_dbg(codec, "Told to look for a headset mic, but didn't find any.\n");
+ if (hpmic)
+ codec_dbg(codec, "Told to look for a headphone mic, but didn't find any.\n");
+ }
+
+ /* FIX-UP:
+ * If no line-out is defined but multiple HPs are found,
+ * some of them might be the real line-outs.
+ */
+ if (!cfg->line_outs && cfg->hp_outs > 1 &&
+ !(cond_flags & HDA_PINCFG_NO_HP_FIXUP)) {
+ i = 0;
+ while (i < cfg->hp_outs) {
+ /* The real HPs should have the sequence 0x0f */
+ if ((hp_out[i].seq & 0x0f) == 0x0f) {
+ i++;
+ continue;
+ }
+ /* Move it to the line-out table */
+ line_out[cfg->line_outs++] = hp_out[i];
+ cfg->hp_outs--;
+ memmove(hp_out + i, hp_out + i + 1,
+ sizeof(hp_out[0]) * (cfg->hp_outs - i));
+ }
+ memset(hp_out + cfg->hp_outs, 0,
+ sizeof(hp_out[0]) * (AUTO_CFG_MAX_OUTS - cfg->hp_outs));
+ if (!cfg->hp_outs)
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+
+ }
+
+ /* sort by sequence */
+ sort_pins_by_sequence(cfg->line_out_pins, line_out, cfg->line_outs);
+ sort_pins_by_sequence(cfg->speaker_pins, speaker_out,
+ cfg->speaker_outs);
+ sort_pins_by_sequence(cfg->hp_pins, hp_out, cfg->hp_outs);
+
+ /*
+ * FIX-UP: if no line-outs are detected, try to use speaker or HP pin
+ * as a primary output
+ */
+ if (!cfg->line_outs &&
+ !(cond_flags & HDA_PINCFG_NO_LO_FIXUP)) {
+ if (cfg->speaker_outs) {
+ cfg->line_outs = cfg->speaker_outs;
+ memcpy(cfg->line_out_pins, cfg->speaker_pins,
+ sizeof(cfg->speaker_pins));
+ cfg->speaker_outs = 0;
+ memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
+ cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
+ } else if (cfg->hp_outs) {
+ cfg->line_outs = cfg->hp_outs;
+ memcpy(cfg->line_out_pins, cfg->hp_pins,
+ sizeof(cfg->hp_pins));
+ cfg->hp_outs = 0;
+ memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+ cfg->line_out_type = AUTO_PIN_HP_OUT;
+ }
+ }
+
+ reorder_outputs(cfg->line_outs, cfg->line_out_pins);
+ reorder_outputs(cfg->hp_outs, cfg->hp_pins);
+ reorder_outputs(cfg->speaker_outs, cfg->speaker_pins);
+
+ /* sort inputs in the order of AUTO_PIN_* type */
+ for (i = 0; i < cfg->num_inputs; i++)
+ cfg->inputs[i].order = i;
+ sort(cfg->inputs, cfg->num_inputs, sizeof(cfg->inputs[0]),
+ compare_input_type, NULL);
+
+ /*
+ * debug prints of the parsed results
+ */
+ codec_info(codec, "autoconfig for %s: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x) type:%s\n",
+ codec->core.chip_name, cfg->line_outs, cfg->line_out_pins[0],
+ cfg->line_out_pins[1], cfg->line_out_pins[2],
+ cfg->line_out_pins[3], cfg->line_out_pins[4],
+ cfg->line_out_type == AUTO_PIN_HP_OUT ? "hp" :
+ (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT ?
+ "speaker" : "line"));
+ codec_info(codec, " speaker_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+ cfg->speaker_outs, cfg->speaker_pins[0],
+ cfg->speaker_pins[1], cfg->speaker_pins[2],
+ cfg->speaker_pins[3], cfg->speaker_pins[4]);
+ codec_info(codec, " hp_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+ cfg->hp_outs, cfg->hp_pins[0],
+ cfg->hp_pins[1], cfg->hp_pins[2],
+ cfg->hp_pins[3], cfg->hp_pins[4]);
+ codec_info(codec, " mono: mono_out=0x%x\n", cfg->mono_out_pin);
+ if (cfg->dig_outs)
+ codec_info(codec, " dig-out=0x%x/0x%x\n",
+ cfg->dig_out_pins[0], cfg->dig_out_pins[1]);
+ codec_info(codec, " inputs:\n");
+ for (i = 0; i < cfg->num_inputs; i++) {
+ codec_info(codec, " %s=0x%x\n",
+ hda_get_autocfg_input_label(codec, cfg, i),
+ cfg->inputs[i].pin);
+ }
+ if (cfg->dig_in_pin)
+ codec_info(codec, " dig-in=0x%x\n", cfg->dig_in_pin);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_parse_pin_defcfg);
+
+/**
+ * snd_hda_get_input_pin_attr - Get the input pin attribute from pin config
+ * @def_conf: pin configuration value
+ *
+ * Guess the input pin attribute (INPUT_PIN_ATTR_XXX) from the given
+ * default pin configuration value.
+ */
+int snd_hda_get_input_pin_attr(unsigned int def_conf)
+{
+ unsigned int loc = get_defcfg_location(def_conf);
+ unsigned int conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ return INPUT_PIN_ATTR_UNUSED;
+ /* Windows may claim the internal mic to be BOTH, too */
+ if (conn == AC_JACK_PORT_FIXED || conn == AC_JACK_PORT_BOTH)
+ return INPUT_PIN_ATTR_INT;
+ if ((loc & 0x30) == AC_JACK_LOC_INTERNAL)
+ return INPUT_PIN_ATTR_INT;
+ if ((loc & 0x30) == AC_JACK_LOC_SEPARATE)
+ return INPUT_PIN_ATTR_DOCK;
+ if (loc == AC_JACK_LOC_REAR)
+ return INPUT_PIN_ATTR_REAR;
+ if (loc == AC_JACK_LOC_FRONT)
+ return INPUT_PIN_ATTR_FRONT;
+ return INPUT_PIN_ATTR_NORMAL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_input_pin_attr);
+
+/**
+ * hda_get_input_pin_label - Give a label for the given input pin
+ * @codec: the HDA codec
+ * @item: ping config item to refer
+ * @pin: the pin NID
+ * @check_location: flag to add the jack location prefix
+ *
+ * When @check_location is true, the function checks the pin location
+ * for mic and line-in pins, and set an appropriate prefix like "Front",
+ * "Rear", "Internal".
+ */
+static const char *hda_get_input_pin_label(struct hda_codec *codec,
+ const struct auto_pin_cfg_item *item,
+ hda_nid_t pin, bool check_location)
+{
+ unsigned int def_conf;
+ static const char * const mic_names[] = {
+ "Internal Mic", "Dock Mic", "Mic", "Rear Mic", "Front Mic"
+ };
+ int attr;
+
+ def_conf = snd_hda_codec_get_pincfg(codec, pin);
+
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_MIC_IN:
+ if (item && item->is_headset_mic)
+ return "Headset Mic";
+ if (item && item->is_headphone_mic)
+ return "Headphone Mic";
+ if (!check_location)
+ return "Mic";
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (!attr)
+ return "None";
+ return mic_names[attr - 1];
+ case AC_JACK_LINE_IN:
+ if (!check_location)
+ return "Line";
+ attr = snd_hda_get_input_pin_attr(def_conf);
+ if (!attr)
+ return "None";
+ if (attr == INPUT_PIN_ATTR_DOCK)
+ return "Dock Line";
+ return "Line";
+ case AC_JACK_AUX:
+ return "Aux";
+ case AC_JACK_CD:
+ return "CD";
+ case AC_JACK_SPDIF_IN:
+ return "SPDIF In";
+ case AC_JACK_DIG_OTHER_IN:
+ return "Digital In";
+ case AC_JACK_HP_OUT:
+ return "Headphone Mic";
+ default:
+ return "Misc";
+ }
+}
+
+/* Check whether the location prefix needs to be added to the label.
+ * If all mic-jacks are in the same location (e.g. rear panel), we don't
+ * have to put "Front" prefix to each label. In such a case, returns false.
+ */
+static int check_mic_location_need(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input)
+{
+ unsigned int defc;
+ int i, attr, attr2;
+
+ defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[input].pin);
+ attr = snd_hda_get_input_pin_attr(defc);
+ /* for internal or docking mics, we need locations */
+ if (attr <= INPUT_PIN_ATTR_NORMAL)
+ return 1;
+
+ attr = 0;
+ for (i = 0; i < cfg->num_inputs; i++) {
+ defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[i].pin);
+ attr2 = snd_hda_get_input_pin_attr(defc);
+ if (attr2 >= INPUT_PIN_ATTR_NORMAL) {
+ if (attr && attr != attr2)
+ return 1; /* different locations found */
+ attr = attr2;
+ }
+ }
+ return 0;
+}
+
+/**
+ * hda_get_autocfg_input_label - Get a label for the given input
+ * @codec: the HDA codec
+ * @cfg: the parsed pin configuration
+ * @input: the input index number
+ *
+ * Get a label for the given input pin defined by the autocfg item.
+ * Unlike hda_get_input_pin_label(), this function checks all inputs
+ * defined in autocfg and avoids the redundant mic/line prefix as much as
+ * possible.
+ */
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input)
+{
+ int type = cfg->inputs[input].type;
+ int has_multiple_pins = 0;
+
+ if ((input > 0 && cfg->inputs[input - 1].type == type) ||
+ (input < cfg->num_inputs - 1 && cfg->inputs[input + 1].type == type))
+ has_multiple_pins = 1;
+ if (has_multiple_pins && type == AUTO_PIN_MIC)
+ has_multiple_pins &= check_mic_location_need(codec, cfg, input);
+ has_multiple_pins |= codec->force_pin_prefix;
+ return hda_get_input_pin_label(codec, &cfg->inputs[input],
+ cfg->inputs[input].pin,
+ has_multiple_pins);
+}
+EXPORT_SYMBOL_GPL(hda_get_autocfg_input_label);
+
+/* return the position of NID in the list, or -1 if not found */
+static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ int i;
+ for (i = 0; i < nums; i++)
+ if (list[i] == nid)
+ return i;
+ return -1;
+}
+
+/* get a unique suffix or an index number */
+static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins,
+ int num_pins, int *indexp)
+{
+ static const char * const channel_sfx[] = {
+ " Front", " Surround", " CLFE", " Side"
+ };
+ int i;
+
+ i = find_idx_in_nid_list(nid, pins, num_pins);
+ if (i < 0)
+ return NULL;
+ if (num_pins == 1)
+ return "";
+ if (num_pins > ARRAY_SIZE(channel_sfx)) {
+ if (indexp)
+ *indexp = i;
+ return "";
+ }
+ return channel_sfx[i];
+}
+
+static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ int attr = snd_hda_get_input_pin_attr(def_conf);
+
+ /* check the location */
+ switch (attr) {
+ case INPUT_PIN_ATTR_DOCK:
+ return "Dock ";
+ case INPUT_PIN_ATTR_FRONT:
+ return "Front ";
+ }
+ return "";
+}
+
+static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid,
+ const hda_nid_t *pins, int num_pins)
+{
+ int i, j, idx = 0;
+
+ const char *pfx = check_output_pfx(codec, nid);
+
+ i = find_idx_in_nid_list(nid, pins, num_pins);
+ if (i < 0)
+ return -1;
+ for (j = 0; j < i; j++)
+ if (pfx == check_output_pfx(codec, pins[j]))
+ idx++;
+
+ return idx;
+}
+
+static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ const char *name, char *label, int maxlen,
+ int *indexp)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ int attr = snd_hda_get_input_pin_attr(def_conf);
+ const char *pfx, *sfx = "";
+
+ /* handle as a speaker if it's a fixed line-out */
+ if (!strcmp(name, "Line Out") && attr == INPUT_PIN_ATTR_INT)
+ name = "Speaker";
+ pfx = check_output_pfx(codec, nid);
+
+ if (cfg) {
+ /* try to give a unique suffix if needed */
+ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs,
+ indexp);
+ if (!sfx)
+ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs,
+ indexp);
+ if (!sfx) {
+ /* don't add channel suffix for Headphone controls */
+ int idx = get_hp_label_index(codec, nid, cfg->hp_pins,
+ cfg->hp_outs);
+ if (idx >= 0 && indexp)
+ *indexp = idx;
+ sfx = "";
+ }
+ }
+ snprintf(label, maxlen, "%s%s%s", pfx, name, sfx);
+ return 1;
+}
+
+#define is_hdmi_cfg(conf) \
+ (get_defcfg_location(conf) == AC_JACK_LOC_HDMI)
+
+/**
+ * snd_hda_get_pin_label - Get a label for the given I/O pin
+ * @codec: the HDA codec
+ * @nid: pin NID
+ * @cfg: the parsed pin configuration
+ * @label: the string buffer to store
+ * @maxlen: the max length of string buffer (including termination)
+ * @indexp: the pointer to return the index number (for multiple ctls)
+ *
+ * Get a label for the given pin. This function works for both input and
+ * output pins. When @cfg is given as non-NULL, the function tries to get
+ * an optimized label using hda_get_autocfg_input_label().
+ *
+ * This function tries to give a unique label string for the pin as much as
+ * possible. For example, when the multiple line-outs are present, it adds
+ * the channel suffix like "Front", "Surround", etc (only when @cfg is given).
+ * If no unique name with a suffix is available and @indexp is non-NULL, the
+ * index number is stored in the pointer.
+ */
+int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ char *label, int maxlen, int *indexp)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ const char *name = NULL;
+ int i;
+ bool hdmi;
+
+ if (indexp)
+ *indexp = 0;
+ if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+ return 0;
+
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_LINE_OUT:
+ return fill_audio_out_name(codec, nid, cfg, "Line Out",
+ label, maxlen, indexp);
+ case AC_JACK_SPEAKER:
+ return fill_audio_out_name(codec, nid, cfg, "Speaker",
+ label, maxlen, indexp);
+ case AC_JACK_HP_OUT:
+ return fill_audio_out_name(codec, nid, cfg, "Headphone",
+ label, maxlen, indexp);
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ hdmi = is_hdmi_cfg(def_conf);
+ name = hdmi ? "HDMI" : "SPDIF";
+ if (cfg && indexp)
+ for (i = 0; i < cfg->dig_outs; i++) {
+ hda_nid_t pin = cfg->dig_out_pins[i];
+ unsigned int c;
+ if (pin == nid)
+ break;
+ c = snd_hda_codec_get_pincfg(codec, pin);
+ if (hdmi == is_hdmi_cfg(c))
+ (*indexp)++;
+ }
+ break;
+ default:
+ if (cfg) {
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].pin != nid)
+ continue;
+ name = hda_get_autocfg_input_label(codec, cfg, i);
+ if (name)
+ break;
+ }
+ }
+ if (!name)
+ name = hda_get_input_pin_label(codec, NULL, nid, true);
+ break;
+ }
+ if (!name)
+ return 0;
+ strscpy(label, name, maxlen);
+ return 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_pin_label);
+
+/**
+ * snd_hda_add_verbs - Add verbs to the init list
+ * @codec: the HDA codec
+ * @list: zero-terminated verb list to add
+ *
+ * Append the given verb list to the execution list. The verbs will be
+ * performed at init and resume time via snd_hda_apply_verbs().
+ */
+int snd_hda_add_verbs(struct hda_codec *codec,
+ const struct hda_verb *list)
+{
+ const struct hda_verb **v;
+ v = snd_array_new(&codec->verbs);
+ if (!v)
+ return -ENOMEM;
+ *v = list;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_verbs);
+
+/**
+ * snd_hda_apply_verbs - Execute the init verb lists
+ * @codec: the HDA codec
+ */
+void snd_hda_apply_verbs(struct hda_codec *codec)
+{
+ const struct hda_verb **v;
+ int i;
+
+ snd_array_for_each(&codec->verbs, i, v)
+ snd_hda_sequence_write(codec, *v);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_verbs);
+
+/**
+ * snd_hda_apply_pincfgs - Set each pin config in the given list
+ * @codec: the HDA codec
+ * @cfg: NULL-terminated pin config table
+ */
+void snd_hda_apply_pincfgs(struct hda_codec *codec,
+ const struct hda_pintbl *cfg)
+{
+ for (; cfg->nid; cfg++)
+ snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_pincfgs);
+
+static void set_pin_targets(struct hda_codec *codec,
+ const struct hda_pintbl *cfg)
+{
+ for (; cfg->nid; cfg++)
+ snd_hda_set_pin_ctl_cache(codec, cfg->nid, cfg->val);
+}
+
+void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth)
+{
+ const char *modelname = codec->fixup_name;
+
+ while (id >= 0) {
+ const struct hda_fixup *fix = codec->fixup_list + id;
+
+ if (++depth > 10)
+ break;
+ if (fix->chained_before)
+ __snd_hda_apply_fixup(codec, fix->chain_id, action, depth + 1);
+
+ switch (fix->type) {
+ case HDA_FIXUP_PINS:
+ if (action != HDA_FIXUP_ACT_PRE_PROBE || !fix->v.pins)
+ break;
+ codec_dbg(codec, "%s: Apply pincfg for %s\n",
+ codec->core.chip_name, modelname);
+ snd_hda_apply_pincfgs(codec, fix->v.pins);
+ break;
+ case HDA_FIXUP_VERBS:
+ if (action != HDA_FIXUP_ACT_PROBE || !fix->v.verbs)
+ break;
+ codec_dbg(codec, "%s: Apply fix-verbs for %s\n",
+ codec->core.chip_name, modelname);
+ snd_hda_add_verbs(codec, fix->v.verbs);
+ break;
+ case HDA_FIXUP_FUNC:
+ if (!fix->v.func)
+ break;
+ codec_dbg(codec, "%s: Apply fix-func for %s\n",
+ codec->core.chip_name, modelname);
+ fix->v.func(codec, fix, action);
+ break;
+ case HDA_FIXUP_PINCTLS:
+ if (action != HDA_FIXUP_ACT_PROBE || !fix->v.pins)
+ break;
+ codec_dbg(codec, "%s: Apply pinctl for %s\n",
+ codec->core.chip_name, modelname);
+ set_pin_targets(codec, fix->v.pins);
+ break;
+ default:
+ codec_err(codec, "%s: Invalid fixup type %d\n",
+ codec->core.chip_name, fix->type);
+ break;
+ }
+ if (!fix->chained || fix->chained_before)
+ break;
+ id = fix->chain_id;
+ }
+}
+EXPORT_SYMBOL_GPL(__snd_hda_apply_fixup);
+
+/**
+ * snd_hda_apply_fixup - Apply the fixup chain with the given action
+ * @codec: the HDA codec
+ * @action: fixup action (HDA_FIXUP_ACT_XXX)
+ */
+void snd_hda_apply_fixup(struct hda_codec *codec, int action)
+{
+ if (codec->fixup_list)
+ __snd_hda_apply_fixup(codec, codec->fixup_id, action, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_apply_fixup);
+
+#define IGNORE_SEQ_ASSOC (~(AC_DEFCFG_SEQUENCE | AC_DEFCFG_DEF_ASSOC))
+
+static bool pin_config_match(struct hda_codec *codec,
+ const struct hda_pintbl *pins,
+ bool match_all_pins)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ hda_nid_t nid = pin->nid;
+ u32 cfg = pin->cfg;
+ const struct hda_pintbl *t_pins;
+ int found;
+
+ t_pins = pins;
+ found = 0;
+ for (; t_pins->nid; t_pins++) {
+ if (t_pins->nid == nid) {
+ found = 1;
+ if ((t_pins->val & IGNORE_SEQ_ASSOC) == (cfg & IGNORE_SEQ_ASSOC))
+ break;
+ else if ((cfg & 0xf0000000) == 0x40000000 && (t_pins->val & 0xf0000000) == 0x40000000)
+ break;
+ else
+ return false;
+ }
+ }
+ if (match_all_pins &&
+ !found && (cfg & 0xf0000000) != 0x40000000)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * snd_hda_pick_pin_fixup - Pick up a fixup matching with the pin quirk list
+ * @codec: the HDA codec
+ * @pin_quirk: zero-terminated pin quirk list
+ * @fixlist: the fixup list
+ * @match_all_pins: all valid pins must match with the table entries
+ */
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist,
+ bool match_all_pins)
+{
+ const struct snd_hda_pin_quirk *pq;
+ const char *name = NULL;
+
+ if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET)
+ return;
+
+ for (pq = pin_quirk; pq->subvendor; pq++) {
+ if ((codec->core.subsystem_id & 0xffff0000) != (pq->subvendor << 16))
+ continue;
+ if (codec->core.vendor_id != pq->codec)
+ continue;
+ if (pin_config_match(codec, pq->pins, match_all_pins)) {
+ codec->fixup_id = pq->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ codec->fixup_name = pq->name;
+ name = pq->name;
+#endif
+ codec_info(codec, "%s: picked fixup %s (pin match)\n",
+ codec->core.chip_name, name ? name : "");
+ codec->fixup_list = fixlist;
+ return;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup);
+
+/* check whether the given quirk entry matches with vendor/device pair */
+static bool hda_quirk_match(u16 vendor, u16 device, const struct hda_quirk *q)
+{
+ if (q->subvendor != vendor)
+ return false;
+ return !q->subdevice ||
+ (device & q->subdevice_mask) == q->subdevice;
+}
+
+/* look through the quirk list and return the matching entry */
+static const struct hda_quirk *
+hda_quirk_lookup_id(u16 vendor, u16 device, const struct hda_quirk *list)
+{
+ const struct hda_quirk *q;
+
+ for (q = list; q->subvendor || q->subdevice; q++) {
+ if (hda_quirk_match(vendor, device, q))
+ return q;
+ }
+ return NULL;
+}
+
+/**
+ * snd_hda_pick_fixup - Pick up a fixup matching with PCI/codec SSID or model string
+ * @codec: the HDA codec
+ * @models: NULL-terminated model string list
+ * @quirk: zero-terminated PCI/codec SSID quirk list
+ * @fixlist: the fixup list
+ *
+ * Pick up a fixup entry matching with the given model string or SSID.
+ * If a fixup was already set beforehand, the function doesn't do anything.
+ * When a special model string "nofixup" is given, also no fixup is applied.
+ *
+ * The function tries to find the matching model name at first, if given.
+ * If the model string contains the SSID alias, try to look up with the given
+ * alias ID.
+ * If nothing matched, try to look up the PCI SSID.
+ * If still nothing matched, try to look up the codec SSID.
+ */
+void snd_hda_pick_fixup(struct hda_codec *codec,
+ const struct hda_model_fixup *models,
+ const struct hda_quirk *quirk,
+ const struct hda_fixup *fixlist)
+{
+ const struct hda_quirk *q;
+ int id = HDA_FIXUP_ID_NOT_SET;
+ const char *name = NULL;
+ const char *type = NULL;
+ unsigned int vendor, device;
+ u16 pci_vendor, pci_device;
+ u16 codec_vendor, codec_device;
+
+ if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET)
+ return;
+
+ /* when model=nofixup is given, don't pick up any fixups */
+ if (codec->modelname && !strcmp(codec->modelname, "nofixup")) {
+ id = HDA_FIXUP_ID_NO_FIXUP;
+ fixlist = NULL;
+ codec_info(codec, "%s: picked no fixup (nofixup specified)\n",
+ codec->core.chip_name);
+ goto found;
+ }
+
+ /* match with the model name string */
+ if (codec->modelname && models) {
+ while (models->name) {
+ if (!strcmp(codec->modelname, models->name)) {
+ id = models->id;
+ name = models->name;
+ codec_info(codec, "%s: picked fixup %s (model specified)\n",
+ codec->core.chip_name, name);
+ goto found;
+ }
+ models++;
+ }
+ }
+
+ if (!quirk)
+ return;
+
+ if (codec->bus->pci) {
+ pci_vendor = codec->bus->pci->subsystem_vendor;
+ pci_device = codec->bus->pci->subsystem_device;
+ }
+
+ codec_vendor = codec->core.subsystem_id >> 16;
+ codec_device = codec->core.subsystem_id & 0xffff;
+
+ /* match with the SSID alias given by the model string "XXXX:YYYY" */
+ if (codec->modelname &&
+ sscanf(codec->modelname, "%04x:%04x", &vendor, &device) == 2) {
+ q = hda_quirk_lookup_id(vendor, device, quirk);
+ if (q) {
+ type = "alias SSID";
+ goto found_device;
+ }
+ }
+
+ /* match primarily with the PCI SSID */
+ for (q = quirk; q->subvendor || q->subdevice; q++) {
+ /* if the entry is specific to codec SSID, check with it */
+ if (!codec->bus->pci || q->match_codec_ssid) {
+ if (hda_quirk_match(codec_vendor, codec_device, q)) {
+ type = "codec SSID";
+ goto found_device;
+ }
+ } else {
+ if (hda_quirk_match(pci_vendor, pci_device, q)) {
+ type = "PCI SSID";
+ goto found_device;
+ }
+ }
+ }
+
+ /* match with the codec SSID */
+ q = hda_quirk_lookup_id(codec_vendor, codec_device, quirk);
+ if (q) {
+ type = "codec SSID";
+ goto found_device;
+ }
+
+ return; /* no matching */
+
+ found_device:
+ id = q->value;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ name = q->name;
+#endif
+ codec_info(codec, "%s: picked fixup %s for %s %04x:%04x\n",
+ codec->core.chip_name, name ? name : "",
+ type, q->subvendor, q->subdevice);
+ found:
+ codec->fixup_id = id;
+ codec->fixup_list = fixlist;
+ codec->fixup_name = name;
+}
+EXPORT_SYMBOL_GPL(snd_hda_pick_fixup);
diff --git a/sound/hda/common/beep.c b/sound/hda/common/beep.c
new file mode 100644
index 000000000000..13a7d92e8d8d
--- /dev/null
+++ b/sound/hda/common/beep.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include "hda_beep.h"
+#include "hda_local.h"
+
+enum {
+ DIGBEEP_HZ_STEP = 46875, /* 46.875 Hz */
+ DIGBEEP_HZ_MIN = 93750, /* 93.750 Hz */
+ DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */
+};
+
+/* generate or stop tone */
+static void generate_tone(struct hda_beep *beep, int tone)
+{
+ struct hda_codec *codec = beep->codec;
+
+ if (tone && !beep->playing) {
+ snd_hda_power_up(codec);
+ if (beep->power_hook)
+ beep->power_hook(beep, true);
+ beep->playing = 1;
+ }
+ if (!codec->beep_just_power_on)
+ snd_hda_codec_write(codec, beep->nid, 0,
+ AC_VERB_SET_BEEP_CONTROL, tone);
+ if (!tone && beep->playing) {
+ beep->playing = 0;
+ if (beep->power_hook)
+ beep->power_hook(beep, false);
+ snd_hda_power_down(codec);
+ }
+}
+
+static void snd_hda_generate_beep(struct work_struct *work)
+{
+ struct hda_beep *beep =
+ container_of(work, struct hda_beep, beep_work);
+
+ if (beep->enabled)
+ generate_tone(beep, beep->tone);
+}
+
+/* (non-standard) Linear beep tone calculation for IDT/STAC codecs
+ *
+ * The tone frequency of beep generator on IDT/STAC codecs is
+ * defined from the 8bit tone parameter, in Hz,
+ * freq = 48000 * (257 - tone) / 1024
+ * that is from 12kHz to 93.75Hz in steps of 46.875 Hz
+ */
+static int beep_linear_tone(struct hda_beep *beep, int hz)
+{
+ if (hz <= 0)
+ return 0;
+ hz *= 1000; /* fixed point */
+ hz = hz - DIGBEEP_HZ_MIN
+ + DIGBEEP_HZ_STEP / 2; /* round to nearest step */
+ if (hz < 0)
+ hz = 0; /* turn off PC beep*/
+ else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN))
+ hz = 1; /* max frequency */
+ else {
+ hz /= DIGBEEP_HZ_STEP;
+ hz = 255 - hz;
+ }
+ return hz;
+}
+
+/* HD-audio standard beep tone parameter calculation
+ *
+ * The tone frequency in Hz is calculated as
+ * freq = 48000 / (tone * 4)
+ * from 47Hz to 12kHz
+ */
+static int beep_standard_tone(struct hda_beep *beep, int hz)
+{
+ if (hz <= 0)
+ return 0; /* disabled */
+ hz = 12000 / hz;
+ if (hz > 0xff)
+ return 0xff;
+ if (hz <= 0)
+ return 1;
+ return hz;
+}
+
+static int snd_hda_beep_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int hz)
+{
+ struct hda_beep *beep = input_get_drvdata(dev);
+
+ switch (code) {
+ case SND_BELL:
+ if (hz)
+ hz = 1000;
+ fallthrough;
+ case SND_TONE:
+ if (beep->linear_tone)
+ beep->tone = beep_linear_tone(beep, hz);
+ else
+ beep->tone = beep_standard_tone(beep, hz);
+ break;
+ default:
+ return -1;
+ }
+
+ /* schedule beep event */
+ schedule_work(&beep->beep_work);
+ return 0;
+}
+
+static void turn_on_beep(struct hda_beep *beep)
+{
+ if (beep->keep_power_at_enable)
+ snd_hda_power_up_pm(beep->codec);
+}
+
+static void turn_off_beep(struct hda_beep *beep)
+{
+ cancel_work_sync(&beep->beep_work);
+ if (beep->playing) {
+ /* turn off beep */
+ generate_tone(beep, 0);
+ }
+ if (beep->keep_power_at_enable)
+ snd_hda_power_down_pm(beep->codec);
+}
+
+/**
+ * snd_hda_enable_beep_device - Turn on/off beep sound
+ * @codec: the HDA codec
+ * @enable: flag to turn on/off
+ */
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable)
+{
+ struct hda_beep *beep = codec->beep;
+ if (!beep)
+ return 0;
+ enable = !!enable;
+ if (beep->enabled != enable) {
+ beep->enabled = enable;
+ if (enable)
+ turn_on_beep(beep);
+ else
+ turn_off_beep(beep);
+ return 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device);
+
+static int beep_dev_register(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+ int err;
+
+ err = input_register_device(beep->dev);
+ if (!err)
+ beep->registered = true;
+ return err;
+}
+
+static int beep_dev_disconnect(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+
+ if (beep->registered)
+ input_unregister_device(beep->dev);
+ else
+ input_free_device(beep->dev);
+ if (beep->enabled)
+ turn_off_beep(beep);
+ return 0;
+}
+
+static int beep_dev_free(struct snd_device *device)
+{
+ struct hda_beep *beep = device->device_data;
+
+ beep->codec->beep = NULL;
+ kfree(beep);
+ return 0;
+}
+
+/**
+ * snd_hda_attach_beep_device - Attach a beep input device
+ * @codec: the HDA codec
+ * @nid: beep NID
+ *
+ * Attach a beep object to the given widget. If beep hint is turned off
+ * explicitly or beep_mode of the codec is turned off, this doesn't nothing.
+ *
+ * Currently, only one beep device is allowed to each codec.
+ */
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
+{
+ static const struct snd_device_ops ops = {
+ .dev_register = beep_dev_register,
+ .dev_disconnect = beep_dev_disconnect,
+ .dev_free = beep_dev_free,
+ };
+ struct input_dev *input_dev;
+ struct hda_beep *beep;
+ int err;
+
+ if (!codec->beep_just_power_on) {
+ if (!snd_hda_get_bool_hint(codec, "beep"))
+ return 0; /* disabled explicitly by hints */
+ if (codec->beep_mode == HDA_BEEP_MODE_OFF)
+ return 0; /* disabled by module option */
+ }
+
+ beep = kzalloc(sizeof(*beep), GFP_KERNEL);
+ if (beep == NULL)
+ return -ENOMEM;
+ snprintf(beep->phys, sizeof(beep->phys),
+ "card%d/codec#%d/beep0", codec->card->number, codec->addr);
+ /* enable linear scale */
+ snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_DIGI_CONVERT_2, 0x01);
+
+ beep->nid = nid;
+ beep->codec = codec;
+ codec->beep = beep;
+
+ INIT_WORK(&beep->beep_work, &snd_hda_generate_beep);
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ err = -ENOMEM;
+ goto err_free;
+ }
+
+ /* setup digital beep device */
+ input_dev->name = "HDA Digital PCBeep";
+ input_dev->phys = beep->phys;
+ input_dev->id.bustype = BUS_PCI;
+ input_dev->dev.parent = &codec->card->card_dev;
+
+ input_dev->id.vendor = codec->core.vendor_id >> 16;
+ input_dev->id.product = codec->core.vendor_id & 0xffff;
+ input_dev->id.version = 0x01;
+
+ input_dev->evbit[0] = BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+ input_dev->event = snd_hda_beep_event;
+ input_set_drvdata(input_dev, beep);
+
+ beep->dev = input_dev;
+
+ err = snd_device_new(codec->card, SNDRV_DEV_JACK, beep, &ops);
+ if (err < 0)
+ goto err_input;
+
+ return 0;
+
+ err_input:
+ input_free_device(beep->dev);
+ err_free:
+ kfree(beep);
+ codec->beep = NULL;
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_attach_beep_device);
+
+/**
+ * snd_hda_detach_beep_device - Detach the beep device
+ * @codec: the HDA codec
+ */
+void snd_hda_detach_beep_device(struct hda_codec *codec)
+{
+ if (!codec->bus->shutdown && codec->beep)
+ snd_device_free(codec->card, codec->beep);
+}
+EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device);
+
+static bool ctl_has_mute(struct snd_kcontrol *kcontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ return query_amp_caps(codec, get_amp_nid(kcontrol),
+ get_amp_direction(kcontrol)) & AC_AMPCAP_MUTE;
+}
+
+/* get/put callbacks for beep mute mixer switches */
+
+/**
+ * snd_hda_mixer_amp_switch_get_beep - Get callback for beep controls
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ */
+int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_beep *beep = codec->beep;
+ int chs = get_amp_channels(kcontrol);
+
+ if (beep && (!beep->enabled || !ctl_has_mute(kcontrol))) {
+ if (chs & 1)
+ ucontrol->value.integer.value[0] = beep->enabled;
+ if (chs & 2)
+ ucontrol->value.integer.value[1] = beep->enabled;
+ return 0;
+ }
+ return snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get_beep);
+
+/**
+ * snd_hda_mixer_amp_switch_put_beep - Put callback for beep controls
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ */
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_beep *beep = codec->beep;
+ if (beep) {
+ u8 chs = get_amp_channels(kcontrol);
+ int enable = 0;
+ long *valp = ucontrol->value.integer.value;
+ if (chs & 1) {
+ enable |= *valp;
+ valp++;
+ }
+ if (chs & 2)
+ enable |= *valp;
+ snd_hda_enable_beep_device(codec, enable);
+ }
+ if (!ctl_has_mute(kcontrol))
+ return 0;
+ return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put_beep);
diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c
new file mode 100644
index 000000000000..bb1090b65699
--- /dev/null
+++ b/sound/hda/common/bind.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio codec driver binding
+ * Copyright (c) Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/pm.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_jack.h"
+
+/*
+ * find a matching codec id
+ */
+static int hda_codec_match(struct hdac_device *dev, const struct hdac_driver *drv)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+ const struct hda_codec_driver *driver =
+ container_of(drv, struct hda_codec_driver, core);
+ const struct hda_device_id *list;
+ /* check probe_id instead of vendor_id if set */
+ u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id;
+ u32 rev_id = codec->core.revision_id;
+
+ for (list = driver->id; list->vendor_id; list++) {
+ if (list->vendor_id == id &&
+ (!list->rev_id || list->rev_id == rev_id)) {
+ codec->preset = list;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* process an unsolicited event */
+static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+
+ /* ignore unsol events during shutdown */
+ if (codec->card->shutdown || codec->bus->shutdown)
+ return;
+
+ /* ignore unsol events during system suspend/resume */
+ if (codec->core.dev.power.power_state.event != PM_EVENT_ON)
+ return;
+
+ if (driver->ops->unsol_event)
+ driver->ops->unsol_event(codec, ev);
+}
+
+/**
+ * snd_hda_codec_set_name - set the codec name
+ * @codec: the HDA codec
+ * @name: name string to set
+ */
+int snd_hda_codec_set_name(struct hda_codec *codec, const char *name)
+{
+ int err;
+
+ if (!name)
+ return 0;
+ err = snd_hdac_device_set_chip_name(&codec->core, name);
+ if (err < 0)
+ return err;
+
+ /* update the mixer name */
+ if (!*codec->card->mixername ||
+ codec->bus->mixer_assigned >= codec->core.addr) {
+ snprintf(codec->card->mixername,
+ sizeof(codec->card->mixername), "%s %s",
+ codec->core.vendor_name, codec->core.chip_name);
+ codec->bus->mixer_assigned = codec->core.addr;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_name);
+
+static int hda_codec_driver_probe(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+ struct module *owner = dev->driver->owner;
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ int err;
+
+ if (codec->bus->core.ext_ops) {
+ if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach))
+ return -EINVAL;
+ return codec->bus->core.ext_ops->hdev_attach(&codec->core);
+ }
+
+ if (WARN_ON(!codec->preset))
+ return -EINVAL;
+
+ err = snd_hda_codec_set_name(codec, codec->preset->name);
+ if (err < 0)
+ goto error;
+ err = snd_hdac_regmap_init(&codec->core);
+ if (err < 0)
+ goto error;
+
+ if (!try_module_get(owner)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (WARN_ON(!(driver->ops && driver->ops->probe))) {
+ err = -EINVAL;
+ goto error_module_put;
+ }
+
+ err = driver->ops->probe(codec, codec->preset);
+ if (err < 0)
+ goto error_module_put;
+ err = snd_hda_codec_build_pcms(codec);
+ if (err < 0)
+ goto error_module;
+ err = snd_hda_codec_build_controls(codec);
+ if (err < 0)
+ goto error_module;
+ /* only register after the bus probe finished; otherwise it's racy */
+ if (!codec->bus->bus_probing && codec->card->registered) {
+ err = snd_card_register(codec->card);
+ if (err < 0)
+ goto error_module;
+ snd_hda_codec_register(codec);
+ }
+
+ codec->core.lazy_cache = true;
+ return 0;
+
+ error_module:
+ if (driver->ops->remove)
+ driver->ops->remove(codec);
+ error_module_put:
+ module_put(owner);
+
+ error:
+ snd_hda_codec_cleanup_for_unbind(codec);
+ codec->preset = NULL;
+ return err;
+}
+
+static int hda_codec_driver_remove(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+
+ if (codec->bus->core.ext_ops) {
+ if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach))
+ return -EINVAL;
+ return codec->bus->core.ext_ops->hdev_detach(&codec->core);
+ }
+
+ snd_hda_codec_disconnect_pcms(codec);
+ snd_hda_jack_tbl_disconnect(codec);
+ if (!refcount_dec_and_test(&codec->pcm_ref))
+ wait_event(codec->remove_sleep, !refcount_read(&codec->pcm_ref));
+ snd_power_sync_ref(codec->bus->card);
+
+ if (driver->ops->remove)
+ driver->ops->remove(codec);
+ snd_hda_codec_cleanup_for_unbind(codec);
+ codec->preset = NULL;
+ module_put(dev->driver->owner);
+ return 0;
+}
+
+static void hda_codec_driver_shutdown(struct device *dev)
+{
+ snd_hda_codec_shutdown(dev_to_hda_codec(dev));
+}
+
+int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name,
+ struct module *owner)
+{
+ drv->core.driver.name = name;
+ drv->core.driver.owner = owner;
+ drv->core.driver.bus = &snd_hda_bus_type;
+ drv->core.driver.probe = hda_codec_driver_probe;
+ drv->core.driver.remove = hda_codec_driver_remove;
+ drv->core.driver.shutdown = hda_codec_driver_shutdown;
+ drv->core.driver.pm = pm_ptr(&hda_codec_driver_pm);
+ drv->core.type = HDA_DEV_LEGACY;
+ drv->core.match = hda_codec_match;
+ drv->core.unsol_event = hda_codec_unsol_event;
+ return driver_register(&drv->core.driver);
+}
+EXPORT_SYMBOL_GPL(__hda_codec_driver_register);
+
+void hda_codec_driver_unregister(struct hda_codec_driver *drv)
+{
+ driver_unregister(&drv->core.driver);
+}
+EXPORT_SYMBOL_GPL(hda_codec_driver_unregister);
+
+static inline bool codec_probed(struct hda_codec *codec)
+{
+ return device_attach(hda_codec_dev(codec)) > 0 && codec->preset;
+}
+
+/* try to auto-load codec module */
+static void request_codec_module(struct hda_codec *codec)
+{
+#ifdef MODULE
+ char modalias[32];
+ const char *mod = NULL;
+
+ switch (codec->probe_id) {
+ case HDA_CODEC_ID_GENERIC_HDMI:
+#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI)
+ mod = "snd-hda-codec-hdmi";
+#endif
+ break;
+ case HDA_CODEC_ID_GENERIC:
+#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
+ mod = "snd-hda-codec-generic";
+#endif
+ break;
+ default:
+ snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias));
+ mod = modalias;
+ break;
+ }
+
+ if (mod)
+ request_module(mod);
+#endif /* MODULE */
+}
+
+/* try to auto-load and bind the codec module */
+static void codec_bind_module(struct hda_codec *codec)
+{
+#ifdef MODULE
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return;
+#endif
+}
+
+#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
+/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */
+static bool is_likely_hdmi_codec(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ /*
+ * For ASoC users, if snd_hda_hdmi_codec module is denylisted and any
+ * event causes i915 enumeration to fail, ->wcaps remains uninitialized.
+ */
+ if (!codec->wcaps)
+ return true;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ switch (get_wcaps_type(wcaps)) {
+ case AC_WID_AUD_IN:
+ return false; /* HDMI parser supports only HDMI out */
+ case AC_WID_AUD_OUT:
+ if (!(wcaps & AC_WCAP_DIGITAL))
+ return false;
+ break;
+ }
+ }
+ return true;
+}
+#else
+/* no HDMI codec parser support */
+#define is_likely_hdmi_codec(codec) false
+#endif /* CONFIG_SND_HDA_CODEC_HDMI */
+
+static int codec_bind_generic(struct hda_codec *codec)
+{
+ if (codec->probe_id)
+ return -ENODEV;
+
+ if (is_likely_hdmi_codec(codec)) {
+ codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI;
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return 0;
+ }
+
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ request_codec_module(codec);
+ if (codec_probed(codec))
+ return 0;
+ return -ENODEV;
+}
+
+#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
+#define is_generic_config(codec) \
+ (codec->modelname && !strcmp(codec->modelname, "generic"))
+#else
+#define is_generic_config(codec) 0
+#endif
+
+/**
+ * snd_hda_codec_configure - (Re-)configure the HD-audio codec
+ * @codec: the HDA codec
+ *
+ * Start parsing of the given codec tree and (re-)initialize the whole
+ * codec driver binding.
+ *
+ * Returns 0 if successful or a negative error code.
+ */
+int snd_hda_codec_configure(struct hda_codec *codec)
+{
+ int err;
+
+ if (codec->configured)
+ return 0;
+
+ if (is_generic_config(codec))
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ else
+ codec->probe_id = 0;
+
+ if (!device_is_registered(&codec->core.dev)) {
+ err = snd_hdac_device_register(&codec->core);
+ if (err < 0)
+ return err;
+ }
+
+ if (!codec->preset)
+ codec_bind_module(codec);
+ if (!codec->preset) {
+ err = codec_bind_generic(codec);
+ if (err < 0) {
+ codec_dbg(codec, "Unable to bind the codec\n");
+ return err;
+ }
+ }
+
+ codec->configured = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_configure);
diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c
new file mode 100644
index 000000000000..eb268d442201
--- /dev/null
+++ b/sound/hda/common/codec.c
@@ -0,0 +1,4060 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include <sound/asoundef.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include "hda_local.h"
+#include "hda_beep.h"
+#include "hda_jack.h"
+#include <sound/hda_hwdep.h>
+#include <sound/hda_component.h>
+
+#define codec_in_pm(codec) snd_hdac_is_in_pm(&codec->core)
+#define hda_codec_is_power_on(codec) snd_hdac_is_power_on(&codec->core)
+#define codec_has_epss(codec) \
+ ((codec)->core.power_caps & AC_PWRST_EPSS)
+#define codec_has_clkstop(codec) \
+ ((codec)->core.power_caps & AC_PWRST_CLKSTOP)
+
+/*
+ * Send and receive a verb - passed to exec_verb override for hdac_device
+ */
+static int codec_exec_verb(struct hdac_device *dev, unsigned int cmd,
+ unsigned int flags, unsigned int *res)
+{
+ struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+ struct hda_bus *bus = codec->bus;
+ int err;
+
+ if (cmd == ~0)
+ return -1;
+
+ again:
+ snd_hda_power_up_pm(codec);
+ mutex_lock(&bus->core.cmd_mutex);
+ if (flags & HDA_RW_NO_RESPONSE_FALLBACK)
+ bus->no_response_fallback = 1;
+ err = snd_hdac_bus_exec_verb_unlocked(&bus->core, codec->core.addr,
+ cmd, res);
+ bus->no_response_fallback = 0;
+ mutex_unlock(&bus->core.cmd_mutex);
+ snd_hda_power_down_pm(codec);
+ if (!codec_in_pm(codec) && res && err == -EAGAIN) {
+ if (bus->response_reset) {
+ codec_dbg(codec,
+ "resetting BUS due to fatal communication error\n");
+ snd_hda_bus_reset(bus);
+ }
+ goto again;
+ }
+ /* clear reset-flag when the communication gets recovered */
+ if (!err || codec_in_pm(codec))
+ bus->response_reset = 0;
+ return err;
+}
+
+/**
+ * snd_hda_sequence_write - sequence writes
+ * @codec: the HDA codec
+ * @seq: VERB array to send
+ *
+ * Send the commands sequentially from the given array.
+ * The array must be terminated with NID=0.
+ */
+void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
+{
+ for (; seq->nid; seq++)
+ snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
+}
+EXPORT_SYMBOL_GPL(snd_hda_sequence_write);
+
+/* connection list element */
+struct hda_conn_list {
+ struct list_head list;
+ int len;
+ hda_nid_t nid;
+ hda_nid_t conns[] __counted_by(len);
+};
+
+/* look up the cached results */
+static struct hda_conn_list *
+lookup_conn_list(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_conn_list *p;
+ list_for_each_entry(p, &codec->conn_list, list) {
+ if (p->nid == nid)
+ return p;
+ }
+ return NULL;
+}
+
+static int add_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
+ const hda_nid_t *list)
+{
+ struct hda_conn_list *p;
+
+ p = kmalloc(struct_size(p, conns, len), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ p->len = len;
+ p->nid = nid;
+ memcpy(p->conns, list, len * sizeof(hda_nid_t));
+ list_add(&p->list, &codec->conn_list);
+ return 0;
+}
+
+static void remove_conn_list(struct hda_codec *codec)
+{
+ while (!list_empty(&codec->conn_list)) {
+ struct hda_conn_list *p;
+ p = list_first_entry(&codec->conn_list, typeof(*p), list);
+ list_del(&p->list);
+ kfree(p);
+ }
+}
+
+/* read the connection and add to the cache */
+static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid)
+{
+ hda_nid_t list[32];
+ hda_nid_t *result = list;
+ int len;
+
+ len = snd_hda_get_raw_connections(codec, nid, list, ARRAY_SIZE(list));
+ if (len == -ENOSPC) {
+ len = snd_hda_get_num_raw_conns(codec, nid);
+ result = kmalloc_array(len, sizeof(hda_nid_t), GFP_KERNEL);
+ if (!result)
+ return -ENOMEM;
+ len = snd_hda_get_raw_connections(codec, nid, result, len);
+ }
+ if (len >= 0)
+ len = snd_hda_override_conn_list(codec, nid, len, result);
+ if (result != list)
+ kfree(result);
+ return len;
+}
+
+/**
+ * snd_hda_get_conn_list - get connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @listp: the pointer to store NID list
+ *
+ * Parses the connection list of the given widget and stores the pointer
+ * to the list of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ *
+ * Note that the returned pointer isn't protected against the list
+ * modification. If snd_hda_override_conn_list() might be called
+ * concurrently, protect with a mutex appropriately.
+ */
+int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
+ const hda_nid_t **listp)
+{
+ bool added = false;
+
+ for (;;) {
+ int err;
+ const struct hda_conn_list *p;
+
+ /* if the connection-list is already cached, read it */
+ p = lookup_conn_list(codec, nid);
+ if (p) {
+ if (listp)
+ *listp = p->conns;
+ return p->len;
+ }
+ if (snd_BUG_ON(added))
+ return -EINVAL;
+
+ err = read_and_add_raw_conns(codec, nid);
+ if (err < 0)
+ return err;
+ added = true;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_conn_list);
+
+/**
+ * snd_hda_get_connections - copy connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @conn_list: connection list array; when NULL, checks only the size
+ * @max_conns: max. number of connections to store
+ *
+ * Parses the connection list of the given widget and stores the list
+ * of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ */
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
+ hda_nid_t *conn_list, int max_conns)
+{
+ const hda_nid_t *list;
+ int len = snd_hda_get_conn_list(codec, nid, &list);
+
+ if (len > 0 && conn_list) {
+ if (len > max_conns) {
+ codec_err(codec, "Too many connections %d for NID 0x%x\n",
+ len, nid);
+ return -EINVAL;
+ }
+ memcpy(conn_list, list, len * sizeof(hda_nid_t));
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_connections);
+
+/**
+ * snd_hda_override_conn_list - add/modify the connection-list to cache
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @len: number of connection list entries
+ * @list: the list of connection entries
+ *
+ * Add or modify the given connection-list to the cache. If the corresponding
+ * cache already exists, invalidate it and append a new one.
+ *
+ * Returns zero or a negative error code.
+ */
+int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
+ const hda_nid_t *list)
+{
+ struct hda_conn_list *p;
+
+ p = lookup_conn_list(codec, nid);
+ if (p) {
+ list_del(&p->list);
+ kfree(p);
+ }
+
+ return add_conn_list(codec, nid, len, list);
+}
+EXPORT_SYMBOL_GPL(snd_hda_override_conn_list);
+
+/**
+ * snd_hda_get_conn_index - get the connection index of the given NID
+ * @codec: the HDA codec
+ * @mux: NID containing the list
+ * @nid: NID to select
+ * @recursive: 1 when searching NID recursively, otherwise 0
+ *
+ * Parses the connection list of the widget @mux and checks whether the
+ * widget @nid is present. If it is, return the connection index.
+ * Otherwise it returns -1.
+ */
+int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux,
+ hda_nid_t nid, int recursive)
+{
+ const hda_nid_t *conn;
+ int i, nums;
+
+ nums = snd_hda_get_conn_list(codec, mux, &conn);
+ for (i = 0; i < nums; i++)
+ if (conn[i] == nid)
+ return i;
+ if (!recursive)
+ return -1;
+ if (recursive > 10) {
+ codec_dbg(codec, "too deep connection for 0x%x\n", nid);
+ return -1;
+ }
+ recursive++;
+ for (i = 0; i < nums; i++) {
+ unsigned int type = get_wcaps_type(get_wcaps(codec, conn[i]));
+ if (type == AC_WID_PIN || type == AC_WID_AUD_OUT)
+ continue;
+ if (snd_hda_get_conn_index(codec, conn[i], nid, recursive) >= 0)
+ return i;
+ }
+ return -1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_conn_index);
+
+/**
+ * snd_hda_get_num_devices - get DEVLIST_LEN parameter of the given widget
+ * @codec: the HDA codec
+ * @nid: NID of the pin to parse
+ *
+ * Get the device entry number on the given widget. This is a feature of
+ * DP MST audio. Each pin can have several device entries in it.
+ */
+unsigned int snd_hda_get_num_devices(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int parm;
+
+ if (!codec->dp_mst || !(wcaps & AC_WCAP_DIGITAL) ||
+ get_wcaps_type(wcaps) != AC_WID_PIN)
+ return 0;
+
+ parm = snd_hdac_read_parm_uncached(&codec->core, nid, AC_PAR_DEVLIST_LEN);
+ if (parm == -1)
+ parm = 0;
+ return parm & AC_DEV_LIST_LEN_MASK;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_num_devices);
+
+/**
+ * snd_hda_get_devices - copy device list without cache
+ * @codec: the HDA codec
+ * @nid: NID of the pin to parse
+ * @dev_list: device list array
+ * @max_devices: max. number of devices to store
+ *
+ * Copy the device list. This info is dynamic and so not cached.
+ * Currently called only from hda_proc.c, so not exported.
+ */
+int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid,
+ u8 *dev_list, int max_devices)
+{
+ unsigned int parm;
+ int i, dev_len, devices;
+
+ parm = snd_hda_get_num_devices(codec, nid);
+ if (!parm) /* not multi-stream capable */
+ return 0;
+
+ dev_len = parm + 1;
+ dev_len = dev_len < max_devices ? dev_len : max_devices;
+
+ devices = 0;
+ while (devices < dev_len) {
+ if (snd_hdac_read(&codec->core, nid,
+ AC_VERB_GET_DEVICE_LIST, devices, &parm))
+ break; /* error */
+
+ for (i = 0; i < 8; i++) {
+ dev_list[devices] = (u8)parm;
+ parm >>= 4;
+ devices++;
+ if (devices >= dev_len)
+ break;
+ }
+ }
+ return devices;
+}
+
+/**
+ * snd_hda_get_dev_select - get device entry select on the pin
+ * @codec: the HDA codec
+ * @nid: NID of the pin to get device entry select
+ *
+ * Get the devcie entry select on the pin. Return the device entry
+ * id selected on the pin. Return 0 means the first device entry
+ * is selected or MST is not supported.
+ */
+int snd_hda_get_dev_select(struct hda_codec *codec, hda_nid_t nid)
+{
+ /* not support dp_mst will always return 0, using first dev_entry */
+ if (!codec->dp_mst)
+ return 0;
+
+ return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DEVICE_SEL, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_dev_select);
+
+/**
+ * snd_hda_set_dev_select - set device entry select on the pin
+ * @codec: the HDA codec
+ * @nid: NID of the pin to set device entry select
+ * @dev_id: device entry id to be set
+ *
+ * Set the device entry select on the pin nid.
+ */
+int snd_hda_set_dev_select(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ int ret, num_devices;
+
+ /* not support dp_mst will always return 0, using first dev_entry */
+ if (!codec->dp_mst)
+ return 0;
+
+ /* AC_PAR_DEVLIST_LEN is 0 based. */
+ num_devices = snd_hda_get_num_devices(codec, nid) + 1;
+ /* If Device List Length is 0 (num_device = 1),
+ * the pin is not multi stream capable.
+ * Do nothing in this case.
+ */
+ if (num_devices == 1)
+ return 0;
+
+ /* Behavior of setting index being equal to or greater than
+ * Device List Length is not predictable
+ */
+ if (num_devices <= dev_id)
+ return -EINVAL;
+
+ ret = snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_DEVICE_SEL, dev_id);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_dev_select);
+
+/*
+ * read widget caps for each widget and store in cache
+ */
+static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node)
+{
+ int i;
+ hda_nid_t nid;
+
+ codec->wcaps = kmalloc_array(codec->core.num_nodes, 4, GFP_KERNEL);
+ if (!codec->wcaps)
+ return -ENOMEM;
+ nid = codec->core.start_nid;
+ for (i = 0; i < codec->core.num_nodes; i++, nid++)
+ codec->wcaps[i] = snd_hdac_read_parm_uncached(&codec->core,
+ nid, AC_PAR_AUDIO_WIDGET_CAP);
+ return 0;
+}
+
+/* read all pin default configurations and save codec->init_pins */
+static int read_pin_defaults(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ struct hda_pincfg *pin;
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int wid_type = get_wcaps_type(wcaps);
+ if (wid_type != AC_WID_PIN)
+ continue;
+ pin = snd_array_new(&codec->init_pins);
+ if (!pin)
+ return -ENOMEM;
+ pin->nid = nid;
+ pin->cfg = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_CONFIG_DEFAULT, 0);
+ /*
+ * all device entries are the same widget control so far
+ * fixme: if any codec is different, need fix here
+ */
+ pin->ctrl = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL,
+ 0);
+ }
+ return 0;
+}
+
+/* look up the given pin config list and return the item matching with NID */
+static struct hda_pincfg *look_up_pincfg(struct hda_codec *codec,
+ struct snd_array *array,
+ hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+ int i;
+
+ snd_array_for_each(array, i, pin) {
+ if (pin->nid == nid)
+ return pin;
+ }
+ return NULL;
+}
+
+/* set the current pin config value for the given NID.
+ * the value is cached, and read via snd_hda_codec_get_pincfg()
+ */
+int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
+ hda_nid_t nid, unsigned int cfg)
+{
+ struct hda_pincfg *pin;
+
+ pin = look_up_pincfg(codec, list, nid);
+ if (!pin) {
+ pin = snd_array_new(list);
+ if (!pin)
+ return -ENOMEM;
+ pin->nid = nid;
+ }
+ pin->cfg = cfg;
+ return 0;
+}
+
+/**
+ * snd_hda_codec_set_pincfg - Override a pin default configuration
+ * @codec: the HDA codec
+ * @nid: NID to set the pin config
+ * @cfg: the pin default config value
+ *
+ * Override a pin default configuration value in the cache.
+ * This value can be read by snd_hda_codec_get_pincfg() in a higher
+ * priority than the real hardware value.
+ */
+int snd_hda_codec_set_pincfg(struct hda_codec *codec,
+ hda_nid_t nid, unsigned int cfg)
+{
+ return snd_hda_add_pincfg(codec, &codec->driver_pins, nid, cfg);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_pincfg);
+
+/**
+ * snd_hda_codec_get_pincfg - Obtain a pin-default configuration
+ * @codec: the HDA codec
+ * @nid: NID to get the pin config
+ *
+ * Get the current pin config value of the given pin NID.
+ * If the pincfg value is cached or overridden via sysfs or driver,
+ * returns the cached value.
+ */
+unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+ {
+ unsigned int cfg = 0;
+ mutex_lock(&codec->user_mutex);
+ pin = look_up_pincfg(codec, &codec->user_pins, nid);
+ if (pin)
+ cfg = pin->cfg;
+ mutex_unlock(&codec->user_mutex);
+ if (cfg)
+ return cfg;
+ }
+#endif
+ pin = look_up_pincfg(codec, &codec->driver_pins, nid);
+ if (pin)
+ return pin->cfg;
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (pin)
+ return pin->cfg;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_get_pincfg);
+
+/**
+ * snd_hda_codec_set_pin_target - remember the current pinctl target value
+ * @codec: the HDA codec
+ * @nid: pin NID
+ * @val: assigned pinctl value
+ *
+ * This function stores the given value to a pinctl target value in the
+ * pincfg table. This isn't always as same as the actually written value
+ * but can be referred at any time via snd_hda_codec_get_pin_target().
+ */
+int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int val)
+{
+ struct hda_pincfg *pin;
+
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (!pin)
+ return -EINVAL;
+ pin->target = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_pin_target);
+
+/**
+ * snd_hda_codec_get_pin_target - return the current pinctl target value
+ * @codec: the HDA codec
+ * @nid: pin NID
+ */
+int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_pincfg *pin;
+
+ pin = look_up_pincfg(codec, &codec->init_pins, nid);
+ if (!pin)
+ return 0;
+ return pin->target;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_get_pin_target);
+
+/**
+ * snd_hda_shutup_pins - Shut up all pins
+ * @codec: the HDA codec
+ *
+ * Clear all pin controls to shup up before suspend for avoiding click noise.
+ * The controls aren't cached so that they can be resumed properly.
+ */
+void snd_hda_shutup_pins(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ /* don't shut up pins when unloading the driver; otherwise it breaks
+ * the default pin setup at the next load of the driver
+ */
+ if (codec->bus->shutdown)
+ return;
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ /* use read here for syncing after issuing each verb */
+ snd_hda_codec_read(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+ }
+ codec->pins_shutup = 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_shutup_pins);
+
+/* Restore the pin controls cleared previously via snd_hda_shutup_pins() */
+static void restore_shutup_pins(struct hda_codec *codec)
+{
+ const struct hda_pincfg *pin;
+ int i;
+
+ if (!codec->pins_shutup)
+ return;
+ if (codec->bus->shutdown)
+ return;
+ snd_array_for_each(&codec->init_pins, i, pin) {
+ snd_hda_codec_write(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ pin->ctrl);
+ }
+ codec->pins_shutup = 0;
+}
+
+static void hda_jackpoll_work(struct work_struct *work)
+{
+ struct hda_codec *codec =
+ container_of(work, struct hda_codec, jackpoll_work.work);
+
+ if (!codec->jackpoll_interval)
+ return;
+
+ /* the power-up/down sequence triggers the runtime resume */
+ snd_hda_power_up(codec);
+ /* update jacks manually if polling is required, too */
+ snd_hda_jack_set_dirty_all(codec);
+ snd_hda_jack_poll_all(codec);
+ schedule_delayed_work(&codec->jackpoll_work, codec->jackpoll_interval);
+ snd_hda_power_down(codec);
+}
+
+/* release all pincfg lists */
+static void free_init_pincfgs(struct hda_codec *codec)
+{
+ snd_array_free(&codec->driver_pins);
+#ifdef CONFIG_SND_HDA_RECONFIG
+ snd_array_free(&codec->user_pins);
+#endif
+ snd_array_free(&codec->init_pins);
+}
+
+/*
+ * audio-converter setup caches
+ */
+struct hda_cvt_setup {
+ hda_nid_t nid;
+ u8 stream_tag;
+ u8 channel_id;
+ u16 format_id;
+ unsigned char active; /* cvt is currently used */
+ unsigned char dirty; /* setups should be cleared */
+};
+
+/* get or create a cache entry for the given audio converter NID */
+static struct hda_cvt_setup *
+get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ snd_array_for_each(&codec->cvt_setups, i, p) {
+ if (p->nid == nid)
+ return p;
+ }
+ p = snd_array_new(&codec->cvt_setups);
+ if (p)
+ p->nid = nid;
+ return p;
+}
+
+/*
+ * PCM device
+ */
+void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
+{
+ if (refcount_dec_and_test(&pcm->codec->pcm_ref))
+ wake_up(&pcm->codec->remove_sleep);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);
+
+struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
+ const char *fmt, ...)
+{
+ struct hda_pcm *pcm;
+ va_list args;
+
+ pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return NULL;
+
+ pcm->codec = codec;
+ va_start(args, fmt);
+ pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
+ va_end(args);
+ if (!pcm->name) {
+ kfree(pcm);
+ return NULL;
+ }
+
+ list_add_tail(&pcm->list, &codec->pcm_list_head);
+ refcount_inc(&codec->pcm_ref);
+ return pcm;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
+
+/*
+ * codec destructor
+ */
+void snd_hda_codec_disconnect_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm *pcm;
+
+ list_for_each_entry(pcm, &codec->pcm_list_head, list) {
+ if (pcm->disconnected)
+ continue;
+ if (pcm->pcm)
+ snd_device_disconnect(codec->card, pcm->pcm);
+ snd_hda_codec_pcm_put(pcm);
+ pcm->disconnected = 1;
+ }
+}
+
+static void codec_release_pcms(struct hda_codec *codec)
+{
+ struct hda_pcm *pcm, *n;
+
+ list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
+ list_del(&pcm->list);
+ if (pcm->pcm)
+ snd_device_free(pcm->codec->card, pcm->pcm);
+ clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
+ kfree(pcm->name);
+ kfree(pcm);
+ }
+}
+
+/**
+ * snd_hda_codec_cleanup_for_unbind - Prepare codec for removal
+ * @codec: codec device to cleanup
+ */
+void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
+{
+ if (codec->core.registered) {
+ /* pm_runtime_put() is called in snd_hdac_device_exit() */
+ pm_runtime_get_noresume(hda_codec_dev(codec));
+ pm_runtime_disable(hda_codec_dev(codec));
+ codec->core.registered = 0;
+ }
+
+ snd_hda_codec_disconnect_pcms(codec);
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ if (!codec->in_freeing)
+ snd_hda_ctls_clear(codec);
+ codec_release_pcms(codec);
+ snd_hda_detach_beep_device(codec);
+ snd_hda_jack_tbl_clear(codec);
+ codec->proc_widget_hook = NULL;
+ codec->spec = NULL;
+
+ /* free only driver_pins so that init_pins + user_pins are restored */
+ snd_array_free(&codec->driver_pins);
+ snd_array_free(&codec->cvt_setups);
+ snd_array_free(&codec->spdif_out);
+ snd_array_free(&codec->verbs);
+ codec->follower_dig_outs = NULL;
+ codec->spdif_status_reset = 0;
+ snd_array_free(&codec->mixers);
+ snd_array_free(&codec->nids);
+ remove_conn_list(codec);
+ snd_hdac_regmap_exit(&codec->core);
+ codec->configured = 0;
+ refcount_set(&codec->pcm_ref, 1); /* reset refcount */
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind);
+
+static unsigned int hda_set_power_state(struct hda_codec *codec,
+ unsigned int power_state);
+
+/* enable/disable display power per codec */
+void snd_hda_codec_display_power(struct hda_codec *codec, bool enable)
+{
+ if (codec->display_power_control)
+ snd_hdac_display_power(&codec->bus->core, codec->addr, enable);
+}
+
+/**
+ * snd_hda_codec_register - Finalize codec initialization
+ * @codec: codec device to register
+ *
+ * Also called from hda_bind.c
+ */
+void snd_hda_codec_register(struct hda_codec *codec)
+{
+ if (codec->core.registered)
+ return;
+ if (device_is_registered(hda_codec_dev(codec))) {
+ snd_hda_codec_display_power(codec, true);
+ pm_runtime_enable(hda_codec_dev(codec));
+ /* it was powered up in snd_hda_codec_new(), now all done */
+ snd_hda_power_down(codec);
+ codec->core.registered = 1;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_register);
+
+static int snd_hda_codec_dev_register(struct snd_device *device)
+{
+ snd_hda_codec_register(device->device_data);
+ return 0;
+}
+
+/**
+ * snd_hda_codec_unregister - Unregister specified codec device
+ * @codec: codec device to unregister
+ */
+void snd_hda_codec_unregister(struct hda_codec *codec)
+{
+ codec->in_freeing = 1;
+ /*
+ * snd_hda_codec_device_new() is used by legacy HDA and ASoC driver.
+ * We can't unregister ASoC device since it will be unregistered in
+ * snd_hdac_ext_bus_device_remove().
+ */
+ if (codec->core.type == HDA_DEV_LEGACY)
+ snd_hdac_device_unregister(&codec->core);
+ snd_hda_codec_display_power(codec, false);
+
+ /*
+ * In the case of ASoC HD-audio bus, the device refcount is released in
+ * snd_hdac_ext_bus_device_remove() explicitly.
+ */
+ if (codec->core.type == HDA_DEV_LEGACY)
+ put_device(hda_codec_dev(codec));
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_unregister);
+
+static int snd_hda_codec_dev_free(struct snd_device *device)
+{
+ snd_hda_codec_unregister(device->device_data);
+ return 0;
+}
+
+static void snd_hda_codec_dev_release(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ free_init_pincfgs(codec);
+ snd_hdac_device_exit(&codec->core);
+ snd_hda_sysfs_clear(codec);
+ kfree(codec->modelname);
+ kfree(codec->wcaps);
+ kfree(codec);
+}
+
+#define DEV_NAME_LEN 31
+
+/**
+ * snd_hda_codec_device_init - allocate HDA codec device
+ * @bus: codec's parent bus
+ * @codec_addr: the codec address on the parent bus
+ * @fmt: format string for the device's name
+ *
+ * Returns newly allocated codec device or ERR_PTR() on failure.
+ */
+struct hda_codec *
+snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr,
+ const char *fmt, ...)
+{
+ va_list vargs;
+ char name[DEV_NAME_LEN];
+ struct hda_codec *codec;
+ int err;
+
+ if (snd_BUG_ON(!bus))
+ return ERR_PTR(-EINVAL);
+ if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+ return ERR_PTR(-EINVAL);
+
+ codec = kzalloc(sizeof(*codec), GFP_KERNEL);
+ if (!codec)
+ return ERR_PTR(-ENOMEM);
+
+ va_start(vargs, fmt);
+ vsprintf(name, fmt, vargs);
+ va_end(vargs);
+
+ err = snd_hdac_device_init(&codec->core, &bus->core, name, codec_addr);
+ if (err < 0) {
+ kfree(codec);
+ return ERR_PTR(err);
+ }
+
+ codec->bus = bus;
+ codec->depop_delay = -1;
+ codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
+ codec->core.dev.release = snd_hda_codec_dev_release;
+ codec->core.type = HDA_DEV_LEGACY;
+
+ mutex_init(&codec->spdif_mutex);
+ mutex_init(&codec->control_mutex);
+ snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32);
+ snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
+ snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
+ snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16);
+ snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16);
+ snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
+ INIT_LIST_HEAD(&codec->conn_list);
+ INIT_LIST_HEAD(&codec->pcm_list_head);
+ INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
+ refcount_set(&codec->pcm_ref, 1);
+ init_waitqueue_head(&codec->remove_sleep);
+
+ return codec;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_device_init);
+
+/**
+ * snd_hda_codec_new - create a HDA codec
+ * @bus: the bus to assign
+ * @card: card for this codec
+ * @codec_addr: the codec address
+ * @codecp: the pointer to store the generated codec
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card,
+ unsigned int codec_addr, struct hda_codec **codecp)
+{
+ struct hda_codec *codec;
+ int ret;
+
+ codec = snd_hda_codec_device_init(bus, codec_addr, "hdaudioC%dD%d",
+ card->number, codec_addr);
+ if (IS_ERR(codec))
+ return PTR_ERR(codec);
+ *codecp = codec;
+
+ ret = snd_hda_codec_device_new(bus, card, codec_addr, *codecp, true);
+ if (ret)
+ put_device(hda_codec_dev(*codecp));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_new);
+
+int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card,
+ unsigned int codec_addr, struct hda_codec *codec,
+ bool snddev_managed)
+{
+ char component[31];
+ hda_nid_t fg;
+ int err;
+ static const struct snd_device_ops dev_ops = {
+ .dev_register = snd_hda_codec_dev_register,
+ .dev_free = snd_hda_codec_dev_free,
+ };
+
+ dev_dbg(card->dev, "%s: entry\n", __func__);
+
+ if (snd_BUG_ON(!bus))
+ return -EINVAL;
+ if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+ return -EINVAL;
+
+ codec->core.exec_verb = codec_exec_verb;
+ codec->card = card;
+ codec->addr = codec_addr;
+
+ codec->power_jiffies = jiffies;
+
+ snd_hda_sysfs_init(codec);
+
+ if (codec->bus->modelname) {
+ codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
+ if (!codec->modelname)
+ return -ENOMEM;
+ }
+
+ fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ err = read_widget_caps(codec, fg);
+ if (err < 0)
+ return err;
+ err = read_pin_defaults(codec);
+ if (err < 0)
+ return err;
+
+ /* power-up all before initialization */
+ hda_set_power_state(codec, AC_PWRST_D0);
+ codec->core.dev.power.power_state = PMSG_ON;
+
+ snd_hda_codec_proc_new(codec);
+
+ snd_hda_create_hwdep(codec);
+
+ sprintf(component, "HDA:%08x,%08x,%08x", codec->core.vendor_id,
+ codec->core.subsystem_id, codec->core.revision_id);
+ snd_component_add(card, component);
+
+ if (snddev_managed) {
+ /* ASoC features component management instead */
+ err = snd_device_new(card, SNDRV_DEV_CODEC, codec, &dev_ops);
+ if (err < 0)
+ return err;
+ }
+
+#ifdef CONFIG_PM
+ /* PM runtime needs to be enabled later after binding codec */
+ if (codec->core.dev.power.runtime_auto)
+ pm_runtime_forbid(&codec->core.dev);
+ else
+ /* Keep the usage_count consistent across subsequent probing */
+ pm_runtime_get_noresume(&codec->core.dev);
+#endif
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_device_new);
+
+/**
+ * snd_hda_codec_update_widgets - Refresh widget caps and pin defaults
+ * @codec: the HDA codec
+ *
+ * Forcibly refresh the all widget caps and the init pin configurations of
+ * the given codec.
+ */
+int snd_hda_codec_update_widgets(struct hda_codec *codec)
+{
+ hda_nid_t fg;
+ int err;
+
+ err = snd_hdac_refresh_widgets(&codec->core);
+ if (err < 0)
+ return err;
+
+ /* Assume the function group node does not change,
+ * only the widget nodes may change.
+ */
+ kfree(codec->wcaps);
+ fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ err = read_widget_caps(codec, fg);
+ if (err < 0)
+ return err;
+
+ snd_array_free(&codec->init_pins);
+ err = read_pin_defaults(codec);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_update_widgets);
+
+/* update the stream-id if changed */
+static void update_pcm_stream_id(struct hda_codec *codec,
+ struct hda_cvt_setup *p, hda_nid_t nid,
+ u32 stream_tag, int channel_id)
+{
+ unsigned int oldval, newval;
+
+ if (p->stream_tag != stream_tag || p->channel_id != channel_id) {
+ oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ newval = (stream_tag << 4) | channel_id;
+ if (oldval != newval)
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ newval);
+ p->stream_tag = stream_tag;
+ p->channel_id = channel_id;
+ }
+}
+
+/* update the format-id if changed */
+static void update_pcm_format(struct hda_codec *codec, struct hda_cvt_setup *p,
+ hda_nid_t nid, int format)
+{
+ unsigned int oldval;
+
+ if (p->format_id != format) {
+ oldval = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ if (oldval != format) {
+ msleep(1);
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_STREAM_FORMAT,
+ format);
+ }
+ p->format_id = format;
+ }
+}
+
+/**
+ * snd_hda_codec_setup_stream - set up the codec for streaming
+ * @codec: the CODEC to set up
+ * @nid: the NID to set up
+ * @stream_tag: stream tag to pass, it's between 0x1 and 0xf.
+ * @channel_id: channel id to pass, zero based.
+ * @format: stream format.
+ */
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
+ u32 stream_tag,
+ int channel_id, int format)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ struct hda_codec *c;
+ struct hda_cvt_setup *p;
+ int type;
+ int i;
+
+ if (!nid)
+ return;
+
+ codec_dbg(codec,
+ "hda_codec_setup_stream: NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
+ nid, stream_tag, channel_id, format);
+ p = get_hda_cvt_setup(codec, nid);
+ if (!p)
+ return;
+
+ if (driver->ops->stream_pm)
+ driver->ops->stream_pm(codec, nid, true);
+ if (codec->pcm_format_first)
+ update_pcm_format(codec, p, nid, format);
+ update_pcm_stream_id(codec, p, nid, stream_tag, channel_id);
+ if (!codec->pcm_format_first)
+ update_pcm_format(codec, p, nid, format);
+
+ p->active = 1;
+ p->dirty = 0;
+
+ /* make other inactive cvts with the same stream-tag dirty */
+ type = get_wcaps_type(get_wcaps(codec, nid));
+ list_for_each_codec(c, codec->bus) {
+ snd_array_for_each(&c->cvt_setups, i, p) {
+ if (!p->active && p->stream_tag == stream_tag &&
+ get_wcaps_type(get_wcaps(c, p->nid)) == type)
+ p->dirty = 1;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_setup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q);
+
+/**
+ * __snd_hda_codec_cleanup_stream - clean up the codec for closing
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @do_now: really clean up the stream instead of clearing the active flag
+ */
+void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
+ int do_now)
+{
+ struct hda_cvt_setup *p;
+
+ if (!nid)
+ return;
+
+ if (codec->no_sticky_stream)
+ do_now = 1;
+
+ codec_dbg(codec, "hda_codec_cleanup_stream: NID=0x%x\n", nid);
+ p = get_hda_cvt_setup(codec, nid);
+ if (p) {
+ /* here we just clear the active flag when do_now isn't set;
+ * actual clean-ups will be done later in
+ * purify_inactive_streams() called from snd_hda_codec_prpapre()
+ */
+ if (do_now)
+ really_cleanup_stream(codec, p);
+ else
+ p->active = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(__snd_hda_codec_cleanup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ hda_nid_t nid = q->nid;
+
+ if (q->stream_tag || q->channel_id)
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+ if (q->format_id)
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0
+);
+ memset(q, 0, sizeof(*q));
+ q->nid = nid;
+ if (driver->ops->stream_pm)
+ driver->ops->stream_pm(codec, nid, false);
+}
+
+/* clean up the all conflicting obsolete streams */
+static void purify_inactive_streams(struct hda_codec *codec)
+{
+ struct hda_codec *c;
+ struct hda_cvt_setup *p;
+ int i;
+
+ list_for_each_codec(c, codec->bus) {
+ snd_array_for_each(&c->cvt_setups, i, p) {
+ if (p->dirty)
+ really_cleanup_stream(c, p);
+ }
+ }
+}
+
+/* clean up all streams; called from suspend */
+static void hda_cleanup_all_streams(struct hda_codec *codec)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ snd_array_for_each(&codec->cvt_setups, i, p) {
+ if (p->stream_tag)
+ really_cleanup_stream(codec, p);
+ }
+}
+
+/*
+ * amp access functions
+ */
+
+/**
+ * query_amp_caps - query AMP capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ * @direction: either #HDA_INPUT or #HDA_OUTPUT
+ *
+ * Query AMP capabilities for the given widget and direction.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
+{
+ if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
+ nid = codec->core.afg;
+ return snd_hda_param_read(codec, nid,
+ direction == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+}
+EXPORT_SYMBOL_GPL(query_amp_caps);
+
+/**
+ * snd_hda_check_amp_caps - query AMP capabilities
+ * @codec: the HD-audio codec
+ * @nid: the NID to query
+ * @dir: either #HDA_INPUT or #HDA_OUTPUT
+ * @bits: bit mask to check the result
+ *
+ * Check whether the widget has the given amp capability for the direction.
+ */
+bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int bits)
+{
+ if (!nid)
+ return false;
+ if (get_wcaps(codec, nid) & (1 << (dir + 1)))
+ if (query_amp_caps(codec, nid, dir) & bits)
+ return true;
+ return false;
+}
+EXPORT_SYMBOL_GPL(snd_hda_check_amp_caps);
+
+/**
+ * snd_hda_override_amp_caps - Override the AMP capabilities
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @dir: either #HDA_INPUT or #HDA_OUTPUT
+ * @caps: the capability bits to set
+ *
+ * Override the cached AMP caps bits value by the given one.
+ * This function is useful if the driver needs to adjust the AMP ranges,
+ * e.g. limit to 0dB, etc.
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int caps)
+{
+ unsigned int parm;
+
+ snd_hda_override_wcaps(codec, nid,
+ get_wcaps(codec, nid) | AC_WCAP_AMP_OVRD);
+ parm = dir == HDA_OUTPUT ? AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP;
+ return snd_hdac_override_parm(&codec->core, nid, parm, caps);
+}
+EXPORT_SYMBOL_GPL(snd_hda_override_amp_caps);
+
+static unsigned int encode_amp(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx)
+{
+ unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
+
+ /* enable fake mute if no h/w mute but min=mute */
+ if ((query_amp_caps(codec, nid, dir) &
+ (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) == AC_AMPCAP_MIN_MUTE)
+ cmd |= AC_AMP_FAKE_MUTE;
+ return cmd;
+}
+
+/**
+ * snd_hda_codec_amp_update - update the AMP mono value
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @ch: channel to update (0 or 1)
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP values for the given channel, direction and index.
+ */
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, int mask, int val)
+{
+ unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
+
+ return snd_hdac_regmap_update_raw(&codec->core, cmd, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_update);
+
+/**
+ * snd_hda_codec_amp_stereo - update the AMP stereo values
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP values like snd_hda_codec_amp_update(), but for a
+ * stereo widget with the same mask and value.
+ */
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int direction, int idx, int mask, int val)
+{
+ int ch, ret = 0;
+
+ if (snd_BUG_ON(mask & ~0xff))
+ mask &= 0xff;
+ for (ch = 0; ch < 2; ch++)
+ ret |= snd_hda_codec_amp_update(codec, nid, ch, direction,
+ idx, mask, val);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_stereo);
+
+/**
+ * snd_hda_codec_amp_init - initialize the AMP value
+ * @codec: the HDA codec
+ * @nid: NID to read the AMP value
+ * @ch: channel (left=0 or right=1)
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Works like snd_hda_codec_amp_update() but it writes the value only at
+ * the first access. If the amp was already initialized / updated beforehand,
+ * this does nothing.
+ */
+int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
+ int dir, int idx, int mask, int val)
+{
+ unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
+
+ if (!codec->core.regmap)
+ return -EINVAL;
+ return snd_hdac_regmap_update_raw_once(&codec->core, cmd, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init);
+
+/**
+ * snd_hda_codec_amp_init_stereo - initialize the stereo AMP value
+ * @codec: the HDA codec
+ * @nid: NID to read the AMP value
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Call snd_hda_codec_amp_init() for both stereo channels.
+ */
+int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int mask, int val)
+{
+ int ch, ret = 0;
+
+ if (snd_BUG_ON(mask & ~0xff))
+ mask &= 0xff;
+ for (ch = 0; ch < 2; ch++)
+ ret |= snd_hda_codec_amp_init(codec, nid, ch, dir,
+ idx, mask, val);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init_stereo);
+
+static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int ofs)
+{
+ u32 caps = query_amp_caps(codec, nid, dir);
+ /* get num steps */
+ caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ if (ofs < caps)
+ caps -= ofs;
+ return caps;
+}
+
+/**
+ * snd_hda_mixer_amp_volume_info - Info callback for a standard AMP mixer
+ * @kcontrol: referred ctl element
+ * @uinfo: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 nid = get_amp_nid(kcontrol);
+ u8 chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = get_amp_max_value(codec, nid, dir, ofs);
+ if (!uinfo->value.integer.max) {
+ codec_warn(codec,
+ "num_steps = 0 for NID=0x%x (ctl = %s)\n",
+ nid, kcontrol->id.name);
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_info);
+
+
+static inline unsigned int
+read_amp_value(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, unsigned int ofs)
+{
+ unsigned int val;
+ val = snd_hda_codec_amp_read(codec, nid, ch, dir, idx);
+ val &= HDA_AMP_VOLMASK;
+ if (val >= ofs)
+ val -= ofs;
+ else
+ val = 0;
+ return val;
+}
+
+static inline int
+update_amp_value(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, unsigned int ofs,
+ unsigned int val)
+{
+ unsigned int maxval;
+
+ if (val > 0)
+ val += ofs;
+ /* ofs = 0: raw max value */
+ maxval = get_amp_max_value(codec, nid, dir, 0);
+ if (val > maxval)
+ return -EINVAL;
+ return snd_hda_codec_amp_update(codec, nid, ch, dir, idx,
+ HDA_AMP_VOLMASK, val);
+}
+
+/**
+ * snd_hda_mixer_amp_volume_get - Get callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = read_amp_value(codec, nid, 0, dir, idx, ofs);
+ if (chs & 2)
+ *valp = read_amp_value(codec, nid, 1, dir, idx, ofs);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_get);
+
+/**
+ * snd_hda_mixer_amp_volume_put - Put callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+ int err;
+
+ if (chs & 1) {
+ err = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp);
+ if (err < 0)
+ return err;
+ change |= err;
+ valp++;
+ }
+ if (chs & 2) {
+ err = update_amp_value(codec, nid, 1, dir, idx, ofs, *valp);
+ if (err < 0)
+ return err;
+ change |= err;
+ }
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_put);
+
+/* inquiry the amp caps and convert to TLV */
+static void get_ctl_amp_tlv(struct snd_kcontrol *kcontrol, unsigned int *tlv)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ unsigned int ofs = get_amp_offset(kcontrol);
+ bool min_mute = get_amp_min_mute(kcontrol);
+ u32 caps, val1, val2;
+
+ caps = query_amp_caps(codec, nid, dir);
+ val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ val2 = (val2 + 1) * 25;
+ val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT);
+ val1 += ofs;
+ val1 = ((int)val1) * ((int)val2);
+ if (min_mute || (caps & AC_AMPCAP_MIN_MUTE))
+ val2 |= TLV_DB_SCALE_MUTE;
+ tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int);
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = val1;
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = val2;
+}
+
+/**
+ * snd_hda_mixer_amp_tlv - TLV callback for a standard AMP mixer volume
+ * @kcontrol: ctl element
+ * @op_flag: operation flag
+ * @size: byte size of input TLV
+ * @_tlv: TLV data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv)
+{
+ unsigned int tlv[4];
+
+ if (size < 4 * sizeof(unsigned int))
+ return -ENOMEM;
+ get_ctl_amp_tlv(kcontrol, tlv);
+ if (copy_to_user(_tlv, tlv, sizeof(tlv)))
+ return -EFAULT;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_tlv);
+
+/**
+ * snd_hda_set_vmaster_tlv - Set TLV for a virtual master control
+ * @codec: HD-audio codec
+ * @nid: NID of a reference widget
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @tlv: TLV data to be stored, at least 4 elements
+ *
+ * Set (static) TLV data for a virtual master volume using the AMP caps
+ * obtained from the reference NID.
+ * The volume range is recalculated as if the max volume is 0dB.
+ */
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int *tlv)
+{
+ u32 caps;
+ int nums, step;
+
+ caps = query_amp_caps(codec, nid, dir);
+ nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ step = (step + 1) * 25;
+ tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int);
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = -nums * step;
+ tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = step;
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_vmaster_tlv);
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *
+find_mixer_ctl(struct hda_codec *codec, const char *name, int dev, int idx)
+{
+ struct snd_ctl_elem_id id;
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ id.device = dev;
+ id.index = idx;
+ if (snd_BUG_ON(strlen(name) >= sizeof(id.name)))
+ return NULL;
+ strscpy(id.name, name);
+ return snd_ctl_find_id(codec->card, &id);
+}
+
+/**
+ * snd_hda_find_mixer_ctl - Find a mixer control element with the given name
+ * @codec: HD-audio codec
+ * @name: ctl id name string
+ *
+ * Get the control element with the given id string and IFACE_MIXER.
+ */
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name)
+{
+ return find_mixer_ctl(codec, name, 0, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hda_find_mixer_ctl);
+
+static int find_empty_mixer_ctl_idx(struct hda_codec *codec, const char *name,
+ int start_idx)
+{
+ int i, idx;
+ /* 16 ctlrs should be large enough */
+ for (i = 0, idx = start_idx; i < 16; i++, idx++) {
+ if (!find_mixer_ctl(codec, name, 0, idx))
+ return idx;
+ }
+ return -EBUSY;
+}
+
+/**
+ * snd_hda_ctl_add - Add a control element and assign to the codec
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ *
+ * Add the given control element to an array inside the codec instance.
+ * All control elements belonging to a codec are supposed to be added
+ * by this function so that a proper clean-up works at the free or
+ * reconfiguration time.
+ *
+ * If non-zero @nid is passed, the NID is assigned to the control element.
+ * The assignment is shown in the codec proc file.
+ *
+ * snd_hda_ctl_add() checks the control subdev id field whether
+ * #HDA_SUBDEV_NID_FLAG bit is set. If set (and @nid is zero), the lower
+ * bits value is taken as the NID to assign. The #HDA_NID_ITEM_AMP bit
+ * specifies if kctl->private_value is a HDA amplifier value.
+ */
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+ struct snd_kcontrol *kctl)
+{
+ int err;
+ unsigned short flags = 0;
+ struct hda_nid_item *item;
+
+ if (kctl->id.subdevice & HDA_SUBDEV_AMP_FLAG) {
+ flags |= HDA_NID_ITEM_AMP;
+ if (nid == 0)
+ nid = get_amp_nid_(kctl->private_value);
+ }
+ if ((kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) != 0 && nid == 0)
+ nid = kctl->id.subdevice & 0xffff;
+ if (kctl->id.subdevice & (HDA_SUBDEV_NID_FLAG|HDA_SUBDEV_AMP_FLAG))
+ kctl->id.subdevice = 0;
+ err = snd_ctl_add(codec->card, kctl);
+ if (err < 0)
+ return err;
+ item = snd_array_new(&codec->mixers);
+ if (!item)
+ return -ENOMEM;
+ item->kctl = kctl;
+ item->nid = nid;
+ item->flags = flags;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_ctl_add);
+
+/**
+ * snd_hda_ctls_clear - Clear all controls assigned to the given codec
+ * @codec: HD-audio codec
+ */
+void snd_hda_ctls_clear(struct hda_codec *codec)
+{
+ int i;
+ struct hda_nid_item *items = codec->mixers.list;
+
+ for (i = 0; i < codec->mixers.used; i++)
+ snd_ctl_remove(codec->card, items[i].kctl);
+ snd_array_free(&codec->mixers);
+ snd_array_free(&codec->nids);
+}
+
+/**
+ * snd_hda_lock_devices - pseudo device locking
+ * @bus: the BUS
+ *
+ * toggle card->shutdown to allow/disallow the device access (as a hack)
+ */
+int snd_hda_lock_devices(struct hda_bus *bus)
+{
+ struct snd_card *card = bus->card;
+ struct hda_codec *codec;
+
+ spin_lock(&card->files_lock);
+ if (card->shutdown)
+ goto err_unlock;
+ card->shutdown = 1;
+ if (!list_empty(&card->ctl_files))
+ goto err_clear;
+
+ list_for_each_codec(codec, bus) {
+ struct hda_pcm *cpcm;
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ if (!cpcm->pcm)
+ continue;
+ if (cpcm->pcm->streams[0].substream_opened ||
+ cpcm->pcm->streams[1].substream_opened)
+ goto err_clear;
+ }
+ }
+ spin_unlock(&card->files_lock);
+ return 0;
+
+ err_clear:
+ card->shutdown = 0;
+ err_unlock:
+ spin_unlock(&card->files_lock);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_lock_devices);
+
+/**
+ * snd_hda_unlock_devices - pseudo device unlocking
+ * @bus: the BUS
+ */
+void snd_hda_unlock_devices(struct hda_bus *bus)
+{
+ struct snd_card *card = bus->card;
+
+ spin_lock(&card->files_lock);
+ card->shutdown = 0;
+ spin_unlock(&card->files_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hda_unlock_devices);
+
+/**
+ * snd_hda_codec_reset - Clear all objects assigned to the codec
+ * @codec: HD-audio codec
+ *
+ * This frees the all PCM and control elements assigned to the codec, and
+ * clears the caches and restores the pin default configurations.
+ *
+ * When a device is being used, it returns -EBSY. If successfully freed,
+ * returns zero.
+ */
+int snd_hda_codec_reset(struct hda_codec *codec)
+{
+ struct hda_bus *bus = codec->bus;
+
+ if (snd_hda_lock_devices(bus) < 0)
+ return -EBUSY;
+
+ /* OK, let it free */
+ device_release_driver(hda_codec_dev(codec));
+
+ /* allow device access again */
+ snd_hda_unlock_devices(bus);
+ return 0;
+}
+
+typedef int (*map_follower_func_t)(struct hda_codec *, void *, struct snd_kcontrol *);
+
+/* apply the function to all matching follower ctls in the mixer list */
+static int map_followers(struct hda_codec *codec, const char * const *followers,
+ const char *suffix, map_follower_func_t func, void *data)
+{
+ struct hda_nid_item *items;
+ const char * const *s;
+ int i, err;
+
+ items = codec->mixers.list;
+ for (i = 0; i < codec->mixers.used; i++) {
+ struct snd_kcontrol *sctl = items[i].kctl;
+ if (!sctl || sctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER)
+ continue;
+ for (s = followers; *s; s++) {
+ char tmpname[sizeof(sctl->id.name)];
+ const char *name = *s;
+ if (suffix) {
+ snprintf(tmpname, sizeof(tmpname), "%s %s",
+ name, suffix);
+ name = tmpname;
+ }
+ if (!strcmp(sctl->id.name, name)) {
+ err = func(codec, data, sctl);
+ if (err)
+ return err;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int check_follower_present(struct hda_codec *codec,
+ void *data, struct snd_kcontrol *sctl)
+{
+ return 1;
+}
+
+/* call kctl->put with the given value(s) */
+static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
+{
+ struct snd_ctl_elem_value *ucontrol;
+ ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL);
+ if (!ucontrol)
+ return -ENOMEM;
+ ucontrol->value.integer.value[0] = val;
+ ucontrol->value.integer.value[1] = val;
+ kctl->put(kctl, ucontrol);
+ kfree(ucontrol);
+ return 0;
+}
+
+struct follower_init_arg {
+ struct hda_codec *codec;
+ int step;
+};
+
+/* initialize the follower volume with 0dB via snd_ctl_apply_vmaster_followers() */
+static int init_follower_0dB(struct snd_kcontrol *follower,
+ struct snd_kcontrol *kctl,
+ void *_arg)
+{
+ struct follower_init_arg *arg = _arg;
+ int _tlv[4];
+ const int *tlv = NULL;
+ int step;
+ int val;
+
+ if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ if (kctl->tlv.c != snd_hda_mixer_amp_tlv) {
+ codec_err(arg->codec,
+ "Unexpected TLV callback for follower %s:%d\n",
+ kctl->id.name, kctl->id.index);
+ return 0; /* ignore */
+ }
+ get_ctl_amp_tlv(kctl, _tlv);
+ tlv = _tlv;
+ } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)
+ tlv = kctl->tlv.p;
+
+ if (!tlv || tlv[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE)
+ return 0;
+
+ step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP];
+ step &= ~TLV_DB_SCALE_MUTE;
+ if (!step)
+ return 0;
+ if (arg->step && arg->step != step) {
+ codec_err(arg->codec,
+ "Mismatching dB step for vmaster follower (%d!=%d)\n",
+ arg->step, step);
+ return 0;
+ }
+
+ arg->step = step;
+ val = -tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] / step;
+ if (val > 0) {
+ put_kctl_with_value(follower, val);
+ return val;
+ }
+
+ return 0;
+}
+
+/* unmute the follower via snd_ctl_apply_vmaster_followers() */
+static int init_follower_unmute(struct snd_kcontrol *follower,
+ struct snd_kcontrol *kctl,
+ void *_arg)
+{
+ return put_kctl_with_value(follower, 1);
+}
+
+static int add_follower(struct hda_codec *codec,
+ void *data, struct snd_kcontrol *follower)
+{
+ return snd_ctl_add_follower(data, follower);
+}
+
+/**
+ * __snd_hda_add_vmaster - create a virtual master control and add followers
+ * @codec: HD-audio codec
+ * @name: vmaster control name
+ * @tlv: TLV data (optional)
+ * @followers: follower control names (optional)
+ * @suffix: suffix string to each follower name (optional)
+ * @init_follower_vol: initialize followers to unmute/0dB
+ * @access: kcontrol access rights
+ * @ctl_ret: store the vmaster kcontrol in return
+ *
+ * Create a virtual master control with the given name. The TLV data
+ * must be either NULL or a valid data.
+ *
+ * @followers is a NULL-terminated array of strings, each of which is a
+ * follower control name. All controls with these names are assigned to
+ * the new virtual master control.
+ *
+ * This function returns zero if successful or a negative error code.
+ */
+int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+ unsigned int *tlv, const char * const *followers,
+ const char *suffix, bool init_follower_vol,
+ unsigned int access, struct snd_kcontrol **ctl_ret)
+{
+ struct snd_kcontrol *kctl;
+ int err;
+
+ if (ctl_ret)
+ *ctl_ret = NULL;
+
+ err = map_followers(codec, followers, suffix, check_follower_present, NULL);
+ if (err != 1) {
+ codec_dbg(codec, "No follower found for %s\n", name);
+ return 0;
+ }
+ kctl = snd_ctl_make_virtual_master(name, tlv);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->vd[0].access |= access;
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (err < 0)
+ return err;
+
+ err = map_followers(codec, followers, suffix, add_follower, kctl);
+ if (err < 0)
+ return err;
+
+ /* init with master mute & zero volume */
+ put_kctl_with_value(kctl, 0);
+ if (init_follower_vol) {
+ struct follower_init_arg arg = {
+ .codec = codec,
+ .step = 0,
+ };
+ snd_ctl_apply_vmaster_followers(kctl,
+ tlv ? init_follower_0dB : init_follower_unmute,
+ &arg);
+ }
+
+ if (ctl_ret)
+ *ctl_ret = kctl;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__snd_hda_add_vmaster);
+
+/* meta hook to call each driver's vmaster hook */
+static void vmaster_hook(void *private_data, int enabled)
+{
+ struct hda_vmaster_mute_hook *hook = private_data;
+
+ hook->hook(hook->codec, enabled);
+}
+
+/**
+ * snd_hda_add_vmaster_hook - Add a vmaster hw specific hook
+ * @codec: the HDA codec
+ * @hook: the vmaster hook object
+ *
+ * Add a hw specific hook (like EAPD) with the given vmaster switch kctl.
+ */
+int snd_hda_add_vmaster_hook(struct hda_codec *codec,
+ struct hda_vmaster_mute_hook *hook)
+{
+ if (!hook->hook || !hook->sw_kctl)
+ return 0;
+ hook->codec = codec;
+ snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook);
+
+/**
+ * snd_hda_sync_vmaster_hook - Sync vmaster hook
+ * @hook: the vmaster hook
+ *
+ * Call the hook with the current value for synchronization.
+ * Should be called in init callback.
+ */
+void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook)
+{
+ if (!hook->hook || !hook->codec)
+ return;
+ /* don't call vmaster hook in the destructor since it might have
+ * been already destroyed
+ */
+ if (hook->codec->bus->shutdown)
+ return;
+ snd_ctl_sync_vmaster_hook(hook->sw_kctl);
+}
+EXPORT_SYMBOL_GPL(snd_hda_sync_vmaster_hook);
+
+
+/**
+ * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch
+ * @kcontrol: referred ctl element
+ * @uinfo: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_info);
+
+/**
+ * snd_hda_mixer_amp_switch_get - Get callback for a standard AMP mixer switch
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) &
+ HDA_AMP_MUTE) ? 0 : 1;
+ if (chs & 2)
+ *valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) &
+ HDA_AMP_MUTE) ? 0 : 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get);
+
+/**
+ * snd_hda_mixer_amp_switch_put - Put callback for a standard AMP mixer switch
+ * @kcontrol: ctl element
+ * @ucontrol: pointer to get/store the data
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ if (chs & 1) {
+ if (*valp < 0 || *valp > 1)
+ return -EINVAL;
+ change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+ HDA_AMP_MUTE,
+ *valp ? 0 : HDA_AMP_MUTE);
+ valp++;
+ }
+ if (chs & 2) {
+ if (*valp < 0 || *valp > 1)
+ return -EINVAL;
+ change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+ HDA_AMP_MUTE,
+ *valp ? 0 : HDA_AMP_MUTE);
+ }
+ hda_call_check_power_status(codec, nid);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put);
+
+/*
+ * SPDIF out controls
+ */
+
+static int snd_hda_spdif_mask_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_hda_spdif_cmask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_NONAUDIO |
+ IEC958_AES0_CON_EMPHASIS_5015 |
+ IEC958_AES0_CON_NOT_COPYRIGHT;
+ ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
+ IEC958_AES1_CON_ORIGINAL;
+ return 0;
+}
+
+static int snd_hda_spdif_pmask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PRO_EMPHASIS_5015;
+ return 0;
+}
+
+static int snd_hda_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ ucontrol->value.iec958.status[0] = spdif->status & 0xff;
+ ucontrol->value.iec958.status[1] = (spdif->status >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (spdif->status >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (spdif->status >> 24) & 0xff;
+ mutex_unlock(&codec->spdif_mutex);
+
+ return 0;
+}
+
+/* convert from SPDIF status bits to HDA SPDIF bits
+ * bit 0 (DigEn) is always set zero (to be filled later)
+ */
+static unsigned short convert_from_spdif_status(unsigned int sbits)
+{
+ unsigned short val = 0;
+
+ if (sbits & IEC958_AES0_PROFESSIONAL)
+ val |= AC_DIG1_PROFESSIONAL;
+ if (sbits & IEC958_AES0_NONAUDIO)
+ val |= AC_DIG1_NONAUDIO;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if ((sbits & IEC958_AES0_PRO_EMPHASIS) ==
+ IEC958_AES0_PRO_EMPHASIS_5015)
+ val |= AC_DIG1_EMPHASIS;
+ } else {
+ if ((sbits & IEC958_AES0_CON_EMPHASIS) ==
+ IEC958_AES0_CON_EMPHASIS_5015)
+ val |= AC_DIG1_EMPHASIS;
+ if (!(sbits & IEC958_AES0_CON_NOT_COPYRIGHT))
+ val |= AC_DIG1_COPYRIGHT;
+ if (sbits & (IEC958_AES1_CON_ORIGINAL << 8))
+ val |= AC_DIG1_LEVEL;
+ val |= sbits & (IEC958_AES1_CON_CATEGORY << 8);
+ }
+ return val;
+}
+
+/* convert to SPDIF status bits from HDA SPDIF bits
+ */
+static unsigned int convert_to_spdif_status(unsigned short val)
+{
+ unsigned int sbits = 0;
+
+ if (val & AC_DIG1_NONAUDIO)
+ sbits |= IEC958_AES0_NONAUDIO;
+ if (val & AC_DIG1_PROFESSIONAL)
+ sbits |= IEC958_AES0_PROFESSIONAL;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if (val & AC_DIG1_EMPHASIS)
+ sbits |= IEC958_AES0_PRO_EMPHASIS_5015;
+ } else {
+ if (val & AC_DIG1_EMPHASIS)
+ sbits |= IEC958_AES0_CON_EMPHASIS_5015;
+ if (!(val & AC_DIG1_COPYRIGHT))
+ sbits |= IEC958_AES0_CON_NOT_COPYRIGHT;
+ if (val & AC_DIG1_LEVEL)
+ sbits |= (IEC958_AES1_CON_ORIGINAL << 8);
+ sbits |= val & (0x7f << 8);
+ }
+ return sbits;
+}
+
+/* set digital convert verbs both for the given NID and its followers */
+static void set_dig_out(struct hda_codec *codec, hda_nid_t nid,
+ int mask, int val)
+{
+ const hda_nid_t *d;
+
+ snd_hdac_regmap_update(&codec->core, nid, AC_VERB_SET_DIGI_CONVERT_1,
+ mask, val);
+ d = codec->follower_dig_outs;
+ if (!d)
+ return;
+ for (; *d; d++)
+ snd_hdac_regmap_update(&codec->core, *d,
+ AC_VERB_SET_DIGI_CONVERT_1, mask, val);
+}
+
+static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid,
+ int dig1, int dig2)
+{
+ unsigned int mask = 0;
+ unsigned int val = 0;
+
+ if (dig1 != -1) {
+ mask |= 0xff;
+ val = dig1;
+ }
+ if (dig2 != -1) {
+ mask |= 0xff00;
+ val |= dig2 << 8;
+ }
+ set_dig_out(codec, nid, mask, val);
+}
+
+static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+ hda_nid_t nid;
+ unsigned short val;
+ int change;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ nid = spdif->nid;
+ spdif->status = ucontrol->value.iec958.status[0] |
+ ((unsigned int)ucontrol->value.iec958.status[1] << 8) |
+ ((unsigned int)ucontrol->value.iec958.status[2] << 16) |
+ ((unsigned int)ucontrol->value.iec958.status[3] << 24);
+ val = convert_from_spdif_status(spdif->status);
+ val |= spdif->ctls & 1;
+ change = spdif->ctls != val;
+ spdif->ctls = val;
+ if (change && nid != (u16)-1)
+ set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff);
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+#define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info
+
+static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ ucontrol->value.integer.value[0] = spdif->ctls & AC_DIG1_ENABLE;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+
+static inline void set_spdif_ctls(struct hda_codec *codec, hda_nid_t nid,
+ int dig1, int dig2)
+{
+ set_dig_out_convert(codec, nid, dig1, dig2);
+ /* unmute amp switch (if any) */
+ if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
+ (dig1 & AC_DIG1_ENABLE))
+ snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+ HDA_AMP_MUTE, 0);
+}
+
+static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ int idx = kcontrol->private_value;
+ struct hda_spdif_out *spdif;
+ hda_nid_t nid;
+ unsigned short val;
+ int change;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return -EINVAL;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ nid = spdif->nid;
+ val = spdif->ctls & ~AC_DIG1_ENABLE;
+ if (ucontrol->value.integer.value[0])
+ val |= AC_DIG1_ENABLE;
+ change = spdif->ctls != val;
+ spdif->ctls = val;
+ if (change && nid != (u16)-1)
+ set_spdif_ctls(codec, nid, val & 0xff, -1);
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+static const struct snd_kcontrol_new dig_mixes[] = {
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_cmask_get,
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_pmask_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_default_get,
+ .put = snd_hda_spdif_default_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+ .info = snd_hda_spdif_out_switch_info,
+ .get = snd_hda_spdif_out_switch_get,
+ .put = snd_hda_spdif_out_switch_put,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_dig_out_ctls - create Output SPDIF-related controls
+ * @codec: the HDA codec
+ * @associated_nid: NID that new ctls associated with
+ * @cvt_nid: converter NID
+ * @type: HDA_PCM_TYPE_*
+ * Creates controls related with the digital output.
+ * Called from each codec driver supporting the digital out.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
+ hda_nid_t associated_nid,
+ hda_nid_t cvt_nid,
+ int type)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+ const struct snd_kcontrol_new *dig_mix;
+ int idx = 0;
+ int val = 0;
+ const int spdif_index = 16;
+ struct hda_spdif_out *spdif;
+ struct hda_bus *bus = codec->bus;
+
+ if (bus->primary_dig_out_type == HDA_PCM_TYPE_HDMI &&
+ type == HDA_PCM_TYPE_SPDIF) {
+ idx = spdif_index;
+ } else if (bus->primary_dig_out_type == HDA_PCM_TYPE_SPDIF &&
+ type == HDA_PCM_TYPE_HDMI) {
+ /* suppose a single SPDIF device */
+ for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+ struct snd_ctl_elem_id id;
+
+ kctl = find_mixer_ctl(codec, dig_mix->name, 0, 0);
+ if (!kctl)
+ break;
+ id = kctl->id;
+ id.index = spdif_index;
+ err = snd_ctl_rename_id(codec->card, &kctl->id, &id);
+ if (err < 0)
+ return err;
+ }
+ bus->primary_dig_out_type = HDA_PCM_TYPE_HDMI;
+ }
+ if (!bus->primary_dig_out_type)
+ bus->primary_dig_out_type = type;
+
+ idx = find_empty_mixer_ctl_idx(codec, "IEC958 Playback Switch", idx);
+ if (idx < 0) {
+ codec_err(codec, "too many IEC958 outputs\n");
+ return -EBUSY;
+ }
+ spdif = snd_array_new(&codec->spdif_out);
+ if (!spdif)
+ return -ENOMEM;
+ for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->id.index = idx;
+ kctl->private_value = codec->spdif_out.used - 1;
+ err = snd_hda_ctl_add(codec, associated_nid, kctl);
+ if (err < 0)
+ return err;
+ }
+ spdif->nid = cvt_nid;
+ snd_hdac_regmap_read(&codec->core, cvt_nid,
+ AC_VERB_GET_DIGI_CONVERT_1, &val);
+ spdif->ctls = val;
+ spdif->status = convert_to_spdif_status(spdif->ctls);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_dig_out_ctls);
+
+/**
+ * snd_hda_spdif_out_of_nid - get the hda_spdif_out entry from the given NID
+ * @codec: the HDA codec
+ * @nid: widget NID
+ *
+ * call within spdif_mutex lock
+ */
+struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec,
+ hda_nid_t nid)
+{
+ struct hda_spdif_out *spdif;
+ int i;
+
+ snd_array_for_each(&codec->spdif_out, i, spdif) {
+ if (spdif->nid == nid)
+ return spdif;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_out_of_nid);
+
+/**
+ * snd_hda_spdif_ctls_unassign - Unassign the given SPDIF ctl
+ * @codec: the HDA codec
+ * @idx: the SPDIF ctl index
+ *
+ * Unassign the widget from the given SPDIF control.
+ */
+void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx)
+{
+ struct hda_spdif_out *spdif;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ spdif->nid = (u16)-1;
+ mutex_unlock(&codec->spdif_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_unassign);
+
+/**
+ * snd_hda_spdif_ctls_assign - Assign the SPDIF controls to the given NID
+ * @codec: the HDA codec
+ * @idx: the SPDIF ctl idx
+ * @nid: widget NID
+ *
+ * Assign the widget to the SPDIF control with the given index.
+ */
+void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid)
+{
+ struct hda_spdif_out *spdif;
+ unsigned short val;
+
+ if (WARN_ON(codec->spdif_out.used <= idx))
+ return;
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_array_elem(&codec->spdif_out, idx);
+ if (spdif->nid != nid) {
+ spdif->nid = nid;
+ val = spdif->ctls;
+ set_spdif_ctls(codec, nid, val & 0xff, (val >> 8) & 0xff);
+ }
+ mutex_unlock(&codec->spdif_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign);
+
+/*
+ * SPDIF sharing with analog output
+ */
+static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = mout->share_spdif;
+ return 0;
+}
+
+static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ mout->share_spdif = !!ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new spdif_share_sw = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Default PCM Playback Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = spdif_share_sw_get,
+ .put = spdif_share_sw_put,
+};
+
+/**
+ * snd_hda_create_spdif_share_sw - create Default PCM switch
+ * @codec: the HDA codec
+ * @mout: multi-out instance
+ */
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ struct snd_kcontrol *kctl;
+
+ if (!mout->dig_out_nid)
+ return 0;
+
+ kctl = snd_ctl_new1(&spdif_share_sw, mout);
+ if (!kctl)
+ return -ENOMEM;
+ /* ATTENTION: here mout is passed as private_data, instead of codec */
+ return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw);
+
+/*
+ * SPDIF input
+ */
+
+#define snd_hda_spdif_in_switch_info snd_hda_spdif_out_switch_info
+
+static int snd_hda_spdif_in_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = codec->spdif_in_enable;
+ return 0;
+}
+
+static int snd_hda_spdif_in_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val = !!ucontrol->value.integer.value[0];
+ int change;
+
+ mutex_lock(&codec->spdif_mutex);
+ change = codec->spdif_in_enable != val;
+ if (change) {
+ codec->spdif_in_enable = val;
+ snd_hdac_regmap_write(&codec->core, nid,
+ AC_VERB_SET_DIGI_CONVERT_1, val);
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ return change;
+}
+
+static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val;
+ unsigned int sbits;
+
+ snd_hdac_regmap_read(&codec->core, nid,
+ AC_VERB_GET_DIGI_CONVERT_1, &val);
+ sbits = convert_to_spdif_status(val);
+ ucontrol->value.iec958.status[0] = sbits;
+ ucontrol->value.iec958.status[1] = sbits >> 8;
+ ucontrol->value.iec958.status[2] = sbits >> 16;
+ ucontrol->value.iec958.status[3] = sbits >> 24;
+ return 0;
+}
+
+static const struct snd_kcontrol_new dig_in_ctls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH),
+ .info = snd_hda_spdif_in_switch_info,
+ .get = snd_hda_spdif_in_switch_get,
+ .put = snd_hda_spdif_in_switch_put,
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_in_status_get,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_in_ctls - create Input SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio in widget NID
+ *
+ * Creates controls related with the SPDIF input.
+ * Called from each codec driver supporting the SPDIF in.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+ const struct snd_kcontrol_new *dig_mix;
+ int idx;
+
+ idx = find_empty_mixer_ctl_idx(codec, "IEC958 Capture Switch", 0);
+ if (idx < 0) {
+ codec_err(codec, "too many IEC958 inputs\n");
+ return -EBUSY;
+ }
+ for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ if (!kctl)
+ return -ENOMEM;
+ kctl->private_value = nid;
+ err = snd_hda_ctl_add(codec, nid, kctl);
+ if (err < 0)
+ return err;
+ }
+ codec->spdif_in_enable =
+ snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DIGI_CONVERT_1, 0) &
+ AC_DIG1_ENABLE;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_create_spdif_in_ctls);
+
+/**
+ * snd_hda_codec_set_power_to_all - Set the power state to all widgets
+ * @codec: the HDA codec
+ * @fg: function group (not used now)
+ * @power_state: the power state to set (AC_PWRST_*)
+ *
+ * Set the given power state to all widgets that have the power control.
+ * If the codec has power_filter set, it evaluates the power state and
+ * filter out if it's unchanged as D3.
+ */
+void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
+ unsigned int power_state)
+{
+ hda_nid_t nid;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int state = power_state;
+ if (!(wcaps & AC_WCAP_POWER))
+ continue;
+ if (codec->power_filter) {
+ state = codec->power_filter(codec, nid, power_state);
+ if (state != power_state && power_state == AC_PWRST_D3)
+ continue;
+ }
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE,
+ state);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_power_to_all);
+
+/**
+ * snd_hda_codec_eapd_power_filter - A power filter callback for EAPD
+ * @codec: the HDA codec
+ * @nid: widget NID
+ * @power_state: power state to evalue
+ *
+ * Don't power down the widget if it controls eapd and EAPD_BTLENABLE is set.
+ * This can be used a codec power_filter callback.
+ */
+unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state)
+{
+ if (nid == codec->core.afg || nid == codec->core.mfg)
+ return power_state;
+ if (power_state == AC_PWRST_D3 &&
+ get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_PIN &&
+ (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) {
+ int eapd = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_EAPD_BTLENABLE, 0);
+ if (eapd & 0x02)
+ return AC_PWRST_D0;
+ }
+ return power_state;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_eapd_power_filter);
+
+/*
+ * set power state of the codec, and return the power state
+ */
+static unsigned int hda_set_power_state(struct hda_codec *codec,
+ unsigned int power_state)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ hda_nid_t fg = codec->core.afg ? codec->core.afg : codec->core.mfg;
+ int count;
+ unsigned int state;
+ int flags = 0;
+
+ /* this delay seems necessary to avoid click noise at power-down */
+ if (power_state == AC_PWRST_D3) {
+ if (codec->depop_delay < 0)
+ msleep(codec_has_epss(codec) ? 10 : 100);
+ else if (codec->depop_delay > 0)
+ msleep(codec->depop_delay);
+ flags = HDA_RW_NO_RESPONSE_FALLBACK;
+ }
+
+ /* repeat power states setting at most 10 times*/
+ for (count = 0; count < 10; count++) {
+ /* might be called before binding to driver, too */
+ if (driver && driver->ops && driver->ops->set_power_state)
+ driver->ops->set_power_state(codec, fg, power_state);
+ else {
+ state = power_state;
+ if (codec->power_filter)
+ state = codec->power_filter(codec, fg, state);
+ if (state == power_state || power_state != AC_PWRST_D3)
+ snd_hda_codec_read(codec, fg, flags,
+ AC_VERB_SET_POWER_STATE,
+ state);
+ snd_hda_codec_set_power_to_all(codec, fg, power_state);
+ }
+ state = snd_hda_sync_power_state(codec, fg, power_state);
+ if (!(state & AC_PWRST_ERROR))
+ break;
+ }
+
+ return state;
+}
+
+/* sync power states of all widgets;
+ * this is called at the end of codec parsing
+ */
+static void sync_power_up_states(struct hda_codec *codec)
+{
+ hda_nid_t nid;
+
+ /* don't care if no filter is used */
+ if (!codec->power_filter)
+ return;
+
+ for_each_hda_codec_node(nid, codec) {
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int target;
+ if (!(wcaps & AC_WCAP_POWER))
+ continue;
+ target = codec->power_filter(codec, nid, AC_PWRST_D0);
+ if (target == AC_PWRST_D0)
+ continue;
+ if (!snd_hda_check_power_state(codec, nid, target))
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_POWER_STATE, target);
+ }
+}
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+/* execute additional init verbs */
+static void hda_exec_init_verbs(struct hda_codec *codec)
+{
+ if (codec->init_verbs.list)
+ snd_hda_sequence_write(codec, codec->init_verbs.list);
+}
+#else
+static inline void hda_exec_init_verbs(struct hda_codec *codec) {}
+#endif
+
+/* update the power on/off account with the current jiffies */
+static void update_power_acct(struct hda_codec *codec, bool on)
+{
+ unsigned long delta = jiffies - codec->power_jiffies;
+
+ if (on)
+ codec->power_on_acct += delta;
+ else
+ codec->power_off_acct += delta;
+ codec->power_jiffies += delta;
+}
+
+void snd_hda_update_power_acct(struct hda_codec *codec)
+{
+ update_power_acct(codec, hda_codec_is_power_on(codec));
+}
+
+/*
+ * call suspend and power-down; used both from PM and power-save
+ * this function returns the power state in the end
+ */
+static unsigned int hda_call_codec_suspend(struct hda_codec *codec)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ unsigned int state;
+
+ snd_hdac_enter_pm(&codec->core);
+ if (driver->ops->suspend)
+ driver->ops->suspend(codec);
+ if (!codec->no_stream_clean_at_suspend)
+ hda_cleanup_all_streams(codec);
+ state = hda_set_power_state(codec, AC_PWRST_D3);
+ update_power_acct(codec, true);
+ snd_hdac_leave_pm(&codec->core);
+ return state;
+}
+
+/*
+ * kick up codec; used both from PM and power-save
+ */
+static void hda_call_codec_resume(struct hda_codec *codec)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+
+ snd_hdac_enter_pm(&codec->core);
+ if (codec->core.regmap)
+ regcache_mark_dirty(codec->core.regmap);
+
+ codec->power_jiffies = jiffies;
+
+ hda_set_power_state(codec, AC_PWRST_D0);
+ restore_shutup_pins(codec);
+ hda_exec_init_verbs(codec);
+ snd_hda_jack_set_dirty_all(codec);
+ if (driver->ops->resume)
+ driver->ops->resume(codec);
+ else {
+ snd_hda_codec_init(codec);
+ snd_hda_regmap_sync(codec);
+ }
+
+ snd_hda_jack_report_sync(codec);
+ codec->core.dev.power.power_state = PMSG_ON;
+ snd_hdac_leave_pm(&codec->core);
+ if (codec->jackpoll_interval)
+ schedule_delayed_work(&codec->jackpoll_work,
+ codec->jackpoll_interval);
+}
+
+static int hda_codec_runtime_suspend(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+ unsigned int state;
+
+ /* Nothing to do if card registration fails and the component driver never probes */
+ if (!codec->card)
+ return 0;
+
+ state = hda_call_codec_suspend(codec);
+ if (codec->link_down_at_suspend ||
+ (codec_has_clkstop(codec) && codec_has_epss(codec) &&
+ (state & AC_PWRST_CLK_STOP_OK)))
+ snd_hdac_codec_link_down(&codec->core);
+ snd_hda_codec_display_power(codec, false);
+
+ return 0;
+}
+
+static int hda_codec_runtime_resume(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ /* Nothing to do if card registration fails and the component driver never probes */
+ if (!codec->card)
+ return 0;
+
+ snd_hda_codec_display_power(codec, true);
+ snd_hdac_codec_link_up(&codec->core);
+ hda_call_codec_resume(codec);
+ pm_runtime_mark_last_busy(dev);
+ return 0;
+}
+
+static int hda_codec_runtime_idle(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ if (codec->jackpoll_interval && !codec->bus->jackpoll_in_suspend)
+ return -EBUSY;
+ return 0;
+}
+
+static int hda_codec_pm_prepare(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ dev->power.power_state = PMSG_SUSPEND;
+ return pm_runtime_suspended(dev);
+}
+
+static void hda_codec_pm_complete(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ /* If no other pm-functions are called between prepare() and complete() */
+ if (dev->power.power_state.event == PM_EVENT_SUSPEND)
+ dev->power.power_state = PMSG_RESUME;
+
+ if (pm_runtime_suspended(dev) && (codec->jackpoll_interval ||
+ hda_codec_need_resume(codec) || codec->forced_resume))
+ pm_request_resume(dev);
+}
+
+static int hda_codec_pm_suspend(struct device *dev)
+{
+ dev->power.power_state = PMSG_SUSPEND;
+ return pm_runtime_force_suspend(dev);
+}
+
+static int hda_codec_pm_resume(struct device *dev)
+{
+ dev->power.power_state = PMSG_RESUME;
+ return pm_runtime_force_resume(dev);
+}
+
+static int hda_codec_pm_freeze(struct device *dev)
+{
+ struct hda_codec *codec = dev_to_hda_codec(dev);
+
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ dev->power.power_state = PMSG_FREEZE;
+ return pm_runtime_force_suspend(dev);
+}
+
+static int hda_codec_pm_thaw(struct device *dev)
+{
+ dev->power.power_state = PMSG_THAW;
+ return pm_runtime_force_resume(dev);
+}
+
+static int hda_codec_pm_restore(struct device *dev)
+{
+ dev->power.power_state = PMSG_RESTORE;
+ return pm_runtime_force_resume(dev);
+}
+
+/* referred in hda_bind.c */
+const struct dev_pm_ops hda_codec_driver_pm = {
+ .prepare = pm_sleep_ptr(hda_codec_pm_prepare),
+ .complete = pm_sleep_ptr(hda_codec_pm_complete),
+ .suspend = pm_sleep_ptr(hda_codec_pm_suspend),
+ .resume = pm_sleep_ptr(hda_codec_pm_resume),
+ .freeze = pm_sleep_ptr(hda_codec_pm_freeze),
+ .thaw = pm_sleep_ptr(hda_codec_pm_thaw),
+ .poweroff = pm_sleep_ptr(hda_codec_pm_suspend),
+ .restore = pm_sleep_ptr(hda_codec_pm_restore),
+ RUNTIME_PM_OPS(hda_codec_runtime_suspend, hda_codec_runtime_resume,
+ hda_codec_runtime_idle)
+};
+
+/* suspend the codec at shutdown; called from driver's shutdown callback */
+void snd_hda_codec_shutdown(struct hda_codec *codec)
+{
+ struct hda_pcm *cpcm;
+
+ /* Skip the shutdown if codec is not registered */
+ if (!codec->core.registered)
+ return;
+
+ codec->jackpoll_interval = 0; /* don't poll any longer */
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list)
+ snd_pcm_suspend_all(cpcm->pcm);
+
+ pm_runtime_force_suspend(hda_codec_dev(codec));
+ pm_runtime_disable(hda_codec_dev(codec));
+}
+
+/*
+ * add standard channel maps if not specified
+ */
+static int add_std_chmaps(struct hda_codec *codec)
+{
+ struct hda_pcm *pcm;
+ int str, err;
+
+ list_for_each_entry(pcm, &codec->pcm_list_head, list) {
+ for (str = 0; str < 2; str++) {
+ struct hda_pcm_stream *hinfo = &pcm->stream[str];
+ struct snd_pcm_chmap *chmap;
+ const struct snd_pcm_chmap_elem *elem;
+
+ if (!pcm->pcm || pcm->own_chmap || !hinfo->substreams)
+ continue;
+ elem = hinfo->chmap ? hinfo->chmap : snd_pcm_std_chmaps;
+ err = snd_pcm_add_chmap_ctls(pcm->pcm, str, elem,
+ hinfo->channels_max,
+ 0, &chmap);
+ if (err < 0)
+ return err;
+ chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+ }
+ }
+ return 0;
+}
+
+/* default channel maps for 2.1 speakers;
+ * since HD-audio supports only stereo, odd number channels are omitted
+ */
+const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[] = {
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_LFE, SNDRV_CHMAP_LFE } },
+ { }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_2_1_chmaps);
+
+int snd_hda_codec_build_controls(struct hda_codec *codec)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ int err;
+
+ hda_exec_init_verbs(codec);
+ /* continue to initialize... */
+ err = snd_hda_codec_init(codec);
+ if (err < 0)
+ return err;
+
+ if (driver->ops->build_controls) {
+ err = driver->ops->build_controls(codec);
+ if (err < 0)
+ return err;
+ }
+
+ /* we create chmaps here instead of build_pcms */
+ err = add_std_chmaps(codec);
+ if (err < 0)
+ return err;
+
+ snd_hda_jack_report_sync(codec); /* call at the last init point */
+ if (codec->jackpoll_interval)
+ schedule_delayed_work(&codec->jackpoll_work,
+ codec->jackpoll_interval);
+
+ sync_power_up_states(codec);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_build_controls);
+
+/*
+ * PCM stuff
+ */
+static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ return 0;
+}
+
+static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ return 0;
+}
+
+static int set_pcm_default_values(struct hda_codec *codec,
+ struct hda_pcm_stream *info)
+{
+ int err;
+
+ /* query support PCM information from the given NID */
+ if (info->nid && (!info->rates || !info->formats)) {
+ err = snd_hda_query_supported_pcm(codec, info->nid,
+ info->rates ? NULL : &info->rates,
+ info->formats ? NULL : &info->formats,
+ info->subformats ? NULL : &info->subformats,
+ info->maxbps ? NULL : &info->maxbps);
+ if (err < 0)
+ return err;
+ }
+ if (info->ops.open == NULL)
+ info->ops.open = hda_pcm_default_open_close;
+ if (info->ops.close == NULL)
+ info->ops.close = hda_pcm_default_open_close;
+ if (info->ops.prepare == NULL) {
+ if (snd_BUG_ON(!info->nid))
+ return -EINVAL;
+ info->ops.prepare = hda_pcm_default_prepare;
+ }
+ if (info->ops.cleanup == NULL) {
+ if (snd_BUG_ON(!info->nid))
+ return -EINVAL;
+ info->ops.cleanup = hda_pcm_default_cleanup;
+ }
+ return 0;
+}
+
+/*
+ * codec prepare/cleanup entries
+ */
+/**
+ * snd_hda_codec_prepare - Prepare a stream
+ * @codec: the HDA codec
+ * @hinfo: PCM information
+ * @stream: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ *
+ * Calls the prepare callback set by the codec with the given arguments.
+ * Clean up the inactive streams when successful.
+ */
+int snd_hda_codec_prepare(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ unsigned int stream,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ mutex_lock(&codec->bus->prepare_mutex);
+ if (hinfo->ops.prepare)
+ ret = hinfo->ops.prepare(hinfo, codec, stream, format,
+ substream);
+ else
+ ret = -ENODEV;
+ if (ret >= 0)
+ purify_inactive_streams(codec);
+ mutex_unlock(&codec->bus->prepare_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_prepare);
+
+/**
+ * snd_hda_codec_cleanup - Clean up stream resources
+ * @codec: the HDA codec
+ * @hinfo: PCM information
+ * @substream: PCM substream
+ *
+ * Calls the cleanup callback set by the codec with the given arguments.
+ */
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->bus->prepare_mutex);
+ if (hinfo->ops.cleanup)
+ hinfo->ops.cleanup(hinfo, codec, substream);
+ mutex_unlock(&codec->bus->prepare_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup);
+
+/* global */
+const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
+ "Audio", "SPDIF", "HDMI", "Modem"
+};
+
+/*
+ * get the empty PCM device number to assign
+ */
+static int get_empty_pcm_device(struct hda_bus *bus, unsigned int type)
+{
+ /* audio device indices; not linear to keep compatibility */
+ /* assigned to static slots up to dev#10; if more needed, assign
+ * the later slot dynamically (when CONFIG_SND_DYNAMIC_MINORS=y)
+ */
+ static const int audio_idx[HDA_PCM_NTYPES][5] = {
+ [HDA_PCM_TYPE_AUDIO] = { 0, 2, 4, 5, -1 },
+ [HDA_PCM_TYPE_SPDIF] = { 1, -1 },
+ [HDA_PCM_TYPE_HDMI] = { 3, 7, 8, 9, -1 },
+ [HDA_PCM_TYPE_MODEM] = { 6, -1 },
+ };
+ int i;
+
+ if (type >= HDA_PCM_NTYPES) {
+ dev_err(bus->card->dev, "Invalid PCM type %d\n", type);
+ return -EINVAL;
+ }
+
+ for (i = 0; audio_idx[type][i] >= 0; i++) {
+#ifndef CONFIG_SND_DYNAMIC_MINORS
+ if (audio_idx[type][i] >= 8)
+ break;
+#endif
+ if (!test_and_set_bit(audio_idx[type][i], bus->pcm_dev_bits))
+ return audio_idx[type][i];
+ }
+
+#ifdef CONFIG_SND_DYNAMIC_MINORS
+ /* non-fixed slots starting from 10 */
+ for (i = 10; i < 32; i++) {
+ if (!test_and_set_bit(i, bus->pcm_dev_bits))
+ return i;
+ }
+#endif
+
+ dev_warn(bus->card->dev, "Too many %s devices\n",
+ snd_hda_pcm_type_name[type]);
+#ifndef CONFIG_SND_DYNAMIC_MINORS
+ dev_warn(bus->card->dev,
+ "Consider building the kernel with CONFIG_SND_DYNAMIC_MINORS=y\n");
+#endif
+ return -EAGAIN;
+}
+
+/* call build_pcms ops of the given codec and set up the default parameters */
+int snd_hda_codec_parse_pcms(struct hda_codec *codec)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ struct hda_pcm *cpcm;
+ int err;
+
+ if (!list_empty(&codec->pcm_list_head))
+ return 0; /* already parsed */
+
+ if (!driver->ops->build_pcms)
+ return 0;
+
+ err = driver->ops->build_pcms(codec);
+ if (err < 0) {
+ codec_err(codec, "cannot build PCMs for #%d (error %d)\n",
+ codec->core.addr, err);
+ return err;
+ }
+
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ int stream;
+
+ for_each_pcm_streams(stream) {
+ struct hda_pcm_stream *info = &cpcm->stream[stream];
+
+ if (!info->substreams)
+ continue;
+ err = set_pcm_default_values(codec, info);
+ if (err < 0) {
+ codec_warn(codec,
+ "fail to setup default for PCM %s\n",
+ cpcm->name);
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_parse_pcms);
+
+/* assign all PCMs of the given codec */
+int snd_hda_codec_build_pcms(struct hda_codec *codec)
+{
+ struct hda_bus *bus = codec->bus;
+ struct hda_pcm *cpcm;
+ int dev, err;
+
+ err = snd_hda_codec_parse_pcms(codec);
+ if (err < 0)
+ return err;
+
+ /* attach a new PCM streams */
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ if (cpcm->pcm)
+ continue; /* already attached */
+ if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
+ continue; /* no substreams assigned */
+
+ dev = get_empty_pcm_device(bus, cpcm->pcm_type);
+ if (dev < 0) {
+ cpcm->device = SNDRV_PCM_INVALID_DEVICE;
+ continue; /* no fatal error */
+ }
+ cpcm->device = dev;
+ err = snd_hda_attach_pcm_stream(bus, codec, cpcm);
+ if (err < 0) {
+ codec_err(codec,
+ "cannot attach PCM stream %d for codec #%d\n",
+ dev, codec->core.addr);
+ continue; /* no fatal error */
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * snd_hda_add_new_ctls - create controls from the array
+ * @codec: the HDA codec
+ * @knew: the array of struct snd_kcontrol_new
+ *
+ * This helper function creates and add new controls in the given array.
+ * The array must be terminated with an empty entry as terminator.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec,
+ const struct snd_kcontrol_new *knew)
+{
+ int err;
+
+ for (; knew->name; knew++) {
+ struct snd_kcontrol *kctl;
+ int addr = 0, idx = 0;
+ if (knew->iface == (__force snd_ctl_elem_iface_t)-1)
+ continue; /* skip this codec private value */
+ for (;;) {
+ kctl = snd_ctl_new1(knew, codec);
+ if (!kctl)
+ return -ENOMEM;
+ /* Do not use the id.device field for MIXER elements.
+ * This field is for real device numbers (like PCM) but codecs
+ * are hidden components from the user space view (unrelated
+ * to the mixer element identification).
+ */
+ if (addr > 0 && codec->ctl_dev_id)
+ kctl->id.device = addr;
+ if (idx > 0)
+ kctl->id.index = idx;
+ err = snd_hda_ctl_add(codec, 0, kctl);
+ if (!err)
+ break;
+ /* try first with another device index corresponding to
+ * the codec addr; if it still fails (or it's the
+ * primary codec), then try another control index
+ */
+ if (!addr && codec->core.addr) {
+ addr = codec->core.addr;
+ if (!codec->ctl_dev_id)
+ idx += 10 * addr;
+ } else if (!idx && !knew->index) {
+ idx = find_empty_mixer_ctl_idx(codec,
+ knew->name, 0);
+ if (idx <= 0)
+ return err;
+ } else
+ return err;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_new_ctls);
+
+/**
+ * snd_hda_codec_set_power_save - Configure codec's runtime PM
+ * @codec: codec device to configure
+ * @delay: autosuspend delay
+ */
+void snd_hda_codec_set_power_save(struct hda_codec *codec, int delay)
+{
+ struct device *dev = hda_codec_dev(codec);
+
+ if (delay == 0 && codec->auto_runtime_pm)
+ delay = 3000;
+
+ if (delay > 0) {
+ pm_runtime_set_autosuspend_delay(dev, delay);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_allow(dev);
+ if (!pm_runtime_suspended(dev))
+ pm_runtime_mark_last_busy(dev);
+ } else {
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_forbid(dev);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_power_save);
+
+/**
+ * snd_hda_set_power_save - reprogram autosuspend for the given delay
+ * @bus: HD-audio bus
+ * @delay: autosuspend delay in msec, 0 = off
+ *
+ * Synchronize the runtime PM autosuspend state from the power_save option.
+ */
+void snd_hda_set_power_save(struct hda_bus *bus, int delay)
+{
+ struct hda_codec *c;
+
+ list_for_each_codec(c, bus)
+ snd_hda_codec_set_power_save(c, delay);
+}
+EXPORT_SYMBOL_GPL(snd_hda_set_power_save);
+
+/**
+ * snd_hda_check_amp_list_power - Check the amp list and update the power
+ * @codec: HD-audio codec
+ * @check: the object containing an AMP list and the status
+ * @nid: NID to check / update
+ *
+ * Check whether the given NID is in the amp list. If it's in the list,
+ * check the current AMP status, and update the power-status according
+ * to the mute status.
+ *
+ * This function is supposed to be set or called from the check_power_status
+ * patch ops.
+ */
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+ struct hda_loopback_check *check,
+ hda_nid_t nid)
+{
+ const struct hda_amp_list *p;
+ int ch, v;
+
+ if (!check->amplist)
+ return 0;
+ for (p = check->amplist; p->nid; p++) {
+ if (p->nid == nid)
+ break;
+ }
+ if (!p->nid)
+ return 0; /* nothing changed */
+
+ for (p = check->amplist; p->nid; p++) {
+ for (ch = 0; ch < 2; ch++) {
+ v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
+ p->idx);
+ if (!(v & HDA_AMP_MUTE) && v > 0) {
+ if (!check->power_on) {
+ check->power_on = 1;
+ snd_hda_power_up_pm(codec);
+ }
+ return 1;
+ }
+ }
+ }
+ if (check->power_on) {
+ check->power_on = 0;
+ snd_hda_power_down_pm(codec);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_check_amp_list_power);
+
+/*
+ * input MUX helper
+ */
+
+/**
+ * snd_hda_input_mux_info - Info callback helper for the input-mux enum
+ * @imux: imux helper object
+ * @uinfo: pointer to get/store the data
+ */
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int index;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = imux->num_items;
+ if (!imux->num_items)
+ return 0;
+ index = uinfo->value.enumerated.item;
+ if (index >= imux->num_items)
+ index = imux->num_items - 1;
+ strscpy(uinfo->value.enumerated.name, imux->items[index].label);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_input_mux_info);
+
+/**
+ * snd_hda_input_mux_put - Put callback helper for the input-mux enum
+ * @codec: the HDA codec
+ * @imux: imux helper object
+ * @ucontrol: pointer to get/store the data
+ * @nid: input mux NID
+ * @cur_val: pointer to get/store the current imux value
+ */
+int snd_hda_input_mux_put(struct hda_codec *codec,
+ const struct hda_input_mux *imux,
+ struct snd_ctl_elem_value *ucontrol,
+ hda_nid_t nid,
+ unsigned int *cur_val)
+{
+ unsigned int idx;
+
+ if (!imux->num_items)
+ return 0;
+ idx = ucontrol->value.enumerated.item[0];
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (*cur_val == idx)
+ return 0;
+ snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL,
+ imux->items[idx].index);
+ *cur_val = idx;
+ return 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_input_mux_put);
+
+
+/**
+ * snd_hda_enum_helper_info - Helper for simple enum ctls
+ * @kcontrol: ctl element
+ * @uinfo: pointer to get/store the data
+ * @num_items: number of enum items
+ * @texts: enum item string array
+ *
+ * process kcontrol info callback of a simple string enum array
+ * when @num_items is 0 or @texts is NULL, assume a boolean enum array
+ */
+int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo,
+ int num_items, const char * const *texts)
+{
+ static const char * const texts_default[] = {
+ "Disabled", "Enabled"
+ };
+
+ if (!texts || !num_items) {
+ num_items = 2;
+ texts = texts_default;
+ }
+
+ return snd_ctl_enum_info(uinfo, 1, num_items, texts);
+}
+EXPORT_SYMBOL_GPL(snd_hda_enum_helper_info);
+
+/*
+ * Multi-channel / digital-out PCM helper functions
+ */
+
+/* setup SPDIF output stream */
+static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int stream_tag, unsigned int format)
+{
+ struct hda_spdif_out *spdif;
+ unsigned int curr_fmt;
+ bool reset;
+
+ spdif = snd_hda_spdif_out_of_nid(codec, nid);
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (WARN_ON(spdif == NULL))
+ return;
+
+ curr_fmt = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ reset = codec->spdif_status_reset &&
+ (spdif->ctls & AC_DIG1_ENABLE) &&
+ curr_fmt != format;
+
+ /* turn off SPDIF if needed; otherwise the IEC958 bits won't be
+ updated */
+ if (reset)
+ set_dig_out_convert(codec, nid,
+ spdif->ctls & ~AC_DIG1_ENABLE & 0xff,
+ -1);
+ snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+ if (codec->follower_dig_outs) {
+ const hda_nid_t *d;
+ for (d = codec->follower_dig_outs; *d; d++)
+ snd_hda_codec_setup_stream(codec, *d, stream_tag, 0,
+ format);
+ }
+ /* turn on again (if needed) */
+ if (reset)
+ set_dig_out_convert(codec, nid,
+ spdif->ctls & 0xff, -1);
+}
+
+static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid)
+{
+ snd_hda_codec_cleanup_stream(codec, nid);
+ if (codec->follower_dig_outs) {
+ const hda_nid_t *d;
+ for (d = codec->follower_dig_outs; *d; d++)
+ snd_hda_codec_cleanup_stream(codec, *d);
+ }
+}
+
+/**
+ * snd_hda_multi_out_dig_open - open the digital out in the exclusive mode
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->dig_out_used == HDA_DIG_ANALOG_DUP)
+ /* already opened as analog dup; reset it once */
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mout->dig_out_used = HDA_DIG_EXCLUSIVE;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_open);
+
+/**
+ * snd_hda_multi_out_dig_prepare - prepare the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @stream_tag: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ */
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->spdif_mutex);
+ setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format);
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_prepare);
+
+/**
+ * snd_hda_multi_out_dig_cleanup - clean-up the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_cleanup);
+
+/**
+ * snd_hda_multi_out_dig_close - release the digital out stream
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ mutex_lock(&codec->spdif_mutex);
+ mout->dig_out_used = 0;
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_close);
+
+/**
+ * snd_hda_multi_out_analog_open - open analog outputs
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @substream: PCM substream to assign
+ * @hinfo: PCM information to assign
+ *
+ * Open analog outputs and set up the hw-constraints.
+ * If the digital outputs can be opened as follower, open the digital
+ * outputs, too.
+ */
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ struct snd_pcm_substream *substream,
+ struct hda_pcm_stream *hinfo)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ runtime->hw.channels_max = mout->max_channels;
+ if (mout->dig_out_nid) {
+ if (!mout->analog_rates) {
+ mout->analog_rates = hinfo->rates;
+ mout->analog_formats = hinfo->formats;
+ mout->analog_maxbps = hinfo->maxbps;
+ } else {
+ runtime->hw.rates = mout->analog_rates;
+ runtime->hw.formats = mout->analog_formats;
+ hinfo->maxbps = mout->analog_maxbps;
+ }
+ if (!mout->spdif_rates) {
+ snd_hda_query_supported_pcm(codec, mout->dig_out_nid,
+ &mout->spdif_rates,
+ &mout->spdif_formats,
+ NULL,
+ &mout->spdif_maxbps);
+ }
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->share_spdif) {
+ if ((runtime->hw.rates & mout->spdif_rates) &&
+ (runtime->hw.formats & mout->spdif_formats)) {
+ runtime->hw.rates &= mout->spdif_rates;
+ runtime->hw.formats &= mout->spdif_formats;
+ if (mout->spdif_maxbps < hinfo->maxbps)
+ hinfo->maxbps = mout->spdif_maxbps;
+ } else {
+ mout->share_spdif = 0;
+ /* FIXME: need notify? */
+ }
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ }
+ return snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open);
+
+/**
+ * snd_hda_multi_out_analog_prepare - Preapre the analog outputs.
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ * @stream_tag: stream tag to assign
+ * @format: format id to assign
+ * @substream: PCM substream to assign
+ *
+ * Set up the i/o for analog out.
+ * When the digital out is available, copy the front out to digital out, too.
+ */
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ const hda_nid_t *nids = mout->dac_nids;
+ int chs = substream->runtime->channels;
+ struct hda_spdif_out *spdif;
+ int i;
+
+ mutex_lock(&codec->spdif_mutex);
+ spdif = snd_hda_spdif_out_of_nid(codec, mout->dig_out_nid);
+ if (mout->dig_out_nid && mout->share_spdif &&
+ mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+ if (chs == 2 && spdif != NULL &&
+ snd_hda_is_supported_format(codec, mout->dig_out_nid,
+ format) &&
+ !(spdif->status & IEC958_AES0_NONAUDIO)) {
+ mout->dig_out_used = HDA_DIG_ANALOG_DUP;
+ setup_dig_out_stream(codec, mout->dig_out_nid,
+ stream_tag, format);
+ } else {
+ mout->dig_out_used = 0;
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ }
+ }
+ mutex_unlock(&codec->spdif_mutex);
+
+ /* front */
+ snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag,
+ 0, format);
+ if (!mout->no_share_stream &&
+ mout->hp_nid && mout->hp_nid != nids[HDA_FRONT])
+ /* headphone out will just decode front left/right (stereo) */
+ snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag,
+ 0, format);
+ /* extra outputs copied from front */
+ for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++)
+ if (!mout->no_share_stream && mout->hp_out_nid[i])
+ snd_hda_codec_setup_stream(codec,
+ mout->hp_out_nid[i],
+ stream_tag, 0, format);
+
+ /* surrounds */
+ for (i = 1; i < mout->num_dacs; i++) {
+ if (chs >= (i + 1) * 2) /* independent out */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+ i * 2, format);
+ else if (!mout->no_share_stream) /* copy front */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+ 0, format);
+ }
+
+ /* extra surrounds */
+ for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) {
+ int ch = 0;
+ if (!mout->extra_out_nid[i])
+ break;
+ if (chs >= (i + 1) * 2)
+ ch = i * 2;
+ else if (!mout->no_share_stream)
+ break;
+ snd_hda_codec_setup_stream(codec, mout->extra_out_nid[i],
+ stream_tag, ch, format);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare);
+
+/**
+ * snd_hda_multi_out_analog_cleanup - clean up the setting for analog out
+ * @codec: the HDA codec
+ * @mout: hda_multi_out object
+ */
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ const hda_nid_t *nids = mout->dac_nids;
+ int i;
+
+ for (i = 0; i < mout->num_dacs; i++)
+ snd_hda_codec_cleanup_stream(codec, nids[i]);
+ if (mout->hp_nid)
+ snd_hda_codec_cleanup_stream(codec, mout->hp_nid);
+ for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++)
+ if (mout->hp_out_nid[i])
+ snd_hda_codec_cleanup_stream(codec,
+ mout->hp_out_nid[i]);
+ for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+ if (mout->extra_out_nid[i])
+ snd_hda_codec_cleanup_stream(codec,
+ mout->extra_out_nid[i]);
+ mutex_lock(&codec->spdif_mutex);
+ if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
+ cleanup_dig_out_stream(codec, mout->dig_out_nid);
+ mout->dig_out_used = 0;
+ }
+ mutex_unlock(&codec->spdif_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_cleanup);
+
+/**
+ * snd_hda_get_default_vref - Get the default (mic) VREF pin bits
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ *
+ * Guess the suitable VREF pin bits to be set as the pin-control value.
+ * Note: the function doesn't set the AC_PINCTL_IN_EN bit.
+ */
+unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pincap;
+ unsigned int oldval;
+ oldval = snd_hda_codec_read(codec, pin, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ pincap = snd_hda_query_pin_caps(codec, pin);
+ pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ /* Exception: if the default pin setup is vref50, we give it priority */
+ if ((pincap & AC_PINCAP_VREF_80) && oldval != PIN_VREF50)
+ return AC_PINCTL_VREF_80;
+ else if (pincap & AC_PINCAP_VREF_50)
+ return AC_PINCTL_VREF_50;
+ else if (pincap & AC_PINCAP_VREF_100)
+ return AC_PINCTL_VREF_100;
+ else if (pincap & AC_PINCAP_VREF_GRD)
+ return AC_PINCTL_VREF_GRD;
+ return AC_PINCTL_VREF_HIZ;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_default_vref);
+
+/**
+ * snd_hda_correct_pin_ctl - correct the pin ctl value for matching with the pin cap
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ * @val: pin ctl value to audit
+ */
+unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
+ hda_nid_t pin, unsigned int val)
+{
+ static const unsigned int cap_lists[][2] = {
+ { AC_PINCTL_VREF_100, AC_PINCAP_VREF_100 },
+ { AC_PINCTL_VREF_80, AC_PINCAP_VREF_80 },
+ { AC_PINCTL_VREF_50, AC_PINCAP_VREF_50 },
+ { AC_PINCTL_VREF_GRD, AC_PINCAP_VREF_GRD },
+ };
+ unsigned int cap;
+
+ if (!val)
+ return 0;
+ cap = snd_hda_query_pin_caps(codec, pin);
+ if (!cap)
+ return val; /* don't know what to do... */
+
+ if (val & AC_PINCTL_OUT_EN) {
+ if (!(cap & AC_PINCAP_OUT))
+ val &= ~(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
+ else if ((val & AC_PINCTL_HP_EN) && !(cap & AC_PINCAP_HP_DRV))
+ val &= ~AC_PINCTL_HP_EN;
+ }
+
+ if (val & AC_PINCTL_IN_EN) {
+ if (!(cap & AC_PINCAP_IN))
+ val &= ~(AC_PINCTL_IN_EN | AC_PINCTL_VREFEN);
+ else {
+ unsigned int vcap, vref;
+ int i;
+ vcap = (cap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ vref = val & AC_PINCTL_VREFEN;
+ for (i = 0; i < ARRAY_SIZE(cap_lists); i++) {
+ if (vref == cap_lists[i][0] &&
+ !(vcap & cap_lists[i][1])) {
+ if (i == ARRAY_SIZE(cap_lists) - 1)
+ vref = AC_PINCTL_VREF_HIZ;
+ else
+ vref = cap_lists[i + 1][0];
+ }
+ }
+ val &= ~AC_PINCTL_VREFEN;
+ val |= vref;
+ }
+ }
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(snd_hda_correct_pin_ctl);
+
+/**
+ * _snd_hda_set_pin_ctl - Helper to set pin ctl value
+ * @codec: the HDA codec
+ * @pin: referred pin NID
+ * @val: pin control value to set
+ * @cached: access over codec pinctl cache or direct write
+ *
+ * This function is a helper to set a pin ctl value more safely.
+ * It corrects the pin ctl value via snd_hda_correct_pin_ctl(), stores the
+ * value in pin target array via snd_hda_codec_set_pin_target(), then
+ * actually writes the value via either snd_hda_codec_write_cache() or
+ * snd_hda_codec_write() depending on @cached flag.
+ */
+int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool cached)
+{
+ val = snd_hda_correct_pin_ctl(codec, pin, val);
+ snd_hda_codec_set_pin_target(codec, pin, val);
+ if (cached)
+ return snd_hda_codec_write_cache(codec, pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+ else
+ return snd_hda_codec_write(codec, pin, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+}
+EXPORT_SYMBOL_GPL(_snd_hda_set_pin_ctl);
+
+/**
+ * snd_hda_add_imux_item - Add an item to input_mux
+ * @codec: the HDA codec
+ * @imux: imux helper object
+ * @label: the name of imux item to assign
+ * @index: index number of imux item to assign
+ * @type_idx: pointer to store the resultant label index
+ *
+ * When the same label is used already in the existing items, the number
+ * suffix is appended to the label. This label index number is stored
+ * to type_idx when non-NULL pointer is given.
+ */
+int snd_hda_add_imux_item(struct hda_codec *codec,
+ struct hda_input_mux *imux, const char *label,
+ int index, int *type_idx)
+{
+ int i, label_idx = 0;
+ if (imux->num_items >= HDA_MAX_NUM_INPUTS) {
+ codec_err(codec, "hda_codec: Too many imux items!\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < imux->num_items; i++) {
+ if (!strncmp(label, imux->items[i].label, strlen(label)))
+ label_idx++;
+ }
+ if (type_idx)
+ *type_idx = label_idx;
+ if (label_idx > 0)
+ snprintf(imux->items[imux->num_items].label,
+ sizeof(imux->items[imux->num_items].label),
+ "%s %d", label, label_idx);
+ else
+ strscpy(imux->items[imux->num_items].label, label,
+ sizeof(imux->items[imux->num_items].label));
+ imux->items[imux->num_items].index = index;
+ imux->num_items++;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_add_imux_item);
+
+/**
+ * snd_hda_bus_reset_codecs - Reset the bus
+ * @bus: HD-audio bus
+ */
+void snd_hda_bus_reset_codecs(struct hda_bus *bus)
+{
+ struct hda_codec *codec;
+
+ list_for_each_codec(codec, bus) {
+ /* FIXME: maybe a better way needed for forced reset */
+ if (current_work() != &codec->jackpoll_work.work)
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ if (hda_codec_is_power_on(codec)) {
+ hda_call_codec_suspend(codec);
+ hda_call_codec_resume(codec);
+ }
+ }
+}
+
+/**
+ * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer
+ * @pcm: PCM caps bits
+ * @buf: the string buffer to write
+ * @buflen: the max buffer length
+ *
+ * used by hda_proc.c and hda_eld.c
+ */
+void snd_print_pcm_bits(int pcm, char *buf, int buflen)
+{
+ static const unsigned int bits[] = { 8, 16, 20, 24, 32 };
+ int i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++)
+ if (pcm & (AC_SUPPCM_BITS_8 << i))
+ j += scnprintf(buf + j, buflen - j, " %d", bits[i]);
+
+ buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_GPL(snd_print_pcm_bits);
+
+MODULE_DESCRIPTION("HDA codec core");
+MODULE_LICENSE("GPL");
diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c
new file mode 100644
index 000000000000..84387ed761be
--- /dev/null
+++ b/sound/hda/common/controller.c
@@ -0,0 +1,1336 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Implementation of primary alsa driver code base for Intel HD Audio.
+ *
+ * Copyright(c) 2004 Intel Corporation
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ * PeiSen Hou <pshou@realtek.com.tw>
+ */
+
+#include <linux/clocksource.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#ifdef CONFIG_X86
+/* for art-tsc conversion */
+#include <asm/tsc.h>
+#endif
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include "hda_controller.h"
+#include "hda_local.h"
+
+#define CREATE_TRACE_POINTS
+#include "controller_trace.h"
+
+/* DSP lock helpers */
+#define dsp_lock(dev) snd_hdac_dsp_lock(azx_stream(dev))
+#define dsp_unlock(dev) snd_hdac_dsp_unlock(azx_stream(dev))
+#define dsp_is_locked(dev) snd_hdac_stream_is_locked(azx_stream(dev))
+
+/* assign a stream for the PCM */
+static inline struct azx_dev *
+azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *s;
+
+ s = snd_hdac_stream_assign(azx_bus(chip), substream);
+ if (!s)
+ return NULL;
+ return stream_to_azx_dev(s);
+}
+
+/* release the assigned stream */
+static inline void azx_release_device(struct azx_dev *azx_dev)
+{
+ snd_hdac_stream_release(azx_stream(azx_dev));
+}
+
+static inline struct hda_pcm_stream *
+to_hda_pcm_stream(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ return &apcm->info->stream[substream->stream];
+}
+
+static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream,
+ u64 nsec)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ u64 codec_frames, codec_nsecs;
+
+ if (!hinfo->ops.get_delay)
+ return nsec;
+
+ codec_frames = hinfo->ops.get_delay(hinfo, apcm->codec, substream);
+ codec_nsecs = div_u64(codec_frames * 1000000000LL,
+ substream->runtime->rate);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return nsec + codec_nsecs;
+
+ return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0;
+}
+
+/*
+ * PCM ops
+ */
+
+static int azx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+
+ trace_azx_pcm_close(chip, azx_dev);
+ mutex_lock(&chip->open_mutex);
+ azx_release_device(azx_dev);
+ if (hinfo->ops.close)
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ snd_hda_power_down(apcm->codec);
+ mutex_unlock(&chip->open_mutex);
+ snd_hda_codec_pcm_put(apcm->info);
+ return 0;
+}
+
+static int azx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hdac_stream *hdas = azx_stream(azx_dev);
+ int ret = 0;
+
+ trace_azx_pcm_hw_params(chip, azx_dev);
+ dsp_lock(azx_dev);
+ if (dsp_is_locked(azx_dev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /* Set up BDLEs here, return -ENOMEM if too many BDLEs are required */
+ hdas->bufsize = params_buffer_bytes(hw_params);
+ hdas->period_bytes = params_period_bytes(hw_params);
+ hdas->format_val = 0;
+ hdas->no_period_wakeup =
+ (hw_params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
+ (hw_params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
+ if (snd_hdac_stream_setup_periods(hdas) < 0)
+ ret = -ENOMEM;
+
+unlock:
+ dsp_unlock(azx_dev);
+ return ret;
+}
+
+static int azx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+
+ /* reset BDL address */
+ dsp_lock(azx_dev);
+ if (!dsp_is_locked(azx_dev))
+ snd_hdac_stream_cleanup(azx_stream(azx_dev));
+
+ snd_hda_codec_cleanup(apcm->codec, hinfo, substream);
+
+ azx_stream(azx_dev)->prepared = 0;
+ dsp_unlock(azx_dev);
+ return 0;
+}
+
+static int azx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int format_val, stream_tag, bits;
+ int err;
+ struct hda_spdif_out *spdif =
+ snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid);
+ unsigned short ctls = spdif ? spdif->ctls : 0;
+
+ trace_azx_pcm_prepare(chip, azx_dev);
+ dsp_lock(azx_dev);
+ if (dsp_is_locked(azx_dev)) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ snd_hdac_stream_reset(azx_stream(azx_dev));
+ bits = snd_hdac_stream_format_bits(runtime->format, SNDRV_PCM_SUBFORMAT_STD, hinfo->maxbps);
+
+ format_val = snd_hdac_spdif_stream_format(runtime->channels, bits, runtime->rate, ctls);
+ if (!format_val) {
+ dev_err(chip->card->dev,
+ "invalid format_val, rate=%d, ch=%d, format=%d\n",
+ runtime->rate, runtime->channels, runtime->format);
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = snd_hdac_stream_set_params(azx_stream(azx_dev), format_val);
+ if (err < 0)
+ goto unlock;
+
+ snd_hdac_stream_setup(azx_stream(azx_dev), false);
+
+ stream_tag = azx_dev->core.stream_tag;
+ /* CA-IBG chips need the playback stream starting from 1 */
+ if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) &&
+ stream_tag > chip->capture_streams)
+ stream_tag -= chip->capture_streams;
+ err = snd_hda_codec_prepare(apcm->codec, hinfo, stream_tag,
+ azx_dev->core.format_val, substream);
+
+ unlock:
+ if (!err)
+ azx_stream(azx_dev)->prepared = 1;
+ dsp_unlock(azx_dev);
+ return err;
+}
+
+static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct hdac_bus *bus = azx_bus(chip);
+ struct azx_dev *azx_dev;
+ struct snd_pcm_substream *s;
+ struct hdac_stream *hstr;
+ bool start;
+ int sbits = 0;
+ int sync_reg;
+
+ azx_dev = get_azx_dev(substream);
+ trace_azx_pcm_trigger(chip, azx_dev, cmd);
+
+ hstr = azx_stream(azx_dev);
+ if (chip->driver_caps & AZX_DCAPS_OLD_SSYNC)
+ sync_reg = AZX_REG_OLD_SSYNC;
+ else
+ sync_reg = AZX_REG_SSYNC;
+
+ if (dsp_is_locked(azx_dev) || !hstr->prepared)
+ return -EPIPE;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ start = true;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ start = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ sbits |= 1 << azx_dev->core.index;
+ snd_pcm_trigger_done(s, substream);
+ }
+
+ spin_lock(&bus->reg_lock);
+
+ /* first, set SYNC bits of corresponding streams */
+ snd_hdac_stream_sync_trigger(hstr, true, sbits, sync_reg);
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ if (start) {
+ azx_dev->insufficient = 1;
+ snd_hdac_stream_start(azx_stream(azx_dev));
+ } else {
+ snd_hdac_stream_stop(azx_stream(azx_dev));
+ }
+ }
+ spin_unlock(&bus->reg_lock);
+
+ snd_hdac_stream_sync(hstr, start, sbits);
+
+ spin_lock(&bus->reg_lock);
+ /* reset SYNC bits */
+ snd_hdac_stream_sync_trigger(hstr, false, sbits, sync_reg);
+ snd_hdac_stream_timecounter_init(hstr, sbits, start);
+ spin_unlock(&bus->reg_lock);
+ return 0;
+}
+
+unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev)
+{
+ return snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev));
+}
+EXPORT_SYMBOL_GPL(azx_get_pos_lpib);
+
+unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev)
+{
+ return snd_hdac_stream_get_pos_posbuf(azx_stream(azx_dev));
+}
+EXPORT_SYMBOL_GPL(azx_get_pos_posbuf);
+
+unsigned int azx_get_position(struct azx *chip,
+ struct azx_dev *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->core.substream;
+ unsigned int pos;
+ int stream = substream->stream;
+ int delay = 0;
+
+ if (chip->get_position[stream])
+ pos = chip->get_position[stream](chip, azx_dev);
+ else /* use the position buffer as default */
+ pos = azx_get_pos_posbuf(chip, azx_dev);
+
+ if (pos >= azx_dev->core.bufsize)
+ pos = 0;
+
+ if (substream->runtime) {
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+
+ if (chip->get_delay[stream])
+ delay += chip->get_delay[stream](chip, azx_dev, pos);
+ if (hinfo->ops.get_delay)
+ delay += hinfo->ops.get_delay(hinfo, apcm->codec,
+ substream);
+ substream->runtime->delay = delay;
+ }
+
+ trace_azx_get_position(chip, azx_dev, pos, delay);
+ return pos;
+}
+EXPORT_SYMBOL_GPL(azx_get_position);
+
+static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ return bytes_to_frames(substream->runtime,
+ azx_get_position(chip, azx_dev));
+}
+
+/*
+ * azx_scale64: Scale base by mult/div while not overflowing sanely
+ *
+ * Derived from scale64_check_overflow in kernel/time/timekeeping.c
+ *
+ * The tmestamps for a 48Khz stream can overflow after (2^64/10^9)/48K which
+ * is about 384307 ie ~4.5 days.
+ *
+ * This scales the calculation so that overflow will happen but after 2^64 /
+ * 48000 secs, which is pretty large!
+ *
+ * In caln below:
+ * base may overflow, but since there isn’t any additional division
+ * performed on base it’s OK
+ * rem can’t overflow because both are 32-bit values
+ */
+
+#ifdef CONFIG_X86
+static u64 azx_scale64(u64 base, u32 num, u32 den)
+{
+ u64 rem;
+
+ rem = do_div(base, den);
+
+ base *= num;
+ rem *= num;
+
+ do_div(rem, den);
+
+ return base + rem;
+}
+
+static int azx_get_sync_time(ktime_t *device,
+ struct system_counterval_t *system, void *ctx)
+{
+ struct snd_pcm_substream *substream = ctx;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct snd_pcm_runtime *runtime;
+ u64 ll_counter, ll_counter_l, ll_counter_h;
+ u64 tsc_counter, tsc_counter_l, tsc_counter_h;
+ u32 wallclk_ctr, wallclk_cycles;
+ bool direction;
+ u32 dma_select;
+ u32 timeout;
+ u32 retry_count = 0;
+
+ runtime = substream->runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ direction = 1;
+ else
+ direction = 0;
+
+ /* 0th stream tag is not used, so DMA ch 0 is for 1st stream tag */
+ do {
+ timeout = 100;
+ dma_select = (direction << GTSCC_CDMAS_DMA_DIR_SHIFT) |
+ (azx_dev->core.stream_tag - 1);
+ snd_hdac_chip_writel(azx_bus(chip), GTSCC, dma_select);
+
+ /* Enable the capture */
+ snd_hdac_chip_updatel(azx_bus(chip), GTSCC, 0, GTSCC_TSCCI_MASK);
+
+ while (timeout) {
+ if (snd_hdac_chip_readl(azx_bus(chip), GTSCC) &
+ GTSCC_TSCCD_MASK)
+ break;
+
+ timeout--;
+ }
+
+ if (!timeout) {
+ dev_err(chip->card->dev, "GTSCC capture Timedout!\n");
+ return -EIO;
+ }
+
+ /* Read wall clock counter */
+ wallclk_ctr = snd_hdac_chip_readl(azx_bus(chip), WALFCC);
+
+ /* Read TSC counter */
+ tsc_counter_l = snd_hdac_chip_readl(azx_bus(chip), TSCCL);
+ tsc_counter_h = snd_hdac_chip_readl(azx_bus(chip), TSCCU);
+
+ /* Read Link counter */
+ ll_counter_l = snd_hdac_chip_readl(azx_bus(chip), LLPCL);
+ ll_counter_h = snd_hdac_chip_readl(azx_bus(chip), LLPCU);
+
+ /* Ack: registers read done */
+ snd_hdac_chip_writel(azx_bus(chip), GTSCC, GTSCC_TSCCD_SHIFT);
+
+ tsc_counter = (tsc_counter_h << TSCCU_CCU_SHIFT) |
+ tsc_counter_l;
+
+ ll_counter = (ll_counter_h << LLPC_CCU_SHIFT) | ll_counter_l;
+ wallclk_cycles = wallclk_ctr & WALFCC_CIF_MASK;
+
+ /*
+ * An error occurs near frame "rollover". The clocks in
+ * frame value indicates whether this error may have
+ * occurred. Here we use the value of 10 i.e.,
+ * HDA_MAX_CYCLE_OFFSET
+ */
+ if (wallclk_cycles < HDA_MAX_CYCLE_VALUE - HDA_MAX_CYCLE_OFFSET
+ && wallclk_cycles > HDA_MAX_CYCLE_OFFSET)
+ break;
+
+ /*
+ * Sleep before we read again, else we may again get
+ * value near to MAX_CYCLE. Try to sleep for different
+ * amount of time so we dont hit the same number again
+ */
+ udelay(retry_count++);
+
+ } while (retry_count != HDA_MAX_CYCLE_READ_RETRY);
+
+ if (retry_count == HDA_MAX_CYCLE_READ_RETRY) {
+ dev_err_ratelimited(chip->card->dev,
+ "Error in WALFCC cycle count\n");
+ return -EIO;
+ }
+
+ *device = ns_to_ktime(azx_scale64(ll_counter,
+ NSEC_PER_SEC, runtime->rate));
+ *device = ktime_add_ns(*device, (wallclk_cycles * NSEC_PER_SEC) /
+ ((HDA_MAX_CYCLE_VALUE + 1) * runtime->rate));
+
+ system->cycles = tsc_counter;
+ system->cs_id = CSID_X86_ART;
+
+ return 0;
+}
+
+#else
+static int azx_get_sync_time(ktime_t *device,
+ struct system_counterval_t *system, void *ctx)
+{
+ return -ENXIO;
+}
+#endif
+
+static int azx_get_crosststamp(struct snd_pcm_substream *substream,
+ struct system_device_crosststamp *xtstamp)
+{
+ return get_device_system_crosststamp(azx_get_sync_time,
+ substream, NULL, xtstamp);
+}
+
+static inline bool is_link_time_supported(struct snd_pcm_runtime *runtime,
+ struct snd_pcm_audio_tstamp_config *ts)
+{
+ if (runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME)
+ if (ts->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED)
+ return true;
+
+ return false;
+}
+
+static int azx_get_time_info(struct snd_pcm_substream *substream,
+ struct timespec64 *system_ts, struct timespec64 *audio_ts,
+ struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
+ struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct system_device_crosststamp xtstamp;
+ int ret;
+ u64 nsec;
+
+ if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) &&
+ (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) {
+
+ snd_pcm_gettime(substream->runtime, system_ts);
+
+ nsec = timecounter_read(&azx_dev->core.tc);
+ if (audio_tstamp_config->report_delay)
+ nsec = azx_adjust_codec_delay(substream, nsec);
+
+ *audio_ts = ns_to_timespec64(nsec);
+
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
+ audio_tstamp_report->accuracy_report = 1; /* rest of structure is valid */
+ audio_tstamp_report->accuracy = 42; /* 24 MHz WallClock == 42ns resolution */
+
+ } else if (is_link_time_supported(runtime, audio_tstamp_config)) {
+
+ ret = azx_get_crosststamp(substream, &xtstamp);
+ if (ret)
+ return ret;
+
+ switch (runtime->tstamp_type) {
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC:
+ return -EINVAL;
+
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW:
+ *system_ts = ktime_to_timespec64(xtstamp.sys_monoraw);
+ break;
+
+ default:
+ *system_ts = ktime_to_timespec64(xtstamp.sys_realtime);
+ break;
+
+ }
+
+ *audio_ts = ktime_to_timespec64(xtstamp.device);
+
+ audio_tstamp_report->actual_type =
+ SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED;
+ audio_tstamp_report->accuracy_report = 1;
+ /* 24 MHz WallClock == 42ns resolution */
+ audio_tstamp_report->accuracy = 42;
+
+ } else {
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
+ }
+
+ return 0;
+}
+
+static const struct snd_pcm_hardware azx_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ /* No full-resume yet implemented */
+ /* SNDRV_PCM_INFO_RESUME |*/
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
+ SNDRV_PCM_INFO_HAS_LINK_ATIME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = AZX_MAX_BUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = AZX_MAX_BUF_SIZE / 2,
+ .periods_min = 2,
+ .periods_max = AZX_MAX_FRAG,
+ .fifo_size = 0,
+};
+
+static int azx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+ int buff_step;
+
+ snd_hda_codec_pcm_get(apcm->info);
+ mutex_lock(&chip->open_mutex);
+ azx_dev = azx_assign_device(chip, substream);
+ trace_azx_pcm_open(chip, azx_dev);
+ if (azx_dev == NULL) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ runtime->private_data = azx_dev;
+
+ runtime->hw = azx_pcm_hw;
+ if (chip->gts_present)
+ runtime->hw.info |= SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME;
+ runtime->hw.channels_min = hinfo->channels_min;
+ runtime->hw.channels_max = hinfo->channels_max;
+ runtime->hw.formats = hinfo->formats;
+ runtime->hw.rates = hinfo->rates;
+ snd_pcm_limit_hw_rates(runtime);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ /* avoid wrap-around with wall-clock */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+ 20,
+ 178000000);
+
+ if (chip->align_buffer_size)
+ /* constrain buffer sizes to be multiple of 128
+ bytes. This is more efficient in terms of memory
+ access but isn't required by the HDA spec and
+ prevents users from specifying exact period/buffer
+ sizes. For example for 44.1kHz, a period size set
+ to 20ms will be rounded to 19.59ms. */
+ buff_step = 128;
+ else
+ /* Don't enforce steps on buffer sizes, still need to
+ be multiple of 4 bytes (HDA spec). Tested on Intel
+ HDA controllers, may not work on all devices where
+ option needs to be disabled */
+ buff_step = 4;
+
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ buff_step);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ buff_step);
+ snd_hda_power_up(apcm->codec);
+ if (hinfo->ops.open)
+ err = hinfo->ops.open(hinfo, apcm->codec, substream);
+ else
+ err = -ENODEV;
+ if (err < 0) {
+ azx_release_device(azx_dev);
+ goto powerdown;
+ }
+ snd_pcm_limit_hw_rates(runtime);
+ /* sanity check */
+ if (snd_BUG_ON(!runtime->hw.channels_min) ||
+ snd_BUG_ON(!runtime->hw.channels_max) ||
+ snd_BUG_ON(!runtime->hw.formats) ||
+ snd_BUG_ON(!runtime->hw.rates)) {
+ azx_release_device(azx_dev);
+ if (hinfo->ops.close)
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ err = -EINVAL;
+ goto powerdown;
+ }
+
+ /* disable LINK_ATIME timestamps for capture streams
+ until we figure out how to handle digital inputs */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME;
+ }
+
+ snd_pcm_set_sync(substream);
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+
+ powerdown:
+ snd_hda_power_down(apcm->codec);
+ unlock:
+ mutex_unlock(&chip->open_mutex);
+ snd_hda_codec_pcm_put(apcm->info);
+ return err;
+}
+
+static const struct snd_pcm_ops azx_pcm_ops = {
+ .open = azx_pcm_open,
+ .close = azx_pcm_close,
+ .hw_params = azx_pcm_hw_params,
+ .hw_free = azx_pcm_hw_free,
+ .prepare = azx_pcm_prepare,
+ .trigger = azx_pcm_trigger,
+ .pointer = azx_pcm_pointer,
+ .get_time_info = azx_get_time_info,
+};
+
+static void azx_pcm_free(struct snd_pcm *pcm)
+{
+ struct azx_pcm *apcm = pcm->private_data;
+ if (apcm) {
+ list_del(&apcm->list);
+ apcm->info->pcm = NULL;
+ kfree(apcm);
+ }
+}
+
+#define MAX_PREALLOC_SIZE (32 * 1024 * 1024)
+
+int snd_hda_attach_pcm_stream(struct hda_bus *_bus, struct hda_codec *codec,
+ struct hda_pcm *cpcm)
+{
+ struct hdac_bus *bus = &_bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct snd_pcm *pcm;
+ struct azx_pcm *apcm;
+ int pcm_dev = cpcm->device;
+ unsigned int size;
+ int s, err;
+ int type = SNDRV_DMA_TYPE_DEV_SG;
+
+ list_for_each_entry(apcm, &chip->pcm_list, list) {
+ if (apcm->pcm->device == pcm_dev) {
+ dev_err(chip->card->dev, "PCM %d already exists\n",
+ pcm_dev);
+ return -EBUSY;
+ }
+ }
+ err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
+ cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
+ cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
+ &pcm);
+ if (err < 0)
+ return err;
+ strscpy(pcm->name, cpcm->name, sizeof(pcm->name));
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (apcm == NULL) {
+ snd_device_free(chip->card, pcm);
+ return -ENOMEM;
+ }
+ apcm->chip = chip;
+ apcm->pcm = pcm;
+ apcm->codec = codec;
+ apcm->info = cpcm;
+ pcm->private_data = apcm;
+ pcm->private_free = azx_pcm_free;
+ if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM)
+ pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+ list_add_tail(&apcm->list, &chip->pcm_list);
+ cpcm->pcm = pcm;
+ for (s = 0; s < 2; s++) {
+ if (cpcm->stream[s].substreams)
+ snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
+ }
+ /* buffer pre-allocation */
+ size = CONFIG_SND_HDA_PREALLOC_SIZE * 1024;
+ if (size > MAX_PREALLOC_SIZE)
+ size = MAX_PREALLOC_SIZE;
+ if (chip->uc_buffer)
+ type = SNDRV_DMA_TYPE_DEV_WC_SG;
+ snd_pcm_set_managed_buffer_all(pcm, type, chip->card->dev,
+ size, MAX_PREALLOC_SIZE);
+ return 0;
+}
+
+static unsigned int azx_command_addr(u32 cmd)
+{
+ unsigned int addr = cmd >> 28;
+
+ if (addr >= AZX_MAX_CODECS) {
+ snd_BUG();
+ addr = 0;
+ }
+
+ return addr;
+}
+
+/* receive a response */
+static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ struct azx *chip = bus_to_azx(bus);
+ struct hda_bus *hbus = &chip->bus;
+ int err;
+
+ again:
+ err = snd_hdac_bus_get_response(bus, addr, res);
+ if (!err)
+ return 0;
+
+ if (hbus->no_response_fallback)
+ return -EIO;
+
+ if (!bus->polling_mode) {
+ dev_warn(chip->card->dev,
+ "azx_get_response timeout, switching to polling mode: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ bus->polling_mode = 1;
+ goto again;
+ }
+
+ if (chip->msi) {
+ dev_warn(chip->card->dev,
+ "No response from codec, disabling MSI: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ if (chip->ops->disable_msi_reset_irq &&
+ chip->ops->disable_msi_reset_irq(chip) < 0)
+ return -EIO;
+ goto again;
+ }
+
+ if (chip->probing) {
+ /* If this critical timeout happens during the codec probing
+ * phase, this is likely an access to a non-existing codec
+ * slot. Better to return an error and reset the system.
+ */
+ return -EIO;
+ }
+
+ /* no fallback mechanism? */
+ if (!chip->fallback_to_single_cmd)
+ return -EIO;
+
+ /* a fatal communication error; need either to reset or to fallback
+ * to the single_cmd mode
+ */
+ if (hbus->allow_bus_reset && !hbus->response_reset && !hbus->in_reset) {
+ hbus->response_reset = 1;
+ dev_err(chip->card->dev,
+ "No response from codec, resetting bus: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ return -EAGAIN; /* give a chance to retry */
+ }
+
+ dev_err(chip->card->dev,
+ "azx_get_response timeout, switching to single_cmd mode: last cmd=0x%08x\n",
+ bus->last_cmd[addr]);
+ chip->single_cmd = 1;
+ hbus->response_reset = 0;
+ snd_hdac_bus_stop_cmd_io(bus);
+ return -EIO;
+}
+
+/*
+ * Use the single immediate command instead of CORB/RIRB for simplicity
+ *
+ * Note: according to Intel, this is not preferred use. The command was
+ * intended for the BIOS only, and may get confused with unsolicited
+ * responses. So, we shouldn't use it for normal operation from the
+ * driver.
+ * I left the codes, however, for debugging/testing purposes.
+ */
+
+/* receive a response */
+static int azx_single_wait_for_response(struct azx *chip, unsigned int addr)
+{
+ int timeout = 50;
+
+ while (timeout--) {
+ /* check IRV busy bit */
+ if (azx_readw(chip, IRS) & AZX_IRS_VALID) {
+ /* reuse rirb.res as the response return value */
+ azx_bus(chip)->rirb.res[addr] = azx_readl(chip, IR);
+ return 0;
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ dev_dbg(chip->card->dev, "get_response timeout: IRS=0x%x\n",
+ azx_readw(chip, IRS));
+ azx_bus(chip)->rirb.res[addr] = -1;
+ return -EIO;
+}
+
+/* send a command */
+static int azx_single_send_cmd(struct hdac_bus *bus, u32 val)
+{
+ struct azx *chip = bus_to_azx(bus);
+ unsigned int addr = azx_command_addr(val);
+ int timeout = 50;
+
+ bus->last_cmd[azx_command_addr(val)] = val;
+ while (timeout--) {
+ /* check ICB busy bit */
+ if (!((azx_readw(chip, IRS) & AZX_IRS_BUSY))) {
+ /* Clear IRV valid bit */
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ AZX_IRS_VALID);
+ azx_writel(chip, IC, val);
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ AZX_IRS_BUSY);
+ return azx_single_wait_for_response(chip, addr);
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ dev_dbg(chip->card->dev,
+ "send_cmd timeout: IRS=0x%x, val=0x%x\n",
+ azx_readw(chip, IRS), val);
+ return -EIO;
+}
+
+/* receive a response */
+static int azx_single_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ if (res)
+ *res = bus->rirb.res[addr];
+ return 0;
+}
+
+/*
+ * The below are the main callbacks from hda_codec.
+ *
+ * They are just the skeleton to call sub-callbacks according to the
+ * current setting of chip->single_cmd.
+ */
+
+/* send a command */
+static int azx_send_cmd(struct hdac_bus *bus, unsigned int val)
+{
+ struct azx *chip = bus_to_azx(bus);
+
+ if (chip->disabled)
+ return 0;
+ if (chip->single_cmd || bus->use_pio_for_commands)
+ return azx_single_send_cmd(bus, val);
+ else
+ return snd_hdac_bus_send_cmd(bus, val);
+}
+
+/* get a response */
+static int azx_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ struct azx *chip = bus_to_azx(bus);
+
+ if (chip->disabled)
+ return 0;
+ if (chip->single_cmd || bus->use_pio_for_commands)
+ return azx_single_get_response(bus, addr, res);
+ else
+ return azx_rirb_get_response(bus, addr, res);
+}
+
+static const struct hdac_bus_ops bus_core_ops = {
+ .command = azx_send_cmd,
+ .get_response = azx_get_response,
+};
+
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+/*
+ * DSP loading code (e.g. for CA0132)
+ */
+
+/* use the first stream for loading DSP */
+static struct azx_dev *
+azx_get_dsp_loader_dev(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ list_for_each_entry(s, &bus->stream_list, list)
+ if (s->index == chip->playback_index_offset)
+ return stream_to_azx_dev(s);
+
+ return NULL;
+}
+
+int snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format,
+ unsigned int byte_size,
+ struct snd_dma_buffer *bufp)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev;
+ struct hdac_stream *hstr;
+ bool saved = false;
+ int err;
+
+ azx_dev = azx_get_dsp_loader_dev(chip);
+ hstr = azx_stream(azx_dev);
+ spin_lock_irq(&bus->reg_lock);
+ if (hstr->opened) {
+ chip->saved_azx_dev = *azx_dev;
+ saved = true;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ err = snd_hdac_dsp_prepare(hstr, format, byte_size, bufp);
+ if (err < 0) {
+ spin_lock_irq(&bus->reg_lock);
+ if (saved)
+ *azx_dev = chip->saved_azx_dev;
+ spin_unlock_irq(&bus->reg_lock);
+ return err;
+ }
+
+ hstr->prepared = 0;
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_prepare);
+
+void snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip);
+
+ snd_hdac_dsp_trigger(azx_stream(azx_dev), start);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_trigger);
+
+void snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec,
+ struct snd_dma_buffer *dmab)
+{
+ struct hdac_bus *bus = &codec->bus->core;
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip);
+ struct hdac_stream *hstr = azx_stream(azx_dev);
+
+ if (!dmab->area || !hstr->locked)
+ return;
+
+ snd_hdac_dsp_cleanup(hstr, dmab);
+ spin_lock_irq(&bus->reg_lock);
+ if (hstr->opened)
+ *azx_dev = chip->saved_azx_dev;
+ hstr->locked = false;
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_cleanup);
+#endif /* CONFIG_SND_HDA_DSP_LOADER */
+
+/*
+ * reset and start the controller registers
+ */
+void azx_init_chip(struct azx *chip, bool full_reset)
+{
+ if (snd_hdac_bus_init_chip(azx_bus(chip), full_reset)) {
+ /* correct RINTCNT for CXT */
+ if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND)
+ azx_writew(chip, RINTCNT, 0xc0);
+ }
+}
+EXPORT_SYMBOL_GPL(azx_init_chip);
+
+void azx_stop_all_streams(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+
+ snd_hdac_stop_streams(bus);
+}
+EXPORT_SYMBOL_GPL(azx_stop_all_streams);
+
+void azx_stop_chip(struct azx *chip)
+{
+ snd_hdac_bus_stop_chip(azx_bus(chip));
+}
+EXPORT_SYMBOL_GPL(azx_stop_chip);
+
+/*
+ * interrupt handler
+ */
+static void stream_update(struct hdac_bus *bus, struct hdac_stream *s)
+{
+ struct azx *chip = bus_to_azx(bus);
+ struct azx_dev *azx_dev = stream_to_azx_dev(s);
+
+ /* check whether this IRQ is really acceptable */
+ if (!chip->ops->position_check ||
+ chip->ops->position_check(chip, azx_dev)) {
+ spin_unlock(&bus->reg_lock);
+ snd_pcm_period_elapsed(azx_stream(azx_dev)->substream);
+ spin_lock(&bus->reg_lock);
+ }
+}
+
+irqreturn_t azx_interrupt(int irq, void *dev_id)
+{
+ struct azx *chip = dev_id;
+ struct hdac_bus *bus = azx_bus(chip);
+ u32 status;
+ bool active, handled = false;
+ int repeat = 0; /* count for avoiding endless loop */
+
+ if (azx_has_pm_runtime(chip))
+ if (!pm_runtime_active(chip->card->dev))
+ return IRQ_NONE;
+
+ spin_lock(&bus->reg_lock);
+
+ if (chip->disabled)
+ goto unlock;
+
+ do {
+ status = azx_readl(chip, INTSTS);
+ if (status == 0 || status == 0xffffffff)
+ break;
+
+ handled = true;
+ active = false;
+ if (snd_hdac_bus_handle_stream_irq(bus, status, stream_update))
+ active = true;
+
+ status = azx_readb(chip, RIRBSTS);
+ if (status & RIRB_INT_MASK) {
+ /*
+ * Clearing the interrupt status here ensures that no
+ * interrupt gets masked after the RIRB wp is read in
+ * snd_hdac_bus_update_rirb. This avoids a possible
+ * race condition where codec response in RIRB may
+ * remain unserviced by IRQ, eventually falling back
+ * to polling mode in azx_rirb_get_response.
+ */
+ azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+ active = true;
+ if (status & RIRB_INT_RESPONSE) {
+ if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND)
+ udelay(80);
+ snd_hdac_bus_update_rirb(bus);
+ }
+ }
+ } while (active && ++repeat < 10);
+
+ unlock:
+ spin_unlock(&bus->reg_lock);
+
+ return IRQ_RETVAL(handled);
+}
+EXPORT_SYMBOL_GPL(azx_interrupt);
+
+/*
+ * Codec initerface
+ */
+
+/*
+ * Probe the given codec address
+ */
+static int probe_codec(struct azx *chip, int addr)
+{
+ unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+ (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+ struct hdac_bus *bus = azx_bus(chip);
+ int err;
+ unsigned int res = -1;
+
+ mutex_lock(&bus->cmd_mutex);
+ chip->probing = 1;
+ azx_send_cmd(bus, cmd);
+ err = azx_get_response(bus, addr, &res);
+ chip->probing = 0;
+ mutex_unlock(&bus->cmd_mutex);
+ if (err < 0 || res == -1)
+ return -EIO;
+ dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr);
+ return 0;
+}
+
+void snd_hda_bus_reset(struct hda_bus *bus)
+{
+ struct azx *chip = bus_to_azx(&bus->core);
+
+ bus->in_reset = 1;
+ azx_stop_chip(chip);
+ azx_init_chip(chip, true);
+ if (bus->core.chip_init)
+ snd_hda_bus_reset_codecs(bus);
+ bus->in_reset = 0;
+}
+
+/* HD-audio bus initialization */
+int azx_bus_init(struct azx *chip, const char *model)
+{
+ struct hda_bus *bus = &chip->bus;
+ int err;
+
+ err = snd_hdac_bus_init(&bus->core, chip->card->dev, &bus_core_ops);
+ if (err < 0)
+ return err;
+
+ bus->card = chip->card;
+ mutex_init(&bus->prepare_mutex);
+ bus->pci = chip->pci;
+ bus->modelname = model;
+ bus->mixer_assigned = -1;
+ bus->core.snoop = azx_snoop(chip);
+ if (chip->get_position[0] != azx_get_pos_lpib ||
+ chip->get_position[1] != azx_get_pos_lpib)
+ bus->core.use_posbuf = true;
+ bus->core.bdl_pos_adj = chip->bdl_pos_adj;
+ if (chip->driver_caps & AZX_DCAPS_CORBRP_SELF_CLEAR)
+ bus->core.corbrp_self_clear = true;
+
+ if (chip->driver_caps & AZX_DCAPS_4K_BDLE_BOUNDARY)
+ bus->core.align_bdle_4k = true;
+
+ if (chip->driver_caps & AZX_DCAPS_PIO_COMMANDS)
+ bus->core.use_pio_for_commands = true;
+
+ /* enable sync_write flag for stable communication as default */
+ bus->core.sync_write = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_bus_init);
+
+/* Probe codecs */
+int azx_probe_codecs(struct azx *chip, unsigned int max_slots)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ int c, codecs, err;
+
+ codecs = 0;
+ if (!max_slots)
+ max_slots = AZX_DEFAULT_CODECS;
+
+ /* First try to probe all given codec slots */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ if (probe_codec(chip, c) < 0) {
+ /* Some BIOSen give you wrong codec addresses
+ * that don't exist
+ */
+ dev_warn(chip->card->dev,
+ "Codec #%d probe error; disabling it...\n", c);
+ bus->codec_mask &= ~(1 << c);
+ /* no codecs */
+ if (bus->codec_mask == 0)
+ break;
+ /* More badly, accessing to a non-existing
+ * codec often screws up the controller chip,
+ * and disturbs the further communications.
+ * Thus if an error occurs during probing,
+ * better to reset the controller chip to
+ * get back to the sanity state.
+ */
+ azx_stop_chip(chip);
+ azx_init_chip(chip, true);
+ }
+ }
+ }
+
+ /* Then create codec instances */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+ struct hda_codec *codec;
+ err = snd_hda_codec_new(&chip->bus, chip->card, c, &codec);
+ if (err < 0)
+ continue;
+ codec->jackpoll_interval = chip->jackpoll_interval;
+ codec->beep_mode = chip->beep_mode;
+ codec->ctl_dev_id = chip->ctl_dev_id;
+ codecs++;
+ }
+ }
+ if (!codecs) {
+ dev_err(chip->card->dev, "no codecs initialized\n");
+ return -ENXIO;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_probe_codecs);
+
+/* configure each codec instance */
+int azx_codec_configure(struct azx *chip)
+{
+ struct hda_codec *codec, *next;
+ int success = 0;
+
+ list_for_each_codec(codec, &chip->bus) {
+ if (!snd_hda_codec_configure(codec))
+ success++;
+ }
+
+ if (success) {
+ /* unregister failed codecs if any codec has been probed */
+ list_for_each_codec_safe(codec, next, &chip->bus) {
+ if (!codec->configured) {
+ codec_err(codec, "Unable to configure, disabling\n");
+ snd_hdac_device_unregister(&codec->core);
+ }
+ }
+ }
+
+ return success ? 0 : -ENODEV;
+}
+EXPORT_SYMBOL_GPL(azx_codec_configure);
+
+static int stream_direction(struct azx *chip, unsigned char index)
+{
+ if (index >= chip->capture_index_offset &&
+ index < chip->capture_index_offset + chip->capture_streams)
+ return SNDRV_PCM_STREAM_CAPTURE;
+ return SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+/* initialize SD streams */
+int azx_init_streams(struct azx *chip)
+{
+ int i;
+ int stream_tags[2] = { 0, 0 };
+
+ /* initialize each stream (aka device)
+ * assign the starting bdl address to each stream (device)
+ * and initialize
+ */
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = kzalloc(sizeof(*azx_dev), GFP_KERNEL);
+ int dir, tag;
+
+ if (!azx_dev)
+ return -ENOMEM;
+
+ dir = stream_direction(chip, i);
+ /* stream tag must be unique throughout
+ * the stream direction group,
+ * valid values 1...15
+ * use separate stream tag if the flag
+ * AZX_DCAPS_SEPARATE_STREAM_TAG is used
+ */
+ if (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG)
+ tag = ++stream_tags[dir];
+ else
+ tag = i + 1;
+ snd_hdac_stream_init(azx_bus(chip), azx_stream(azx_dev),
+ i, dir, tag);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(azx_init_streams);
+
+void azx_free_streams(struct azx *chip)
+{
+ struct hdac_bus *bus = azx_bus(chip);
+ struct hdac_stream *s;
+
+ while (!list_empty(&bus->stream_list)) {
+ s = list_first_entry(&bus->stream_list, struct hdac_stream, list);
+ list_del(&s->list);
+ kfree(stream_to_azx_dev(s));
+ }
+}
+EXPORT_SYMBOL_GPL(azx_free_streams);
diff --git a/sound/hda/common/controller_trace.h b/sound/hda/common/controller_trace.h
new file mode 100644
index 000000000000..7f5841f8919e
--- /dev/null
+++ b/sound/hda/common/controller_trace.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM hda_controller
+#define TRACE_INCLUDE_FILE controller_trace
+
+#if !defined(_TRACE_HDA_CONTROLLER_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_HDA_CONTROLLER_H
+
+#include <linux/tracepoint.h>
+
+struct azx;
+struct azx_dev;
+
+TRACE_EVENT(azx_pcm_trigger,
+
+ TP_PROTO(struct azx *chip, struct azx_dev *dev, int cmd),
+
+ TP_ARGS(chip, dev, cmd),
+
+ TP_STRUCT__entry(
+ __field( int, card )
+ __field( int, idx )
+ __field( int, cmd )
+ ),
+
+ TP_fast_assign(
+ __entry->card = (chip)->card->number;
+ __entry->idx = (dev)->core.index;
+ __entry->cmd = cmd;
+ ),
+
+ TP_printk("[%d:%d] cmd=%d", __entry->card, __entry->idx, __entry->cmd)
+);
+
+TRACE_EVENT(azx_get_position,
+
+ TP_PROTO(struct azx *chip, struct azx_dev *dev, unsigned int pos, unsigned int delay),
+
+ TP_ARGS(chip, dev, pos, delay),
+
+ TP_STRUCT__entry(
+ __field( int, card )
+ __field( int, idx )
+ __field( unsigned int, pos )
+ __field( unsigned int, delay )
+ ),
+
+ TP_fast_assign(
+ __entry->card = (chip)->card->number;
+ __entry->idx = (dev)->core.index;
+ __entry->pos = pos;
+ __entry->delay = delay;
+ ),
+
+ TP_printk("[%d:%d] pos=%u, delay=%u", __entry->card, __entry->idx, __entry->pos, __entry->delay)
+);
+
+DECLARE_EVENT_CLASS(azx_pcm,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+
+ TP_ARGS(chip, azx_dev),
+
+ TP_STRUCT__entry(
+ __field( unsigned char, stream_tag )
+ ),
+
+ TP_fast_assign(
+ __entry->stream_tag = (azx_dev)->core.stream_tag;
+ ),
+
+ TP_printk("stream_tag: %d", __entry->stream_tag)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_open,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_close,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_hw_params,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+DEFINE_EVENT(azx_pcm, azx_pcm_prepare,
+ TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
+ TP_ARGS(chip, azx_dev)
+);
+
+#endif /* _TRACE_HDA_CONTROLLER_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h
new file mode 100644
index 000000000000..87af3d8c02f7
--- /dev/null
+++ b/sound/hda/common/hda_auto_parser.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * BIOS auto-parser helper functions for HD-audio
+ *
+ * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_AUTO_PARSER_H
+#define __SOUND_HDA_AUTO_PARSER_H
+
+#include "hda_local.h"
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+enum {
+ AUTO_PIN_MIC,
+ AUTO_PIN_LINE_IN,
+ AUTO_PIN_CD,
+ AUTO_PIN_AUX,
+ AUTO_PIN_LAST
+};
+
+enum {
+ AUTO_PIN_LINE_OUT,
+ AUTO_PIN_SPEAKER_OUT,
+ AUTO_PIN_HP_OUT
+};
+
+#define AUTO_CFG_MAX_OUTS HDA_MAX_OUTS
+#define AUTO_CFG_MAX_INS 18
+
+struct auto_pin_cfg_item {
+ hda_nid_t pin;
+ int type;
+ unsigned int is_headset_mic:1;
+ unsigned int is_headphone_mic:1; /* Mic-only in headphone jack */
+ unsigned int has_boost_on_pin:1;
+ int order;
+};
+
+struct auto_pin_cfg;
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg,
+ int input);
+int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ char *label, int maxlen, int *indexp);
+
+enum {
+ INPUT_PIN_ATTR_UNUSED, /* pin not connected */
+ INPUT_PIN_ATTR_INT, /* internal mic/line-in */
+ INPUT_PIN_ATTR_DOCK, /* docking mic/line-in */
+ INPUT_PIN_ATTR_NORMAL, /* mic/line-in jack */
+ INPUT_PIN_ATTR_REAR, /* mic/line-in jack in rear */
+ INPUT_PIN_ATTR_FRONT, /* mic/line-in jack in front */
+ INPUT_PIN_ATTR_LAST = INPUT_PIN_ATTR_FRONT,
+};
+
+int snd_hda_get_input_pin_attr(unsigned int def_conf);
+
+struct auto_pin_cfg {
+ int line_outs;
+ /* sorted in the order of Front/Surr/CLFE/Side */
+ hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS];
+ int speaker_outs;
+ hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS];
+ int hp_outs;
+ int line_out_type; /* AUTO_PIN_XXX_OUT */
+ hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS];
+ int num_inputs;
+ struct auto_pin_cfg_item inputs[AUTO_CFG_MAX_INS];
+ int dig_outs;
+ hda_nid_t dig_out_pins[2];
+ hda_nid_t dig_in_pin;
+ hda_nid_t mono_out_pin;
+ int dig_out_type[2]; /* HDA_PCM_TYPE_XXX */
+ int dig_in_type; /* HDA_PCM_TYPE_XXX */
+};
+
+/* bit-flags for snd_hda_parse_pin_def_config() behavior */
+#define HDA_PINCFG_NO_HP_FIXUP (1 << 0) /* no HP-split */
+#define HDA_PINCFG_NO_LO_FIXUP (1 << 1) /* don't take other outs as LO */
+#define HDA_PINCFG_HEADSET_MIC (1 << 2) /* Try to find headset mic; mark seq number as 0xc to trigger */
+#define HDA_PINCFG_HEADPHONE_MIC (1 << 3) /* Try to find headphone mic; mark seq number as 0xd to trigger */
+
+int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg,
+ const hda_nid_t *ignore_nids,
+ unsigned int cond_flags);
+
+/* older function */
+#define snd_hda_parse_pin_def_config(codec, cfg, ignore) \
+ snd_hda_parse_pin_defcfg(codec, cfg, ignore, 0)
+
+static inline int auto_cfg_hp_outs(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
+ cfg->line_outs : cfg->hp_outs;
+}
+static inline const hda_nid_t *auto_cfg_hp_pins(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
+ cfg->line_out_pins : cfg->hp_pins;
+}
+static inline int auto_cfg_speaker_outs(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
+ cfg->line_outs : cfg->speaker_outs;
+}
+static inline const hda_nid_t *auto_cfg_speaker_pins(const struct auto_pin_cfg *cfg)
+{
+ return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
+ cfg->line_out_pins : cfg->speaker_pins;
+}
+
+#endif /* __SOUND_HDA_AUTO_PARSER_H */
diff --git a/sound/hda/common/hda_beep.h b/sound/hda/common/hda_beep.h
new file mode 100644
index 000000000000..923ea862446a
--- /dev/null
+++ b/sound/hda/common/hda_beep.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ */
+
+#ifndef __SOUND_HDA_BEEP_H
+#define __SOUND_HDA_BEEP_H
+
+#include <sound/hda_codec.h>
+
+#define HDA_BEEP_MODE_OFF 0
+#define HDA_BEEP_MODE_ON 1
+
+/* beep information */
+struct hda_beep {
+ struct input_dev *dev;
+ struct hda_codec *codec;
+ char phys[32];
+ int tone;
+ hda_nid_t nid;
+ unsigned int registered:1;
+ unsigned int enabled:1;
+ unsigned int linear_tone:1; /* linear tone for IDT/STAC codec */
+ unsigned int playing:1;
+ unsigned int keep_power_at_enable:1; /* set by driver */
+ struct work_struct beep_work; /* scheduled task for beep event */
+ void (*power_hook)(struct hda_beep *beep, bool on);
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable);
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid);
+void snd_hda_detach_beep_device(struct hda_codec *codec);
+#else
+static inline int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
+{
+ return 0;
+}
+static inline void snd_hda_detach_beep_device(struct hda_codec *codec)
+{
+}
+#endif
+#endif
diff --git a/sound/hda/common/hda_controller.h b/sound/hda/common/hda_controller.h
new file mode 100644
index 000000000000..c2d0109866e6
--- /dev/null
+++ b/sound/hda/common/hda_controller.h
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Common functionality for the alsa driver code base for HD Audio.
+ */
+
+#ifndef __SOUND_HDA_CONTROLLER_H
+#define __SOUND_HDA_CONTROLLER_H
+
+#include <linux/timecounter.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/hda_codec.h>
+#include <sound/hda_register.h>
+
+#define AZX_MAX_CODECS HDA_MAX_CODECS
+#define AZX_DEFAULT_CODECS 4
+
+/* driver quirks (capabilities) */
+/* bits 0-7 are used for indicating driver type */
+#define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */
+#define AZX_DCAPS_NO_MSI (1 << 9) /* No MSI support */
+#define AZX_DCAPS_SNOOP_MASK (3 << 10) /* snoop type mask */
+#define AZX_DCAPS_SNOOP_OFF (1 << 12) /* snoop default off */
+#ifdef CONFIG_SND_HDA_I915
+#define AZX_DCAPS_I915_COMPONENT (1 << 13) /* bind with i915 gfx */
+#else
+#define AZX_DCAPS_I915_COMPONENT 0 /* NOP */
+#endif
+/* 14 unused */
+#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */
+#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */
+#define AZX_DCAPS_AMD_WORKAROUND (1 << 17) /* AMD-specific workaround */
+#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */
+/* 19 unused */
+#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */
+#define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21) /* no buffer size alignment */
+/* 22 unused */
+#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */
+/* 24 unused */
+#define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */
+#define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */
+#define AZX_DCAPS_RETRY_PROBE (1 << 27) /* retry probe if no codec is configured */
+#define AZX_DCAPS_CORBRP_SELF_CLEAR (1 << 28) /* CORBRP clears itself after reset */
+#define AZX_DCAPS_NO_MSI64 (1 << 29) /* Stick to 32-bit MSIs */
+#define AZX_DCAPS_SEPARATE_STREAM_TAG (1 << 30) /* capture and playback use separate stream tag */
+#define AZX_DCAPS_PIO_COMMANDS (1 << 31) /* Use PIO instead of CORB for commands */
+
+enum {
+ AZX_SNOOP_TYPE_NONE,
+ AZX_SNOOP_TYPE_SCH,
+ AZX_SNOOP_TYPE_ATI,
+ AZX_SNOOP_TYPE_NVIDIA,
+};
+
+struct azx_dev {
+ struct hdac_stream core;
+
+ unsigned int irq_pending:1;
+ /*
+ * For VIA:
+ * A flag to ensure DMA position is 0
+ * when link position is not greater than FIFO size
+ */
+ unsigned int insufficient:1;
+};
+
+#define azx_stream(dev) (&(dev)->core)
+#define stream_to_azx_dev(s) container_of(s, struct azx_dev, core)
+
+struct azx;
+
+/* Functions to read/write to hda registers. */
+struct hda_controller_ops {
+ /* Disable msi if supported, PCI only */
+ int (*disable_msi_reset_irq)(struct azx *);
+ /* Check if current position is acceptable */
+ int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
+ /* enable/disable the link power */
+ int (*link_power)(struct azx *chip, bool enable);
+};
+
+struct azx_pcm {
+ struct azx *chip;
+ struct snd_pcm *pcm;
+ struct hda_codec *codec;
+ struct hda_pcm *info;
+ struct list_head list;
+};
+
+typedef unsigned int (*azx_get_pos_callback_t)(struct azx *, struct azx_dev *);
+typedef int (*azx_get_delay_callback_t)(struct azx *, struct azx_dev *, unsigned int pos);
+
+struct azx {
+ struct hda_bus bus;
+
+ struct snd_card *card;
+ struct pci_dev *pci;
+ int dev_index;
+
+ /* chip type specific */
+ int driver_type;
+ unsigned int driver_caps;
+ int playback_streams;
+ int playback_index_offset;
+ int capture_streams;
+ int capture_index_offset;
+ int num_streams;
+ int jackpoll_interval; /* jack poll interval in jiffies */
+
+ /* Register interaction. */
+ const struct hda_controller_ops *ops;
+
+ /* position adjustment callbacks */
+ azx_get_pos_callback_t get_position[2];
+ azx_get_delay_callback_t get_delay[2];
+
+ /* locks */
+ struct mutex open_mutex; /* Prevents concurrent open/close operations */
+
+ /* PCM */
+ struct list_head pcm_list; /* azx_pcm list */
+
+ /* HD codec */
+ int codec_probe_mask; /* copied from probe_mask option */
+ unsigned int beep_mode;
+ bool ctl_dev_id;
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ const struct firmware *fw;
+#endif
+
+ /* flags */
+ int bdl_pos_adj;
+ unsigned int running:1;
+ unsigned int fallback_to_single_cmd:1;
+ unsigned int single_cmd:1;
+ unsigned int msi:1;
+ unsigned int probing:1; /* codec probing phase */
+ unsigned int snoop:1;
+ unsigned int uc_buffer:1; /* non-cached pages for stream buffers */
+ unsigned int align_buffer_size:1;
+ unsigned int disabled:1; /* disabled by vga_switcheroo */
+ unsigned int pm_prepared:1;
+
+ /* GTS present */
+ unsigned int gts_present:1;
+
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+ struct azx_dev saved_azx_dev;
+#endif
+};
+
+#define azx_bus(chip) (&(chip)->bus.core)
+#define bus_to_azx(_bus) container_of(_bus, struct azx, bus.core)
+
+static inline bool azx_snoop(struct azx *chip)
+{
+ return !IS_ENABLED(CONFIG_X86) || chip->snoop;
+}
+
+/*
+ * macros for easy use
+ */
+
+#define azx_writel(chip, reg, value) \
+ snd_hdac_chip_writel(azx_bus(chip), reg, value)
+#define azx_readl(chip, reg) \
+ snd_hdac_chip_readl(azx_bus(chip), reg)
+#define azx_writew(chip, reg, value) \
+ snd_hdac_chip_writew(azx_bus(chip), reg, value)
+#define azx_readw(chip, reg) \
+ snd_hdac_chip_readw(azx_bus(chip), reg)
+#define azx_writeb(chip, reg, value) \
+ snd_hdac_chip_writeb(azx_bus(chip), reg, value)
+#define azx_readb(chip, reg) \
+ snd_hdac_chip_readb(azx_bus(chip), reg)
+
+#define azx_has_pm_runtime(chip) \
+ ((chip)->driver_caps & AZX_DCAPS_PM_RUNTIME)
+
+/* PCM setup */
+static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream)
+{
+ return substream->runtime->private_data;
+}
+unsigned int azx_get_position(struct azx *chip, struct azx_dev *azx_dev);
+unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev);
+unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev);
+
+/* Stream control. */
+void azx_stop_all_streams(struct azx *chip);
+
+/* Allocation functions. */
+#define azx_alloc_stream_pages(chip) \
+ snd_hdac_bus_alloc_stream_pages(azx_bus(chip))
+#define azx_free_stream_pages(chip) \
+ snd_hdac_bus_free_stream_pages(azx_bus(chip))
+
+/* Low level azx interface */
+void azx_init_chip(struct azx *chip, bool full_reset);
+void azx_stop_chip(struct azx *chip);
+#define azx_enter_link_reset(chip) \
+ snd_hdac_bus_enter_link_reset(azx_bus(chip))
+irqreturn_t azx_interrupt(int irq, void *dev_id);
+
+/* Codec interface */
+int azx_bus_init(struct azx *chip, const char *model);
+int azx_probe_codecs(struct azx *chip, unsigned int max_slots);
+int azx_codec_configure(struct azx *chip);
+int azx_init_streams(struct azx *chip);
+void azx_free_streams(struct azx *chip);
+
+#endif /* __SOUND_HDA_CONTROLLER_H */
diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h
new file mode 100644
index 000000000000..ff7d289c034b
--- /dev/null
+++ b/sound/hda/common/hda_jack.h
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Jack-detection handling for HD-audio
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_JACK_H
+#define __SOUND_HDA_JACK_H
+
+#include <linux/err.h>
+#include <sound/jack.h>
+
+struct auto_pin_cfg;
+struct hda_jack_tbl;
+struct hda_jack_callback;
+
+typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callback *);
+
+struct hda_jack_callback {
+ hda_nid_t nid;
+ int dev_id;
+ hda_jack_callback_fn func;
+ unsigned int private_data; /* arbitrary data */
+ unsigned int unsol_res; /* unsolicited event bits */
+ struct hda_jack_tbl *jack; /* associated jack entry */
+ struct hda_jack_callback *next;
+};
+
+struct hda_jack_tbl {
+ hda_nid_t nid;
+ int dev_id;
+ unsigned char tag; /* unsol event tag */
+ struct hda_jack_callback *callback;
+ /* jack-detection stuff */
+ unsigned int pin_sense; /* cached pin-sense value */
+ unsigned int jack_detect:1; /* capable of jack-detection? */
+ unsigned int jack_dirty:1; /* needs to update? */
+ unsigned int phantom_jack:1; /* a fixed, always present port? */
+ unsigned int block_report:1; /* in a transitional state - do not report to userspace */
+ hda_nid_t gating_jack; /* valid when gating jack plugged */
+ hda_nid_t gated_jack; /* gated is dependent on this jack */
+ hda_nid_t key_report_jack; /* key reports to this jack */
+ int type;
+ int button_state;
+ struct snd_jack *jack;
+};
+
+struct hda_jack_keymap {
+ enum snd_jack_types type;
+ int key;
+};
+
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id);
+
+/**
+ * snd_hda_jack_tbl_get - query the jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to refer to
+ */
+static inline struct hda_jack_tbl *
+snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_tbl_get_mst(codec, nid, 0);
+}
+
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
+ unsigned char tag, int dev_id);
+
+void snd_hda_jack_tbl_disconnect(struct hda_codec *codec);
+void snd_hda_jack_tbl_clear(struct hda_codec *codec);
+
+void snd_hda_jack_set_dirty_all(struct hda_codec *codec);
+
+int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id);
+
+struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, hda_jack_callback_fn func);
+
+/**
+ * snd_hda_jack_detect_enable - enable the jack-detection
+ * @codec: the HDA codec
+ * @nid: pin NID to enable
+ * @func: callback function to register
+ *
+ * In the case of error, the return value will be a pointer embedded with
+ * errno. Check and handle the return value appropriately with standard
+ * macros such as @IS_ERR() and @PTR_ERR().
+ */
+static inline struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
+ hda_jack_callback_fn cb)
+{
+ return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb);
+}
+
+int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
+ hda_nid_t gating_nid);
+
+int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid,
+ const struct hda_jack_keymap *keymap,
+ hda_nid_t jack_nid);
+
+void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid,
+ int button_state);
+
+u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id);
+
+/* the jack state returned from snd_hda_jack_detect_state() */
+enum {
+ HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM,
+};
+
+int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id);
+
+/**
+ * snd_hda_jack_detect_state - query pin Presence Detect status
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ *
+ * Query and return the pin's Presence Detect status, as either
+ * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
+ */
+static inline int
+snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_detect_state_mst(codec, nid, 0);
+}
+
+/**
+ * snd_hda_jack_detect_mst - Detect the jack
+ * @codec: the HDA codec
+ * @nid: pin NID to check jack detection
+ * @dev_id: pin device entry id
+ */
+static inline bool
+snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ return snd_hda_jack_detect_state_mst(codec, nid, dev_id) !=
+ HDA_JACK_NOT_PRESENT;
+}
+
+/**
+ * snd_hda_jack_detect - Detect the jack
+ * @codec: the HDA codec
+ * @nid: pin NID to check jack detection
+ */
+static inline bool
+snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_jack_detect_mst(codec, nid, 0);
+}
+
+bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
+
+int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap);
+
+/**
+ * snd_hda_jack_add_kctl - Add a kctl for the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @name: string name for the jack
+ * @phantom_jack: flag to deal as a phantom jack
+ * @type: jack type bits to be reported, 0 for guessing from pincfg
+ * @keymap: optional jack / key mapping
+ *
+ * This assigns a jack-detection kctl to the given pin. The kcontrol
+ * will have the given name and index.
+ */
+static inline int
+snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
+ const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap)
+{
+ return snd_hda_jack_add_kctl_mst(codec, nid, 0,
+ name, phantom_jack, type, keymap);
+}
+
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg);
+
+void snd_hda_jack_report_sync(struct hda_codec *codec);
+
+void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res);
+
+void snd_hda_jack_poll_all(struct hda_codec *codec);
+
+#endif /* __SOUND_HDA_JACK_H */
diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h
new file mode 100644
index 000000000000..a7e53277a0fe
--- /dev/null
+++ b/sound/hda/common/hda_local.h
@@ -0,0 +1,725 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Local helper functions
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SOUND_HDA_LOCAL_H
+#define __SOUND_HDA_LOCAL_H
+
+#include <sound/pcm_drm_eld.h>
+
+/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
+ * the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
+ * snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
+ *
+ * Note that the subdevice field is cleared again before the real registration
+ * in snd_hda_ctl_add(), so that this value won't appear in the outside.
+ */
+#define HDA_SUBDEV_NID_FLAG (1U << 31)
+#define HDA_SUBDEV_AMP_FLAG (1U << 30)
+
+/*
+ * for mixer controls
+ */
+#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \
+ ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
+#define HDA_AMP_VAL_MIN_MUTE (1<<29)
+#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
+ HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
+/* mono volume with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+ .info = snd_hda_mixer_amp_volume_info, \
+ .get = snd_hda_mixer_amp_volume_get, \
+ .put = snd_hda_mixer_amp_volume_put, \
+ .tlv = { .c = snd_hda_mixer_amp_tlv }, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags }
+/* stereo volume with index */
+#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0)
+/* mono volume */
+#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0)
+/* stereo volume */
+#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
+/* stereo volume with min=mute */
+#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \
+ HDA_AMP_VAL_MIN_MUTE)
+/* mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = snd_hda_mixer_amp_switch_get, \
+ .put = snd_hda_mixer_amp_switch_put, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+/* stereo mute switch with index */
+#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
+/* mono mute switch */
+#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* stereo mute switch */
+#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .subdevice = HDA_SUBDEV_AMP_FLAG, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = snd_hda_mixer_amp_switch_get_beep, \
+ .put = snd_hda_mixer_amp_switch_put_beep, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+#else
+/* no digital beep - just the standard one */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir)
+#endif /* CONFIG_SND_HDA_INPUT_BEEP */
+/* special beep mono mute switch */
+#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* special beep stereo mute switch */
+#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \
+ HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction)
+
+extern const char *snd_hda_pcm_type_name[];
+
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv);
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+#endif
+/* lowlevel accessor with caching; use carefully */
+#define snd_hda_codec_amp_read(codec, nid, ch, dir, idx) \
+ snd_hdac_regmap_get_amp(&(codec)->core, nid, ch, dir, idx)
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
+ int ch, int dir, int idx, int mask, int val);
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int direction, int idx, int mask, int val);
+int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
+ int direction, int idx, int mask, int val);
+int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx, int mask, int val);
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int *tlv);
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name);
+int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+ unsigned int *tlv, const char * const *followers,
+ const char *suffix, bool init_follower_vol,
+ unsigned int access, struct snd_kcontrol **ctl_ret);
+#define snd_hda_add_vmaster(codec, name, tlv, followers, suffix, access) \
+ __snd_hda_add_vmaster(codec, name, tlv, followers, suffix, true, access, NULL)
+int snd_hda_codec_reset(struct hda_codec *codec);
+void snd_hda_codec_disconnect_pcms(struct hda_codec *codec);
+
+#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
+
+struct hda_vmaster_mute_hook {
+ /* below two fields must be filled by the caller of
+ * snd_hda_add_vmaster_hook() beforehand
+ */
+ struct snd_kcontrol *sw_kctl;
+ void (*hook)(void *, int);
+ /* below are initialized automatically */
+ struct hda_codec *codec;
+};
+
+int snd_hda_add_vmaster_hook(struct hda_codec *codec,
+ struct hda_vmaster_mute_hook *hook);
+void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook);
+
+/* amp value bits */
+#define HDA_AMP_MUTE 0x80
+#define HDA_AMP_UNMUTE 0x00
+#define HDA_AMP_VOLMASK 0x7f
+
+/*
+ * SPDIF I/O
+ */
+int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
+ hda_nid_t associated_nid,
+ hda_nid_t cvt_nid, int type);
+#define snd_hda_create_spdif_out_ctls(codec, anid, cnid) \
+ snd_hda_create_dig_out_ctls(codec, anid, cnid, HDA_PCM_TYPE_SPDIF)
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
+
+/*
+ * input MUX helper
+ */
+#define HDA_MAX_NUM_INPUTS 36
+struct hda_input_mux_item {
+ char label[32];
+ unsigned int index;
+};
+struct hda_input_mux {
+ unsigned int num_items;
+ struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
+};
+
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+ struct snd_ctl_elem_info *uinfo);
+int snd_hda_input_mux_put(struct hda_codec *codec,
+ const struct hda_input_mux *imux,
+ struct snd_ctl_elem_value *ucontrol, hda_nid_t nid,
+ unsigned int *cur_val);
+int snd_hda_add_imux_item(struct hda_codec *codec,
+ struct hda_input_mux *imux, const char *label,
+ int index, int *type_idx);
+
+/*
+ * Multi-channel / digital-out PCM helper
+ */
+
+enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
+enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
+
+#define HDA_MAX_OUTS 5
+
+struct hda_multi_out {
+ int num_dacs; /* # of DACs, must be more than 1 */
+ const hda_nid_t *dac_nids; /* DAC list */
+ hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
+ hda_nid_t hp_out_nid[HDA_MAX_OUTS]; /* DACs for multiple HPs */
+ hda_nid_t extra_out_nid[HDA_MAX_OUTS]; /* other (e.g. speaker) DACs */
+ hda_nid_t dig_out_nid; /* digital out audio widget */
+ const hda_nid_t *follower_dig_outs;
+ int max_channels; /* currently supported analog channels */
+ int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
+ int no_share_stream; /* don't share a stream with multiple pins */
+ int share_spdif; /* share SPDIF pin */
+ /* PCM information for both analog and SPDIF DACs */
+ unsigned int analog_rates;
+ unsigned int analog_maxbps;
+ u64 analog_formats;
+ unsigned int spdif_rates;
+ unsigned int spdif_maxbps;
+ u64 spdif_formats;
+};
+
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream);
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ struct snd_pcm_substream *substream,
+ struct hda_pcm_stream *hinfo);
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+ struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream);
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+ struct hda_multi_out *mout);
+
+/*
+ * generic proc interface
+ */
+#ifdef CONFIG_SND_PROC_FS
+int snd_hda_codec_proc_new(struct hda_codec *codec);
+#else
+static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
+#endif
+
+#define SND_PRINT_BITS_ADVISED_BUFSIZE 16
+void snd_print_pcm_bits(int pcm, char *buf, int buflen);
+
+/*
+ * Misc
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec,
+ const struct snd_kcontrol_new *knew);
+
+/*
+ * Fix-up pin default configurations and add default verbs
+ */
+
+struct hda_pintbl {
+ hda_nid_t nid;
+ u32 val;
+};
+
+struct hda_model_fixup {
+ const int id;
+ const char *name;
+};
+
+struct hda_fixup {
+ int type;
+ bool chained:1; /* call the chained fixup(s) after this */
+ bool chained_before:1; /* call the chained fixup(s) before this */
+ int chain_id;
+ union {
+ const struct hda_pintbl *pins;
+ const struct hda_verb *verbs;
+ void (*func)(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action);
+ } v;
+};
+
+/*
+ * extended form of snd_pci_quirk:
+ * for PCI SSID matching, use SND_PCI_QUIRK() like before;
+ * for codec SSID matching, use the new HDA_CODEC_QUIRK() instead
+ */
+struct hda_quirk {
+ unsigned short subvendor; /* PCI subvendor ID */
+ unsigned short subdevice; /* PCI subdevice ID */
+ unsigned short subdevice_mask; /* bitmask to match */
+ bool match_codec_ssid; /* match only with codec SSID */
+ int value; /* value */
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ const char *name; /* name of the device (optional) */
+#endif
+};
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
+ { _SND_PCI_QUIRK_ID(vend, dev), .value = (val), .name = (xname),\
+ .match_codec_ssid = true }
+#else
+#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
+ { _SND_PCI_QUIRK_ID(vend, dev), .value = (val), \
+ .match_codec_ssid = true }
+#endif
+
+struct snd_hda_pin_quirk {
+ unsigned int codec; /* Codec vendor/device ID */
+ unsigned short subvendor; /* PCI subvendor ID */
+ const struct hda_pintbl *pins; /* list of matching pins */
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ const char *name;
+#endif
+ int value; /* quirk value */
+};
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+
+#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
+ { .codec = _codec,\
+ .subvendor = _subvendor,\
+ .name = _name,\
+ .value = _value,\
+ .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
+ }
+#else
+
+#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
+ { .codec = _codec,\
+ .subvendor = _subvendor,\
+ .value = _value,\
+ .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
+ }
+
+#endif
+
+#define HDA_FIXUP_ID_NOT_SET -1
+#define HDA_FIXUP_ID_NO_FIXUP -2
+
+/* fixup types */
+enum {
+ HDA_FIXUP_INVALID,
+ HDA_FIXUP_PINS,
+ HDA_FIXUP_VERBS,
+ HDA_FIXUP_FUNC,
+ HDA_FIXUP_PINCTLS,
+};
+
+/* fixup action definitions */
+enum {
+ HDA_FIXUP_ACT_PRE_PROBE,
+ HDA_FIXUP_ACT_PROBE,
+ HDA_FIXUP_ACT_INIT,
+ HDA_FIXUP_ACT_BUILD,
+ HDA_FIXUP_ACT_FREE,
+};
+
+int snd_hda_add_verbs(struct hda_codec *codec, const struct hda_verb *list);
+void snd_hda_apply_verbs(struct hda_codec *codec);
+void snd_hda_apply_pincfgs(struct hda_codec *codec,
+ const struct hda_pintbl *cfg);
+void snd_hda_apply_fixup(struct hda_codec *codec, int action);
+void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth);
+void snd_hda_pick_fixup(struct hda_codec *codec,
+ const struct hda_model_fixup *models,
+ const struct hda_quirk *quirk,
+ const struct hda_fixup *fixlist);
+void snd_hda_pick_pin_fixup(struct hda_codec *codec,
+ const struct snd_hda_pin_quirk *pin_quirk,
+ const struct hda_fixup *fixlist,
+ bool match_all_pins);
+
+/* helper macros to retrieve pin default-config values */
+#define get_defcfg_connect(cfg) \
+ ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
+#define get_defcfg_association(cfg) \
+ ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
+#define get_defcfg_location(cfg) \
+ ((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
+#define get_defcfg_sequence(cfg) \
+ (cfg & AC_DEFCFG_SEQUENCE)
+#define get_defcfg_device(cfg) \
+ ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
+#define get_defcfg_misc(cfg) \
+ ((cfg & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT)
+
+/* amp values */
+#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8))
+#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8))
+#define AMP_OUT_MUTE 0xb080
+#define AMP_OUT_UNMUTE 0xb000
+#define AMP_OUT_ZERO 0xb000
+/* pinctl values */
+#define PIN_IN (AC_PINCTL_IN_EN)
+#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ)
+#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50)
+#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD)
+#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80)
+#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100)
+#define PIN_OUT (AC_PINCTL_OUT_EN)
+#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN)
+#define PIN_HP_AMP (AC_PINCTL_HP_EN)
+
+unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin);
+unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
+ hda_nid_t pin, unsigned int val);
+int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool cached);
+
+/**
+ * _snd_hda_set_pin_ctl - Set a pin-control value safely
+ * @codec: the codec instance
+ * @pin: the pin NID to set the control
+ * @val: the pin-control value (AC_PINCTL_* bits)
+ *
+ * This function sets the pin-control value to the given pin, but
+ * filters out the invalid pin-control bits when the pin has no such
+ * capabilities. For example, when PIN_HP is passed but the pin has no
+ * HP-drive capability, the HP bit is omitted.
+ *
+ * The function doesn't check the input VREF capability bits, though.
+ * Use snd_hda_get_default_vref() to guess the right value.
+ * Also, this function is only for analog pins, not for HDMI pins.
+ */
+static inline int
+snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val)
+{
+ return _snd_hda_set_pin_ctl(codec, pin, val, false);
+}
+
+/**
+ * snd_hda_set_pin_ctl_cache - Set a pin-control value safely
+ * @codec: the codec instance
+ * @pin: the pin NID to set the control
+ * @val: the pin-control value (AC_PINCTL_* bits)
+ *
+ * Just like snd_hda_set_pin_ctl() but write to cache as well.
+ */
+static inline int
+snd_hda_set_pin_ctl_cache(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val)
+{
+ return _snd_hda_set_pin_ctl(codec, pin, val, true);
+}
+
+int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int val);
+
+#define for_each_hda_codec_node(nid, codec) \
+ for ((nid) = (codec)->core.start_nid; (nid) < (codec)->core.end_nid; (nid)++)
+
+/* Set the codec power_state flag to indicate to allow unsol event handling;
+ * see hda_codec_unsol_event() in hda_bind.c. Calling this might confuse the
+ * state tracking, so use with care.
+ */
+static inline void snd_hda_codec_allow_unsol_events(struct hda_codec *codec)
+{
+ codec->core.dev.power.power_state = PMSG_ON;
+}
+
+/*
+ * get widget capabilities
+ */
+static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
+{
+ if (nid < codec->core.start_nid ||
+ nid >= codec->core.start_nid + codec->core.num_nodes)
+ return 0;
+ return codec->wcaps[nid - codec->core.start_nid];
+}
+
+/* get the widget type from widget capability bits */
+static inline int get_wcaps_type(unsigned int wcaps)
+{
+ if (!wcaps)
+ return -1; /* invalid type */
+ return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+}
+
+static inline unsigned int get_wcaps_channels(u32 wcaps)
+{
+ unsigned int chans;
+
+ chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
+ chans = ((chans << 1) | 1) + 1;
+
+ return chans;
+}
+
+static inline void snd_hda_override_wcaps(struct hda_codec *codec,
+ hda_nid_t nid, u32 val)
+{
+ if (nid >= codec->core.start_nid &&
+ nid < codec->core.start_nid + codec->core.num_nodes)
+ codec->wcaps[nid - codec->core.start_nid] = val;
+}
+
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction);
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int caps);
+/**
+ * snd_hda_query_pin_caps - Query PIN capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ *
+ * Query PIN capabilities for the given widget.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+static inline u32
+snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
+{
+ return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+
+}
+
+/**
+ * snd_hda_override_pin_caps - Override the pin capabilities
+ * @codec: the CODEC
+ * @nid: the NID to override
+ * @caps: the capability bits to set
+ *
+ * Override the cached PIN capabilitiy bits value by the given one.
+ *
+ * Returns zero if successful or a negative error code.
+ */
+static inline int
+snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int caps)
+{
+ return snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, caps);
+}
+
+bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int bits);
+
+#define nid_has_mute(codec, nid, dir) \
+ snd_hda_check_amp_caps(codec, nid, dir, (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE))
+#define nid_has_volume(codec, nid, dir) \
+ snd_hda_check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS)
+
+
+/* flags for hda_nid_item */
+#define HDA_NID_ITEM_AMP (1<<0)
+
+struct hda_nid_item {
+ struct snd_kcontrol *kctl;
+ unsigned int index;
+ hda_nid_t nid;
+ unsigned short flags;
+};
+
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+ struct snd_kcontrol *kctl);
+void snd_hda_ctls_clear(struct hda_codec *codec);
+
+/*
+ * hwdep interface
+ */
+#ifdef CONFIG_SND_HDA_HWDEP
+int snd_hda_create_hwdep(struct hda_codec *codec);
+#else
+static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
+#endif
+
+void snd_hda_sysfs_init(struct hda_codec *codec);
+void snd_hda_sysfs_clear(struct hda_codec *codec);
+
+extern const struct attribute_group *snd_hda_dev_attr_groups[];
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp);
+#else
+static inline
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+ return NULL;
+}
+
+static inline
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+ return -ENOENT;
+}
+
+static inline
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
+{
+ return -ENOENT;
+}
+#endif
+
+/*
+ * power-management
+ */
+
+void snd_hda_schedule_power_save(struct hda_codec *codec);
+
+struct hda_amp_list {
+ hda_nid_t nid;
+ unsigned char dir;
+ unsigned char idx;
+};
+
+struct hda_loopback_check {
+ const struct hda_amp_list *amplist;
+ int power_on;
+};
+
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+ struct hda_loopback_check *check,
+ hda_nid_t nid);
+
+/* check whether the actual power state matches with the target state */
+static inline bool
+snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int target_state)
+{
+ return snd_hdac_check_power_state(&codec->core, nid, target_state);
+}
+
+static inline unsigned int snd_hda_sync_power_state(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int target_state)
+{
+ return snd_hdac_sync_power_state(&codec->core, nid, target_state);
+}
+unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
+ hda_nid_t nid,
+ unsigned int power_state);
+
+void snd_hda_codec_shutdown(struct hda_codec *codec);
+
+static inline int snd_hda_codec_init(struct hda_codec *codec)
+{
+ struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+
+ if (driver->ops->init)
+ return driver->ops->init(codec);
+ return 0;
+}
+
+/*
+ * AMP control callbacks
+ */
+/* retrieve parameters from private_value */
+#define get_amp_nid_(pv) ((pv) & 0xffff)
+#define get_amp_nid(kc) get_amp_nid_((kc)->private_value)
+#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
+#define get_amp_direction_(pv) (((pv) >> 18) & 0x1)
+#define get_amp_direction(kc) get_amp_direction_((kc)->private_value)
+#define get_amp_index_(pv) (((pv) >> 19) & 0xf)
+#define get_amp_index(kc) get_amp_index_((kc)->private_value)
+#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
+#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1)
+
+/*
+ * enum control helper
+ */
+int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo,
+ int num_items, const char * const *texts);
+#define snd_hda_enum_bool_helper_info(kcontrol, uinfo) \
+ snd_hda_enum_helper_info(kcontrol, uinfo, 0, NULL)
+
+struct hdmi_eld {
+ bool monitor_present;
+ bool eld_valid;
+ int eld_size;
+ char eld_buffer[ELD_MAX_SIZE];
+ struct snd_parsed_hdmi_eld info;
+};
+
+int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
+int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size);
+void snd_hdmi_eld_update_pcm_info(struct snd_parsed_hdmi_eld *e,
+ struct hda_pcm_stream *hinfo);
+
+#ifdef CONFIG_SND_PROC_FS
+void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer,
+ hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid);
+void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
+ struct snd_info_buffer *buffer);
+#endif
+
+#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
+void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
+
+void snd_hda_codec_display_power(struct hda_codec *codec, bool enable);
+
+/*
+ */
+#define codec_err(codec, fmt, args...) \
+ dev_err(hda_codec_dev(codec), fmt, ##args)
+#define codec_warn(codec, fmt, args...) \
+ dev_warn(hda_codec_dev(codec), fmt, ##args)
+#define codec_info(codec, fmt, args...) \
+ dev_info(hda_codec_dev(codec), fmt, ##args)
+#define codec_dbg(codec, fmt, args...) \
+ dev_dbg(hda_codec_dev(codec), fmt, ##args)
+
+#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/sound/hda/common/hwdep.c b/sound/hda/common/hwdep.c
new file mode 100644
index 000000000000..9325e5c3cbe6
--- /dev/null
+++ b/sound/hda/common/hwdep.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HWDEP Interface for HD-audio codec
+ *
+ * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/nospec.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+#include <sound/minors.h>
+
+/*
+ * write/read an out-of-bound verb
+ */
+static int verb_write_ioctl(struct hda_codec *codec,
+ struct hda_verb_ioctl __user *arg)
+{
+ u32 verb, res;
+
+ if (get_user(verb, &arg->verb))
+ return -EFAULT;
+ res = snd_hda_codec_read(codec, verb >> 24, 0,
+ (verb >> 8) & 0xffff, verb & 0xff);
+ if (put_user(res, &arg->res))
+ return -EFAULT;
+ return 0;
+}
+
+static int get_wcap_ioctl(struct hda_codec *codec,
+ struct hda_verb_ioctl __user *arg)
+{
+ u32 verb, res;
+
+ if (get_user(verb, &arg->verb))
+ return -EFAULT;
+ /* open-code get_wcaps(verb>>24) with nospec */
+ verb >>= 24;
+ if (verb < codec->core.start_nid ||
+ verb >= codec->core.start_nid + codec->core.num_nodes) {
+ res = 0;
+ } else {
+ verb -= codec->core.start_nid;
+ verb = array_index_nospec(verb, codec->core.num_nodes);
+ res = codec->wcaps[verb];
+ }
+ if (put_user(res, &arg->res))
+ return -EFAULT;
+ return 0;
+}
+
+
+/*
+ */
+static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct hda_codec *codec = hw->private_data;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case HDA_IOCTL_PVERSION:
+ return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
+ case HDA_IOCTL_VERB_WRITE:
+ return verb_write_ioctl(codec, argp);
+ case HDA_IOCTL_GET_WCAP:
+ return get_wcap_ioctl(codec, argp);
+ }
+ return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+ return 0;
+}
+
+int snd_hda_create_hwdep(struct hda_codec *codec)
+{
+ char hwname[16];
+ struct snd_hwdep *hwdep;
+ int err;
+
+ sprintf(hwname, "HDA Codec %d", codec->addr);
+ err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
+ if (err < 0)
+ return err;
+ codec->hwdep = hwdep;
+ sprintf(hwdep->name, "HDA Codec %d", codec->addr);
+ hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
+ hwdep->private_data = codec;
+ hwdep->exclusive = 1;
+
+ hwdep->ops.open = hda_hwdep_open;
+ hwdep->ops.ioctl = hda_hwdep_ioctl;
+#ifdef CONFIG_COMPAT
+ hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
+#endif
+
+ /* for sysfs */
+ hwdep->dev->groups = snd_hda_dev_attr_groups;
+ dev_set_drvdata(hwdep->dev, codec);
+
+ return 0;
+}
diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c
new file mode 100644
index 000000000000..7d7786df60ea
--- /dev/null
+++ b/sound/hda/common/jack.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Jack-detection handling for HD-audio
+ *
+ * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/jack.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_jack.h"
+
+/**
+ * is_jack_detectable - Check whether the given pin is jack-detectable
+ * @codec: the HDA codec
+ * @nid: pin NID
+ *
+ * Check whether the given pin is capable to report the jack detection.
+ * The jack detection might not work by various reasons, e.g. the jack
+ * detection is prohibited in the codec level, the pin config has
+ * AC_DEFCFG_MISC_NO_PRESENCE bit, no unsol support, etc.
+ */
+bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid)
+{
+ if (codec->no_jack_detect)
+ return false;
+ if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_PRES_DETECT))
+ return false;
+ if (get_defcfg_misc(snd_hda_codec_get_pincfg(codec, nid)) &
+ AC_DEFCFG_MISC_NO_PRESENCE)
+ return false;
+ if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) &&
+ !codec->jackpoll_interval)
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(is_jack_detectable);
+
+/* execute pin sense measurement */
+static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ u32 pincap;
+ u32 val;
+
+ if (!codec->no_trigger_sense) {
+ pincap = snd_hda_query_pin_caps(codec, nid);
+ if (pincap & AC_PINCAP_TRIG_REQ) /* need trigger? */
+ snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_SET_PIN_SENSE, 0);
+ }
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_SENSE, dev_id);
+ if (codec->inv_jack_detect)
+ val ^= AC_PINSENSE_PRESENCE;
+ return val;
+}
+
+/**
+ * snd_hda_jack_tbl_get_mst - query the jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to refer to
+ * @dev_id: pin device entry id
+ */
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!nid || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid == nid && jack->dev_id == dev_id)
+ return jack;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_mst);
+
+/**
+ * snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
+ * @codec: the HDA codec
+ * @tag: tag value to refer to
+ * @dev_id: pin device entry id
+ */
+struct hda_jack_tbl *
+snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
+ unsigned char tag, int dev_id)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!tag || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->tag == tag && jack->dev_id == dev_id)
+ return jack;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
+
+static struct hda_jack_tbl *
+any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ if (!nid || !jack)
+ return NULL;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid == nid)
+ return jack;
+ return NULL;
+}
+
+/**
+ * snd_hda_jack_tbl_new - create a jack-table entry for the given NID
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @dev_id: pin device entry id
+ */
+static struct hda_jack_tbl *
+snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ struct hda_jack_tbl *existing_nid_jack =
+ any_jack_tbl_get_from_nid(codec, nid);
+
+ WARN_ON(dev_id != 0 && !codec->dp_mst);
+
+ if (jack)
+ return jack;
+ jack = snd_array_new(&codec->jacktbl);
+ if (!jack)
+ return NULL;
+ jack->nid = nid;
+ jack->dev_id = dev_id;
+ jack->jack_dirty = 1;
+ if (existing_nid_jack) {
+ jack->tag = existing_nid_jack->tag;
+
+ /*
+ * Copy jack_detect from existing_nid_jack to avoid
+ * snd_hda_jack_detect_enable_callback_mst() making multiple
+ * SET_UNSOLICITED_ENABLE calls on the same pin.
+ */
+ jack->jack_detect = existing_nid_jack->jack_detect;
+ } else {
+ jack->tag = codec->jacktbl.used;
+ }
+
+ return jack;
+}
+
+void snd_hda_jack_tbl_disconnect(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ if (!codec->bus->shutdown && jack->jack)
+ snd_device_disconnect(codec->card, jack->jack);
+ }
+}
+
+void snd_hda_jack_tbl_clear(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ struct hda_jack_callback *cb, *next;
+
+ /* free jack instances manually when clearing/reconfiguring */
+ if (!codec->bus->shutdown && jack->jack)
+ snd_device_free(codec->card, jack->jack);
+
+ for (cb = jack->callback; cb; cb = next) {
+ next = cb->next;
+ kfree(cb);
+ }
+ }
+ snd_array_free(&codec->jacktbl);
+}
+
+#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE)
+
+/* update the cached value and notification flag if needed */
+static void jack_detect_update(struct hda_codec *codec,
+ struct hda_jack_tbl *jack)
+{
+ if (!jack->jack_dirty)
+ return;
+
+ if (jack->phantom_jack)
+ jack->pin_sense = AC_PINSENSE_PRESENCE;
+ else
+ jack->pin_sense = read_pin_sense(codec, jack->nid,
+ jack->dev_id);
+
+ /* A gating jack indicates the jack is invalid if gating is unplugged */
+ if (jack->gating_jack &&
+ !snd_hda_jack_detect_mst(codec, jack->gating_jack, jack->dev_id))
+ jack->pin_sense &= ~AC_PINSENSE_PRESENCE;
+
+ jack->jack_dirty = 0;
+
+ /* If a jack is gated by this one update it. */
+ if (jack->gated_jack) {
+ struct hda_jack_tbl *gated =
+ snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
+ jack->dev_id);
+ if (gated) {
+ gated->jack_dirty = 1;
+ jack_detect_update(codec, gated);
+ }
+ }
+}
+
+/**
+ * snd_hda_jack_set_dirty_all - Mark all the cached as dirty
+ * @codec: the HDA codec
+ *
+ * This function sets the dirty flag to all entries of jack table.
+ * It's called from the resume path in hda_codec.c.
+ */
+void snd_hda_jack_set_dirty_all(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid)
+ jack->jack_dirty = 1;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all);
+
+/**
+ * snd_hda_jack_pin_sense - execute pin sense measurement
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ * @dev_id: pin device entry id
+ *
+ * Execute necessary pin sense measurement and return its Presence Detect,
+ * Impedance, ELD Valid etc. status bits.
+ */
+u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ if (jack) {
+ jack_detect_update(codec, jack);
+ return jack->pin_sense;
+ }
+ return read_pin_sense(codec, nid, dev_id);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_pin_sense);
+
+/**
+ * snd_hda_jack_detect_state_mst - query pin Presence Detect status
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ * @dev_id: pin device entry id
+ *
+ * Query and return the pin's Presence Detect status, as either
+ * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
+ */
+int snd_hda_jack_detect_state_mst(struct hda_codec *codec,
+ hda_nid_t nid, int dev_id)
+{
+ struct hda_jack_tbl *jack =
+ snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
+ if (jack && jack->phantom_jack)
+ return HDA_JACK_PHANTOM;
+ else if (snd_hda_jack_pin_sense(codec, nid, dev_id) &
+ AC_PINSENSE_PRESENCE)
+ return HDA_JACK_PRESENT;
+ else
+ return HDA_JACK_NOT_PRESENT;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state_mst);
+
+static struct hda_jack_callback *
+find_callback_from_list(struct hda_jack_tbl *jack,
+ hda_jack_callback_fn func)
+{
+ struct hda_jack_callback *cb;
+
+ if (!func)
+ return NULL;
+
+ for (cb = jack->callback; cb; cb = cb->next) {
+ if (cb->func == func)
+ return cb;
+ }
+
+ return NULL;
+}
+
+/**
+ * snd_hda_jack_detect_enable_callback_mst - enable the jack-detection
+ * @codec: the HDA codec
+ * @nid: pin NID to enable
+ * @func: callback function to register
+ * @dev_id: pin device entry id
+ *
+ * In the case of error, the return value will be a pointer embedded with
+ * errno. Check and handle the return value appropriately with standard
+ * macros such as @IS_ERR() and @PTR_ERR().
+ */
+struct hda_jack_callback *
+snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, hda_jack_callback_fn func)
+{
+ struct hda_jack_tbl *jack;
+ struct hda_jack_callback *callback = NULL;
+ int err;
+
+ jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
+ if (!jack)
+ return ERR_PTR(-ENOMEM);
+
+ callback = find_callback_from_list(jack, func);
+
+ if (func && !callback) {
+ callback = kzalloc(sizeof(*callback), GFP_KERNEL);
+ if (!callback)
+ return ERR_PTR(-ENOMEM);
+ callback->func = func;
+ callback->nid = jack->nid;
+ callback->dev_id = jack->dev_id;
+ callback->next = jack->callback;
+ jack->callback = callback;
+ }
+
+ if (jack->jack_detect)
+ return callback; /* already registered */
+ jack->jack_detect = 1;
+ if (codec->jackpoll_interval > 0)
+ return callback; /* No unsol if we're polling instead */
+ err = snd_hda_codec_write_cache(codec, nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE,
+ AC_USRSP_EN | jack->tag);
+ if (err < 0)
+ return ERR_PTR(err);
+ return callback;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback_mst);
+
+/**
+ * snd_hda_jack_detect_enable - Enable the jack detection on the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to enable jack detection
+ * @dev_id: pin device entry id
+ *
+ * Enable the jack detection with the default callback. Returns zero if
+ * successful or a negative error code.
+ */
+int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id)
+{
+ return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback_mst(codec,
+ nid,
+ dev_id,
+ NULL));
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
+
+/**
+ * snd_hda_jack_set_gating_jack - Set gating jack.
+ * @codec: the HDA codec
+ * @gated_nid: gated pin NID
+ * @gating_nid: gating pin NID
+ *
+ * Indicates the gated jack is only valid when the gating jack is plugged.
+ */
+int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
+ hda_nid_t gating_nid)
+{
+ struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0);
+ struct hda_jack_tbl *gating =
+ snd_hda_jack_tbl_new(codec, gating_nid, 0);
+
+ WARN_ON(codec->dp_mst);
+
+ if (!gated || !gating)
+ return -EINVAL;
+
+ gated->gating_jack = gating_nid;
+ gating->gated_jack = gated_nid;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_set_gating_jack);
+
+/**
+ * snd_hda_jack_bind_keymap - bind keys generated from one NID to another jack.
+ * @codec: the HDA codec
+ * @key_nid: key event is generated by this pin NID
+ * @keymap: map of key type and key code
+ * @jack_nid: key reports to the jack of this pin NID
+ *
+ * This function is used in the case of key is generated from one NID while is
+ * reported to the jack of another NID.
+ */
+int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid,
+ const struct hda_jack_keymap *keymap,
+ hda_nid_t jack_nid)
+{
+ const struct hda_jack_keymap *map;
+ struct hda_jack_tbl *key_gen = snd_hda_jack_tbl_get(codec, key_nid);
+ struct hda_jack_tbl *report_to = snd_hda_jack_tbl_get(codec, jack_nid);
+
+ WARN_ON(codec->dp_mst);
+
+ if (!key_gen || !report_to || !report_to->jack)
+ return -EINVAL;
+
+ key_gen->key_report_jack = jack_nid;
+
+ if (keymap)
+ for (map = keymap; map->type; map++)
+ snd_jack_set_key(report_to->jack, map->type, map->key);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_bind_keymap);
+
+/**
+ * snd_hda_jack_set_button_state - report button event to the hda_jack_tbl button_state.
+ * @codec: the HDA codec
+ * @jack_nid: the button event reports to the jack_tbl of this NID
+ * @button_state: the button event captured by codec
+ *
+ * Codec driver calls this function to report the button event.
+ */
+void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid,
+ int button_state)
+{
+ struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, jack_nid);
+
+ if (!jack)
+ return;
+
+ if (jack->key_report_jack) {
+ struct hda_jack_tbl *report_to =
+ snd_hda_jack_tbl_get(codec, jack->key_report_jack);
+
+ if (report_to) {
+ report_to->button_state = button_state;
+ return;
+ }
+ }
+
+ jack->button_state = button_state;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state);
+
+/**
+ * snd_hda_jack_report_sync - sync the states of all jacks and report if changed
+ * @codec: the HDA codec
+ */
+void snd_hda_jack_report_sync(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack;
+ int i, state;
+
+ /* update all jacks at first */
+ jack = codec->jacktbl.list;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid)
+ jack_detect_update(codec, jack);
+
+ /* report the updated jacks; it's done after updating all jacks
+ * to make sure that all gating jacks properly have been set
+ */
+ jack = codec->jacktbl.list;
+ for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ if (jack->nid) {
+ if (!jack->jack || jack->block_report)
+ continue;
+ state = jack->button_state;
+ if (get_jack_plug_state(jack->pin_sense))
+ state |= jack->type;
+ snd_jack_report(jack->jack, state);
+ if (jack->button_state) {
+ snd_jack_report(jack->jack,
+ state & ~jack->button_state);
+ jack->button_state = 0; /* button released */
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync);
+
+/* guess the jack type from the pin-config */
+static int get_input_jack_type(struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ return SND_JACK_LINEOUT;
+ case AC_JACK_HP_OUT:
+ return SND_JACK_HEADPHONE;
+ case AC_JACK_SPDIF_OUT:
+ case AC_JACK_DIG_OTHER_OUT:
+ return SND_JACK_AVOUT;
+ case AC_JACK_MIC_IN:
+ return SND_JACK_MICROPHONE;
+ default:
+ return SND_JACK_LINEIN;
+ }
+}
+
+static void hda_free_jack_priv(struct snd_jack *jack)
+{
+ struct hda_jack_tbl *jacks = jack->private_data;
+ jacks->nid = 0;
+ jacks->jack = NULL;
+}
+
+/**
+ * snd_hda_jack_add_kctl_mst - Add a kctl for the given pin
+ * @codec: the HDA codec
+ * @nid: pin NID to assign
+ * @dev_id : pin device entry id
+ * @name: string name for the jack
+ * @phantom_jack: flag to deal as a phantom jack
+ * @type: jack type bits to be reported, 0 for guessing from pincfg
+ * @keymap: optional jack / key mapping
+ *
+ * This assigns a jack-detection kctl to the given pin. The kcontrol
+ * will have the given name and index.
+ */
+int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
+ int dev_id, const char *name, bool phantom_jack,
+ int type, const struct hda_jack_keymap *keymap)
+{
+ struct hda_jack_tbl *jack;
+ const struct hda_jack_keymap *map;
+ int err, state, buttons;
+
+ jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
+ if (!jack)
+ return 0;
+ if (jack->jack)
+ return 0; /* already created */
+
+ if (!type)
+ type = get_input_jack_type(codec, nid);
+
+ buttons = 0;
+ if (keymap) {
+ for (map = keymap; map->type; map++)
+ buttons |= map->type;
+ }
+
+ err = snd_jack_new(codec->card, name, type | buttons,
+ &jack->jack, true, phantom_jack);
+ if (err < 0)
+ return err;
+
+ jack->phantom_jack = !!phantom_jack;
+ jack->type = type;
+ jack->button_state = 0;
+ jack->jack->private_data = jack;
+ jack->jack->private_free = hda_free_jack_priv;
+ if (keymap) {
+ for (map = keymap; map->type; map++)
+ snd_jack_set_key(jack->jack, map->type, map->key);
+ }
+
+ state = snd_hda_jack_detect_mst(codec, nid, dev_id);
+ snd_jack_report(jack->jack, state ? jack->type : 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl_mst);
+
+static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
+ const struct auto_pin_cfg *cfg,
+ const char *base_name)
+{
+ unsigned int def_conf, conn;
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ int err;
+ bool phantom_jack;
+
+ WARN_ON(codec->dp_mst);
+
+ if (!nid)
+ return 0;
+ def_conf = snd_hda_codec_get_pincfg(codec, nid);
+ conn = get_defcfg_connect(def_conf);
+ if (conn == AC_JACK_PORT_NONE)
+ return 0;
+ phantom_jack = (conn != AC_JACK_PORT_COMPLEX) ||
+ !is_jack_detectable(codec, nid);
+
+ if (base_name)
+ strscpy(name, base_name, sizeof(name));
+ else
+ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL);
+ if (phantom_jack)
+ /* Example final name: "Internal Mic Phantom Jack" */
+ strncat(name, " Phantom", sizeof(name) - strlen(name) - 1);
+ err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL);
+ if (err < 0)
+ return err;
+
+ if (!phantom_jack)
+ return snd_hda_jack_detect_enable(codec, nid, 0);
+ return 0;
+}
+
+/**
+ * snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
+ * @codec: the HDA codec
+ * @cfg: pin config table to parse
+ */
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+ const struct auto_pin_cfg *cfg)
+{
+ const hda_nid_t *p;
+ int i, err;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ /* If we have headphone mics; make sure they get the right name
+ before grabbed by output pins */
+ if (cfg->inputs[i].is_headphone_mic) {
+ if (auto_cfg_hp_outs(cfg) == 1)
+ err = add_jack_kctl(codec, auto_cfg_hp_pins(cfg)[0],
+ cfg, "Headphone Mic");
+ else
+ err = add_jack_kctl(codec, cfg->inputs[i].pin,
+ cfg, "Headphone Mic");
+ } else
+ err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg,
+ NULL);
+ if (err < 0)
+ return err;
+ }
+
+ for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) {
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->hp_pins; i < cfg->hp_outs; i++, p++) {
+ if (*p == *cfg->line_out_pins) /* might be duplicated */
+ break;
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->speaker_pins; i < cfg->speaker_outs; i++, p++) {
+ if (*p == *cfg->line_out_pins) /* might be duplicated */
+ break;
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ for (i = 0, p = cfg->dig_out_pins; i < cfg->dig_outs; i++, p++) {
+ err = add_jack_kctl(codec, *p, cfg, NULL);
+ if (err < 0)
+ return err;
+ }
+ err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, NULL);
+ if (err < 0)
+ return err;
+ err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, NULL);
+ if (err < 0)
+ return err;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctls);
+
+static void call_jack_callback(struct hda_codec *codec, unsigned int res,
+ struct hda_jack_tbl *jack)
+{
+ struct hda_jack_callback *cb;
+
+ for (cb = jack->callback; cb; cb = cb->next) {
+ cb->jack = jack;
+ cb->unsol_res = res;
+ cb->func(codec, cb);
+ }
+ if (jack->gated_jack) {
+ struct hda_jack_tbl *gated =
+ snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
+ jack->dev_id);
+ if (gated) {
+ for (cb = gated->callback; cb; cb = cb->next) {
+ cb->jack = gated;
+ cb->unsol_res = res;
+ cb->func(codec, cb);
+ }
+ }
+ }
+}
+
+/**
+ * snd_hda_jack_unsol_event - Handle an unsolicited event
+ * @codec: the HDA codec
+ * @res: the unsolicited event data
+ */
+void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+ struct hda_jack_tbl *event;
+ int tag = (res & AC_UNSOL_RES_TAG) >> AC_UNSOL_RES_TAG_SHIFT;
+
+ if (codec->dp_mst) {
+ int dev_entry =
+ (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
+
+ event = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
+ } else {
+ event = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
+ }
+ if (!event)
+ return;
+
+ if (event->key_report_jack) {
+ struct hda_jack_tbl *report_to =
+ snd_hda_jack_tbl_get_mst(codec, event->key_report_jack,
+ event->dev_id);
+ if (report_to)
+ report_to->jack_dirty = 1;
+ } else
+ event->jack_dirty = 1;
+
+ call_jack_callback(codec, res, event);
+ snd_hda_jack_report_sync(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event);
+
+/**
+ * snd_hda_jack_poll_all - Poll all jacks
+ * @codec: the HDA codec
+ *
+ * Poll all detectable jacks with dirty flag, update the status, call
+ * callbacks and call snd_hda_jack_report_sync() if any changes are found.
+ */
+void snd_hda_jack_poll_all(struct hda_codec *codec)
+{
+ struct hda_jack_tbl *jack = codec->jacktbl.list;
+ int i, changes = 0;
+
+ for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ unsigned int old_sense;
+ if (!jack->nid || !jack->jack_dirty || jack->phantom_jack)
+ continue;
+ old_sense = get_jack_plug_state(jack->pin_sense);
+ jack_detect_update(codec, jack);
+ if (old_sense == get_jack_plug_state(jack->pin_sense))
+ continue;
+ changes = 1;
+ call_jack_callback(codec, 0, jack);
+ }
+ if (changes)
+ snd_hda_jack_report_sync(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all);
+
diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c
new file mode 100644
index 000000000000..00c2eeb2c472
--- /dev/null
+++ b/sound/hda/common/proc.c
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic proc interface
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <linux/module.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+
+static int dump_coef = -1;
+module_param(dump_coef, int, 0644);
+MODULE_PARM_DESC(dump_coef, "Dump processing coefficients in codec proc file (-1=auto, 0=disable, 1=enable)");
+
+/* always use noncached version */
+#define param_read(codec, nid, parm) \
+ snd_hdac_read_parm_uncached(&(codec)->core, nid, parm)
+
+static const char *get_wid_type_name(unsigned int wid_value)
+{
+ static const char * const names[16] = {
+ [AC_WID_AUD_OUT] = "Audio Output",
+ [AC_WID_AUD_IN] = "Audio Input",
+ [AC_WID_AUD_MIX] = "Audio Mixer",
+ [AC_WID_AUD_SEL] = "Audio Selector",
+ [AC_WID_PIN] = "Pin Complex",
+ [AC_WID_POWER] = "Power Widget",
+ [AC_WID_VOL_KNB] = "Volume Knob Widget",
+ [AC_WID_BEEP] = "Beep Generator Widget",
+ [AC_WID_VENDOR] = "Vendor Defined Widget",
+ };
+ if (wid_value == -1)
+ return "UNKNOWN Widget";
+ wid_value &= 0xf;
+ if (names[wid_value])
+ return names[wid_value];
+ else
+ return "UNKNOWN Widget";
+}
+
+static void print_nid_array(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ struct snd_array *array)
+{
+ int i;
+ struct hda_nid_item *items = array->list, *item;
+ struct snd_kcontrol *kctl;
+ for (i = 0; i < array->used; i++) {
+ item = &items[i];
+ if (item->nid == nid) {
+ kctl = item->kctl;
+ snd_iprintf(buffer,
+ " Control: name=\"%s\", index=%i, device=%i\n",
+ kctl->id.name, kctl->id.index + item->index,
+ kctl->id.device);
+ if (item->flags & HDA_NID_ITEM_AMP)
+ snd_iprintf(buffer,
+ " ControlAmp: chs=%lu, dir=%s, "
+ "idx=%lu, ofs=%lu\n",
+ get_amp_channels(kctl),
+ get_amp_direction(kctl) ? "Out" : "In",
+ get_amp_index(kctl),
+ get_amp_offset(kctl));
+ }
+ }
+}
+
+static void print_nid_pcms(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int type;
+ struct hda_pcm *cpcm;
+
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ for (type = 0; type < 2; type++) {
+ if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
+ continue;
+ snd_iprintf(buffer, " Device: name=\"%s\", "
+ "type=\"%s\", device=%i\n",
+ cpcm->name,
+ snd_hda_pcm_type_name[cpcm->pcm_type],
+ cpcm->pcm->device);
+ }
+ }
+}
+
+static void print_amp_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+ unsigned int caps;
+ caps = param_read(codec, nid, dir == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+ if (caps == -1 || caps == 0) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, "
+ "mute=%x\n",
+ caps & AC_AMPCAP_OFFSET,
+ (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
+ (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
+ (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
+}
+
+/* is this a stereo widget or a stereo-to-mono mix? */
+static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int wcaps, int indices)
+{
+ hda_nid_t conn;
+
+ if (wcaps & AC_WCAP_STEREO)
+ return true;
+ /* check for a stereo-to-mono mix; it must be:
+ * only a single connection, only for input, and only a mixer widget
+ */
+ if (indices != 1 || dir != HDA_INPUT ||
+ get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
+ return false;
+
+ if (snd_hda_get_raw_connections(codec, nid, &conn, 1) < 0)
+ return false;
+ /* the connection source is a stereo? */
+ wcaps = snd_hda_param_read(codec, conn, AC_PAR_AUDIO_WIDGET_CAP);
+ return !!(wcaps & AC_WCAP_STEREO);
+}
+
+static void print_amp_vals(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int dir, unsigned int wcaps, int indices)
+{
+ unsigned int val;
+ bool stereo;
+ int i;
+
+ stereo = is_stereo_amps(codec, nid, dir, wcaps, indices);
+
+ dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
+ for (i = 0; i < indices; i++) {
+ snd_iprintf(buffer, " [");
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_LEFT | dir | i);
+ snd_iprintf(buffer, "0x%02x", val);
+ if (stereo) {
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_RIGHT | dir | i);
+ snd_iprintf(buffer, " 0x%02x", val);
+ }
+ snd_iprintf(buffer, "]");
+ }
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+ static const unsigned int rates[] = {
+ 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 384000
+ };
+ int i;
+
+ pcm &= AC_SUPPCM_RATES;
+ snd_iprintf(buffer, " rates [0x%x]:", pcm);
+ for (i = 0; i < ARRAY_SIZE(rates); i++)
+ if (pcm & (1 << i))
+ snd_iprintf(buffer, " %d", rates[i]);
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+ char buf[SND_PRINT_BITS_ADVISED_BUFSIZE];
+
+ snd_iprintf(buffer, " bits [0x%x]:", (pcm >> 16) & 0xff);
+ snd_print_pcm_bits(pcm, buf, sizeof(buf));
+ snd_iprintf(buffer, "%s\n", buf);
+}
+
+static void print_pcm_formats(struct snd_info_buffer *buffer,
+ unsigned int streams)
+{
+ snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf);
+ if (streams & AC_SUPFMT_PCM)
+ snd_iprintf(buffer, " PCM");
+ if (streams & AC_SUPFMT_FLOAT32)
+ snd_iprintf(buffer, " FLOAT");
+ if (streams & AC_SUPFMT_AC3)
+ snd_iprintf(buffer, " AC3");
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int pcm = param_read(codec, nid, AC_PAR_PCM);
+ unsigned int stream = param_read(codec, nid, AC_PAR_STREAM);
+ if (pcm == -1 || stream == -1) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ print_pcm_rates(buffer, pcm);
+ print_pcm_bits(buffer, pcm);
+ print_pcm_formats(buffer, stream);
+}
+
+static const char *get_jack_connection(u32 cfg)
+{
+ static const char * const names[16] = {
+ "Unknown", "1/8", "1/4", "ATAPI",
+ "RCA", "Optical","Digital", "Analog",
+ "DIN", "XLR", "RJ11", "Comb",
+ NULL, NULL, NULL, "Other"
+ };
+ cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+static const char *get_jack_color(u32 cfg)
+{
+ static const char * const names[16] = {
+ "Unknown", "Black", "Grey", "Blue",
+ "Green", "Red", "Orange", "Yellow",
+ "Purple", "Pink", NULL, NULL,
+ NULL, NULL, "White", "Other",
+ };
+ cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack location, e.g. "Rear", "Front", etc.
+ */
+static const char *get_jack_location(u32 cfg)
+{
+ static const char * const bases[7] = {
+ "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
+ };
+ static const unsigned char specials_idx[] = {
+ 0x07, 0x08,
+ 0x17, 0x18, 0x19,
+ 0x37, 0x38
+ };
+ static const char * const specials[] = {
+ "Rear Panel", "Drive Bar",
+ "Riser", "HDMI", "ATAPI",
+ "Mobile-In", "Mobile-Out"
+ };
+ int i;
+
+ cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+ if ((cfg & 0x0f) < 7)
+ return bases[cfg & 0x0f];
+ for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
+ if (cfg == specials_idx[i])
+ return specials[i];
+ }
+ return "UNKNOWN";
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack connectivity, i.e. external or internal connection.
+ */
+static const char *get_jack_connectivity(u32 cfg)
+{
+ static const char * const jack_locations[4] = {
+ "Ext", "Int", "Sep", "Oth"
+ };
+
+ return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3];
+}
+
+/*
+ * Parse the pin default config value and returns the string of the
+ * jack type, i.e. the purpose of the jack, such as Line-Out or CD.
+ */
+static const char *get_jack_type(u32 cfg)
+{
+ static const char * const jack_types[16] = {
+ "Line Out", "Speaker", "HP Out", "CD",
+ "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
+ "Line In", "Aux", "Mic", "Telephony",
+ "SPDIF In", "Digital In", "Reserved", "Other"
+ };
+
+ return jack_types[(cfg & AC_DEFCFG_DEVICE)
+ >> AC_DEFCFG_DEVICE_SHIFT];
+}
+
+static void print_pin_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int *supports_vref)
+{
+ static const char * const jack_conns[4] = {
+ "Jack", "N/A", "Fixed", "Both"
+ };
+ unsigned int caps, val;
+
+ caps = param_read(codec, nid, AC_PAR_PIN_CAP);
+ snd_iprintf(buffer, " Pincap 0x%08x:", caps);
+ if (caps & AC_PINCAP_IN)
+ snd_iprintf(buffer, " IN");
+ if (caps & AC_PINCAP_OUT)
+ snd_iprintf(buffer, " OUT");
+ if (caps & AC_PINCAP_HP_DRV)
+ snd_iprintf(buffer, " HP");
+ if (caps & AC_PINCAP_EAPD)
+ snd_iprintf(buffer, " EAPD");
+ if (caps & AC_PINCAP_PRES_DETECT)
+ snd_iprintf(buffer, " Detect");
+ if (caps & AC_PINCAP_BALANCE)
+ snd_iprintf(buffer, " Balanced");
+ if (caps & AC_PINCAP_HDMI) {
+ /* Realtek uses this bit as a different meaning */
+ if ((codec->core.vendor_id >> 16) == 0x10ec)
+ snd_iprintf(buffer, " R/L");
+ else {
+ if (caps & AC_PINCAP_HBR)
+ snd_iprintf(buffer, " HBR");
+ snd_iprintf(buffer, " HDMI");
+ }
+ }
+ if (caps & AC_PINCAP_DP)
+ snd_iprintf(buffer, " DP");
+ if (caps & AC_PINCAP_TRIG_REQ)
+ snd_iprintf(buffer, " Trigger");
+ if (caps & AC_PINCAP_IMP_SENSE)
+ snd_iprintf(buffer, " ImpSense");
+ snd_iprintf(buffer, "\n");
+ if (caps & AC_PINCAP_VREF) {
+ unsigned int vref =
+ (caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ snd_iprintf(buffer, " Vref caps:");
+ if (vref & AC_PINCAP_VREF_HIZ)
+ snd_iprintf(buffer, " HIZ");
+ if (vref & AC_PINCAP_VREF_50)
+ snd_iprintf(buffer, " 50");
+ if (vref & AC_PINCAP_VREF_GRD)
+ snd_iprintf(buffer, " GRD");
+ if (vref & AC_PINCAP_VREF_80)
+ snd_iprintf(buffer, " 80");
+ if (vref & AC_PINCAP_VREF_100)
+ snd_iprintf(buffer, " 100");
+ snd_iprintf(buffer, "\n");
+ *supports_vref = 1;
+ } else
+ *supports_vref = 0;
+ if (caps & AC_PINCAP_EAPD) {
+ val = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_EAPD_BTLENABLE, 0);
+ snd_iprintf(buffer, " EAPD 0x%x:", val);
+ if (val & AC_EAPDBTL_BALANCED)
+ snd_iprintf(buffer, " BALANCED");
+ if (val & AC_EAPDBTL_EAPD)
+ snd_iprintf(buffer, " EAPD");
+ if (val & AC_EAPDBTL_LR_SWAP)
+ snd_iprintf(buffer, " R/L");
+ snd_iprintf(buffer, "\n");
+ }
+ caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+ snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps,
+ jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT],
+ get_jack_type(caps),
+ get_jack_connectivity(caps),
+ get_jack_location(caps));
+ snd_iprintf(buffer, " Conn = %s, Color = %s\n",
+ get_jack_connection(caps),
+ get_jack_color(caps));
+ /* Default association and sequence values refer to default grouping
+ * of pin complexes and their sequence within the group. This is used
+ * for priority and resource allocation.
+ */
+ snd_iprintf(buffer, " DefAssociation = 0x%x, Sequence = 0x%x\n",
+ (caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT,
+ caps & AC_DEFCFG_SEQUENCE);
+ if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) &
+ AC_DEFCFG_MISC_NO_PRESENCE) {
+ /* Miscellaneous bit indicates external hardware does not
+ * support presence detection even if the pin complex
+ * indicates it is supported.
+ */
+ snd_iprintf(buffer, " Misc = NO_PRESENCE\n");
+ }
+}
+
+static void print_pin_ctls(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int supports_vref)
+{
+ unsigned int pinctls;
+
+ pinctls = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
+ if (pinctls & AC_PINCTL_IN_EN)
+ snd_iprintf(buffer, " IN");
+ if (pinctls & AC_PINCTL_OUT_EN)
+ snd_iprintf(buffer, " OUT");
+ if (pinctls & AC_PINCTL_HP_EN)
+ snd_iprintf(buffer, " HP");
+ if (supports_vref) {
+ int vref = pinctls & AC_PINCTL_VREFEN;
+ switch (vref) {
+ case AC_PINCTL_VREF_HIZ:
+ snd_iprintf(buffer, " VREF_HIZ");
+ break;
+ case AC_PINCTL_VREF_50:
+ snd_iprintf(buffer, " VREF_50");
+ break;
+ case AC_PINCTL_VREF_GRD:
+ snd_iprintf(buffer, " VREF_GRD");
+ break;
+ case AC_PINCTL_VREF_80:
+ snd_iprintf(buffer, " VREF_80");
+ break;
+ case AC_PINCTL_VREF_100:
+ snd_iprintf(buffer, " VREF_100");
+ break;
+ }
+ }
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_vol_knob(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int cap = param_read(codec, nid, AC_PAR_VOL_KNB_CAP);
+ snd_iprintf(buffer, " Volume-Knob: delta=%d, steps=%d, ",
+ (cap >> 7) & 1, cap & 0x7f);
+ cap = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
+ snd_iprintf(buffer, "direct=%d, val=%d\n",
+ (cap >> 7) & 1, cap & 0x7f);
+}
+
+static void print_audio_io(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ unsigned int wid_type)
+{
+ int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ snd_iprintf(buffer,
+ " Converter: stream=%d, channel=%d\n",
+ (conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT,
+ conv & AC_CONV_CHANNEL);
+
+ if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) {
+ int sdi = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_SDI_SELECT, 0);
+ snd_iprintf(buffer, " SDI-Select: %d\n",
+ sdi & AC_SDI_SELECT);
+ }
+}
+
+static void print_digital_conv(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int digi1 = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DIGI_CONVERT_1, 0);
+ unsigned char digi2 = digi1 >> 8;
+ unsigned char digi3 = digi1 >> 16;
+
+ snd_iprintf(buffer, " Digital:");
+ if (digi1 & AC_DIG1_ENABLE)
+ snd_iprintf(buffer, " Enabled");
+ if (digi1 & AC_DIG1_V)
+ snd_iprintf(buffer, " Validity");
+ if (digi1 & AC_DIG1_VCFG)
+ snd_iprintf(buffer, " ValidityCfg");
+ if (digi1 & AC_DIG1_EMPHASIS)
+ snd_iprintf(buffer, " Preemphasis");
+ if (digi1 & AC_DIG1_COPYRIGHT)
+ snd_iprintf(buffer, " Non-Copyright");
+ if (digi1 & AC_DIG1_NONAUDIO)
+ snd_iprintf(buffer, " Non-Audio");
+ if (digi1 & AC_DIG1_PROFESSIONAL)
+ snd_iprintf(buffer, " Pro");
+ if (digi1 & AC_DIG1_LEVEL)
+ snd_iprintf(buffer, " GenLevel");
+ if (digi3 & AC_DIG3_KAE)
+ snd_iprintf(buffer, " KAE");
+ snd_iprintf(buffer, "\n");
+ snd_iprintf(buffer, " Digital category: 0x%x\n",
+ digi2 & AC_DIG2_CC);
+ snd_iprintf(buffer, " IEC Coding Type: 0x%x\n",
+ digi3 & AC_DIG3_ICT);
+}
+
+static const char *get_pwr_state(u32 state)
+{
+ static const char * const buf[] = {
+ "D0", "D1", "D2", "D3", "D3cold"
+ };
+ if (state < ARRAY_SIZE(buf))
+ return buf[state];
+ return "UNKNOWN";
+}
+
+static void print_power_state(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ static const char * const names[] = {
+ [ilog2(AC_PWRST_D0SUP)] = "D0",
+ [ilog2(AC_PWRST_D1SUP)] = "D1",
+ [ilog2(AC_PWRST_D2SUP)] = "D2",
+ [ilog2(AC_PWRST_D3SUP)] = "D3",
+ [ilog2(AC_PWRST_D3COLDSUP)] = "D3cold",
+ [ilog2(AC_PWRST_S3D3COLDSUP)] = "S3D3cold",
+ [ilog2(AC_PWRST_CLKSTOP)] = "CLKSTOP",
+ [ilog2(AC_PWRST_EPSS)] = "EPSS",
+ };
+
+ int sup = param_read(codec, nid, AC_PAR_POWER_STATE);
+ int pwr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_POWER_STATE, 0);
+ if (sup != -1) {
+ int i;
+
+ snd_iprintf(buffer, " Power states: ");
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ if (sup & (1U << i))
+ snd_iprintf(buffer, " %s", names[i]);
+ }
+ snd_iprintf(buffer, "\n");
+ }
+
+ snd_iprintf(buffer, " Power: setting=%s, actual=%s",
+ get_pwr_state(pwr & AC_PWRST_SETTING),
+ get_pwr_state((pwr & AC_PWRST_ACTUAL) >>
+ AC_PWRST_ACTUAL_SHIFT));
+ if (pwr & AC_PWRST_ERROR)
+ snd_iprintf(buffer, ", Error");
+ if (pwr & AC_PWRST_CLK_STOP_OK)
+ snd_iprintf(buffer, ", Clock-stop-OK");
+ if (pwr & AC_PWRST_SETTING_RESET)
+ snd_iprintf(buffer, ", Setting-reset");
+ snd_iprintf(buffer, "\n");
+}
+
+static void print_unsol_cap(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_UNSOLICITED_RESPONSE, 0);
+ snd_iprintf(buffer,
+ " Unsolicited: tag=%02x, enabled=%d\n",
+ unsol & AC_UNSOL_TAG,
+ (unsol & AC_UNSOL_ENABLED) ? 1 : 0);
+}
+
+static inline bool can_dump_coef(struct hda_codec *codec)
+{
+ switch (dump_coef) {
+ case 0: return false;
+ case 1: return true;
+ default: return codec->dump_coef;
+ }
+}
+
+static void print_proc_caps(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int i, ncoeff, oldindex;
+ unsigned int proc_caps = param_read(codec, nid, AC_PAR_PROC_CAP);
+ ncoeff = (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT;
+ snd_iprintf(buffer, " Processing caps: benign=%d, ncoeff=%d\n",
+ proc_caps & AC_PCAP_BENIGN, ncoeff);
+
+ if (!can_dump_coef(codec))
+ return;
+
+ /* Note: This is racy - another process could run in parallel and change
+ the coef index too. */
+ oldindex = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_COEF_INDEX, 0);
+ for (i = 0; i < ncoeff; i++) {
+ unsigned int val;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, i);
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF,
+ 0);
+ snd_iprintf(buffer, " Coeff 0x%02x: 0x%04x\n", i, val);
+ }
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, oldindex);
+}
+
+static void print_conn_list(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ unsigned int wid_type, hda_nid_t *conn,
+ int conn_len)
+{
+ int c, curr = -1;
+ const hda_nid_t *list;
+ int cache_len;
+
+ if (conn_len > 1 &&
+ wid_type != AC_WID_AUD_MIX &&
+ wid_type != AC_WID_VOL_KNB &&
+ wid_type != AC_WID_POWER)
+ curr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_CONNECT_SEL, 0);
+ snd_iprintf(buffer, " Connection: %d\n", conn_len);
+ if (conn_len > 0) {
+ snd_iprintf(buffer, " ");
+ for (c = 0; c < conn_len; c++) {
+ snd_iprintf(buffer, " 0x%02x", conn[c]);
+ if (c == curr)
+ snd_iprintf(buffer, "*");
+ }
+ snd_iprintf(buffer, "\n");
+ }
+
+ /* Get Cache connections info */
+ cache_len = snd_hda_get_conn_list(codec, nid, &list);
+ if (cache_len >= 0 && (cache_len != conn_len ||
+ memcmp(list, conn, conn_len) != 0)) {
+ snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len);
+ if (cache_len > 0) {
+ snd_iprintf(buffer, " ");
+ for (c = 0; c < cache_len; c++)
+ snd_iprintf(buffer, " 0x%02x", list[c]);
+ snd_iprintf(buffer, "\n");
+ }
+ }
+}
+
+static void print_gpio(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int gpio =
+ param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
+ unsigned int enable, direction, wake, unsol, sticky, data;
+ int i, max;
+ snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
+ "unsolicited=%d, wake=%d\n",
+ gpio & AC_GPIO_IO_COUNT,
+ (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT,
+ (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT,
+ (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
+ (gpio & AC_GPIO_WAKE) ? 1 : 0);
+ max = gpio & AC_GPIO_IO_COUNT;
+ if (!max || max > 8)
+ return;
+ enable = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_MASK, 0);
+ direction = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DIRECTION, 0);
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ for (i = 0; i < max; ++i)
+ snd_iprintf(buffer,
+ " IO[%d]: enable=%d, dir=%d, wake=%d, "
+ "sticky=%d, data=%d, unsol=%d\n", i,
+ (enable & (1<<i)) ? 1 : 0,
+ (direction & (1<<i)) ? 1 : 0,
+ (wake & (1<<i)) ? 1 : 0,
+ (sticky & (1<<i)) ? 1 : 0,
+ (data & (1<<i)) ? 1 : 0,
+ (unsol & (1<<i)) ? 1 : 0);
+ /* FIXME: add GPO and GPI pin information */
+ print_nid_array(buffer, codec, nid, &codec->mixers);
+ print_nid_array(buffer, codec, nid, &codec->nids);
+}
+
+static void print_dpmst_connections(struct snd_info_buffer *buffer, struct hda_codec *codec,
+ hda_nid_t nid, int dev_num)
+{
+ int c, conn_len, curr, dev_id_saved;
+ hda_nid_t *conn;
+
+ conn_len = snd_hda_get_num_raw_conns(codec, nid);
+ if (conn_len <= 0)
+ return;
+
+ conn = kmalloc_array(conn_len, sizeof(hda_nid_t), GFP_KERNEL);
+ if (!conn)
+ return;
+
+ dev_id_saved = snd_hda_get_dev_select(codec, nid);
+
+ snd_hda_set_dev_select(codec, nid, dev_num);
+ curr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0);
+ if (snd_hda_get_raw_connections(codec, nid, conn, conn_len) < 0)
+ goto out;
+
+ for (c = 0; c < conn_len; c++) {
+ snd_iprintf(buffer, " 0x%02x", conn[c]);
+ if (c == curr)
+ snd_iprintf(buffer, "*");
+ }
+
+out:
+ kfree(conn);
+ snd_hda_set_dev_select(codec, nid, dev_id_saved);
+}
+
+static void print_device_list(struct snd_info_buffer *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ int i, curr = -1;
+ u8 dev_list[AC_MAX_DEV_LIST_LEN];
+ int devlist_len;
+
+ devlist_len = snd_hda_get_devices(codec, nid, dev_list,
+ AC_MAX_DEV_LIST_LEN);
+ snd_iprintf(buffer, " Devices: %d\n", devlist_len);
+ if (devlist_len <= 0)
+ return;
+
+ curr = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DEVICE_SEL, 0);
+
+ for (i = 0; i < devlist_len; i++) {
+ if (i == curr)
+ snd_iprintf(buffer, " *");
+ else
+ snd_iprintf(buffer, " ");
+
+ snd_iprintf(buffer,
+ "Dev %02d: PD = %d, ELDV = %d, IA = %d, Connections [", i,
+ !!(dev_list[i] & AC_DE_PD),
+ !!(dev_list[i] & AC_DE_ELDV),
+ !!(dev_list[i] & AC_DE_IA));
+
+ print_dpmst_connections(buffer, codec, nid, i);
+
+ snd_iprintf(buffer, " ]\n");
+ }
+}
+
+static void print_codec_core_info(struct hdac_device *codec,
+ struct snd_info_buffer *buffer)
+{
+ snd_iprintf(buffer, "Codec: ");
+ if (codec->vendor_name && codec->chip_name)
+ snd_iprintf(buffer, "%s %s\n",
+ codec->vendor_name, codec->chip_name);
+ else
+ snd_iprintf(buffer, "Not Set\n");
+ snd_iprintf(buffer, "Address: %d\n", codec->addr);
+ if (codec->afg)
+ snd_iprintf(buffer, "AFG Function Id: 0x%x (unsol %u)\n",
+ codec->afg_function_id, codec->afg_unsol);
+ if (codec->mfg)
+ snd_iprintf(buffer, "MFG Function Id: 0x%x (unsol %u)\n",
+ codec->mfg_function_id, codec->mfg_unsol);
+ snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id);
+ snd_iprintf(buffer, "Subsystem Id: 0x%08x\n", codec->subsystem_id);
+ snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
+
+ if (codec->mfg)
+ snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg);
+ else
+ snd_iprintf(buffer, "No Modem Function Group found\n");
+}
+
+static void print_codec_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct hda_codec *codec = entry->private_data;
+ hda_nid_t nid, fg;
+ int i, nodes;
+
+ print_codec_core_info(&codec->core, buffer);
+ fg = codec->core.afg;
+ if (!fg)
+ return;
+ snd_hda_power_up(codec);
+ snd_iprintf(buffer, "Default PCM:\n");
+ print_pcm_caps(buffer, codec, fg);
+ snd_iprintf(buffer, "Default Amp-In caps: ");
+ print_amp_caps(buffer, codec, fg, HDA_INPUT);
+ snd_iprintf(buffer, "Default Amp-Out caps: ");
+ print_amp_caps(buffer, codec, fg, HDA_OUTPUT);
+ snd_iprintf(buffer, "State of AFG node 0x%02x:\n", fg);
+ print_power_state(buffer, codec, fg);
+
+ nodes = snd_hda_get_sub_nodes(codec, fg, &nid);
+ if (! nid || nodes < 0) {
+ snd_iprintf(buffer, "Invalid AFG subtree\n");
+ snd_hda_power_down(codec);
+ return;
+ }
+
+ print_gpio(buffer, codec, fg);
+ if (codec->proc_widget_hook)
+ codec->proc_widget_hook(buffer, codec, fg);
+
+ for (i = 0; i < nodes; i++, nid++) {
+ unsigned int wid_caps =
+ param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+ unsigned int wid_type = get_wcaps_type(wid_caps);
+ hda_nid_t *conn = NULL;
+ int conn_len = 0;
+
+ snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
+ get_wid_type_name(wid_type), wid_caps);
+ if (wid_caps & AC_WCAP_STEREO) {
+ unsigned int chans = get_wcaps_channels(wid_caps);
+ if (chans == 2)
+ snd_iprintf(buffer, " Stereo");
+ else
+ snd_iprintf(buffer, " %d-Channels", chans);
+ } else
+ snd_iprintf(buffer, " Mono");
+ if (wid_caps & AC_WCAP_DIGITAL)
+ snd_iprintf(buffer, " Digital");
+ if (wid_caps & AC_WCAP_IN_AMP)
+ snd_iprintf(buffer, " Amp-In");
+ if (wid_caps & AC_WCAP_OUT_AMP)
+ snd_iprintf(buffer, " Amp-Out");
+ if (wid_caps & AC_WCAP_STRIPE)
+ snd_iprintf(buffer, " Stripe");
+ if (wid_caps & AC_WCAP_LR_SWAP)
+ snd_iprintf(buffer, " R/L");
+ if (wid_caps & AC_WCAP_CP_CAPS)
+ snd_iprintf(buffer, " CP");
+ snd_iprintf(buffer, "\n");
+
+ print_nid_array(buffer, codec, nid, &codec->mixers);
+ print_nid_array(buffer, codec, nid, &codec->nids);
+ print_nid_pcms(buffer, codec, nid);
+
+ /* volume knob is a special widget that always have connection
+ * list
+ */
+ if (wid_type == AC_WID_VOL_KNB)
+ wid_caps |= AC_WCAP_CONN_LIST;
+
+ if (wid_caps & AC_WCAP_CONN_LIST) {
+ conn_len = snd_hda_get_num_raw_conns(codec, nid);
+ if (conn_len > 0) {
+ conn = kmalloc_array(conn_len,
+ sizeof(hda_nid_t),
+ GFP_KERNEL);
+ if (!conn)
+ return;
+ if (snd_hda_get_raw_connections(codec, nid, conn,
+ conn_len) < 0)
+ conn_len = 0;
+ }
+ }
+
+ if (wid_caps & AC_WCAP_IN_AMP) {
+ snd_iprintf(buffer, " Amp-In caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_INPUT);
+ snd_iprintf(buffer, " Amp-In vals: ");
+ if (wid_type == AC_WID_PIN ||
+ (codec->single_adc_amp &&
+ wid_type == AC_WID_AUD_IN))
+ print_amp_vals(buffer, codec, nid, HDA_INPUT,
+ wid_caps, 1);
+ else
+ print_amp_vals(buffer, codec, nid, HDA_INPUT,
+ wid_caps, conn_len);
+ }
+ if (wid_caps & AC_WCAP_OUT_AMP) {
+ snd_iprintf(buffer, " Amp-Out caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
+ snd_iprintf(buffer, " Amp-Out vals: ");
+ if (wid_type == AC_WID_PIN &&
+ codec->pin_amp_workaround)
+ print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+ wid_caps, conn_len);
+ else
+ print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+ wid_caps, 1);
+ }
+
+ switch (wid_type) {
+ case AC_WID_PIN: {
+ int supports_vref;
+ print_pin_caps(buffer, codec, nid, &supports_vref);
+ print_pin_ctls(buffer, codec, nid, supports_vref);
+ break;
+ }
+ case AC_WID_VOL_KNB:
+ print_vol_knob(buffer, codec, nid);
+ break;
+ case AC_WID_AUD_OUT:
+ case AC_WID_AUD_IN:
+ print_audio_io(buffer, codec, nid, wid_type);
+ if (wid_caps & AC_WCAP_DIGITAL)
+ print_digital_conv(buffer, codec, nid);
+ if (wid_caps & AC_WCAP_FORMAT_OVRD) {
+ snd_iprintf(buffer, " PCM:\n");
+ print_pcm_caps(buffer, codec, nid);
+ }
+ break;
+ }
+
+ if (wid_caps & AC_WCAP_UNSOL_CAP)
+ print_unsol_cap(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_POWER)
+ print_power_state(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_DELAY)
+ snd_iprintf(buffer, " Delay: %d samples\n",
+ (wid_caps & AC_WCAP_DELAY) >>
+ AC_WCAP_DELAY_SHIFT);
+
+ if (wid_type == AC_WID_PIN && codec->dp_mst)
+ print_device_list(buffer, codec, nid);
+
+ if (wid_caps & AC_WCAP_CONN_LIST)
+ print_conn_list(buffer, codec, nid, wid_type,
+ conn, conn_len);
+
+ if (wid_caps & AC_WCAP_PROC_WID)
+ print_proc_caps(buffer, codec, nid);
+
+ if (codec->proc_widget_hook)
+ codec->proc_widget_hook(buffer, codec, nid);
+
+ kfree(conn);
+ }
+ snd_hda_power_down(codec);
+}
+
+/*
+ * create a proc read
+ */
+int snd_hda_codec_proc_new(struct hda_codec *codec)
+{
+ char name[32];
+
+ snprintf(name, sizeof(name), "codec#%d", codec->core.addr);
+ return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info);
+}
+
diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c
new file mode 100644
index 000000000000..140e24bf4d7f
--- /dev/null
+++ b/sound/hda/common/sysfs.c
@@ -0,0 +1,792 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sysfs interface for HD-audio codec
+ *
+ * Copyright (c) 2014 Takashi Iwai <tiwai@suse.de>
+ *
+ * split from hda_hwdep.c
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+#include <linux/mutex.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/hda_codec.h>
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+#include <sound/minors.h>
+
+/* hint string pair */
+struct hda_hint {
+ const char *key;
+ const char *val; /* contained in the same alloc as key */
+};
+
+static ssize_t power_on_acct_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ snd_hda_update_power_acct(codec);
+ return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct));
+}
+
+static ssize_t power_off_acct_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ snd_hda_update_power_acct(codec);
+ return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct));
+}
+
+static DEVICE_ATTR_RO(power_on_acct);
+static DEVICE_ATTR_RO(power_off_acct);
+
+#define CODEC_INFO_SHOW(type, field) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ return sysfs_emit(buf, "0x%x\n", codec->field); \
+}
+
+#define CODEC_INFO_STR_SHOW(type, field) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ return sysfs_emit(buf, "%s\n", \
+ codec->field ? codec->field : ""); \
+}
+
+CODEC_INFO_SHOW(vendor_id, core.vendor_id);
+CODEC_INFO_SHOW(subsystem_id, core.subsystem_id);
+CODEC_INFO_SHOW(revision_id, core.revision_id);
+CODEC_INFO_SHOW(afg, core.afg);
+CODEC_INFO_SHOW(mfg, core.mfg);
+CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name);
+CODEC_INFO_STR_SHOW(chip_name, core.chip_name);
+CODEC_INFO_STR_SHOW(modelname, modelname);
+
+static ssize_t pin_configs_show(struct hda_codec *codec,
+ struct snd_array *list,
+ char *buf)
+{
+ const struct hda_pincfg *pin;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(list, i, pin) {
+ len += sysfs_emit_at(buf, len, "0x%02x 0x%08x\n",
+ pin->nid, pin->cfg);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static ssize_t init_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->init_pins, buf);
+}
+
+static ssize_t driver_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->driver_pins, buf);
+}
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+
+/*
+ * sysfs interface
+ */
+
+static int clear_codec(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ codec_err(codec, "The codec is being used, can't free.\n");
+ return err;
+ }
+ snd_hda_sysfs_clear(codec);
+ return 0;
+}
+
+static int reconfig_codec(struct hda_codec *codec)
+{
+ int err;
+
+ snd_hda_power_up(codec);
+ codec_info(codec, "hda-codec: reconfiguring\n");
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ codec_err(codec,
+ "The codec is being used, can't reconfigure.\n");
+ goto error;
+ }
+ err = device_reprobe(hda_codec_dev(codec));
+ if (err < 0)
+ goto error;
+ err = snd_card_register(codec->card);
+ error:
+ snd_hda_power_down(codec);
+ return err;
+}
+
+/*
+ * allocate a string at most len chars, and remove the trailing EOL
+ */
+static char *kstrndup_noeol(const char *src, size_t len)
+{
+ char *s = kstrndup(src, len, GFP_KERNEL);
+ char *p;
+ if (!s)
+ return NULL;
+ p = strchr(s, '\n');
+ if (p)
+ *p = 0;
+ return s;
+}
+
+#define CODEC_INFO_STORE(type, field) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ unsigned long val; \
+ int err = kstrtoul(buf, 0, &val); \
+ if (err < 0) \
+ return err; \
+ codec->field = val; \
+ return count; \
+}
+
+#define CODEC_INFO_STR_STORE(type, field) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ char *s = kstrndup_noeol(buf, 64); \
+ if (!s) \
+ return -ENOMEM; \
+ kfree(codec->field); \
+ codec->field = s; \
+ return count; \
+}
+
+CODEC_INFO_STORE(vendor_id, core.vendor_id);
+CODEC_INFO_STORE(subsystem_id, core.subsystem_id);
+CODEC_INFO_STORE(revision_id, core.revision_id);
+CODEC_INFO_STR_STORE(vendor_name, core.vendor_name);
+CODEC_INFO_STR_STORE(chip_name, core.chip_name);
+CODEC_INFO_STR_STORE(modelname, modelname);
+
+#define CODEC_ACTION_STORE(type) \
+static ssize_t type##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct hda_codec *codec = dev_get_drvdata(dev); \
+ int err = 0; \
+ if (*buf) \
+ err = type##_codec(codec); \
+ return err < 0 ? err : count; \
+}
+
+CODEC_ACTION_STORE(reconfig);
+CODEC_ACTION_STORE(clear);
+
+static ssize_t init_verbs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ const struct hda_verb *v;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(&codec->init_verbs, i, v) {
+ len += sysfs_emit_at(buf, len, "0x%02x 0x%03x 0x%04x\n",
+ v->nid, v->verb, v->param);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static int parse_init_verbs(struct hda_codec *codec, const char *buf)
+{
+ struct hda_verb *v;
+ int nid, verb, param;
+
+ if (sscanf(buf, "%i %i %i", &nid, &verb, &param) != 3)
+ return -EINVAL;
+ if (!nid || !verb)
+ return -EINVAL;
+ mutex_lock(&codec->user_mutex);
+ v = snd_array_new(&codec->init_verbs);
+ if (!v) {
+ mutex_unlock(&codec->user_mutex);
+ return -ENOMEM;
+ }
+ v->nid = nid;
+ v->verb = verb;
+ v->param = param;
+ mutex_unlock(&codec->user_mutex);
+ return 0;
+}
+
+static ssize_t init_verbs_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_init_verbs(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t hints_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ const struct hda_hint *hint;
+ int i, len = 0;
+ mutex_lock(&codec->user_mutex);
+ snd_array_for_each(&codec->hints, i, hint) {
+ len += sysfs_emit_at(buf, len, "%s = %s\n",
+ hint->key, hint->val);
+ }
+ mutex_unlock(&codec->user_mutex);
+ return len;
+}
+
+static struct hda_hint *get_hint(struct hda_codec *codec, const char *key)
+{
+ struct hda_hint *hint;
+ int i;
+
+ snd_array_for_each(&codec->hints, i, hint) {
+ if (!strcmp(hint->key, key))
+ return hint;
+ }
+ return NULL;
+}
+
+static void remove_trail_spaces(char *str)
+{
+ char *p;
+ if (!*str)
+ return;
+ p = str + strlen(str) - 1;
+ for (; isspace(*p); p--) {
+ *p = 0;
+ if (p == str)
+ return;
+ }
+}
+
+#define MAX_HINTS 1024
+
+static int parse_hints(struct hda_codec *codec, const char *buf)
+{
+ char *key, *val;
+ struct hda_hint *hint;
+ int err = 0;
+
+ buf = skip_spaces(buf);
+ if (!*buf || *buf == '#' || *buf == '\n')
+ return 0;
+ if (*buf == '=')
+ return -EINVAL;
+ key = kstrndup_noeol(buf, 1024);
+ if (!key)
+ return -ENOMEM;
+ /* extract key and val */
+ val = strchr(key, '=');
+ if (!val) {
+ kfree(key);
+ return -EINVAL;
+ }
+ *val++ = 0;
+ val = skip_spaces(val);
+ remove_trail_spaces(key);
+ remove_trail_spaces(val);
+ mutex_lock(&codec->user_mutex);
+ hint = get_hint(codec, key);
+ if (hint) {
+ /* replace */
+ kfree(hint->key);
+ hint->key = key;
+ hint->val = val;
+ goto unlock;
+ }
+ /* allocate a new hint entry */
+ if (codec->hints.used >= MAX_HINTS)
+ hint = NULL;
+ else
+ hint = snd_array_new(&codec->hints);
+ if (hint) {
+ hint->key = key;
+ hint->val = val;
+ } else {
+ err = -ENOMEM;
+ }
+ unlock:
+ mutex_unlock(&codec->user_mutex);
+ if (err)
+ kfree(key);
+ return err;
+}
+
+static ssize_t hints_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_hints(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t user_pin_configs_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ return pin_configs_show(codec, &codec->user_pins, buf);
+}
+
+static int parse_user_pin_configs(struct hda_codec *codec, const char *buf)
+{
+ int nid, cfg, err;
+
+ if (sscanf(buf, "%i %i", &nid, &cfg) != 2)
+ return -EINVAL;
+ if (!nid)
+ return -EINVAL;
+ mutex_lock(&codec->user_mutex);
+ err = snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg);
+ mutex_unlock(&codec->user_mutex);
+ return err;
+}
+
+static ssize_t user_pin_configs_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hda_codec *codec = dev_get_drvdata(dev);
+ int err = parse_user_pin_configs(codec, buf);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */
+static DEVICE_ATTR_RW(init_verbs);
+static DEVICE_ATTR_RW(hints);
+static DEVICE_ATTR_RW(user_pin_configs);
+static DEVICE_ATTR_WO(reconfig);
+static DEVICE_ATTR_WO(clear);
+
+/**
+ * snd_hda_get_hint - Look for hint string
+ * @codec: the HDA codec
+ * @key: the hint key string
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and returns the value string. If nothing found, returns NULL.
+ */
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+ struct hda_hint *hint = get_hint(codec, key);
+ return hint ? hint->val : NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_hint);
+
+/**
+ * snd_hda_get_bool_hint - Get a boolean hint value
+ * @codec: the HDA codec
+ * @key: the hint key string
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and returns a boolean value parsed from the value. If no matching
+ * key is found, return a negative value.
+ */
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+ const char *p;
+ int ret;
+
+ mutex_lock(&codec->user_mutex);
+ p = snd_hda_get_hint(codec, key);
+ if (!p || !*p)
+ ret = -ENOENT;
+ else {
+ switch (toupper(*p)) {
+ case 'T': /* true */
+ case 'Y': /* yes */
+ case '1':
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&codec->user_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint);
+
+/**
+ * snd_hda_get_int_hint - Get an integer hint value
+ * @codec: the HDA codec
+ * @key: the hint key string
+ * @valp: pointer to store a value
+ *
+ * Look for a hint key/value pair matching with the given key string
+ * and stores the integer value to @valp. If no matching key is found,
+ * return a negative error code. Otherwise it returns zero.
+ */
+int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
+{
+ const char *p;
+ unsigned long val;
+ int ret;
+
+ mutex_lock(&codec->user_mutex);
+ p = snd_hda_get_hint(codec, key);
+ if (!p)
+ ret = -ENOENT;
+ else if (kstrtoul(p, 0, &val))
+ ret = -EINVAL;
+ else {
+ *valp = val;
+ ret = 0;
+ }
+ mutex_unlock(&codec->user_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hda_get_int_hint);
+#endif /* CONFIG_SND_HDA_RECONFIG */
+
+/*
+ * common sysfs attributes
+ */
+#ifdef CONFIG_SND_HDA_RECONFIG
+#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name)
+#else
+#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name)
+#endif
+static RECONFIG_DEVICE_ATTR(vendor_id);
+static RECONFIG_DEVICE_ATTR(subsystem_id);
+static RECONFIG_DEVICE_ATTR(revision_id);
+static DEVICE_ATTR_RO(afg);
+static DEVICE_ATTR_RO(mfg);
+static RECONFIG_DEVICE_ATTR(vendor_name);
+static RECONFIG_DEVICE_ATTR(chip_name);
+static RECONFIG_DEVICE_ATTR(modelname);
+static DEVICE_ATTR_RO(init_pin_configs);
+static DEVICE_ATTR_RO(driver_pin_configs);
+
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+
+/* parser mode */
+enum {
+ LINE_MODE_NONE,
+ LINE_MODE_CODEC,
+ LINE_MODE_MODEL,
+ LINE_MODE_PINCFG,
+ LINE_MODE_VERB,
+ LINE_MODE_HINT,
+ LINE_MODE_VENDOR_ID,
+ LINE_MODE_SUBSYSTEM_ID,
+ LINE_MODE_REVISION_ID,
+ LINE_MODE_CHIP_NAME,
+ NUM_LINE_MODES,
+};
+
+static inline int strmatch(const char *a, const char *b)
+{
+ return strncasecmp(a, b, strlen(b)) == 0;
+}
+
+/* parse the contents after the line "[codec]"
+ * accept only the line with three numbers, and assign the current codec
+ */
+static void parse_codec_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ int vendorid, subid, caddr;
+ struct hda_codec *codec;
+
+ *codecp = NULL;
+ if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) {
+ list_for_each_codec(codec, bus) {
+ if ((vendorid <= 0 || codec->core.vendor_id == vendorid) &&
+ (subid <= 0 || codec->core.subsystem_id == subid) &&
+ codec->core.addr == caddr) {
+ *codecp = codec;
+ break;
+ }
+ }
+ }
+}
+
+/* parse the contents after the other command tags, [pincfg], [verb],
+ * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model]
+ * just pass to the sysfs helper (only when any codec was specified)
+ */
+static void parse_pincfg_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_user_pin_configs(*codecp, buf);
+}
+
+static void parse_verb_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_init_verbs(*codecp, buf);
+}
+
+static void parse_hint_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ parse_hints(*codecp, buf);
+}
+
+static void parse_model_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ kfree((*codecp)->modelname);
+ (*codecp)->modelname = kstrdup(buf, GFP_KERNEL);
+}
+
+static void parse_chip_name_mode(char *buf, struct hda_bus *bus,
+ struct hda_codec **codecp)
+{
+ snd_hda_codec_set_name(*codecp, buf);
+}
+
+#define DEFINE_PARSE_ID_MODE(name) \
+static void parse_##name##_mode(char *buf, struct hda_bus *bus, \
+ struct hda_codec **codecp) \
+{ \
+ unsigned long val; \
+ if (!kstrtoul(buf, 0, &val)) \
+ (*codecp)->core.name = val; \
+}
+
+DEFINE_PARSE_ID_MODE(vendor_id);
+DEFINE_PARSE_ID_MODE(subsystem_id);
+DEFINE_PARSE_ID_MODE(revision_id);
+
+
+struct hda_patch_item {
+ const char *tag;
+ const char *alias;
+ void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc);
+};
+
+static const struct hda_patch_item patch_items[NUM_LINE_MODES] = {
+ [LINE_MODE_CODEC] = {
+ .tag = "[codec]",
+ .parser = parse_codec_mode,
+ },
+ [LINE_MODE_MODEL] = {
+ .tag = "[model]",
+ .parser = parse_model_mode,
+ },
+ [LINE_MODE_VERB] = {
+ .tag = "[verb]",
+ .alias = "[init_verbs]",
+ .parser = parse_verb_mode,
+ },
+ [LINE_MODE_PINCFG] = {
+ .tag = "[pincfg]",
+ .alias = "[user_pin_configs]",
+ .parser = parse_pincfg_mode,
+ },
+ [LINE_MODE_HINT] = {
+ .tag = "[hint]",
+ .alias = "[hints]",
+ .parser = parse_hint_mode
+ },
+ [LINE_MODE_VENDOR_ID] = {
+ .tag = "[vendor_id]",
+ .parser = parse_vendor_id_mode,
+ },
+ [LINE_MODE_SUBSYSTEM_ID] = {
+ .tag = "[subsystem_id]",
+ .parser = parse_subsystem_id_mode,
+ },
+ [LINE_MODE_REVISION_ID] = {
+ .tag = "[revision_id]",
+ .parser = parse_revision_id_mode,
+ },
+ [LINE_MODE_CHIP_NAME] = {
+ .tag = "[chip_name]",
+ .parser = parse_chip_name_mode,
+ },
+};
+
+/* check the line starting with '[' -- change the parser mode accordingly */
+static int parse_line_mode(char *buf, struct hda_bus *bus)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(patch_items); i++) {
+ if (!patch_items[i].tag)
+ continue;
+ if (strmatch(buf, patch_items[i].tag))
+ return i;
+ if (patch_items[i].alias && strmatch(buf, patch_items[i].alias))
+ return i;
+ }
+ return LINE_MODE_NONE;
+}
+
+/* copy one line from the buffer in fw, and update the fields in fw
+ * return zero if it reaches to the end of the buffer, or non-zero
+ * if successfully copied a line
+ *
+ * the spaces at the beginning and the end of the line are stripped
+ */
+static int get_line_from_fw(char *buf, int size, size_t *fw_size_p,
+ const void **fw_data_p)
+{
+ int len;
+ size_t fw_size = *fw_size_p;
+ const char *p = *fw_data_p;
+
+ while (isspace(*p) && fw_size) {
+ p++;
+ fw_size--;
+ }
+ if (!fw_size)
+ return 0;
+
+ for (len = 0; len < fw_size; len++) {
+ if (!*p)
+ break;
+ if (*p == '\n') {
+ p++;
+ len++;
+ break;
+ }
+ if (len < size)
+ *buf++ = *p++;
+ }
+ *buf = 0;
+ *fw_size_p = fw_size - len;
+ *fw_data_p = p;
+ remove_trail_spaces(buf);
+ return 1;
+}
+
+/**
+ * snd_hda_load_patch - load a "patch" firmware file and parse it
+ * @bus: HD-audio bus
+ * @fw_size: the firmware byte size
+ * @fw_buf: the firmware data
+ */
+int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf)
+{
+ char buf[128];
+ struct hda_codec *codec;
+ int line_mode;
+
+ line_mode = LINE_MODE_NONE;
+ codec = NULL;
+ while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) {
+ if (!*buf || *buf == '#' || *buf == '\n')
+ continue;
+ if (*buf == '[')
+ line_mode = parse_line_mode(buf, bus);
+ else if (patch_items[line_mode].parser &&
+ (codec || line_mode <= LINE_MODE_CODEC))
+ patch_items[line_mode].parser(buf, bus, &codec);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hda_load_patch);
+#endif /* CONFIG_SND_HDA_PATCH_LOADER */
+
+/*
+ * sysfs entries
+ */
+static struct attribute *hda_dev_attrs[] = {
+ &dev_attr_vendor_id.attr,
+ &dev_attr_subsystem_id.attr,
+ &dev_attr_revision_id.attr,
+ &dev_attr_afg.attr,
+ &dev_attr_mfg.attr,
+ &dev_attr_vendor_name.attr,
+ &dev_attr_chip_name.attr,
+ &dev_attr_modelname.attr,
+ &dev_attr_init_pin_configs.attr,
+ &dev_attr_driver_pin_configs.attr,
+ &dev_attr_power_on_acct.attr,
+ &dev_attr_power_off_acct.attr,
+#ifdef CONFIG_SND_HDA_RECONFIG
+ &dev_attr_init_verbs.attr,
+ &dev_attr_hints.attr,
+ &dev_attr_user_pin_configs.attr,
+ &dev_attr_reconfig.attr,
+ &dev_attr_clear.attr,
+#endif
+ NULL
+};
+
+static const struct attribute_group hda_dev_attr_group = {
+ .attrs = hda_dev_attrs,
+};
+
+const struct attribute_group *snd_hda_dev_attr_groups[] = {
+ &hda_dev_attr_group,
+ NULL
+};
+
+void snd_hda_sysfs_init(struct hda_codec *codec)
+{
+ mutex_init(&codec->user_mutex);
+#ifdef CONFIG_SND_HDA_RECONFIG
+ snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32);
+ snd_array_init(&codec->hints, sizeof(struct hda_hint), 32);
+ snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16);
+#endif
+}
+
+void snd_hda_sysfs_clear(struct hda_codec *codec)
+{
+#ifdef CONFIG_SND_HDA_RECONFIG
+ struct hda_hint *hint;
+ int i;
+
+ /* clear init verbs */
+ snd_array_free(&codec->init_verbs);
+ /* clear hints */
+ snd_array_for_each(&codec->hints, i, hint) {
+ kfree(hint->key); /* we don't need to free hint->val */
+ }
+ snd_array_free(&codec->hints);
+ snd_array_free(&codec->user_pins);
+#endif
+}