Chapter 13. Power Management
Prev |  | Next

# Chapter 13. Power Management

If the chip is supposed to work with suspend/resume functions, you need to add power-management code to the driver. The additional code for power-management should be `ifdef`'ed with `CONFIG_PM`.

If the driver fully supports suspend/resume that is, the device can be properly resumed to its state when suspend was called, you can set the `SNDRV_PCM_INFO_RESUME` flag in the pcm info field. Usually, this is possible when the registers of the chip can be safely saved and restored to RAM. If this is set, the trigger callback is called with `SNDRV_PCM_TRIGGER_RESUME` after the resume callback completes.

Even if the driver doesn't support PM fully but partial suspend/resume is still possible, it's still worthy to implement suspend/resume callbacks. In such a case, applications would reset the status by calling `snd_pcm_prepare()` and restart the stream appropriately. Hence, you can define suspend/resume callbacks below but don't set `SNDRV_PCM_INFO_RESUME` info flag to the PCM.

Note that the trigger with SUSPEND can always be called when `snd_pcm_suspend_all` is called, regardless of the `SNDRV_PCM_INFO_RESUME` flag. The `RESUME` flag affects only the behavior of `snd_pcm_resume()`. (Thus, in theory, `SNDRV_PCM_TRIGGER_RESUME` isn't needed to be handled in the trigger callback when no `SNDRV_PCM_INFO_RESUME` flag is set. But, it's better to keep it for compatibility reasons.)

In the earlier version of ALSA drivers, a common power-management layer was provided, but it has been removed. The driver needs to define the suspend/resume hooks according to the bus the device is connected to. In the case of PCI drivers, the callbacks look like below:

      #ifdef CONFIG_PM
      static int snd_my_suspend(struct pci_dev *pci, pm_message_t state)
      {
              .... /* do things for suspend */
              return 0;
      }
      static int snd_my_resume(struct pci_dev *pci)
      {
              .... /* do things for suspend */
              return 0;
      }
      #endif

The scheme of the real suspend job is as follows.

1. Retrieve the card and the chip data.

2. Call `snd_power_change_state()` with `SNDRV_CTL_POWER_D3hot` to change the power status.

3. Call `snd_pcm_suspend_all()` to suspend the running PCM streams.

4. If AC97 codecs are used, call `snd_ac97_suspend()` for each codec.

5. Save the register values if necessary.

6. Stop the hardware if necessary.

7. Disable the PCI device by calling `pci_disable_device()`. Then, call `pci_save_state()` at last.

A typical code would be like:

      static int mychip_suspend(struct pci_dev *pci, pm_message_t state)
      {
              /* (1) */
              struct snd_card *card = pci_get_drvdata(pci);
              struct mychip *chip = card->private_data;
              /* (2) */
              snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
              /* (3) */
              snd_pcm_suspend_all(chip->pcm);
              /* (4) */
              snd_ac97_suspend(chip->ac97);
              /* (5) */
              snd_mychip_save_registers(chip);
              /* (6) */
              snd_mychip_stop_hardware(chip);
              /* (7) */
              pci_disable_device(pci);
              pci_save_state(pci);
              return 0;
      }

The scheme of the real resume job is as follows.

1. Retrieve the card and the chip data.

2. Set up PCI. First, call `pci_restore_state()`. Then enable the pci device again by calling `pci_enable_device()`. Call `pci_set_master()` if necessary, too.

3. Re-initialize the chip.

4. Restore the saved registers if necessary.

5. Resume the mixer, e.g. calling `snd_ac97_resume()`.

6. Restart the hardware (if any).

7. Call `snd_power_change_state()` with `SNDRV_CTL_POWER_D0` to notify the processes.

A typical code would be like:

      static int mychip_resume(struct pci_dev *pci)
      {
              /* (1) */
              struct snd_card *card = pci_get_drvdata(pci);
              struct mychip *chip = card->private_data;
              /* (2) */
              pci_restore_state(pci);
              pci_enable_device(pci);
              pci_set_master(pci);
              /* (3) */
              snd_mychip_reinit_chip(chip);
              /* (4) */
              snd_mychip_restore_registers(chip);
              /* (5) */
              snd_ac97_resume(chip->ac97);
              /* (6) */
              snd_mychip_restart_chip(chip);
              /* (7) */
              snd_power_change_state(card, SNDRV_CTL_POWER_D0);
              return 0;
      }

As shown in the above, it's better to save registers after suspending the PCM operations via `snd_pcm_suspend_all()` or `snd_pcm_suspend()`. It means that the PCM streams are already stoppped when the register snapshot is taken. But, remember that you don't have to restart the PCM stream in the resume callback. It'll be restarted via trigger call with `SNDRV_PCM_TRIGGER_RESUME` when necessary.

OK, we have all callbacks now. Let's set them up. In the initialization of the card, make sure that you can get the chip data from the card instance, typically via `private_data` field, in case you created the chip data individually.

      static int snd_mychip_probe(struct pci_dev *pci,
                                  const struct pci_device_id *pci_id)
      {
              ....
              struct snd_card *card;
              struct mychip *chip;
              int err;
              ....
              err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
              ....
              chip = kzalloc(sizeof(*chip), GFP_KERNEL);
              ....
              card->private_data = chip;
              ....
      }

When you created the chip data with `snd_card_create()`, it's anyway accessible via `private_data` field.

      static int snd_mychip_probe(struct pci_dev *pci,
                                  const struct pci_device_id *pci_id)
      {
              ....
              struct snd_card *card;
              struct mychip *chip;
              int err;
              ....
              err = snd_card_create(index[dev], id[dev], THIS_MODULE,
                                    sizeof(struct mychip), &card);
              ....
              chip = card->private_data;
              ....
      }

If you need a space to save the registers, allocate the buffer for it here, too, since it would be fatal if you cannot allocate a memory in the suspend phase. The allocated buffer should be released in the corresponding destructor.

And next, set suspend/resume callbacks to the pci_driver.

      static struct pci_driver driver = {
              .name = KBUILD_MODNAME,
              .id_table = snd_my_ids,
              .probe = snd_my_probe,
              .remove = snd_my_remove,
      #ifdef CONFIG_PM
              .suspend = snd_my_suspend,
              .resume = snd_my_resume,
      #endif
      };

Prev |  | Next
Chapter 12. Proc Interface | Home | Chapter 14. Module Parameters