[PATCH] Suspend support for libata

This patch adds suspend patch to libata, and ata_piix in particular. For
most low level drivers, they should just need to add the 4 hooks to
work. As I can only test ata_piix, I didn't enable it for more
though.

Suspend support is the single most important feature on a notebook, and
most new notebooks have sata drives. It's quite embarrassing that we
_still_ do not support this. Right now, it's perfectly possible to
suspend the drive in mid-transfer.

Signed-off-by: Jens Axboe <axboe@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Jens Axboe
2006-01-06 09:28:07 +01:00
committed by Linus Torvalds
parent 88202a0c84
commit 9b84754866
7 changed files with 181 additions and 0 deletions

View File

@@ -4154,6 +4154,96 @@ err_out:
* Inherited from caller.
*/
/*
* Execute a 'simple' command, that only consists of the opcode 'cmd' itself,
* without filling any other registers
*/
static int ata_do_simple_cmd(struct ata_port *ap, struct ata_device *dev,
u8 cmd)
{
struct ata_taskfile tf;
int err;
ata_tf_init(ap, &tf, dev->devno);
tf.command = cmd;
tf.flags |= ATA_TFLAG_DEVICE;
tf.protocol = ATA_PROT_NODATA;
err = ata_exec_internal(ap, dev, &tf, DMA_NONE, NULL, 0);
if (err)
printk(KERN_ERR "%s: ata command failed: %d\n",
__FUNCTION__, err);
return err;
}
static int ata_flush_cache(struct ata_port *ap, struct ata_device *dev)
{
u8 cmd;
if (!ata_try_flush_cache(dev))
return 0;
if (ata_id_has_flush_ext(dev->id))
cmd = ATA_CMD_FLUSH_EXT;
else
cmd = ATA_CMD_FLUSH;
return ata_do_simple_cmd(ap, dev, cmd);
}
static int ata_standby_drive(struct ata_port *ap, struct ata_device *dev)
{
return ata_do_simple_cmd(ap, dev, ATA_CMD_STANDBYNOW1);
}
static int ata_start_drive(struct ata_port *ap, struct ata_device *dev)
{
return ata_do_simple_cmd(ap, dev, ATA_CMD_IDLEIMMEDIATE);
}
/**
* ata_device_resume - wakeup a previously suspended devices
*
* Kick the drive back into action, by sending it an idle immediate
* command and making sure its transfer mode matches between drive
* and host.
*
*/
int ata_device_resume(struct ata_port *ap, struct ata_device *dev)
{
if (ap->flags & ATA_FLAG_SUSPENDED) {
ap->flags &= ~ATA_FLAG_SUSPENDED;
ata_set_mode(ap);
}
if (!ata_dev_present(dev))
return 0;
if (dev->class == ATA_DEV_ATA)
ata_start_drive(ap, dev);
return 0;
}
/**
* ata_device_suspend - prepare a device for suspend
*
* Flush the cache on the drive, if appropriate, then issue a
* standbynow command.
*
*/
int ata_device_suspend(struct ata_port *ap, struct ata_device *dev)
{
if (!ata_dev_present(dev))
return 0;
if (dev->class == ATA_DEV_ATA)
ata_flush_cache(ap, dev);
ata_standby_drive(ap, dev);
ap->flags |= ATA_FLAG_SUSPENDED;
return 0;
}
int ata_port_start (struct ata_port *ap)
{
struct device *dev = ap->host_set->dev;
@@ -4902,6 +4992,23 @@ int pci_test_config_bits(struct pci_dev *pdev, const struct pci_bits *bits)
return (tmp == bits->val) ? 1 : 0;
}
int ata_pci_device_suspend(struct pci_dev *pdev, pm_message_t state)
{
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
return 0;
}
int ata_pci_device_resume(struct pci_dev *pdev)
{
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
pci_enable_device(pdev);
pci_set_master(pdev);
return 0;
}
#endif /* CONFIG_PCI */
@@ -5005,4 +5112,11 @@ EXPORT_SYMBOL_GPL(ata_pci_host_stop);
EXPORT_SYMBOL_GPL(ata_pci_init_native_mode);
EXPORT_SYMBOL_GPL(ata_pci_init_one);
EXPORT_SYMBOL_GPL(ata_pci_remove_one);
EXPORT_SYMBOL_GPL(ata_pci_device_suspend);
EXPORT_SYMBOL_GPL(ata_pci_device_resume);
#endif /* CONFIG_PCI */
EXPORT_SYMBOL_GPL(ata_device_suspend);
EXPORT_SYMBOL_GPL(ata_device_resume);
EXPORT_SYMBOL_GPL(ata_scsi_device_suspend);
EXPORT_SYMBOL_GPL(ata_scsi_device_resume);