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