Commit ca54bde3 authored by Andreas Mohr's avatar Andreas Mohr Committed by Jaroslav Kysela
Browse files

[ALSA] azt3328.c: add suspend/resume support



- add suspend/resume handlers
- fix problem (private_data members not set)
Playing a file while suspending will resume correctly with this patch,
so I assume the hardware to get fully correctly reinitialized with
this patch.

Signed-off-by: default avatarAndreas Mohr <andi@lisas.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 746df948
Loading
Loading
Loading
Loading
+116 −3
Original line number Diff line number Diff line
@@ -90,9 +90,11 @@
 * 
 * TODO
 *  - test MPU401 MIDI playback etc.
 *  - power management. See e.g. intel8x0 or cs4281.
 *    This would be nice since the chip runs a bit hot, and it's *required*
 *    anyway for proper ACPI power management.
 *  - add some power micro-management (disable various units of the card
 *    as long as they're unused). However this requires I/O ports which I
 *    haven't figured out yet and which thus might not even exist...
 *    The standard suspend/resume functionality could probably make use of
 *    some improvement, too...
 *  - figure out what all unknown port bits are responsible for
 */

@@ -214,6 +216,16 @@ struct snd_azf3328 {

	struct pci_dev *pci;
	int irq;

#ifdef CONFIG_PM
	/* register value containers for power management
	 * Note: not always full I/O range preserved (just like Win driver!) */
	u16 saved_regs_codec [AZF_IO_SIZE_CODEC_PM / 2];
	u16 saved_regs_io2   [AZF_IO_SIZE_IO2_PM / 2];
	u16 saved_regs_mpu   [AZF_IO_SIZE_MPU_PM / 2];
	u16 saved_regs_synth[AZF_IO_SIZE_SYNTH_PM / 2];
	u16 saved_regs_mixer[AZF_IO_SIZE_MIXER_PM / 2];
#endif
};

static const struct pci_device_id snd_azf3328_ids[] __devinitdata = {
@@ -961,6 +973,13 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd)
		chip->is_playing = 1;
		snd_azf3328_dbgplay("STARTED PLAYBACK\n");
		break;
	case SNDRV_PCM_TRIGGER_RESUME:
		snd_azf3328_dbgplay("RESUME PLAYBACK\n");
		/* resume playback if we were active */
		if (chip->is_playing)
			snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
				snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) | DMA_RESUME);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		snd_azf3328_dbgplay("STOP PLAYBACK\n");

@@ -988,6 +1007,12 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd)
		chip->is_playing = 0;
		snd_azf3328_dbgplay("STOPPED PLAYBACK\n");
		break;
	case SNDRV_PCM_TRIGGER_SUSPEND:
		snd_azf3328_dbgplay("SUSPEND PLAYBACK\n");
		/* make sure playback is stopped */
		snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
			snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) & ~DMA_RESUME);
		break;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
                break;
@@ -995,6 +1020,7 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd)
		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
                break;
        default:
		printk(KERN_ERR "FIXME: unknown trigger mode!\n");
                return -EINVAL;
	}
	
@@ -1068,6 +1094,13 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd)
		chip->is_recording = 1;
		snd_azf3328_dbgplay("STARTED CAPTURE\n");
		break;
	case SNDRV_PCM_TRIGGER_RESUME:
		snd_azf3328_dbgplay("RESUME CAPTURE\n");
		/* resume recording if we were active */
		if (chip->is_recording)
			snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
				snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) | DMA_RESUME);
		break;
        case SNDRV_PCM_TRIGGER_STOP:
		snd_azf3328_dbgplay("STOP CAPTURE\n");

@@ -1088,6 +1121,12 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd)
		chip->is_recording = 0;
		snd_azf3328_dbgplay("STOPPED CAPTURE\n");
		break;
	case SNDRV_PCM_TRIGGER_SUSPEND:
		snd_azf3328_dbgplay("SUSPEND CAPTURE\n");
		/* make sure recording is stopped */
		snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
			snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) & ~DMA_RESUME);
		break;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
                break;
@@ -1095,6 +1134,7 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd)
		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
                break;
        default:
		printk(KERN_ERR "FIXME: unknown trigger mode!\n");
                return -EINVAL;
	}
	
@@ -1766,6 +1806,8 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
		goto out_err;
	}

	card->private_data = chip;

	if ((err = snd_mpu401_uart_new( card, 0, MPU401_HW_MPU401,
				        chip->mpu_port, 1, pci->irq, 0,
				        &chip->rmidi)) < 0) {
@@ -1791,6 +1833,8 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
		}
	}

	opl3->private_data = chip;

	sprintf(card->longname, "%s at 0x%lx, irq %i",
		card->shortname, chip->codec_port, chip->irq);

@@ -1834,11 +1878,80 @@ snd_azf3328_remove(struct pci_dev *pci)
	snd_azf3328_dbgcallleave();
}

#ifdef CONFIG_PM
static int
snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state)
{
	struct snd_card *card = pci_get_drvdata(pci);
	struct snd_azf3328 *chip = card->private_data;
	int reg;

	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
	
	snd_pcm_suspend_all(chip->pcm);

	for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; reg++)
		chip->saved_regs_mixer[reg] = inw(chip->mixer_port + reg * 2);

	/* make sure to disable master volume etc. to prevent looping sound */
	snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
	snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
	
	for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; reg++)
		chip->saved_regs_codec[reg] = inw(chip->codec_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_IO2_PM / 2; reg++)
		chip->saved_regs_io2[reg] = inw(chip->io2_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; reg++)
		chip->saved_regs_mpu[reg] = inw(chip->mpu_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_SYNTH_PM / 2; reg++)
		chip->saved_regs_synth[reg] = inw(chip->synth_port + reg * 2);

	pci_set_power_state(pci, PCI_D3hot);
	pci_disable_device(pci);
	pci_save_state(pci);
	return 0;
}

static int
snd_azf3328_resume(struct pci_dev *pci)
{
	struct snd_card *card = pci_get_drvdata(pci);
	struct snd_azf3328 *chip = card->private_data;
	int reg;

	pci_restore_state(pci);
	pci_enable_device(pci);
	pci_set_power_state(pci, PCI_D0);
	pci_set_master(pci);

	for (reg = 0; reg < AZF_IO_SIZE_IO2_PM / 2; reg++)
		outw(chip->saved_regs_io2[reg], chip->io2_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_MPU_PM / 2; reg++)
		outw(chip->saved_regs_mpu[reg], chip->mpu_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_SYNTH_PM / 2; reg++)
		outw(chip->saved_regs_synth[reg], chip->synth_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; reg++)
		outw(chip->saved_regs_mixer[reg], chip->mixer_port + reg * 2);
	for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; reg++)
		outw(chip->saved_regs_codec[reg], chip->codec_port + reg * 2);

	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
	return 0;
}
#endif




static struct pci_driver driver = {
	.name = "AZF3328",
	.id_table = snd_azf3328_ids,
	.probe = snd_azf3328_probe,
	.remove = __devexit_p(snd_azf3328_remove),
#ifdef CONFIG_PM
	.suspend = snd_azf3328_suspend,
	.resume = snd_azf3328_resume,
#endif
};

static int __init
+17 −3
Original line number Diff line number Diff line
@@ -5,6 +5,9 @@

/*** main I/O area port indices ***/
/* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */
#define AZF_IO_SIZE_CODEC	0x80
#define AZF_IO_SIZE_CODEC_PM	0x70

/* the driver initialisation suggests a layout of 4 main areas:
 * from 0x00 (playback), from 0x20 (recording) and from 0x40 (maybe MPU401??).
 * And another area from 0x60 to 0x6f (DirectX timer, IRQ management,
@@ -107,7 +110,8 @@
  #define IRQ_UNKNOWN2			0x0080 /* probably unused */
#define IDX_IO_66H		0x66    /* writing 0xffff returns 0x0000 */
#define IDX_IO_SOME_VALUE	0x68	/* this is set to e.g. 0x3ff or 0x300, and writable; maybe some buffer limit, but I couldn't find out more, PU:0x00ff */
#define IDX_IO_6AH		0x6A	/* this WORD can be set to have bits 0x0028 activated; actually inhibits PCM playback!!! maybe power management?? */
#define IDX_IO_6AH		0x6A	/* this WORD can be set to have bits 0x0028 activated (FIXME: correct??); actually inhibits PCM playback!!! maybe power management?? */
  #define IO_6A_PAUSE_PLAYBACK		0x0200 /* bit 9; sure, this pauses playback, but what the heck is this really about?? */
#define IDX_IO_6CH		0x6C
#define IDX_IO_6EH		0x6E	/* writing 0xffff returns 0x83fe */
/* further I/O indices not saved/restored, so probably not used */
@@ -115,15 +119,25 @@

/*** I/O 2 area port indices ***/
/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */ 
#define AZF_IO_SIZE_IO2		0x08
#define AZF_IO_SIZE_IO2_PM	0x06

#define IDX_IO2_LEGACY_ADDR	0x04
  #define LEGACY_SOMETHING		0x01 /* OPL3?? */
  #define LEGACY_JOY			0x08

#define AZF_IO_SIZE_MPU		0x04
#define AZF_IO_SIZE_MPU_PM	0x04

#define AZF_IO_SIZE_SYNTH	0x08
#define AZF_IO_SIZE_SYNTH_PM	0x06

/*** mixer I/O area port indices ***/
/* (only 0x22 of 0x40 bytes saved/restored by Windows driver)
 * generally spoken: AC97 register index = AZF3328 mixer reg index + 2
 * (in other words: AZF3328 NOT fully AC97 compliant) */
 * UNFORTUNATELY azf3328 is NOT truly AC97 compliant: see main file intro */
#define AZF_IO_SIZE_MIXER	0x40
#define AZF_IO_SIZE_MIXER_PM	0x22

  #define MIXER_VOLUME_RIGHT_MASK	0x001f
  #define MIXER_VOLUME_LEFT_MASK	0x1f00
  #define MIXER_MUTE_MASK		0x8000