Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 528ba522 authored by Knut Petersen's avatar Knut Petersen Committed by Takashi Iwai
Browse files

ALSA: rme96: Add PM support v3



Without proper power management handling, the first use
of a Digi96/8 anytime after a suspend / resume cycle will
start playback with distortions.

v3: Abort if vmalloc() of suspend buffers fail, but do not
leak memory in that case.

[fixed wrong memory leak fix again -- tiwai]

Signed-off-by: default avatarKnut Petersen <Knut_Petersen@t-online.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent b43dd416
Loading
Loading
Loading
Loading
+117 −0
Original line number Original line Diff line number Diff line
@@ -239,6 +239,13 @@ struct rme96 {


	u8 rev; /* card revision number */
	u8 rev; /* card revision number */


#ifdef CONFIG_PM
	u32 playback_pointer;
	u32 capture_pointer;
	void *playback_suspend_buffer;
	void *capture_suspend_buffer;
#endif

	struct snd_pcm_substream *playback_substream;
	struct snd_pcm_substream *playback_substream;
	struct snd_pcm_substream *capture_substream;
	struct snd_pcm_substream *capture_substream;


@@ -370,6 +377,7 @@ static struct snd_pcm_hardware snd_rme96_playback_spdif_info =
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_RESUME |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_PAUSE),
			      SNDRV_PCM_INFO_PAUSE),
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
@@ -400,6 +408,7 @@ static struct snd_pcm_hardware snd_rme96_capture_spdif_info =
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_RESUME |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_PAUSE),
			      SNDRV_PCM_INFO_PAUSE),
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
@@ -430,6 +439,7 @@ static struct snd_pcm_hardware snd_rme96_playback_adat_info =
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_RESUME |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_PAUSE),
			      SNDRV_PCM_INFO_PAUSE),
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
@@ -456,6 +466,7 @@ static struct snd_pcm_hardware snd_rme96_capture_adat_info =
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_MMAP_VALID |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_SYNC_START |
			      SNDRV_PCM_INFO_RESUME |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_INTERLEAVED |
			      SNDRV_PCM_INFO_PAUSE),
			      SNDRV_PCM_INFO_PAUSE),
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
@@ -1386,6 +1397,7 @@ snd_rme96_playback_trigger(struct snd_pcm_substream *substream,
		}
		}
		break;
		break;


	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_STOP:
		if (RME96_ISPLAYING(rme96)) {
		if (RME96_ISPLAYING(rme96)) {
			if (substream != rme96->playback_substream)
			if (substream != rme96->playback_substream)
@@ -1401,6 +1413,7 @@ snd_rme96_playback_trigger(struct snd_pcm_substream *substream,
						 : RME96_STOP_PLAYBACK);
						 : RME96_STOP_PLAYBACK);
		break;
		break;


	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (!RME96_ISPLAYING(rme96))
		if (!RME96_ISPLAYING(rme96))
			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH
			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH
@@ -1441,6 +1454,7 @@ snd_rme96_capture_trigger(struct snd_pcm_substream *substream,
		}
		}
		break;
		break;


	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_STOP:
		if (RME96_ISRECORDING(rme96)) {
		if (RME96_ISRECORDING(rme96)) {
			if (substream != rme96->capture_substream)
			if (substream != rme96->capture_substream)
@@ -1456,6 +1470,7 @@ snd_rme96_capture_trigger(struct snd_pcm_substream *substream,
						 : RME96_STOP_CAPTURE);
						 : RME96_STOP_CAPTURE);
		break;
		break;


	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (!RME96_ISRECORDING(rme96))
		if (!RME96_ISRECORDING(rme96))
			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH
			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH
@@ -1556,6 +1571,10 @@ snd_rme96_free(void *private_data)
		pci_release_regions(rme96->pci);
		pci_release_regions(rme96->pci);
		rme96->port = 0;
		rme96->port = 0;
	}
	}
#ifdef CONFIG_PM
	vfree(rme96->playback_suspend_buffer);
	vfree(rme96->capture_suspend_buffer);
#endif
	pci_disable_device(rme96->pci);
	pci_disable_device(rme96->pci);
}
}


@@ -2354,6 +2373,83 @@ snd_rme96_create_switches(struct snd_card *card,
 * Card initialisation
 * Card initialisation
 */
 */


#ifdef CONFIG_PM

static int
snd_rme96_suspend(struct pci_dev *pci,
		  pm_message_t state)
{
	struct snd_card *card = pci_get_drvdata(pci);
	struct rme96 *rme96 = card->private_data;

	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
	snd_pcm_suspend(rme96->playback_substream);
	snd_pcm_suspend(rme96->capture_substream);

	/* save capture & playback pointers */
	rme96->playback_pointer = readl(rme96->iobase + RME96_IO_GET_PLAY_POS)
				  & RME96_RCR_AUDIO_ADDR_MASK;
	rme96->capture_pointer = readl(rme96->iobase + RME96_IO_GET_REC_POS)
				 & RME96_RCR_AUDIO_ADDR_MASK;

	/* save playback and capture buffers */
	memcpy_fromio(rme96->playback_suspend_buffer,
		      rme96->iobase + RME96_IO_PLAY_BUFFER, RME96_BUFFER_SIZE);
	memcpy_fromio(rme96->capture_suspend_buffer,
		      rme96->iobase + RME96_IO_REC_BUFFER, RME96_BUFFER_SIZE);

	/* disable the DAC  */
	rme96->areg &= ~RME96_AR_DAC_EN;
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);

	pci_disable_device(pci);
	pci_save_state(pci);

	return 0;
}

static int
snd_rme96_resume(struct pci_dev *pci)
{
	struct snd_card *card = pci_get_drvdata(pci);
	struct rme96 *rme96 = card->private_data;

	pci_restore_state(pci);
	pci_enable_device(pci);

	/* reset playback and record buffer pointers */
	writel(0, rme96->iobase + RME96_IO_SET_PLAY_POS
		  + rme96->playback_pointer);
	writel(0, rme96->iobase + RME96_IO_SET_REC_POS
		  + rme96->capture_pointer);

	/* restore playback and capture buffers */
	memcpy_toio(rme96->iobase + RME96_IO_PLAY_BUFFER,
		    rme96->playback_suspend_buffer, RME96_BUFFER_SIZE);
	memcpy_toio(rme96->iobase + RME96_IO_REC_BUFFER,
		    rme96->capture_suspend_buffer, RME96_BUFFER_SIZE);

	/* reset the ADC */
	writel(rme96->areg | RME96_AR_PD2,
	       rme96->iobase + RME96_IO_ADDITIONAL_REG);
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);

	/* reset and enable DAC, restore analog volume */
	snd_rme96_reset_dac(rme96);
	rme96->areg |= RME96_AR_DAC_EN;
	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
	if (RME96_HAS_ANALOG_OUT(rme96)) {
		usleep_range(3000, 10000);
		snd_rme96_apply_dac_volume(rme96);
	}

	snd_power_change_state(card, SNDRV_CTL_POWER_D0);

	return 0;
}

#endif

static void snd_rme96_card_free(struct snd_card *card)
static void snd_rme96_card_free(struct snd_card *card)
{
{
	snd_rme96_free(card->private_data);
	snd_rme96_free(card->private_data);
@@ -2390,6 +2486,23 @@ snd_rme96_probe(struct pci_dev *pci,
		return err;
		return err;
	}
	}
	
	
#ifdef CONFIG_PM
	rme96->playback_suspend_buffer = vmalloc(RME96_BUFFER_SIZE);
	if (!rme96->playback_suspend_buffer) {
		snd_printk(KERN_ERR
			   "Failed to allocate playback suspend buffer!\n");
		snd_card_free(card);
		return -ENOMEM;
	}
	rme96->capture_suspend_buffer = vmalloc(RME96_BUFFER_SIZE);
	if (!rme96->capture_suspend_buffer) {
		snd_printk(KERN_ERR
			   "Failed to allocate capture suspend buffer!\n");
		snd_card_free(card);
		return -ENOMEM;
	}
#endif

	strcpy(card->driver, "Digi96");
	strcpy(card->driver, "Digi96");
	switch (rme96->pci->device) {
	switch (rme96->pci->device) {
	case PCI_DEVICE_ID_RME_DIGI96:
	case PCI_DEVICE_ID_RME_DIGI96:
@@ -2432,6 +2545,10 @@ static struct pci_driver rme96_driver = {
	.id_table = snd_rme96_ids,
	.id_table = snd_rme96_ids,
	.probe = snd_rme96_probe,
	.probe = snd_rme96_probe,
	.remove = snd_rme96_remove,
	.remove = snd_rme96_remove,
#ifdef CONFIG_PM
	.suspend = snd_rme96_suspend,
	.resume = snd_rme96_resume,
#endif
};
};


module_pci_driver(rme96_driver);
module_pci_driver(rme96_driver);