[ALSA] fm801 - Add PM support

Modules: FM801 driver

Add PM support to fm801 driver.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c
index a57aca7..6ab4aef 100644
--- a/sound/pci/fm801.c
+++ b/sound/pci/fm801.c
@@ -103,7 +103,11 @@
 #define FM801_OPL3_DATA1	0x6b	/* OPL3 Bank 1 Write */
 #define FM801_POWERDOWN		0x70	/* Blocks Power Down Control */
 
-#define FM801_AC97_ADDR_SHIFT	10
+/* codec access */
+#define FM801_AC97_READ		(1<<7)	/* read=1, write=0 */
+#define FM801_AC97_VALID	(1<<8)	/* port valid=1 */
+#define FM801_AC97_BUSY		(1<<9)	/* busy=1 */
+#define FM801_AC97_ADDR_SHIFT	10	/* codec id (2bit) */
 
 /* playback and record control register bits */
 #define FM801_BUF1_LAST		(1<<1)
@@ -189,6 +193,10 @@
 #ifdef TEA575X_RADIO
 	struct snd_tea575x tea;
 #endif
+
+#ifdef CONFIG_PM
+	u16 saved_regs[0x20];
+#endif
 };
 
 static struct pci_device_id snd_fm801_ids[] = {
@@ -231,7 +239,7 @@
 	 *  Wait until the codec interface is not ready..
 	 */
 	for (idx = 0; idx < 100; idx++) {
-		if (!(inw(FM801_REG(chip, AC97_CMD)) & (1<<9)))
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
 			goto ok1;
 		udelay(10);
 	}
@@ -246,7 +254,7 @@
 	 *  Wait until the write command is not completed..
          */
 	for (idx = 0; idx < 1000; idx++) {
-		if (!(inw(FM801_REG(chip, AC97_CMD)) & (1<<9)))
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
 			return;
 		udelay(10);
 	}
@@ -262,7 +270,7 @@
 	 *  Wait until the codec interface is not ready..
 	 */
 	for (idx = 0; idx < 100; idx++) {
-		if (!(inw(FM801_REG(chip, AC97_CMD)) & (1<<9)))
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
 			goto ok1;
 		udelay(10);
 	}
@@ -271,9 +279,10 @@
 
  ok1:
 	/* read command */
-	outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | (1<<7), FM801_REG(chip, AC97_CMD));
+	outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ,
+	     FM801_REG(chip, AC97_CMD));
 	for (idx = 0; idx < 100; idx++) {
-		if (!(inw(FM801_REG(chip, AC97_CMD)) & (1<<9)))
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
 			goto ok2;
 		udelay(10);
 	}
@@ -282,7 +291,7 @@
 
  ok2:
 	for (idx = 0; idx < 1000; idx++) {
-		if (inw(FM801_REG(chip, AC97_CMD)) & (1<<8))
+		if (inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_VALID)
 			goto ok3;
 		udelay(10);
 	}
@@ -354,9 +363,11 @@
 		chip->ply_ctrl &= ~(FM801_START | FM801_PAUSE);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
 		chip->ply_ctrl |= FM801_PAUSE;
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
 		chip->ply_ctrl &= ~FM801_PAUSE;
 		break;
 	default:
@@ -387,9 +398,11 @@
 		chip->cap_ctrl &= ~(FM801_START | FM801_PAUSE);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
 		chip->cap_ctrl |= FM801_PAUSE;
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
 		chip->cap_ctrl &= ~FM801_PAUSE;
 		break;
 	default:
@@ -557,7 +570,7 @@
 {
 	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
 				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
-				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
 				 SNDRV_PCM_INFO_MMAP_VALID),
 	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
 	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
@@ -577,7 +590,7 @@
 {
 	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
 				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
-				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
 				 SNDRV_PCM_INFO_MMAP_VALID),
 	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
 	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
@@ -1218,6 +1231,85 @@
  *  initialization routines
  */
 
+static int wait_for_codec(struct fm801 *chip, unsigned int codec_id,
+			  unsigned short reg, unsigned long waits)
+{
+	unsigned long timeout = jiffies + waits;
+
+	outw(FM801_AC97_READ | (codec_id << FM801_AC97_ADDR_SHIFT) | reg,
+	     FM801_REG(chip, AC97_CMD));
+	udelay(5);
+	do {
+		if ((inw(FM801_REG(chip, AC97_CMD)) & (FM801_AC97_VALID|FM801_AC97_BUSY))
+		    == FM801_AC97_VALID)
+			return 0;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(timeout, jiffies));
+	return -EIO;
+}
+
+static int snd_fm801_chip_init(struct fm801 *chip, int resume)
+{
+	int id;
+	unsigned short cmdw;
+
+	/* codec cold reset + AC'97 warm reset */
+	outw((1<<5) | (1<<6), FM801_REG(chip, CODEC_CTRL));
+	inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */
+	udelay(100);
+	outw(0, FM801_REG(chip, CODEC_CTRL));
+
+	if (wait_for_codec(chip, 0, AC97_RESET, msecs_to_jiffies(750)) < 0) {
+		snd_printk(KERN_ERR "Primary AC'97 codec not found\n");
+		if (! resume)
+			return -EIO;
+	}
+
+	if (chip->multichannel) {
+		if (chip->secondary_addr) {
+			wait_for_codec(chip, chip->secondary_addr,
+				       AC97_VENDOR_ID1, msecs_to_jiffies(50));
+		} else {
+			/* my card has the secondary codec */
+			/* at address #3, so the loop is inverted */
+			for (id = 3; id > 0; id--) {
+				if (! wait_for_codec(chip, id, AC97_VENDOR_ID1,
+						     msecs_to_jiffies(50))) {
+					cmdw = inw(FM801_REG(chip, AC97_DATA));
+					if (cmdw != 0xffff && cmdw != 0) {
+						chip->secondary = 1;
+						chip->secondary_addr = id;
+						break;
+					}
+				}
+			}
+		}
+
+		/* the recovery phase, it seems that probing for non-existing codec might */
+		/* cause timeout problems */
+		wait_for_codec(chip, 0, AC97_VENDOR_ID1, msecs_to_jiffies(750));
+	}
+
+	/* init volume */
+	outw(0x0808, FM801_REG(chip, PCM_VOL));
+	outw(0x9f1f, FM801_REG(chip, FM_VOL));
+	outw(0x8808, FM801_REG(chip, I2S_VOL));
+
+	/* I2S control - I2S mode */
+	outw(0x0003, FM801_REG(chip, I2S_MODE));
+
+	/* interrupt setup - unmask MPU, PLAYBACK & CAPTURE */
+	cmdw = inw(FM801_REG(chip, IRQ_MASK));
+	cmdw &= ~0x0083;
+	outw(cmdw, FM801_REG(chip, IRQ_MASK));
+
+	/* interrupt clear */
+	outw(FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU, FM801_REG(chip, IRQ_STATUS));
+
+	return 0;
+}
+
+
 static int snd_fm801_free(struct fm801 *chip)
 {
 	unsigned short cmdw;
@@ -1255,9 +1347,7 @@
 				      struct fm801 ** rchip)
 {
 	struct fm801 *chip;
-	unsigned char rev, id;
-	unsigned short cmdw;
-	unsigned long timeout;
+	unsigned char rev;
 	int err;
 	static struct snd_device_ops ops = {
 		.dev_free =	snd_fm801_dev_free,
@@ -1294,81 +1384,7 @@
 	if (rev >= 0xb1)	/* FM801-AU */
 		chip->multichannel = 1;
 
-	/* codec cold reset + AC'97 warm reset */
-	outw((1<<5)|(1<<6), FM801_REG(chip, CODEC_CTRL));
-	inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */
-	udelay(100);
-	outw(0, FM801_REG(chip, CODEC_CTRL));
-
-	timeout = (jiffies + (3 * HZ) / 4) + 1;		/* min 750ms */
-
-	outw((1<<7) | (0 << FM801_AC97_ADDR_SHIFT), FM801_REG(chip, AC97_CMD));
-	udelay(5);
-	do {
-		if ((inw(FM801_REG(chip, AC97_CMD)) & (3<<8)) == (1<<8))
-			goto __ac97_secondary;
-		schedule_timeout_uninterruptible(1);
-	} while (time_after(timeout, jiffies));
-	snd_printk(KERN_ERR "Primary AC'97 codec not found\n");
-	snd_fm801_free(chip);
-	return -EIO;
-
-      __ac97_secondary:
-      	if (!chip->multichannel)	/* lookup is not required */
-      		goto __ac97_ok;
-	for (id = 3; id > 0; id--) {	/* my card has the secondary codec */
-					/* at address #3, so the loop is inverted */
-
-		timeout = jiffies + HZ / 20;
-
-		outw((1<<7) | (id << FM801_AC97_ADDR_SHIFT) | AC97_VENDOR_ID1,
-		     FM801_REG(chip, AC97_CMD));
-		udelay(5);
-		do {
-			if ((inw(FM801_REG(chip, AC97_CMD)) & (3<<8)) == (1<<8)) {
-				cmdw = inw(FM801_REG(chip, AC97_DATA));
-				if (cmdw != 0xffff && cmdw != 0) {
-					chip->secondary = 1;
-					chip->secondary_addr = id;
-					goto __ac97_ok;
-				}
-			}
-			schedule_timeout_uninterruptible(1);
-		} while (time_after(timeout, jiffies));
-	}
-
-	/* the recovery phase, it seems that probing for non-existing codec might */
-	/* cause timeout problems */
-	timeout = (jiffies + (3 * HZ) / 4) + 1;		/* min 750ms */
-
-	outw((1<<7) | (0 << FM801_AC97_ADDR_SHIFT), FM801_REG(chip, AC97_CMD));
-	udelay(5);
-	do {
-		if ((inw(FM801_REG(chip, AC97_CMD)) & (3<<8)) == (1<<8))
-			goto __ac97_ok;
-		schedule_timeout_uninterruptible(1);
-	} while (time_after(timeout, jiffies));
-	snd_printk(KERN_ERR "Primary AC'97 codec not responding\n");
-	snd_fm801_free(chip);
-	return -EIO;
-
-      __ac97_ok:
-
-	/* init volume */
-	outw(0x0808, FM801_REG(chip, PCM_VOL));
-	outw(0x9f1f, FM801_REG(chip, FM_VOL));
-	outw(0x8808, FM801_REG(chip, I2S_VOL));
-
-	/* I2S control - I2S mode */
-	outw(0x0003, FM801_REG(chip, I2S_MODE));
-
-	/* interrupt setup - unmask MPU, PLAYBACK & CAPTURE */
-	cmdw = inw(FM801_REG(chip, IRQ_MASK));
-	cmdw &= ~0x0083;
-	outw(cmdw, FM801_REG(chip, IRQ_MASK));
-
-	/* interrupt clear */
-	outw(FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU, FM801_REG(chip, IRQ_STATUS));
+	snd_fm801_chip_init(chip, 0);
 
 	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
 		snd_fm801_free(chip);
@@ -1415,6 +1431,7 @@
 		snd_card_free(card);
 		return err;
 	}
+	card->private_data = chip;
 
 	strcpy(card->driver, "FM801");
 	strcpy(card->shortname, "ForteMedia FM801-");
@@ -1462,11 +1479,65 @@
 	pci_set_drvdata(pci, NULL);
 }
 
+#ifdef CONFIG_PM
+static unsigned char saved_regs[] = {
+	FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC,
+	FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2,
+	FM801_CAP_CTRL, FM801_CAP_COUNT, FM801_CAP_BUF1, FM801_CAP_BUF2,
+	FM801_CODEC_CTRL, FM801_I2S_MODE, FM801_VOLUME, FM801_GEN_CTRL,
+};
+
+static int snd_fm801_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct fm801 *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+	snd_ac97_suspend(chip->ac97_sec);
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		chip->saved_regs[i] = inw(chip->port + saved_regs[i]);
+	/* FIXME: tea575x suspend */
+
+	pci_set_power_state(pci, PCI_D3hot);
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	return 0;
+}
+
+static int snd_fm801_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct fm801 *chip = card->private_data;
+	int i;
+
+	pci_restore_state(pci);
+	pci_enable_device(pci);
+	pci_set_power_state(pci, PCI_D0);
+	pci_set_master(pci);
+
+	snd_fm801_chip_init(chip, 1);
+	snd_ac97_resume(chip->ac97);
+	snd_ac97_resume(chip->ac97_sec);
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		outw(chip->saved_regs[i], chip->port + saved_regs[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
 static struct pci_driver driver = {
 	.name = "FM801",
 	.id_table = snd_fm801_ids,
 	.probe = snd_card_fm801_probe,
 	.remove = __devexit_p(snd_card_fm801_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_fm801_suspend,
+	.resume = snd_fm801_resume,
+#endif
 };
 
 static int __init alsa_card_fm801_init(void)