[ALSA] fm801 - Add PM support
authorTakashi Iwai <tiwai@suse.de>
Thu, 17 Nov 2005 15:14:33 +0000 (16:14 +0100)
committerJaroslav Kysela <perex@suse.cz>
Tue, 3 Jan 2006 11:27:59 +0000 (12:27 +0100)
Modules: FM801 driver

Add PM support to fm801 driver.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/fm801.c

index a57aca7..6ab4aef 100644 (file)
@@ -103,7 +103,11 @@ MODULE_PARM_DESC(tea575x_tuner, "Enable TEA575x tuner.");
 #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 @@ struct fm801 {
 #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 @@ static void snd_fm801_codec_write(struct snd_ac97 *ac97,
         *  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 @@ static void snd_fm801_codec_write(struct snd_ac97 *ac97,
         *  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 @@ static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short
         *  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 @@ static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short
 
  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 @@ static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short
 
  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 @@ static int snd_fm801_playback_trigger(struct snd_pcm_substream *substream,
                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 @@ static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream,
                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 @@ static struct snd_pcm_hardware snd_fm801_playback =
 {
        .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 @@ static struct snd_pcm_hardware snd_fm801_capture =
 {
        .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 @@ static int __devinit snd_fm801_mixer(struct fm801 *chip)
  *  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 @@ static int __devinit snd_fm801_create(struct snd_card *card,
                                      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 @@ static int __devinit snd_fm801_create(struct snd_card *card,
        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 @@ static int __devinit snd_card_fm801_probe(struct pci_dev *pci,
                snd_card_free(card);
                return err;
        }
+       card->private_data = chip;
 
        strcpy(card->driver, "FM801");
        strcpy(card->shortname, "ForteMedia FM801-");
@@ -1462,11 +1479,65 @@ static void __devexit snd_card_fm801_remove(struct pci_dev *pci)
        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)