diff options
| author | Knut Petersen <Knut_Petersen@t-online.de> | 2013-08-21 09:18:54 +0200 | 
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2013-08-22 11:54:57 +0200 | 
| commit | 528ba522e18b95d25adc62367f04290776c390e5 (patch) | |
| tree | 65f4fff4b10d155aa6fc5a21be2b52f4f4d2ccac | |
| parent | b43dd416be21bc8ad60984e13def032f01aaaa18 (diff) | |
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: Knut Petersen <Knut_Petersen@t-online.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
| -rw-r--r-- | sound/pci/rme96.c | 117 | 
1 files changed, 117 insertions, 0 deletions
| diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c index 4e9a5563eeca..0506530de5a5 100644 --- a/sound/pci/rme96.c +++ b/sound/pci/rme96.c @@ -239,6 +239,13 @@ struct rme96 {  	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 *capture_substream; @@ -370,6 +377,7 @@ static struct snd_pcm_hardware snd_rme96_playback_spdif_info =  	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |  			      SNDRV_PCM_INFO_MMAP_VALID |  			      SNDRV_PCM_INFO_SYNC_START | +			      SNDRV_PCM_INFO_RESUME |  			      SNDRV_PCM_INFO_INTERLEAVED |  			      SNDRV_PCM_INFO_PAUSE),  	.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 |  			      SNDRV_PCM_INFO_MMAP_VALID |  			      SNDRV_PCM_INFO_SYNC_START | +			      SNDRV_PCM_INFO_RESUME |  			      SNDRV_PCM_INFO_INTERLEAVED |  			      SNDRV_PCM_INFO_PAUSE),  	.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 |  			      SNDRV_PCM_INFO_MMAP_VALID |  			      SNDRV_PCM_INFO_SYNC_START | +			      SNDRV_PCM_INFO_RESUME |  			      SNDRV_PCM_INFO_INTERLEAVED |  			      SNDRV_PCM_INFO_PAUSE),  	.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 |  			      SNDRV_PCM_INFO_MMAP_VALID |  			      SNDRV_PCM_INFO_SYNC_START | +			      SNDRV_PCM_INFO_RESUME |  			      SNDRV_PCM_INFO_INTERLEAVED |  			      SNDRV_PCM_INFO_PAUSE),  	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE | @@ -1386,6 +1397,7 @@ snd_rme96_playback_trigger(struct snd_pcm_substream *substream,  		}  		break; +	case SNDRV_PCM_TRIGGER_SUSPEND:  	case SNDRV_PCM_TRIGGER_STOP:  		if (RME96_ISPLAYING(rme96)) {  			if (substream != rme96->playback_substream) @@ -1401,6 +1413,7 @@ snd_rme96_playback_trigger(struct snd_pcm_substream *substream,  						 : RME96_STOP_PLAYBACK);  		break; +	case SNDRV_PCM_TRIGGER_RESUME:  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  		if (!RME96_ISPLAYING(rme96))  			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH @@ -1441,6 +1454,7 @@ snd_rme96_capture_trigger(struct snd_pcm_substream *substream,  		}  		break; +	case SNDRV_PCM_TRIGGER_SUSPEND:  	case SNDRV_PCM_TRIGGER_STOP:  		if (RME96_ISRECORDING(rme96)) {  			if (substream != rme96->capture_substream) @@ -1456,6 +1470,7 @@ snd_rme96_capture_trigger(struct snd_pcm_substream *substream,  						 : RME96_STOP_CAPTURE);  		break; +	case SNDRV_PCM_TRIGGER_RESUME:  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  		if (!RME96_ISRECORDING(rme96))  			snd_rme96_trigger(rme96, sync ? RME96_RESUME_BOTH @@ -1556,6 +1571,10 @@ snd_rme96_free(void *private_data)  		pci_release_regions(rme96->pci);  		rme96->port = 0;  	} +#ifdef CONFIG_PM +	vfree(rme96->playback_suspend_buffer); +	vfree(rme96->capture_suspend_buffer); +#endif  	pci_disable_device(rme96->pci);  } @@ -2354,6 +2373,83 @@ snd_rme96_create_switches(struct snd_card *card,   * 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)  {  	snd_rme96_free(card->private_data); @@ -2390,6 +2486,23 @@ snd_rme96_probe(struct pci_dev *pci,  		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");  	switch (rme96->pci->device) {  	case PCI_DEVICE_ID_RME_DIGI96: @@ -2432,6 +2545,10 @@ static struct pci_driver rme96_driver = {  	.id_table = snd_rme96_ids,  	.probe = snd_rme96_probe,  	.remove = snd_rme96_remove, +#ifdef CONFIG_PM +	.suspend = snd_rme96_suspend, +	.resume = snd_rme96_resume, +#endif  };  module_pci_driver(rme96_driver); | 
