+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_tlv);
+
+/*
+ * set (static) TLV for virtual master volume; recalculated as max 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[0] = SNDRV_CTL_TLVT_DB_SCALE;
+ tlv[1] = 2 * sizeof(unsigned int);
+ tlv[2] = -nums * step;
+ tlv[3] = step;
+}
+EXPORT_SYMBOL_HDA(snd_hda_set_vmaster_tlv);
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *
+_snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name, int idx)
+{
+ struct snd_ctl_elem_id id;
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ id.index = idx;
+ strcpy(id.name, name);
+ return snd_ctl_find_id(codec->bus->card, &id);
+}
+
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+ const char *name)
+{
+ return _snd_hda_find_mixer_ctl(codec, name, 0);
+}
+EXPORT_SYMBOL_HDA(snd_hda_find_mixer_ctl);
+
+/* Add a control element and assign to the codec */
+int snd_hda_ctl_add(struct hda_codec *codec, struct snd_kcontrol *kctl)
+{
+ int err;
+ struct snd_kcontrol **knewp;
+
+ err = snd_ctl_add(codec->bus->card, kctl);
+ if (err < 0)
+ return err;
+ knewp = snd_array_new(&codec->mixers);
+ if (!knewp)
+ return -ENOMEM;
+ *knewp = kctl;
+ return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ctl_add);
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+/* Clear all controls assigned to the given codec */
+void snd_hda_ctls_clear(struct hda_codec *codec)
+{
+ int i;
+ struct snd_kcontrol **kctls = codec->mixers.list;
+ for (i = 0; i < codec->mixers.used; i++)
+ snd_ctl_remove(codec->bus->card, kctls[i]);
+ snd_array_free(&codec->mixers);
+}
+
+void snd_hda_codec_reset(struct hda_codec *codec)
+{
+ int i;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+ cancel_delayed_work(&codec->power_work);
+ flush_workqueue(codec->bus->workq);
+#endif
+ snd_hda_ctls_clear(codec);
+ /* relase PCMs */
+ for (i = 0; i < codec->num_pcms; i++) {
+ if (codec->pcm_info[i].pcm) {
+ snd_device_free(codec->bus->card,
+ codec->pcm_info[i].pcm);
+ clear_bit(codec->pcm_info[i].device,
+ codec->bus->pcm_dev_bits);
+ }
+ }
+ if (codec->patch_ops.free)
+ codec->patch_ops.free(codec);
+ codec->proc_widget_hook = NULL;
+ codec->spec = NULL;
+ free_hda_cache(&codec->amp_cache);
+ free_hda_cache(&codec->cmd_cache);
+ init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
+ init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
+ codec->num_pcms = 0;
+ codec->pcm_info = NULL;
+ codec->preset = NULL;
+ module_put(codec->owner);
+ codec->owner = NULL;
+}
+#endif /* CONFIG_SND_HDA_RECONFIG */
+
+/* create a virtual master control and add slaves */
+int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+ unsigned int *tlv, const char **slaves)
+{
+ struct snd_kcontrol *kctl;
+ const char **s;
+ int err;
+
+ for (s = slaves; *s && !snd_hda_find_mixer_ctl(codec, *s); s++)
+ ;
+ if (!*s) {
+ snd_printdd("No slave found for %s\n", name);
+ return 0;
+ }
+ kctl = snd_ctl_make_virtual_master(name, tlv);
+ if (!kctl)
+ return -ENOMEM;
+ err = snd_hda_ctl_add(codec, kctl);
+ if (err < 0)
+ return err;
+
+ for (s = slaves; *s; s++) {
+ struct snd_kcontrol *sctl;
+
+ sctl = snd_hda_find_mixer_ctl(codec, *s);
+ if (!sctl) {
+ snd_printdd("Cannot find slave %s, skipped\n", *s);
+ continue;
+ }
+ err = snd_ctl_add_slave(kctl, sctl);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_vmaster);