]>
Commit | Line | Data |
---|---|---|
1c2a49f6 AV |
1 | /* |
2 | * AHCI SATA platform driver | |
3 | * | |
4 | * Copyright 2004-2005 Red Hat, Inc. | |
5 | * Jeff Garzik <jgarzik@pobox.com> | |
6 | * Copyright 2010 MontaVista Software, LLC. | |
7 | * Anton Vorontsov <avorontsov@ru.mvista.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2, or (at your option) | |
12 | * any later version. | |
13 | */ | |
14 | ||
f1e70c2c | 15 | #include <linux/clk.h> |
1c2a49f6 | 16 | #include <linux/kernel.h> |
fbaf666b | 17 | #include <linux/gfp.h> |
1c2a49f6 | 18 | #include <linux/module.h> |
18c25ff4 | 19 | #include <linux/pm.h> |
1c2a49f6 AV |
20 | #include <linux/interrupt.h> |
21 | #include <linux/device.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/libata.h> | |
24 | #include <linux/ahci_platform.h> | |
21b5faee | 25 | #include <linux/phy/phy.h> |
e708e46e | 26 | #include <linux/pm_runtime.h> |
1c2a49f6 AV |
27 | #include "ahci.h" |
28 | ||
1896b15e BN |
29 | static void ahci_host_stop(struct ata_host *host); |
30 | ||
904c04fe RZ |
31 | enum ahci_type { |
32 | AHCI, /* standard platform ahci */ | |
33 | IMX53_AHCI, /* ahci on i.mx53 */ | |
34 | }; | |
35 | ||
36 | static struct platform_device_id ahci_devtype[] = { | |
37 | { | |
38 | .name = "ahci", | |
39 | .driver_data = AHCI, | |
40 | }, { | |
41 | .name = "imx53-ahci", | |
42 | .driver_data = IMX53_AHCI, | |
43 | }, { | |
44 | /* sentinel */ | |
45 | } | |
46 | }; | |
47 | MODULE_DEVICE_TABLE(platform, ahci_devtype); | |
48 | ||
8b789d89 | 49 | struct ata_port_operations ahci_platform_ops = { |
1896b15e BN |
50 | .inherits = &ahci_ops, |
51 | .host_stop = ahci_host_stop, | |
52 | }; | |
8b789d89 | 53 | EXPORT_SYMBOL_GPL(ahci_platform_ops); |
1896b15e | 54 | |
071d3ad3 | 55 | static struct ata_port_operations ahci_platform_retry_srst_ops = { |
1896b15e BN |
56 | .inherits = &ahci_pmp_retry_srst_ops, |
57 | .host_stop = ahci_host_stop, | |
58 | }; | |
904c04fe RZ |
59 | |
60 | static const struct ata_port_info ahci_port_info[] = { | |
61 | /* by features */ | |
62 | [AHCI] = { | |
63 | .flags = AHCI_FLAG_COMMON, | |
64 | .pio_mask = ATA_PIO4, | |
65 | .udma_mask = ATA_UDMA6, | |
1896b15e | 66 | .port_ops = &ahci_platform_ops, |
904c04fe RZ |
67 | }, |
68 | [IMX53_AHCI] = { | |
69 | .flags = AHCI_FLAG_COMMON, | |
70 | .pio_mask = ATA_PIO4, | |
71 | .udma_mask = ATA_UDMA6, | |
1896b15e | 72 | .port_ops = &ahci_platform_retry_srst_ops, |
904c04fe RZ |
73 | }, |
74 | }; | |
75 | ||
fad16e7a TH |
76 | static struct scsi_host_template ahci_platform_sht = { |
77 | AHCI_SHT("ahci_platform"), | |
78 | }; | |
79 | ||
156c5887 HG |
80 | /** |
81 | * ahci_platform_enable_clks - Enable platform clocks | |
82 | * @hpriv: host private area to store config values | |
83 | * | |
84 | * This function enables all the clks found in hpriv->clks, starting at | |
85 | * index 0. If any clk fails to enable it disables all the clks already | |
86 | * enabled in reverse order, and then returns an error. | |
87 | * | |
88 | * RETURNS: | |
89 | * 0 on success otherwise a negative error code | |
90 | */ | |
91 | int ahci_platform_enable_clks(struct ahci_host_priv *hpriv) | |
92 | { | |
93 | int c, rc; | |
94 | ||
95 | for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++) { | |
96 | rc = clk_prepare_enable(hpriv->clks[c]); | |
97 | if (rc) | |
98 | goto disable_unprepare_clk; | |
99 | } | |
100 | return 0; | |
101 | ||
102 | disable_unprepare_clk: | |
103 | while (--c >= 0) | |
104 | clk_disable_unprepare(hpriv->clks[c]); | |
105 | return rc; | |
106 | } | |
107 | EXPORT_SYMBOL_GPL(ahci_platform_enable_clks); | |
108 | ||
109 | /** | |
110 | * ahci_platform_disable_clks - Disable platform clocks | |
111 | * @hpriv: host private area to store config values | |
112 | * | |
113 | * This function disables all the clks found in hpriv->clks, in reverse | |
114 | * order of ahci_platform_enable_clks (starting at the end of the array). | |
115 | */ | |
116 | void ahci_platform_disable_clks(struct ahci_host_priv *hpriv) | |
117 | { | |
118 | int c; | |
119 | ||
120 | for (c = AHCI_MAX_CLKS - 1; c >= 0; c--) | |
121 | if (hpriv->clks[c]) | |
122 | clk_disable_unprepare(hpriv->clks[c]); | |
123 | } | |
124 | EXPORT_SYMBOL_GPL(ahci_platform_disable_clks); | |
125 | ||
96a01ba5 HG |
126 | /** |
127 | * ahci_platform_enable_resources - Enable platform resources | |
128 | * @hpriv: host private area to store config values | |
129 | * | |
130 | * This function enables all ahci_platform managed resources in the | |
131 | * following order: | |
132 | * 1) Regulator | |
133 | * 2) Clocks (through ahci_platform_enable_clks) | |
21b5faee | 134 | * 3) Phy |
96a01ba5 HG |
135 | * |
136 | * If resource enabling fails at any point the previous enabled resources | |
137 | * are disabled in reverse order. | |
138 | * | |
139 | * RETURNS: | |
140 | * 0 on success otherwise a negative error code | |
141 | */ | |
142 | int ahci_platform_enable_resources(struct ahci_host_priv *hpriv) | |
143 | { | |
144 | int rc; | |
145 | ||
146 | if (hpriv->target_pwr) { | |
147 | rc = regulator_enable(hpriv->target_pwr); | |
148 | if (rc) | |
149 | return rc; | |
150 | } | |
151 | ||
152 | rc = ahci_platform_enable_clks(hpriv); | |
153 | if (rc) | |
154 | goto disable_regulator; | |
155 | ||
21b5faee RQ |
156 | if (hpriv->phy) { |
157 | rc = phy_init(hpriv->phy); | |
158 | if (rc) | |
159 | goto disable_clks; | |
160 | ||
161 | rc = phy_power_on(hpriv->phy); | |
162 | if (rc) { | |
163 | phy_exit(hpriv->phy); | |
164 | goto disable_clks; | |
165 | } | |
166 | } | |
167 | ||
96a01ba5 HG |
168 | return 0; |
169 | ||
21b5faee RQ |
170 | disable_clks: |
171 | ahci_platform_disable_clks(hpriv); | |
172 | ||
96a01ba5 HG |
173 | disable_regulator: |
174 | if (hpriv->target_pwr) | |
175 | regulator_disable(hpriv->target_pwr); | |
176 | return rc; | |
177 | } | |
178 | EXPORT_SYMBOL_GPL(ahci_platform_enable_resources); | |
179 | ||
180 | /** | |
181 | * ahci_platform_disable_resources - Disable platform resources | |
182 | * @hpriv: host private area to store config values | |
183 | * | |
184 | * This function disables all ahci_platform managed resources in the | |
185 | * following order: | |
21b5faee RQ |
186 | * 1) Phy |
187 | * 2) Clocks (through ahci_platform_disable_clks) | |
188 | * 3) Regulator | |
96a01ba5 HG |
189 | */ |
190 | void ahci_platform_disable_resources(struct ahci_host_priv *hpriv) | |
191 | { | |
21b5faee RQ |
192 | if (hpriv->phy) { |
193 | phy_power_off(hpriv->phy); | |
194 | phy_exit(hpriv->phy); | |
195 | } | |
196 | ||
96a01ba5 HG |
197 | ahci_platform_disable_clks(hpriv); |
198 | ||
199 | if (hpriv->target_pwr) | |
200 | regulator_disable(hpriv->target_pwr); | |
201 | } | |
202 | EXPORT_SYMBOL_GPL(ahci_platform_disable_resources); | |
203 | ||
23b07d4c | 204 | static void ahci_platform_put_resources(struct device *dev, void *res) |
156c5887 | 205 | { |
23b07d4c | 206 | struct ahci_host_priv *hpriv = res; |
156c5887 HG |
207 | int c; |
208 | ||
e708e46e RQ |
209 | if (hpriv->got_runtime_pm) { |
210 | pm_runtime_put_sync(dev); | |
211 | pm_runtime_disable(dev); | |
212 | } | |
213 | ||
156c5887 HG |
214 | for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++) |
215 | clk_put(hpriv->clks[c]); | |
216 | } | |
217 | ||
23b07d4c HG |
218 | /** |
219 | * ahci_platform_get_resources - Get platform resources | |
220 | * @pdev: platform device to get resources for | |
221 | * | |
222 | * This function allocates an ahci_host_priv struct, and gets the following | |
223 | * resources, storing a reference to them inside the returned struct: | |
224 | * | |
225 | * 1) mmio registers (IORESOURCE_MEM 0, mandatory) | |
226 | * 2) regulator for controlling the targets power (optional) | |
227 | * 3) 0 - AHCI_MAX_CLKS clocks, as specified in the devs devicetree node, | |
228 | * or for non devicetree enabled platforms a single clock | |
21b5faee | 229 | * 4) phy (optional) |
23b07d4c HG |
230 | * |
231 | * RETURNS: | |
232 | * The allocated ahci_host_priv on success, otherwise an ERR_PTR value | |
233 | */ | |
234 | struct ahci_host_priv *ahci_platform_get_resources( | |
235 | struct platform_device *pdev) | |
1c2a49f6 AV |
236 | { |
237 | struct device *dev = &pdev->dev; | |
1c2a49f6 | 238 | struct ahci_host_priv *hpriv; |
156c5887 | 239 | struct clk *clk; |
23b07d4c | 240 | int i, rc = -ENOMEM; |
1c2a49f6 | 241 | |
23b07d4c HG |
242 | if (!devres_open_group(dev, NULL, GFP_KERNEL)) |
243 | return ERR_PTR(-ENOMEM); | |
1c2a49f6 | 244 | |
23b07d4c HG |
245 | hpriv = devres_alloc(ahci_platform_put_resources, sizeof(*hpriv), |
246 | GFP_KERNEL); | |
247 | if (!hpriv) | |
248 | goto err_out; | |
1c2a49f6 | 249 | |
23b07d4c | 250 | devres_add(dev, hpriv); |
1c2a49f6 | 251 | |
23b07d4c HG |
252 | hpriv->mmio = devm_ioremap_resource(dev, |
253 | platform_get_resource(pdev, IORESOURCE_MEM, 0)); | |
1c2a49f6 | 254 | if (!hpriv->mmio) { |
23b07d4c HG |
255 | dev_err(dev, "no mmio space\n"); |
256 | goto err_out; | |
08354809 JB |
257 | } |
258 | ||
4b3e603a HG |
259 | hpriv->target_pwr = devm_regulator_get_optional(dev, "target"); |
260 | if (IS_ERR(hpriv->target_pwr)) { | |
261 | rc = PTR_ERR(hpriv->target_pwr); | |
262 | if (rc == -EPROBE_DEFER) | |
23b07d4c | 263 | goto err_out; |
4b3e603a HG |
264 | hpriv->target_pwr = NULL; |
265 | } | |
266 | ||
156c5887 HG |
267 | for (i = 0; i < AHCI_MAX_CLKS; i++) { |
268 | /* | |
269 | * For now we must use clk_get(dev, NULL) for the first clock, | |
270 | * because some platforms (da850, spear13xx) are not yet | |
271 | * converted to use devicetree for clocks. For new platforms | |
272 | * this is equivalent to of_clk_get(dev->of_node, 0). | |
273 | */ | |
274 | if (i == 0) | |
275 | clk = clk_get(dev, NULL); | |
276 | else | |
277 | clk = of_clk_get(dev->of_node, i); | |
278 | ||
279 | if (IS_ERR(clk)) { | |
280 | rc = PTR_ERR(clk); | |
281 | if (rc == -EPROBE_DEFER) | |
23b07d4c | 282 | goto err_out; |
156c5887 | 283 | break; |
f1e70c2c | 284 | } |
156c5887 | 285 | hpriv->clks[i] = clk; |
f1e70c2c VK |
286 | } |
287 | ||
21b5faee RQ |
288 | hpriv->phy = devm_phy_get(dev, "sata-phy"); |
289 | if (IS_ERR(hpriv->phy)) { | |
290 | rc = PTR_ERR(hpriv->phy); | |
291 | switch (rc) { | |
292 | case -ENODEV: | |
293 | case -ENOSYS: | |
294 | /* continue normally */ | |
295 | hpriv->phy = NULL; | |
296 | break; | |
297 | ||
298 | case -EPROBE_DEFER: | |
299 | goto err_out; | |
300 | ||
301 | default: | |
302 | dev_err(dev, "couldn't get sata-phy\n"); | |
303 | goto err_out; | |
304 | } | |
305 | } | |
306 | ||
e708e46e RQ |
307 | pm_runtime_enable(dev); |
308 | pm_runtime_get_sync(dev); | |
309 | hpriv->got_runtime_pm = true; | |
310 | ||
23b07d4c HG |
311 | devres_remove_group(dev, NULL); |
312 | return hpriv; | |
156c5887 | 313 | |
23b07d4c HG |
314 | err_out: |
315 | devres_release_group(dev, NULL); | |
316 | return ERR_PTR(rc); | |
317 | } | |
318 | EXPORT_SYMBOL_GPL(ahci_platform_get_resources); | |
319 | ||
320 | /** | |
321 | * ahci_platform_init_host - Bring up an ahci-platform host | |
322 | * @pdev: platform device pointer for the host | |
323 | * @hpriv: ahci-host private data for the host | |
324 | * @pi_template: template for the ata_port_info to use | |
325 | * @force_port_map: param passed to ahci_save_initial_config | |
326 | * @mask_port_map: param passed to ahci_save_initial_config | |
327 | * | |
328 | * This function does all the usual steps needed to bring up an | |
329 | * ahci-platform host, note any necessary resources (ie clks, phy, etc.) | |
330 | * must be initialized / enabled before calling this. | |
331 | * | |
332 | * RETURNS: | |
333 | * 0 on success otherwise a negative error code | |
334 | */ | |
335 | int ahci_platform_init_host(struct platform_device *pdev, | |
336 | struct ahci_host_priv *hpriv, | |
337 | const struct ata_port_info *pi_template, | |
338 | unsigned int force_port_map, | |
339 | unsigned int mask_port_map) | |
340 | { | |
341 | struct device *dev = &pdev->dev; | |
342 | struct ata_port_info pi = *pi_template; | |
343 | const struct ata_port_info *ppi[] = { &pi, NULL }; | |
344 | struct ata_host *host; | |
345 | int i, irq, n_ports, rc; | |
1c2a49f6 | 346 | |
23b07d4c HG |
347 | irq = platform_get_irq(pdev, 0); |
348 | if (irq <= 0) { | |
349 | dev_err(dev, "no irq\n"); | |
350 | return -EINVAL; | |
351 | } | |
1c2a49f6 AV |
352 | |
353 | /* prepare host */ | |
23b07d4c HG |
354 | hpriv->flags |= (unsigned long)pi.private_data; |
355 | ||
356 | ahci_save_initial_config(dev, hpriv, force_port_map, mask_port_map); | |
357 | ||
1c2a49f6 AV |
358 | if (hpriv->cap & HOST_CAP_NCQ) |
359 | pi.flags |= ATA_FLAG_NCQ; | |
360 | ||
361 | if (hpriv->cap & HOST_CAP_PMP) | |
362 | pi.flags |= ATA_FLAG_PMP; | |
363 | ||
364 | ahci_set_em_messages(hpriv, &pi); | |
365 | ||
366 | /* CAP.NP sometimes indicate the index of the last enabled | |
367 | * port, at other times, that of the last possible port, so | |
368 | * determining the maximum port number requires looking at | |
369 | * both CAP.NP and port_map. | |
370 | */ | |
371 | n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map)); | |
372 | ||
373 | host = ata_host_alloc_pinfo(dev, ppi, n_ports); | |
23b07d4c HG |
374 | if (!host) |
375 | return -ENOMEM; | |
1c2a49f6 AV |
376 | |
377 | host->private_data = hpriv; | |
378 | ||
379 | if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss) | |
380 | host->flags |= ATA_HOST_PARALLEL_SCAN; | |
381 | else | |
0fed4c09 | 382 | dev_info(dev, "SSS flag set, parallel bus scan disabled\n"); |
1c2a49f6 AV |
383 | |
384 | if (pi.flags & ATA_FLAG_EM) | |
385 | ahci_reset_em(host); | |
386 | ||
387 | for (i = 0; i < host->n_ports; i++) { | |
388 | struct ata_port *ap = host->ports[i]; | |
389 | ||
23b07d4c HG |
390 | ata_port_desc(ap, "mmio %pR", |
391 | platform_get_resource(pdev, IORESOURCE_MEM, 0)); | |
1c2a49f6 AV |
392 | ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80); |
393 | ||
1c2a49f6 AV |
394 | /* set enclosure management message type */ |
395 | if (ap->flags & ATA_FLAG_EM) | |
55787183 | 396 | ap->em_message_type = hpriv->em_msg_type; |
1c2a49f6 AV |
397 | |
398 | /* disabled/not-implemented port */ | |
399 | if (!(hpriv->port_map & (1 << i))) | |
400 | ap->ops = &ata_dummy_port_ops; | |
401 | } | |
402 | ||
403 | rc = ahci_reset_controller(host); | |
404 | if (rc) | |
23b07d4c | 405 | return rc; |
1c2a49f6 AV |
406 | |
407 | ahci_init_controller(host); | |
408 | ahci_print_info(host, "platform"); | |
409 | ||
23b07d4c HG |
410 | return ata_host_activate(host, irq, ahci_interrupt, IRQF_SHARED, |
411 | &ahci_platform_sht); | |
412 | } | |
413 | EXPORT_SYMBOL_GPL(ahci_platform_init_host); | |
414 | ||
415 | static int ahci_probe(struct platform_device *pdev) | |
416 | { | |
417 | struct device *dev = &pdev->dev; | |
418 | struct ahci_platform_data *pdata = dev_get_platdata(dev); | |
419 | const struct platform_device_id *id = platform_get_device_id(pdev); | |
420 | const struct ata_port_info *pi_template; | |
421 | struct ahci_host_priv *hpriv; | |
422 | int rc; | |
423 | ||
424 | hpriv = ahci_platform_get_resources(pdev); | |
425 | if (IS_ERR(hpriv)) | |
426 | return PTR_ERR(hpriv); | |
427 | ||
428 | rc = ahci_platform_enable_resources(hpriv); | |
429 | if (rc) | |
430 | return rc; | |
431 | ||
432 | /* | |
433 | * Some platforms might need to prepare for mmio region access, | |
434 | * which could be done in the following init call. So, the mmio | |
435 | * region shouldn't be accessed before init (if provided) has | |
436 | * returned successfully. | |
437 | */ | |
438 | if (pdata && pdata->init) { | |
439 | rc = pdata->init(dev, hpriv->mmio); | |
440 | if (rc) | |
441 | goto disable_resources; | |
442 | } | |
443 | ||
444 | if (pdata && pdata->ata_port_info) | |
445 | pi_template = pdata->ata_port_info; | |
446 | else | |
447 | pi_template = &ahci_port_info[id ? id->driver_data : 0]; | |
448 | ||
449 | rc = ahci_platform_init_host(pdev, hpriv, pi_template, | |
450 | pdata ? pdata->force_port_map : 0, | |
451 | pdata ? pdata->mask_port_map : 0); | |
1c2a49f6 | 452 | if (rc) |
f1e70c2c | 453 | goto pdata_exit; |
1c2a49f6 AV |
454 | |
455 | return 0; | |
f1e70c2c | 456 | pdata_exit: |
1c2a49f6 AV |
457 | if (pdata && pdata->exit) |
458 | pdata->exit(dev); | |
96a01ba5 HG |
459 | disable_resources: |
460 | ahci_platform_disable_resources(hpriv); | |
1c2a49f6 AV |
461 | return rc; |
462 | } | |
463 | ||
1896b15e BN |
464 | static void ahci_host_stop(struct ata_host *host) |
465 | { | |
466 | struct device *dev = host->dev; | |
467 | struct ahci_platform_data *pdata = dev_get_platdata(dev); | |
468 | struct ahci_host_priv *hpriv = host->private_data; | |
469 | ||
1c2a49f6 AV |
470 | if (pdata && pdata->exit) |
471 | pdata->exit(dev); | |
472 | ||
96a01ba5 | 473 | ahci_platform_disable_resources(hpriv); |
1c2a49f6 AV |
474 | } |
475 | ||
29448ec1 | 476 | #ifdef CONFIG_PM_SLEEP |
648cb6fd HG |
477 | /** |
478 | * ahci_platform_suspend_host - Suspend an ahci-platform host | |
479 | * @dev: device pointer for the host | |
480 | * | |
481 | * This function does all the usual steps needed to suspend an | |
482 | * ahci-platform host, note any necessary resources (ie clks, phy, etc.) | |
483 | * must be disabled after calling this. | |
484 | * | |
485 | * RETURNS: | |
486 | * 0 on success otherwise a negative error code | |
487 | */ | |
488 | int ahci_platform_suspend_host(struct device *dev) | |
17ab594f | 489 | { |
17ab594f BN |
490 | struct ata_host *host = dev_get_drvdata(dev); |
491 | struct ahci_host_priv *hpriv = host->private_data; | |
492 | void __iomem *mmio = hpriv->mmio; | |
493 | u32 ctl; | |
17ab594f BN |
494 | |
495 | if (hpriv->flags & AHCI_HFLAG_NO_SUSPEND) { | |
496 | dev_err(dev, "firmware update required for suspend/resume\n"); | |
497 | return -EIO; | |
498 | } | |
499 | ||
500 | /* | |
501 | * AHCI spec rev1.1 section 8.3.3: | |
502 | * Software must disable interrupts prior to requesting a | |
503 | * transition of the HBA to D3 state. | |
504 | */ | |
505 | ctl = readl(mmio + HOST_CTL); | |
506 | ctl &= ~HOST_IRQ_EN; | |
507 | writel(ctl, mmio + HOST_CTL); | |
508 | readl(mmio + HOST_CTL); /* flush */ | |
509 | ||
648cb6fd HG |
510 | return ata_host_suspend(host, PMSG_SUSPEND); |
511 | } | |
512 | EXPORT_SYMBOL_GPL(ahci_platform_suspend_host); | |
513 | ||
514 | /** | |
515 | * ahci_platform_resume_host - Resume an ahci-platform host | |
516 | * @dev: device pointer for the host | |
517 | * | |
518 | * This function does all the usual steps needed to resume an ahci-platform | |
519 | * host, note any necessary resources (ie clks, phy, etc.) must be | |
520 | * initialized / enabled before calling this. | |
521 | * | |
522 | * RETURNS: | |
523 | * 0 on success otherwise a negative error code | |
524 | */ | |
525 | int ahci_platform_resume_host(struct device *dev) | |
526 | { | |
527 | struct ata_host *host = dev_get_drvdata(dev); | |
528 | int rc; | |
529 | ||
530 | if (dev->power.power_state.event == PM_EVENT_SUSPEND) { | |
531 | rc = ahci_reset_controller(host); | |
532 | if (rc) | |
533 | return rc; | |
534 | ||
535 | ahci_init_controller(host); | |
536 | } | |
537 | ||
538 | ata_host_resume(host); | |
539 | ||
540 | return 0; | |
541 | } | |
542 | EXPORT_SYMBOL_GPL(ahci_platform_resume_host); | |
543 | ||
544 | /** | |
545 | * ahci_platform_suspend - Suspend an ahci-platform device | |
546 | * @dev: the platform device to suspend | |
547 | * | |
548 | * This function suspends the host associated with the device, followed by | |
549 | * disabling all the resources of the device. | |
550 | * | |
551 | * RETURNS: | |
552 | * 0 on success otherwise a negative error code | |
553 | */ | |
554 | int ahci_platform_suspend(struct device *dev) | |
555 | { | |
556 | struct ahci_platform_data *pdata = dev_get_platdata(dev); | |
557 | struct ata_host *host = dev_get_drvdata(dev); | |
558 | struct ahci_host_priv *hpriv = host->private_data; | |
559 | int rc; | |
560 | ||
561 | rc = ahci_platform_suspend_host(dev); | |
17ab594f BN |
562 | if (rc) |
563 | return rc; | |
564 | ||
565 | if (pdata && pdata->suspend) | |
566 | return pdata->suspend(dev); | |
f1e70c2c | 567 | |
96a01ba5 | 568 | ahci_platform_disable_resources(hpriv); |
4b3e603a | 569 | |
17ab594f BN |
570 | return 0; |
571 | } | |
648cb6fd | 572 | EXPORT_SYMBOL_GPL(ahci_platform_suspend); |
17ab594f | 573 | |
648cb6fd HG |
574 | /** |
575 | * ahci_platform_resume - Resume an ahci-platform device | |
576 | * @dev: the platform device to resume | |
577 | * | |
578 | * This function enables all the resources of the device followed by | |
579 | * resuming the host associated with the device. | |
580 | * | |
581 | * RETURNS: | |
582 | * 0 on success otherwise a negative error code | |
583 | */ | |
584 | int ahci_platform_resume(struct device *dev) | |
17ab594f BN |
585 | { |
586 | struct ahci_platform_data *pdata = dev_get_platdata(dev); | |
587 | struct ata_host *host = dev_get_drvdata(dev); | |
f1e70c2c | 588 | struct ahci_host_priv *hpriv = host->private_data; |
17ab594f BN |
589 | int rc; |
590 | ||
96a01ba5 | 591 | rc = ahci_platform_enable_resources(hpriv); |
156c5887 | 592 | if (rc) |
96a01ba5 | 593 | return rc; |
f1e70c2c | 594 | |
17ab594f BN |
595 | if (pdata && pdata->resume) { |
596 | rc = pdata->resume(dev); | |
597 | if (rc) | |
96a01ba5 | 598 | goto disable_resources; |
17ab594f BN |
599 | } |
600 | ||
648cb6fd HG |
601 | rc = ahci_platform_resume_host(dev); |
602 | if (rc) | |
603 | goto disable_resources; | |
17ab594f | 604 | |
e708e46e RQ |
605 | /* We resumed so update PM runtime state */ |
606 | pm_runtime_disable(dev); | |
607 | pm_runtime_set_active(dev); | |
608 | pm_runtime_enable(dev); | |
609 | ||
17ab594f | 610 | return 0; |
f1e70c2c | 611 | |
96a01ba5 HG |
612 | disable_resources: |
613 | ahci_platform_disable_resources(hpriv); | |
f1e70c2c VK |
614 | |
615 | return rc; | |
17ab594f | 616 | } |
648cb6fd | 617 | EXPORT_SYMBOL_GPL(ahci_platform_resume); |
17ab594f BN |
618 | #endif |
619 | ||
648cb6fd HG |
620 | static SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_platform_suspend, |
621 | ahci_platform_resume); | |
18c25ff4 | 622 | |
02aac316 | 623 | static const struct of_device_id ahci_of_match[] = { |
5f098a3e | 624 | { .compatible = "snps,spear-ahci", }, |
1e8f5f76 | 625 | { .compatible = "snps,exynos5440-ahci", }, |
2435dcb9 | 626 | { .compatible = "ibm,476gtr-ahci", }, |
c4311471 | 627 | { .compatible = "snps,dwc-ahci", }, |
02aac316 RH |
628 | {}, |
629 | }; | |
630 | MODULE_DEVICE_TABLE(of, ahci_of_match); | |
631 | ||
1c2a49f6 | 632 | static struct platform_driver ahci_driver = { |
941c77fd | 633 | .probe = ahci_probe, |
83291d65 | 634 | .remove = ata_platform_remove_one, |
1c2a49f6 AV |
635 | .driver = { |
636 | .name = "ahci", | |
637 | .owner = THIS_MODULE, | |
02aac316 | 638 | .of_match_table = ahci_of_match, |
17ab594f | 639 | .pm = &ahci_pm_ops, |
1c2a49f6 | 640 | }, |
904c04fe | 641 | .id_table = ahci_devtype, |
1c2a49f6 | 642 | }; |
9a99e476 | 643 | module_platform_driver(ahci_driver); |
1c2a49f6 AV |
644 | |
645 | MODULE_DESCRIPTION("AHCI SATA platform driver"); | |
646 | MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); | |
647 | MODULE_LICENSE("GPL"); | |
648 | MODULE_ALIAS("platform:ahci"); |