1 /* 2 * scsi_pm.c Copyright (C) 2010 Alan Stern 3 * 4 * SCSI dynamic Power Management 5 * Initial version: Alan Stern <stern@rowland.harvard.edu> 6 */ 7 8 #include <linux/pm_runtime.h> 9 #include <linux/export.h> 10 11 #include <scsi/scsi.h> 12 #include <scsi/scsi_device.h> 13 #include <scsi/scsi_driver.h> 14 #include <scsi/scsi_host.h> 15 16 #include "scsi_priv.h" 17 18 static int scsi_dev_type_suspend(struct device *dev, pm_message_t msg) 19 { 20 struct device_driver *drv; 21 int err; 22 23 err = scsi_device_quiesce(to_scsi_device(dev)); 24 if (err == 0) { 25 drv = dev->driver; 26 if (drv && drv->suspend) 27 err = drv->suspend(dev, msg); 28 } 29 dev_dbg(dev, "scsi suspend: %d\n", err); 30 return err; 31 } 32 33 static int scsi_dev_type_resume(struct device *dev) 34 { 35 struct device_driver *drv; 36 int err = 0; 37 38 drv = dev->driver; 39 if (drv && drv->resume) 40 err = drv->resume(dev); 41 scsi_device_resume(to_scsi_device(dev)); 42 dev_dbg(dev, "scsi resume: %d\n", err); 43 return err; 44 } 45 46 #ifdef CONFIG_PM_SLEEP 47 48 static int scsi_bus_suspend_common(struct device *dev, pm_message_t msg) 49 { 50 int err = 0; 51 52 if (scsi_is_sdev_device(dev)) { 53 /* 54 * sd is the only high-level SCSI driver to implement runtime 55 * PM, and sd treats runtime suspend, system suspend, and 56 * system hibernate identically (but not system freeze). 57 */ 58 if (pm_runtime_suspended(dev)) { 59 if (msg.event == PM_EVENT_SUSPEND || 60 msg.event == PM_EVENT_HIBERNATE) 61 return 0; /* already suspended */ 62 63 /* wake up device so that FREEZE will succeed */ 64 pm_runtime_resume(dev); 65 } 66 err = scsi_dev_type_suspend(dev, msg); 67 } 68 return err; 69 } 70 71 static int scsi_bus_resume_common(struct device *dev) 72 { 73 int err = 0; 74 75 if (scsi_is_sdev_device(dev)) { 76 /* 77 * Parent device may have runtime suspended as soon as 78 * it is woken up during the system resume. 79 * 80 * Resume it on behalf of child. 81 */ 82 pm_runtime_get_sync(dev->parent); 83 err = scsi_dev_type_resume(dev); 84 pm_runtime_put_sync(dev->parent); 85 } 86 87 if (err == 0) { 88 pm_runtime_disable(dev); 89 pm_runtime_set_active(dev); 90 pm_runtime_enable(dev); 91 } 92 return err; 93 } 94 95 static int scsi_bus_suspend(struct device *dev) 96 { 97 return scsi_bus_suspend_common(dev, PMSG_SUSPEND); 98 } 99 100 static int scsi_bus_freeze(struct device *dev) 101 { 102 return scsi_bus_suspend_common(dev, PMSG_FREEZE); 103 } 104 105 static int scsi_bus_poweroff(struct device *dev) 106 { 107 return scsi_bus_suspend_common(dev, PMSG_HIBERNATE); 108 } 109 110 #else /* CONFIG_PM_SLEEP */ 111 112 #define scsi_bus_resume_common NULL 113 #define scsi_bus_suspend NULL 114 #define scsi_bus_freeze NULL 115 #define scsi_bus_poweroff NULL 116 117 #endif /* CONFIG_PM_SLEEP */ 118 119 #ifdef CONFIG_PM_RUNTIME 120 121 static int scsi_runtime_suspend(struct device *dev) 122 { 123 int err = 0; 124 125 dev_dbg(dev, "scsi_runtime_suspend\n"); 126 if (scsi_is_sdev_device(dev)) { 127 err = scsi_dev_type_suspend(dev, PMSG_AUTO_SUSPEND); 128 if (err == -EAGAIN) 129 pm_schedule_suspend(dev, jiffies_to_msecs( 130 round_jiffies_up_relative(HZ/10))); 131 } 132 133 /* Insert hooks here for targets, hosts, and transport classes */ 134 135 return err; 136 } 137 138 static int scsi_runtime_resume(struct device *dev) 139 { 140 int err = 0; 141 142 dev_dbg(dev, "scsi_runtime_resume\n"); 143 if (scsi_is_sdev_device(dev)) 144 err = scsi_dev_type_resume(dev); 145 146 /* Insert hooks here for targets, hosts, and transport classes */ 147 148 return err; 149 } 150 151 static int scsi_runtime_idle(struct device *dev) 152 { 153 int err; 154 155 dev_dbg(dev, "scsi_runtime_idle\n"); 156 157 /* Insert hooks here for targets, hosts, and transport classes */ 158 159 if (scsi_is_sdev_device(dev)) 160 err = pm_schedule_suspend(dev, 100); 161 else 162 err = pm_runtime_suspend(dev); 163 return err; 164 } 165 166 int scsi_autopm_get_device(struct scsi_device *sdev) 167 { 168 int err; 169 170 err = pm_runtime_get_sync(&sdev->sdev_gendev); 171 if (err < 0 && err !=-EACCES) 172 pm_runtime_put_sync(&sdev->sdev_gendev); 173 else 174 err = 0; 175 return err; 176 } 177 EXPORT_SYMBOL_GPL(scsi_autopm_get_device); 178 179 void scsi_autopm_put_device(struct scsi_device *sdev) 180 { 181 pm_runtime_put_sync(&sdev->sdev_gendev); 182 } 183 EXPORT_SYMBOL_GPL(scsi_autopm_put_device); 184 185 void scsi_autopm_get_target(struct scsi_target *starget) 186 { 187 pm_runtime_get_sync(&starget->dev); 188 } 189 190 void scsi_autopm_put_target(struct scsi_target *starget) 191 { 192 pm_runtime_put_sync(&starget->dev); 193 } 194 195 int scsi_autopm_get_host(struct Scsi_Host *shost) 196 { 197 int err; 198 199 err = pm_runtime_get_sync(&shost->shost_gendev); 200 if (err < 0 && err !=-EACCES) 201 pm_runtime_put_sync(&shost->shost_gendev); 202 else 203 err = 0; 204 return err; 205 } 206 207 void scsi_autopm_put_host(struct Scsi_Host *shost) 208 { 209 pm_runtime_put_sync(&shost->shost_gendev); 210 } 211 212 #else 213 214 #define scsi_runtime_suspend NULL 215 #define scsi_runtime_resume NULL 216 #define scsi_runtime_idle NULL 217 218 #endif /* CONFIG_PM_RUNTIME */ 219 220 const struct dev_pm_ops scsi_bus_pm_ops = { 221 .suspend = scsi_bus_suspend, 222 .resume = scsi_bus_resume_common, 223 .freeze = scsi_bus_freeze, 224 .thaw = scsi_bus_resume_common, 225 .poweroff = scsi_bus_poweroff, 226 .restore = scsi_bus_resume_common, 227 .runtime_suspend = scsi_runtime_suspend, 228 .runtime_resume = scsi_runtime_resume, 229 .runtime_idle = scsi_runtime_idle, 230 }; 231