# SPDX-License-Identifier: GPL-2.0-only
+menu "HD-Audio"
+
+source "sound/hda/common/Kconfig"
source "sound/hda/core/Kconfig"
+
+endmenu
# SPDX-License-Identifier: GPL-2.0
obj-y += core/
+obj-$(CONFIG_SND_HDA) += common/
--- /dev/null
+# 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.
+endif
--- /dev/null
+# 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
--- /dev/null
+// 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);
--- /dev/null
+// 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);
--- /dev/null
+// 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);
+
+ /* 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 (codec->patch_ops.unsol_event)
+ codec->patch_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;
+ hda_codec_patch_t patch;
+ 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;
+ }
+
+ patch = (hda_codec_patch_t)codec->preset->driver_data;
+ if (patch) {
+ err = patch(codec);
+ 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 (codec->patch_ops.free)
+ codec->patch_ops.free(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);
+
+ 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 (codec->patch_ops.free)
+ codec->patch_ops.free(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
+ * patch instance.
+ *
+ * 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);
--- /dev/null
+// 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);
+ memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
+ 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 *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 (codec->patch_ops.stream_pm)
+ codec->patch_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)
+{
+ 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 (codec->patch_ops.stream_pm)
+ codec->patch_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;
+ strcpy(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 patch 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 patch 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)
+{
+ 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++) {
+ if (codec->patch_ops.set_power_state)
+ codec->patch_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)
+{
+ unsigned int state;
+
+ snd_hdac_enter_pm(&codec->core);
+ if (codec->patch_ops.suspend)
+ codec->patch_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)
+{
+ 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 (codec->patch_ops.resume)
+ codec->patch_ops.resume(codec);
+ else {
+ if (codec->patch_ops.init)
+ codec->patch_ops.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)
+{
+ int err = 0;
+ hda_exec_init_verbs(codec);
+ /* continue to initialize... */
+ if (codec->patch_ops.init)
+ err = codec->patch_ops.init(codec);
+ if (!err && codec->patch_ops.build_controls)
+ err = codec->patch_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_pcm *cpcm;
+ int err;
+
+ if (!list_empty(&codec->pcm_list_head))
+ return 0; /* already parsed */
+
+ if (!codec->patch_ops.build_pcms)
+ return 0;
+
+ err = codec->patch_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;
+ strcpy(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");
--- /dev/null
+// 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);
--- /dev/null
+/* 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>
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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);
+
+/*
+ * 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);
+
+int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size,
+ bool rev3_or_later);
+
+#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 */
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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);
+
--- /dev/null
+// 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);
+}
+
--- /dev/null
+// 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, ¶m) != 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
+}
# SPDX-License-Identifier: GPL-2.0-only
menu "HD-Audio"
-config SND_HDA
- tristate
- select SND_PCM
- select SND_VMASTER
- select SND_JACK
- select SND_HDA_CORE
-
config SND_HDA_GENERIC_LEDS
bool
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_CIRRUS_SCODEC
tristate
comment "Set to Y if you want auto-loading the codec driver"
depends on SND_HDA=y && SND_HDA_GENERIC=m
-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_INTEL_HDMI_SILENT_STREAM
bool "Enable Silent Stream always for HDMI"
depends on SND_HDA_INTEL
This feature can impact power consumption as resources
are kept reserved both at transmitter and receiver.
-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.
-
endif
endmenu
snd-hda-tegra-y := hda_tegra.o
snd-hda-acpi-y := hda_acpi.o
-snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
-snd-hda-codec-y += hda_controller.o
-snd-hda-codec-$(CONFIG_SND_PROC_FS) += hda_proc.o
-
-snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
-snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o
-
# for trace-points
-CFLAGS_hda_controller.o := -I$(src)
CFLAGS_hda_intel.o := -I$(src)
+subdir-ccflags-y += -I$(src)/../../hda/common
+
snd-hda-codec-generic-y := hda_generic.o
snd-hda-codec-realtek-y := patch_realtek.o
snd-hda-codec-cmedia-y := patch_cmedia.o
snd-hda-scodec-tas2781-i2c-y := tas2781_hda_i2c.o
snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o
-# common driver
-obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
-
# codec drivers
obj-$(CONFIG_SND_HDA_GENERIC) += snd-hda-codec-generic.o
obj-$(CONFIG_SND_HDA_CODEC_REALTEK) += snd-hda-codec-realtek.o
+++ /dev/null
-// 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);
+++ /dev/null
-/* 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 */
+++ /dev/null
-// 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);
+++ /dev/null
-/* 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
+++ /dev/null
-// 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);
-
- /* 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 (codec->patch_ops.unsol_event)
- codec->patch_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;
- hda_codec_patch_t patch;
- 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;
- }
-
- patch = (hda_codec_patch_t)codec->preset->driver_data;
- if (patch) {
- err = patch(codec);
- 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 (codec->patch_ops.free)
- codec->patch_ops.free(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);
-
- 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 (codec->patch_ops.free)
- codec->patch_ops.free(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
- * patch instance.
- *
- * 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);
+++ /dev/null
-// 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);
- memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
- 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 *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 (codec->patch_ops.stream_pm)
- codec->patch_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)
-{
- 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 (codec->patch_ops.stream_pm)
- codec->patch_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;
- strcpy(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 patch 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 patch 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)
-{
- 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++) {
- if (codec->patch_ops.set_power_state)
- codec->patch_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)
-{
- unsigned int state;
-
- snd_hdac_enter_pm(&codec->core);
- if (codec->patch_ops.suspend)
- codec->patch_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)
-{
- 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 (codec->patch_ops.resume)
- codec->patch_ops.resume(codec);
- else {
- if (codec->patch_ops.init)
- codec->patch_ops.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)
-{
- int err = 0;
- hda_exec_init_verbs(codec);
- /* continue to initialize... */
- if (codec->patch_ops.init)
- err = codec->patch_ops.init(codec);
- if (!err && codec->patch_ops.build_controls)
- err = codec->patch_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_pcm *cpcm;
- int err;
-
- if (!list_empty(&codec->pcm_list_head))
- return 0; /* already parsed */
-
- if (!codec->patch_ops.build_pcms)
- return 0;
-
- err = codec->patch_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;
- strcpy(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");
+++ /dev/null
-// 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 "hda_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);
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* SPDX-License-Identifier: GPL-2.0 */
-#undef TRACE_SYSTEM
-#define TRACE_SYSTEM hda_controller
-#define TRACE_INCLUDE_FILE hda_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>
+++ /dev/null
-// 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;
-}
+++ /dev/null
-// 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);
-
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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);
-
-/*
- * 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);
-
-int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid,
- unsigned char *buf, int *eld_size,
- bool rev3_or_later);
-
-#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 */
+++ /dev/null
-// 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);
-}
-
+++ /dev/null
-// 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, ¶m) != 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
-}