ALSA: hda - Add codec reconfiguration feature

Added the reconfiguration feature of any individual codec.
Via the reconfiguration, the old resources are released and
the patch is called again to recreate the PCM and mixers in
addition to the re-initialization.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index bc3ed24..5b54ac0 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -344,7 +344,7 @@
 /*
  * initialize unsolicited queue
  */
-static int __devinit init_unsol_queue(struct hda_bus *bus)
+static int init_unsol_queue(struct hda_bus *bus)
 {
 	struct hda_bus_unsolicited *unsol;
 
@@ -454,7 +454,7 @@
 /*
  * find a matching codec preset
  */
-static const struct hda_codec_preset __devinit *
+static const struct hda_codec_preset *
 find_codec_preset(struct hda_codec *codec)
 {
 	const struct hda_codec_preset **tbl, *preset;
@@ -624,6 +624,13 @@
 	init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
 	init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
 	snd_array_init(&codec->mixers, sizeof(struct snd_kcontrol *), 32);
+	if (codec->bus->modelname) {
+		codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
+		if (!codec->modelname) {
+			snd_hda_codec_free(codec);
+			return -ENODEV;
+		}
+	}
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	INIT_DELAYED_WORK(&codec->power_work, hda_power_work);
@@ -672,6 +679,30 @@
 	if (bus->modelname)
 		codec->modelname = kstrdup(bus->modelname, GFP_KERNEL);
 
+	err = snd_hda_codec_configure(codec);
+	if (err < 0) {
+		snd_hda_codec_free(codec);
+		return err;
+	}
+	snd_hda_codec_proc_new(codec);
+
+#ifdef CONFIG_SND_HDA_HWDEP
+	snd_hda_create_hwdep(codec);
+#endif
+
+	sprintf(component, "HDA:%08x,%08x,%08x", codec->vendor_id,
+		codec->subsystem_id, codec->revision_id);
+	snd_component_add(codec->bus->card, component);
+
+	if (codecp)
+		*codecp = codec;
+	return 0;
+}
+
+int snd_hda_codec_configure(struct hda_codec *codec)
+{
+	int err;
+
 	codec->preset = find_codec_preset(codec);
 	if (!codec->name) {
 		err = get_codec_name(codec);
@@ -698,25 +729,9 @@
 		printk(KERN_ERR "hda-codec: No codec parser is available\n");
 
  patched:
-	if (err < 0) {
-		snd_hda_codec_free(codec);
-		return err;
-	}
-
-	if (codec->patch_ops.unsol_event)
-		init_unsol_queue(bus);
-
-	snd_hda_codec_proc_new(codec);
-#ifdef CONFIG_SND_HDA_HWDEP
-	snd_hda_create_hwdep(codec);
-#endif
-
-	sprintf(component, "HDA:%08x,%08x,%08x", codec->vendor_id, codec->subsystem_id, codec->revision_id);
-	snd_component_add(codec->bus->card, component);
-
-	if (codecp)
-		*codecp = codec;
-	return 0;
+	if (!err && codec->patch_ops.unsol_event)
+		err = init_unsol_queue(codec->bus);
+	return err;
 }
 
 /**
@@ -1118,6 +1133,31 @@
 	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_scheduled_work();
+#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);
+	}
+	if (codec->patch_ops.free)
+		codec->patch_ops.free(codec);
+	codec->spec = NULL;
+	free_hda_cache(&codec->amp_cache);
+	free_hda_cache(&codec->cmd_cache);
+	codec->num_pcms = 0;
+	codec->pcm_info = NULL;
+	codec->preset = NULL;
+}
+
 /* 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)
@@ -1939,23 +1979,30 @@
 	struct hda_codec *codec;
 
 	list_for_each_entry(codec, &bus->codec_list, list) {
-		int err = 0;
-		/* fake as if already powered-on */
-		hda_keep_power_on(codec);
-		/* then fire up */
-		hda_set_power_state(codec,
-				    codec->afg ? codec->afg : codec->mfg,
-				    AC_PWRST_D0);
-		/* 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);
-		snd_hda_power_down(codec);
+		int err = snd_hda_codec_build_controls(codec);
 		if (err < 0)
 			return err;
 	}
+	return 0;
+}
 
+int snd_hda_codec_build_controls(struct hda_codec *codec)
+{
+	int err = 0;
+	/* fake as if already powered-on */
+	hda_keep_power_on(codec);
+	/* then fire up */
+	hda_set_power_state(codec,
+			    codec->afg ? codec->afg : codec->mfg,
+			    AC_PWRST_D0);
+	/* 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);
+	snd_hda_power_down(codec);
+	if (err < 0)
+		return err;
 	return 0;
 }
 
@@ -2256,8 +2303,8 @@
 	return 0;
 }
 
-static int __devinit set_pcm_default_values(struct hda_codec *codec,
-					    struct hda_pcm_stream *info)
+static int set_pcm_default_values(struct hda_codec *codec,
+				  struct hda_pcm_stream *info)
 {
 	/* query support PCM information from the given NID */
 	if (info->nid && (!info->rates || !info->formats)) {
@@ -2331,7 +2378,7 @@
  *
  * This function returns 0 if successfull, or a negative error code.
  */
-int __devinit snd_hda_build_pcms(struct hda_bus *bus)
+int snd_hda_build_pcms(struct hda_bus *bus)
 {
 	static const char *dev_name[HDA_PCM_NTYPES] = {
 		"Audio", "SPDIF", "HDMI", "Modem"
@@ -2352,14 +2399,17 @@
 	list_for_each_entry(codec, &bus->codec_list, list) {
 		unsigned int pcm;
 		int err;
-		if (!codec->patch_ops.build_pcms)
-			continue;
-		err = codec->patch_ops.build_pcms(codec);
-		if (err < 0)
-			return err;
+		if (!codec->num_pcms) {
+			if (!codec->patch_ops.build_pcms)
+				continue;
+			err = codec->patch_ops.build_pcms(codec);
+			if (err < 0)
+				return err;
+		}
 		for (pcm = 0; pcm < codec->num_pcms; pcm++) {
 			struct hda_pcm *cpcm = &codec->pcm_info[pcm];
 			int type = cpcm->pcm_type;
+			int dev;
 			switch (type) {
 			case HDA_PCM_TYPE_AUDIO:
 				if (num_devs[type] >= ARRAY_SIZE(audio_idx)) {
@@ -2367,7 +2417,7 @@
 						   "Too many audio devices\n");
 					continue;
 				}
-				cpcm->device = audio_idx[num_devs[type]];
+				dev = audio_idx[num_devs[type]];
 				break;
 			case HDA_PCM_TYPE_SPDIF:
 			case HDA_PCM_TYPE_HDMI:
@@ -2378,7 +2428,7 @@
 						   dev_name[type]);
 					continue;
 				}
-				cpcm->device = dev_idx[type];
+				dev = dev_idx[type];
 				break;
 			default:
 				snd_printk(KERN_WARNING
@@ -2386,9 +2436,12 @@
 				continue;
 			}
 			num_devs[type]++;
-			err = snd_hda_attach_pcm(codec, cpcm);
-			if (err < 0)
-				return err;
+			if (!cpcm->pcm) {
+				cpcm->device = dev;
+				err = snd_hda_attach_pcm(codec, cpcm);
+				if (err < 0)
+					return err;
+			}
 		}
 	}
 	return 0;