diff options
Diffstat (limited to 'drivers/ata/libata-zpodd.c')
-rw-r--r-- | drivers/ata/libata-zpodd.c | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c new file mode 100644 index 000000000000..90b159b740b3 --- /dev/null +++ b/drivers/ata/libata-zpodd.c | |||
@@ -0,0 +1,299 @@ | |||
1 | #include <linux/libata.h> | ||
2 | #include <linux/cdrom.h> | ||
3 | #include <linux/pm_runtime.h> | ||
4 | #include <linux/module.h> | ||
5 | #include <scsi/scsi_device.h> | ||
6 | |||
7 | #include "libata.h" | ||
8 | |||
9 | static int zpodd_poweroff_delay = 30; /* 30 seconds for power off delay */ | ||
10 | module_param(zpodd_poweroff_delay, int, 0644); | ||
11 | MODULE_PARM_DESC(zpodd_poweroff_delay, "Poweroff delay for ZPODD in seconds"); | ||
12 | |||
13 | enum odd_mech_type { | ||
14 | ODD_MECH_TYPE_SLOT, | ||
15 | ODD_MECH_TYPE_DRAWER, | ||
16 | ODD_MECH_TYPE_UNSUPPORTED, | ||
17 | }; | ||
18 | |||
19 | struct zpodd { | ||
20 | enum odd_mech_type mech_type; /* init during probe, RO afterwards */ | ||
21 | struct ata_device *dev; | ||
22 | |||
23 | /* The following fields are synchronized by PM core. */ | ||
24 | bool from_notify; /* resumed as a result of | ||
25 | * acpi wake notification */ | ||
26 | bool zp_ready; /* ZP ready state */ | ||
27 | unsigned long last_ready; /* last ZP ready timestamp */ | ||
28 | bool zp_sampled; /* ZP ready state sampled */ | ||
29 | bool powered_off; /* ODD is powered off | ||
30 | * during suspend */ | ||
31 | }; | ||
32 | |||
33 | static int eject_tray(struct ata_device *dev) | ||
34 | { | ||
35 | struct ata_taskfile tf = {}; | ||
36 | const char cdb[] = { GPCMD_START_STOP_UNIT, | ||
37 | 0, 0, 0, | ||
38 | 0x02, /* LoEj */ | ||
39 | 0, 0, 0, 0, 0, 0, 0, | ||
40 | }; | ||
41 | |||
42 | tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; | ||
43 | tf.command = ATA_CMD_PACKET; | ||
44 | tf.protocol = ATAPI_PROT_NODATA; | ||
45 | |||
46 | return ata_exec_internal(dev, &tf, cdb, DMA_NONE, NULL, 0, 0); | ||
47 | } | ||
48 | |||
49 | /* Per the spec, only slot type and drawer type ODD can be supported */ | ||
50 | static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev) | ||
51 | { | ||
52 | char buf[16]; | ||
53 | unsigned int ret; | ||
54 | struct rm_feature_desc *desc = (void *)(buf + 8); | ||
55 | struct ata_taskfile tf = {}; | ||
56 | |||
57 | char cdb[] = { GPCMD_GET_CONFIGURATION, | ||
58 | 2, /* only 1 feature descriptor requested */ | ||
59 | 0, 3, /* 3, removable medium feature */ | ||
60 | 0, 0, 0,/* reserved */ | ||
61 | 0, sizeof(buf), | ||
62 | 0, 0, 0, | ||
63 | }; | ||
64 | |||
65 | tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; | ||
66 | tf.command = ATA_CMD_PACKET; | ||
67 | tf.protocol = ATAPI_PROT_PIO; | ||
68 | tf.lbam = sizeof(buf); | ||
69 | |||
70 | ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE, | ||
71 | buf, sizeof(buf), 0); | ||
72 | if (ret) | ||
73 | return ODD_MECH_TYPE_UNSUPPORTED; | ||
74 | |||
75 | if (be16_to_cpu(desc->feature_code) != 3) | ||
76 | return ODD_MECH_TYPE_UNSUPPORTED; | ||
77 | |||
78 | if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) | ||
79 | return ODD_MECH_TYPE_SLOT; | ||
80 | else if (desc->mech_type == 1 && desc->load == 0 && desc->eject == 1) | ||
81 | return ODD_MECH_TYPE_DRAWER; | ||
82 | else | ||
83 | return ODD_MECH_TYPE_UNSUPPORTED; | ||
84 | } | ||
85 | |||
86 | static bool odd_can_poweroff(struct ata_device *ata_dev) | ||
87 | { | ||
88 | acpi_handle handle; | ||
89 | acpi_status status; | ||
90 | struct acpi_device *acpi_dev; | ||
91 | |||
92 | handle = ata_dev_acpi_handle(ata_dev); | ||
93 | if (!handle) | ||
94 | return false; | ||
95 | |||
96 | status = acpi_bus_get_device(handle, &acpi_dev); | ||
97 | if (ACPI_FAILURE(status)) | ||
98 | return false; | ||
99 | |||
100 | return acpi_device_can_poweroff(acpi_dev); | ||
101 | } | ||
102 | |||
103 | /* Test if ODD is zero power ready by sense code */ | ||
104 | static bool zpready(struct ata_device *dev) | ||
105 | { | ||
106 | u8 sense_key, *sense_buf; | ||
107 | unsigned int ret, asc, ascq, add_len; | ||
108 | struct zpodd *zpodd = dev->zpodd; | ||
109 | |||
110 | ret = atapi_eh_tur(dev, &sense_key); | ||
111 | |||
112 | if (!ret || sense_key != NOT_READY) | ||
113 | return false; | ||
114 | |||
115 | sense_buf = dev->link->ap->sector_buf; | ||
116 | ret = atapi_eh_request_sense(dev, sense_buf, sense_key); | ||
117 | if (ret) | ||
118 | return false; | ||
119 | |||
120 | /* sense valid */ | ||
121 | if ((sense_buf[0] & 0x7f) != 0x70) | ||
122 | return false; | ||
123 | |||
124 | add_len = sense_buf[7]; | ||
125 | /* has asc and ascq */ | ||
126 | if (add_len < 6) | ||
127 | return false; | ||
128 | |||
129 | asc = sense_buf[12]; | ||
130 | ascq = sense_buf[13]; | ||
131 | |||
132 | if (zpodd->mech_type == ODD_MECH_TYPE_SLOT) | ||
133 | /* no media inside */ | ||
134 | return asc == 0x3a; | ||
135 | else | ||
136 | /* no media inside and door closed */ | ||
137 | return asc == 0x3a && ascq == 0x01; | ||
138 | } | ||
139 | |||
140 | /* | ||
141 | * Update the zpodd->zp_ready field. This field will only be set | ||
142 | * if the ODD has stayed in ZP ready state for zpodd_poweroff_delay | ||
143 | * time, and will be used to decide if power off is allowed. If it | ||
144 | * is set, it will be cleared during resume from powered off state. | ||
145 | */ | ||
146 | void zpodd_on_suspend(struct ata_device *dev) | ||
147 | { | ||
148 | struct zpodd *zpodd = dev->zpodd; | ||
149 | unsigned long expires; | ||
150 | |||
151 | if (!zpready(dev)) { | ||
152 | zpodd->zp_sampled = false; | ||
153 | zpodd->zp_ready = false; | ||
154 | return; | ||
155 | } | ||
156 | |||
157 | if (!zpodd->zp_sampled) { | ||
158 | zpodd->zp_sampled = true; | ||
159 | zpodd->last_ready = jiffies; | ||
160 | return; | ||
161 | } | ||
162 | |||
163 | expires = zpodd->last_ready + | ||
164 | msecs_to_jiffies(zpodd_poweroff_delay * 1000); | ||
165 | if (time_before(jiffies, expires)) | ||
166 | return; | ||
167 | |||
168 | zpodd->zp_ready = true; | ||
169 | } | ||
170 | |||
171 | bool zpodd_zpready(struct ata_device *dev) | ||
172 | { | ||
173 | struct zpodd *zpodd = dev->zpodd; | ||
174 | return zpodd->zp_ready; | ||
175 | } | ||
176 | |||
177 | /* | ||
178 | * Enable runtime wake capability through ACPI and set the powered_off flag, | ||
179 | * this flag will be used during resume to decide what operations are needed | ||
180 | * to take. | ||
181 | * | ||
182 | * Also, media poll needs to be silenced, so that it doesn't bring the ODD | ||
183 | * back to full power state every few seconds. | ||
184 | */ | ||
185 | void zpodd_enable_run_wake(struct ata_device *dev) | ||
186 | { | ||
187 | struct zpodd *zpodd = dev->zpodd; | ||
188 | |||
189 | sdev_disable_disk_events(dev->sdev); | ||
190 | |||
191 | zpodd->powered_off = true; | ||
192 | device_set_run_wake(&dev->sdev->sdev_gendev, true); | ||
193 | acpi_pm_device_run_wake(&dev->sdev->sdev_gendev, true); | ||
194 | } | ||
195 | |||
196 | /* Disable runtime wake capability if it is enabled */ | ||
197 | void zpodd_disable_run_wake(struct ata_device *dev) | ||
198 | { | ||
199 | struct zpodd *zpodd = dev->zpodd; | ||
200 | |||
201 | if (zpodd->powered_off) { | ||
202 | acpi_pm_device_run_wake(&dev->sdev->sdev_gendev, false); | ||
203 | device_set_run_wake(&dev->sdev->sdev_gendev, false); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | /* | ||
208 | * Post power on processing after the ODD has been recovered. If the | ||
209 | * ODD wasn't powered off during suspend, it doesn't do anything. | ||
210 | * | ||
211 | * For drawer type ODD, if it is powered on due to user pressed the | ||
212 | * eject button, the tray needs to be ejected. This can only be done | ||
213 | * after the ODD has been recovered, i.e. link is initialized and | ||
214 | * device is able to process NON_DATA PIO command, as eject needs to | ||
215 | * send command for the ODD to process. | ||
216 | * | ||
217 | * The from_notify flag set in wake notification handler function | ||
218 | * zpodd_wake_dev represents if power on is due to user's action. | ||
219 | * | ||
220 | * For both types of ODD, several fields need to be reset. | ||
221 | */ | ||
222 | void zpodd_post_poweron(struct ata_device *dev) | ||
223 | { | ||
224 | struct zpodd *zpodd = dev->zpodd; | ||
225 | |||
226 | if (!zpodd->powered_off) | ||
227 | return; | ||
228 | |||
229 | zpodd->powered_off = false; | ||
230 | |||
231 | if (zpodd->from_notify) { | ||
232 | zpodd->from_notify = false; | ||
233 | if (zpodd->mech_type == ODD_MECH_TYPE_DRAWER) | ||
234 | eject_tray(dev); | ||
235 | } | ||
236 | |||
237 | zpodd->zp_sampled = false; | ||
238 | zpodd->zp_ready = false; | ||
239 | |||
240 | sdev_enable_disk_events(dev->sdev); | ||
241 | } | ||
242 | |||
243 | static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context) | ||
244 | { | ||
245 | struct ata_device *ata_dev = context; | ||
246 | struct zpodd *zpodd = ata_dev->zpodd; | ||
247 | struct device *dev = &ata_dev->sdev->sdev_gendev; | ||
248 | |||
249 | if (event == ACPI_NOTIFY_DEVICE_WAKE && pm_runtime_suspended(dev)) { | ||
250 | zpodd->from_notify = true; | ||
251 | pm_runtime_resume(dev); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | static void ata_acpi_add_pm_notifier(struct ata_device *dev) | ||
256 | { | ||
257 | acpi_handle handle = ata_dev_acpi_handle(dev); | ||
258 | acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, | ||
259 | zpodd_wake_dev, dev); | ||
260 | } | ||
261 | |||
262 | static void ata_acpi_remove_pm_notifier(struct ata_device *dev) | ||
263 | { | ||
264 | acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->sdev->sdev_gendev); | ||
265 | acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, zpodd_wake_dev); | ||
266 | } | ||
267 | |||
268 | void zpodd_init(struct ata_device *dev) | ||
269 | { | ||
270 | enum odd_mech_type mech_type; | ||
271 | struct zpodd *zpodd; | ||
272 | |||
273 | if (dev->zpodd) | ||
274 | return; | ||
275 | |||
276 | if (!odd_can_poweroff(dev)) | ||
277 | return; | ||
278 | |||
279 | mech_type = zpodd_get_mech_type(dev); | ||
280 | if (mech_type == ODD_MECH_TYPE_UNSUPPORTED) | ||
281 | return; | ||
282 | |||
283 | zpodd = kzalloc(sizeof(struct zpodd), GFP_KERNEL); | ||
284 | if (!zpodd) | ||
285 | return; | ||
286 | |||
287 | zpodd->mech_type = mech_type; | ||
288 | |||
289 | ata_acpi_add_pm_notifier(dev); | ||
290 | zpodd->dev = dev; | ||
291 | dev->zpodd = zpodd; | ||
292 | } | ||
293 | |||
294 | void zpodd_exit(struct ata_device *dev) | ||
295 | { | ||
296 | ata_acpi_remove_pm_notifier(dev); | ||
297 | kfree(dev->zpodd); | ||
298 | dev->zpodd = NULL; | ||
299 | } | ||