ALSA: hda - Add infrastructure for dynamic stream allocation

Added the infrastructure for dynamic stream allocation on HD-audio.

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 6447754..19b4530 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -2262,6 +2262,28 @@
 	return 0;
 }
 
+/*
+ * attach a new PCM stream
+ */
+static int __devinit
+snd_hda_attach_pcm(struct hda_codec *codec, struct hda_pcm *pcm)
+{
+	struct hda_pcm_stream *info;
+	int stream, err;
+
+	if (!pcm->name)
+		return -EINVAL;
+	for (stream = 0; stream < 2; stream++) {
+		info = &pcm->stream[stream];
+		if (info->substreams) {
+			err = set_pcm_default_values(codec, info);
+			if (err < 0)
+				return err;
+		}
+	}
+	return codec->bus->ops.attach_pcm(codec, pcm);
+}
+
 /**
  * snd_hda_build_pcms - build PCM information
  * @bus: the BUS
@@ -2290,10 +2312,24 @@
  */
 int __devinit snd_hda_build_pcms(struct hda_bus *bus)
 {
+	static const char *dev_name[HDA_PCM_NTYPES] = {
+		"Audio", "SPDIF", "HDMI", "Modem"
+	};
+	/* starting device index for each PCM type */
+	static int dev_idx[HDA_PCM_NTYPES] = {
+		[HDA_PCM_TYPE_AUDIO] = 0,
+		[HDA_PCM_TYPE_SPDIF] = 1,
+		[HDA_PCM_TYPE_HDMI] = 3,
+		[HDA_PCM_TYPE_MODEM] = 6
+	};
+	/* normal audio device indices; not linear to keep compatibility */
+	static int audio_idx[4] = { 0, 2, 4, 5 };
 	struct hda_codec *codec;
+	int num_devs[HDA_PCM_NTYPES];
 
+	memset(num_devs, 0, sizeof(num_devs));
 	list_for_each_entry(codec, &bus->codec_list, list) {
-		unsigned int pcm, s;
+		unsigned int pcm;
 		int err;
 		if (!codec->patch_ops.build_pcms)
 			continue;
@@ -2301,15 +2337,37 @@
 		if (err < 0)
 			return err;
 		for (pcm = 0; pcm < codec->num_pcms; pcm++) {
-			for (s = 0; s < 2; s++) {
-				struct hda_pcm_stream *info;
-				info = &codec->pcm_info[pcm].stream[s];
-				if (!info->substreams)
+			struct hda_pcm *cpcm = &codec->pcm_info[pcm];
+			int type = cpcm->pcm_type;
+			switch (type) {
+			case HDA_PCM_TYPE_AUDIO:
+				if (num_devs[type] >= ARRAY_SIZE(audio_idx)) {
+					snd_printk(KERN_WARNING
+						   "Too many audio devices\n");
 					continue;
-				err = set_pcm_default_values(codec, info);
-				if (err < 0)
-					return err;
+				}
+				cpcm->device = audio_idx[num_devs[type]];
+				break;
+			case HDA_PCM_TYPE_SPDIF:
+			case HDA_PCM_TYPE_HDMI:
+			case HDA_PCM_TYPE_MODEM:
+				if (num_devs[type]) {
+					snd_printk(KERN_WARNING
+						   "%s already defined\n",
+						   dev_name[type]);
+					continue;
+				}
+				cpcm->device = dev_idx[type];
+				break;
+			default:
+				snd_printk(KERN_WARNING
+					   "Invalid PCM type %d\n", type);
+				continue;
 			}
+			num_devs[type]++;
+			err = snd_hda_attach_pcm(codec, cpcm);
+			if (err < 0)
+				return err;
 		}
 	}
 	return 0;
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index 60468f5..70e8fa0 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -542,6 +542,8 @@
 	unsigned int (*get_response)(struct hda_codec *codec);
 	/* free the private data */
 	void (*private_free)(struct hda_bus *);
+	/* attach a PCM stream */
+	int (*attach_pcm)(struct hda_codec *codec, struct hda_pcm *pcm);
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	/* notify power-up/down from codec to controller */
 	void (*pm_notify)(struct hda_codec *codec);
@@ -680,7 +682,8 @@
 	char *name;
 	struct hda_pcm_stream stream[2];
 	unsigned int pcm_type;	/* HDA_PCM_TYPE_XXX */
-	int device;	/* assigned device number */
+	int device;		/* device number to assign */
+	struct snd_pcm *pcm;	/* assigned PCM instance */
 };
 
 /* codec information */
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 9f316c1..7b0abf0 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -1180,6 +1180,7 @@
 	return 0;
 }
 
+static int azx_attach_pcm_stream(struct hda_codec *codec, struct hda_pcm *cpcm);
 
 /*
  * Codec initialization
@@ -1212,6 +1213,7 @@
 	bus_temp.pci = chip->pci;
 	bus_temp.ops.command = azx_send_cmd;
 	bus_temp.ops.get_response = azx_get_response;
+	bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	bus_temp.ops.pm_notify = azx_power_notify;
 #endif
@@ -1718,111 +1720,58 @@
 
 static void azx_pcm_free(struct snd_pcm *pcm)
 {
-	kfree(pcm->private_data);
+	struct azx_pcm *apcm = pcm->private_data;
+	if (apcm) {
+		apcm->chip->pcm[pcm->device] = NULL;
+		kfree(apcm);
+	}
 }
 
-static int __devinit create_codec_pcm(struct azx *chip, struct hda_codec *codec,
-				      struct hda_pcm *cpcm)
+static int
+azx_attach_pcm_stream(struct hda_codec *codec, struct hda_pcm *cpcm)
 {
-	int err;
+	struct azx *chip = codec->bus->private_data;
 	struct snd_pcm *pcm;
 	struct azx_pcm *apcm;
+	int pcm_dev = cpcm->device;
+	int s, err;
 
-	/* if no substreams are defined for both playback and capture,
-	 * it's just a placeholder.  ignore it.
-	 */
-	if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
-		return 0;
-
-	if (snd_BUG_ON(!cpcm->name))
+	if (pcm_dev >= AZX_MAX_PCMS) {
+		snd_printk(KERN_ERR SFX "Invalid PCM device number %d\n",
+			   pcm_dev);
 		return -EINVAL;
-
-	err = snd_pcm_new(chip->card, cpcm->name, cpcm->device,
-			  cpcm->stream[0].substreams,
-			  cpcm->stream[1].substreams,
+	}
+	if (chip->pcm[pcm_dev]) {
+		snd_printk(KERN_ERR SFX "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;
 	strcpy(pcm->name, cpcm->name);
-	apcm = kmalloc(sizeof(*apcm), GFP_KERNEL);
+	apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
 	if (apcm == NULL)
 		return -ENOMEM;
 	apcm->chip = chip;
 	apcm->codec = codec;
-	apcm->hinfo[0] = &cpcm->stream[0];
-	apcm->hinfo[1] = &cpcm->stream[1];
 	pcm->private_data = apcm;
 	pcm->private_free = azx_pcm_free;
-	if (cpcm->stream[0].substreams)
-		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops);
-	if (cpcm->stream[1].substreams)
-		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops);
+	if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM)
+		pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+	chip->pcm[pcm_dev] = pcm;
+	cpcm->pcm = pcm;
+	for (s = 0; s < 2; s++) {
+		apcm->hinfo[s] = &cpcm->stream[s];
+		if (cpcm->stream[s].substreams)
+			snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
+	}
+	/* buffer pre-allocation */
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
 					      snd_dma_pci_data(chip->pci),
 					      1024 * 64, 32 * 1024 * 1024);
-	chip->pcm[cpcm->device] = pcm;
-	return 0;
-}
-
-static int __devinit azx_pcm_create(struct azx *chip)
-{
-	static const char *dev_name[HDA_PCM_NTYPES] = {
-		"Audio", "SPDIF", "HDMI", "Modem"
-	};
-	/* starting device index for each PCM type */
-	static int dev_idx[HDA_PCM_NTYPES] = {
-		[HDA_PCM_TYPE_AUDIO] = 0,
-		[HDA_PCM_TYPE_SPDIF] = 1,
-		[HDA_PCM_TYPE_HDMI] = 3,
-		[HDA_PCM_TYPE_MODEM] = 6
-	};
-	/* normal audio device indices; not linear to keep compatibility */
-	static int audio_idx[4] = { 0, 2, 4, 5 };
-	struct hda_codec *codec;
-	int c, err;
-	int num_devs[HDA_PCM_NTYPES];
-
-	err = snd_hda_build_pcms(chip->bus);
-	if (err < 0)
-		return err;
-
-	/* create audio PCMs */
-	memset(num_devs, 0, sizeof(num_devs));
-	list_for_each_entry(codec, &chip->bus->codec_list, list) {
-		for (c = 0; c < codec->num_pcms; c++) {
-			struct hda_pcm *cpcm = &codec->pcm_info[c];
-			int type = cpcm->pcm_type;
-			switch (type) {
-			case HDA_PCM_TYPE_AUDIO:
-				if (num_devs[type] >= ARRAY_SIZE(audio_idx)) {
-					snd_printk(KERN_WARNING
-						   "Too many audio devices\n");
-					continue;
-				}
-				cpcm->device = audio_idx[num_devs[type]];
-				break;
-			case HDA_PCM_TYPE_SPDIF:
-			case HDA_PCM_TYPE_HDMI:
-			case HDA_PCM_TYPE_MODEM:
-				if (num_devs[type]) {
-					snd_printk(KERN_WARNING
-						   "%s already defined\n",
-						   dev_name[type]);
-					continue;
-				}
-				cpcm->device = dev_idx[type];
-				break;
-			default:
-				snd_printk(KERN_WARNING
-					   "Invalid PCM type %d\n", type);
-				continue;
-			}
-			num_devs[type]++;
-			err = create_codec_pcm(chip, codec, cpcm);
-			if (err < 0)
-				return err;
-		}
-	}
 	return 0;
 }
 
@@ -2324,7 +2273,7 @@
 	}
 
 	/* create PCM streams */
-	err = azx_pcm_create(chip);
+	err = snd_hda_build_pcms(chip->bus);
 	if (err < 0) {
 		snd_card_free(card);
 		return err;