@@ -985,8 +985,10 @@ static void ata_acpi_wake_dev(acpi_handle handle, u32 event, void *context)
struct ata_device *ata_dev = context;
if (event == ACPI_NOTIFY_DEVICE_WAKE && ata_dev &&
- pm_runtime_suspended(&ata_dev->sdev->sdev_gendev))
- scsi_autopm_get_device(ata_dev->sdev);
+ pm_runtime_suspended(&ata_dev->sdev->sdev_gendev)) {
+ ata_dev->sdev->need_eject = 1;
+ pm_runtime_resume(&ata_dev->sdev->sdev_gendev);
+ }
}
static void ata_acpi_add_pm_notifier(struct ata_device *dev)
@@ -176,7 +176,7 @@ scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_if.o
sd_mod-objs := sd.o
sd_mod-$(CONFIG_BLK_DEV_INTEGRITY) += sd_dif.o
-sr_mod-objs := sr.o sr_ioctl.o sr_vendor.o
+sr_mod-objs := sr.o sr_ioctl.o sr_vendor.o sr_pm_ops.o
ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \
:= -DCONFIG_NCR53C8XX_PREFETCH -DSCSI_NCR_BIG_ENDIAN \
-DCONFIG_SCSI_NCR53C8XX_NO_WORD_TRANSFERS
@@ -45,6 +45,7 @@
#include <linux/blkdev.h>
#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
#include <asm/uaccess.h>
#include <scsi/scsi.h>
@@ -79,6 +80,8 @@ static DEFINE_MUTEX(sr_mutex);
static int sr_probe(struct device *);
static int sr_remove(struct device *);
static int sr_done(struct scsi_cmnd *);
+static int sr_suspend(struct device *, pm_message_t msg);
+static int sr_resume(struct device *);
static struct scsi_driver sr_template = {
.owner = THIS_MODULE,
@@ -86,6 +89,8 @@ static struct scsi_driver sr_template = {
.name = "sr",
.probe = sr_probe,
.remove = sr_remove,
+ .suspend = sr_suspend,
+ .resume = sr_resume,
},
.done = sr_done,
};
@@ -146,8 +151,12 @@ static inline struct scsi_cd *scsi_cd_get(struct gendisk *disk)
kref_get(&cd->kref);
if (scsi_device_get(cd->device))
goto out_put;
+ if (scsi_autopm_get_device(cd->device))
+ goto out_pm;
goto out;
+ out_pm:
+ scsi_device_put(cd->device);
out_put:
kref_put(&cd->kref, sr_kref_release);
cd = NULL;
@@ -163,9 +172,28 @@ static void scsi_cd_put(struct scsi_cd *cd)
mutex_lock(&sr_ref_mutex);
kref_put(&cd->kref, sr_kref_release);
scsi_device_put(sdev);
+ scsi_autopm_put_device(sdev);
mutex_unlock(&sr_ref_mutex);
}
+static int sr_suspend(struct device *dev, pm_message_t msg)
+{
+ struct scsi_cd *cd;
+
+ /* no action for system pm */
+ if (!PMSG_IS_AUTO(msg))
+ return 0;
+
+ cd = dev_get_drvdata(dev);
+ return cd->pm_ops->suspend(dev);
+}
+
+static int sr_resume(struct device *dev)
+{
+ struct scsi_cd *cd = dev_get_drvdata(dev);
+ return cd->pm_ops->resume(dev);
+}
+
static unsigned int sr_get_events(struct scsi_device *sdev)
{
u8 buf[8];
@@ -220,6 +248,8 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi,
if (CDSL_CURRENT != slot)
return 0;
+ scsi_autopm_get_device(cd->device);
+
events = sr_get_events(cd->device);
cd->get_event_changed |= events & DISK_EVENT_MEDIA_CHANGE;
@@ -246,7 +276,7 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi,
}
if (!(clearing & DISK_EVENT_MEDIA_CHANGE))
- return events;
+ goto out;
do_tur:
/* let's see whether the media is there with TUR */
last_present = cd->media_present;
@@ -270,7 +300,7 @@ do_tur:
}
if (cd->ignore_get_event)
- return events;
+ goto out;
/* check whether GET_EVENT is reporting spurious MEDIA_CHANGE */
if (!cd->tur_changed) {
@@ -287,6 +317,8 @@ do_tur:
cd->tur_changed = false;
cd->get_event_changed = false;
+out:
+ scsi_autopm_put_device(cd->device);
return events;
}
@@ -702,6 +734,7 @@ static int sr_probe(struct device *dev)
get_capabilities(cd);
blk_queue_prep_rq(sdev->request_queue, sr_prep_fn);
sr_vendor_init(cd);
+ sr_pm_ops_init(cd);
disk->driverfs_dev = &sdev->sdev_gendev;
set_capacity(disk, cd->capacity);
@@ -718,6 +751,10 @@ static int sr_probe(struct device *dev)
sdev_printk(KERN_DEBUG, sdev,
"Attached scsi CD-ROM %s\n", cd->cdi.name);
+
+ /* enable runtime pm */
+ scsi_autopm_put_device(cd->device);
+
return 0;
fail_put:
@@ -965,6 +1002,9 @@ static int sr_remove(struct device *dev)
{
struct scsi_cd *cd = dev_get_drvdata(dev);
+ /* disable runtime pm */
+ scsi_autopm_get_device(cd->device);
+
blk_queue_prep_rq(cd->device->request_queue, scsi_prep_fn);
del_gendisk(cd->disk);
@@ -25,6 +25,11 @@
struct scsi_device;
+struct sr_pm_ops {
+ int (*suspend)(struct device *dev);
+ int (*resume)(struct device *dev);
+};
+
/* The CDROM is fairly slow, so we need a little extra time */
/* In fact, it is very slow if it has to spin up first */
#define IOCTL_TIMEOUT 30*HZ
@@ -49,6 +54,7 @@ typedef struct scsi_cd {
bool ignore_get_event:1; /* GET_EVENT is unreliable, use TUR */
struct cdrom_device_info cdi;
+ struct sr_pm_ops *pm_ops;
/* We hold gendisk and scsi_device references on probe and use
* the refs on this kref to decide when to release them */
struct kref kref;
@@ -74,4 +80,7 @@ void sr_vendor_init(Scsi_CD *);
int sr_cd_check(struct cdrom_device_info *);
int sr_set_blocklength(Scsi_CD *, int blocklength);
+/* sr_pm_ops.c */
+void sr_pm_ops_init(struct scsi_cd *cd);
+
#endif
new file mode 100644
@@ -0,0 +1,104 @@
+#include <linux/cdrom.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include "sr.h"
+
+static int sr_rom_suspend(struct device *dev);
+static int sr_rom_resume(struct device *dev);
+static int sr_zpodd_suspend(struct device *dev);
+static int sr_zpodd_resume(struct device *dev);
+static int sr_worm_suspend(struct device *dev);
+static int sr_worm_resume(struct device *dev);
+
+static struct sr_pm_ops sr_rom_pm_ops = {
+ .suspend = sr_rom_suspend,
+ .resume = sr_rom_resume,
+};
+
+static struct sr_pm_ops sr_zpodd_pm_ops = {
+ .suspend = sr_zpodd_suspend,
+ .resume = sr_zpodd_resume,
+};
+
+static struct sr_pm_ops sr_worm_pm_ops = {
+ .suspend = sr_worm_suspend,
+ .resume = sr_worm_resume,
+};
+
+static int sr_rom_suspend(struct device *dev)
+{
+ /* TODO: add requirement for ROM */
+ return 0;
+}
+
+static int sr_rom_resume(struct device *dev)
+{
+ return 0;
+}
+
+static int sr_worm_suspend(struct device *dev)
+{
+ /* TODO: add requirement for WORM */
+ return 0;
+}
+
+static int sr_worm_resume(struct device *dev)
+{
+ return 0;
+}
+
+static int sr_zpodd_suspend(struct device *dev)
+{
+ int suspend;
+ struct scsi_sense_hdr sshdr;
+ struct scsi_cd *cd = dev_get_drvdata(dev);
+
+ /*
+ * ZPODD can be runtime suspended when:
+ * tray type: no media inside and tray closed
+ * slot type: no media inside
+ */
+ scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr);
+
+ if (cd->cdi.mask & CDC_CLOSE_TRAY)
+ /* no media for caddy/slot type ODD */
+ suspend = scsi_sense_valid(&sshdr) && sshdr.asc == 0x3a;
+ else
+ /* no media and door closed for tray type ODD */
+ suspend = scsi_sense_valid(&sshdr) && sshdr.asc == 0x3a &&
+ sshdr.ascq == 0x01;
+
+ if (!suspend)
+ return -EBUSY;
+
+ return 0;
+}
+
+static int sr_zpodd_resume(struct device *dev)
+{
+ struct scsi_cd *cd = dev_get_drvdata(dev);
+ struct scsi_sense_hdr sshdr;
+
+ /* get the disk ready */
+ scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr);
+
+ if (cd->device->need_eject) {
+ cd->device->need_eject = 0;
+ if (!(cd->cdi.mask & CDC_CLOSE_TRAY))
+ sr_tray_move(&cd->cdi, 1);
+ }
+
+ return 0;
+
+}
+
+void sr_pm_ops_init(struct scsi_cd *cd)
+{
+ if (cd->device->type == TYPE_ROM) {
+ cd->pm_ops = &sr_rom_pm_ops;
+ if (cd->device->can_power_off)
+ cd->pm_ops = &sr_zpodd_pm_ops;
+ } else if (cd->device->type == TYPE_WORM)
+ cd->pm_ops = &sr_worm_pm_ops;
+}
@@ -156,6 +156,7 @@ struct scsi_device {
unsigned is_visible:1; /* is the device visible in sysfs */
unsigned can_power_off:1; /* Device supports runtime power off */
unsigned wce_default_on:1; /* Cache is ON by default */
+ unsigned need_eject:1; /* Need eject the tray when wakes up */
DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */
struct list_head event_list; /* asserted events */