diff options
-rw-r--r-- | drivers/thunderbolt/nhi.c | 33 | ||||
-rw-r--r-- | drivers/thunderbolt/switch.c | 84 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 61 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 5 |
4 files changed, 183 insertions, 0 deletions
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d2b9ce857818..346b41e7d5d1 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c | |||
@@ -7,6 +7,7 @@ | |||
7 | * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> | 7 | * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> |
8 | */ | 8 | */ |
9 | 9 | ||
10 | #include <linux/pm_runtime.h> | ||
10 | #include <linux/slab.h> | 11 | #include <linux/slab.h> |
11 | #include <linux/errno.h> | 12 | #include <linux/errno.h> |
12 | #include <linux/pci.h> | 13 | #include <linux/pci.h> |
@@ -492,6 +493,22 @@ static irqreturn_t nhi_msi(int irq, void *data) | |||
492 | return IRQ_HANDLED; | 493 | return IRQ_HANDLED; |
493 | } | 494 | } |
494 | 495 | ||
496 | static int nhi_suspend_noirq(struct device *dev) | ||
497 | { | ||
498 | struct pci_dev *pdev = to_pci_dev(dev); | ||
499 | struct tb *tb = pci_get_drvdata(pdev); | ||
500 | thunderbolt_suspend(tb); | ||
501 | return 0; | ||
502 | } | ||
503 | |||
504 | static int nhi_resume_noirq(struct device *dev) | ||
505 | { | ||
506 | struct pci_dev *pdev = to_pci_dev(dev); | ||
507 | struct tb *tb = pci_get_drvdata(pdev); | ||
508 | thunderbolt_resume(tb); | ||
509 | return 0; | ||
510 | } | ||
511 | |||
495 | static void nhi_shutdown(struct tb_nhi *nhi) | 512 | static void nhi_shutdown(struct tb_nhi *nhi) |
496 | { | 513 | { |
497 | int i; | 514 | int i; |
@@ -600,6 +617,21 @@ static void nhi_remove(struct pci_dev *pdev) | |||
600 | nhi_shutdown(nhi); | 617 | nhi_shutdown(nhi); |
601 | } | 618 | } |
602 | 619 | ||
620 | /* | ||
621 | * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable | ||
622 | * the tunnels asap. A corresponding pci quirk blocks the downstream bridges | ||
623 | * resume_noirq until we are done. | ||
624 | */ | ||
625 | static const struct dev_pm_ops nhi_pm_ops = { | ||
626 | .suspend_noirq = nhi_suspend_noirq, | ||
627 | .resume_noirq = nhi_resume_noirq, | ||
628 | .freeze_noirq = nhi_suspend_noirq, /* | ||
629 | * we just disable hotplug, the | ||
630 | * pci-tunnels stay alive. | ||
631 | */ | ||
632 | .restore_noirq = nhi_resume_noirq, | ||
633 | }; | ||
634 | |||
603 | struct pci_device_id nhi_ids[] = { | 635 | struct pci_device_id nhi_ids[] = { |
604 | /* | 636 | /* |
605 | * We have to specify class, the TB bridges use the same device and | 637 | * We have to specify class, the TB bridges use the same device and |
@@ -626,6 +658,7 @@ static struct pci_driver nhi_driver = { | |||
626 | .id_table = nhi_ids, | 658 | .id_table = nhi_ids, |
627 | .probe = nhi_probe, | 659 | .probe = nhi_probe, |
628 | .remove = nhi_remove, | 660 | .remove = nhi_remove, |
661 | .driver.pm = &nhi_pm_ops, | ||
629 | }; | 662 | }; |
630 | 663 | ||
631 | static int __init nhi_init(void) | 664 | static int __init nhi_init(void) |
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index aeb5c30f8d76..c2a24b6fb883 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c | |||
@@ -229,6 +229,30 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) | |||
229 | sw->__unknown1, sw->__unknown4); | 229 | sw->__unknown1, sw->__unknown4); |
230 | } | 230 | } |
231 | 231 | ||
232 | /** | ||
233 | * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET | ||
234 | * | ||
235 | * Return: Returns 0 on success or an error code on failure. | ||
236 | */ | ||
237 | int tb_switch_reset(struct tb *tb, u64 route) | ||
238 | { | ||
239 | struct tb_cfg_result res; | ||
240 | struct tb_regs_switch_header header = { | ||
241 | header.route_hi = route >> 32, | ||
242 | header.route_lo = route, | ||
243 | header.enabled = true, | ||
244 | }; | ||
245 | tb_info(tb, "resetting switch at %llx\n", route); | ||
246 | res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route, | ||
247 | 0, 2, 2, 2); | ||
248 | if (res.err) | ||
249 | return res.err; | ||
250 | res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT); | ||
251 | if (res.err > 0) | ||
252 | return -EIO; | ||
253 | return res.err; | ||
254 | } | ||
255 | |||
232 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) | 256 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) |
233 | { | 257 | { |
234 | u8 next_port = route; /* | 258 | u8 next_port = route; /* |
@@ -412,3 +436,63 @@ void tb_sw_set_unpplugged(struct tb_switch *sw) | |||
412 | } | 436 | } |
413 | } | 437 | } |
414 | 438 | ||
439 | int tb_switch_resume(struct tb_switch *sw) | ||
440 | { | ||
441 | int i, err; | ||
442 | u64 uid; | ||
443 | tb_sw_info(sw, "resuming switch\n"); | ||
444 | |||
445 | err = tb_eeprom_read_uid(sw, &uid); | ||
446 | if (err) { | ||
447 | tb_sw_warn(sw, "uid read failed\n"); | ||
448 | return err; | ||
449 | } | ||
450 | if (sw->uid != uid) { | ||
451 | tb_sw_info(sw, | ||
452 | "changed while suspended (uid %#llx -> %#llx)\n", | ||
453 | sw->uid, uid); | ||
454 | return -ENODEV; | ||
455 | } | ||
456 | |||
457 | /* upload configuration */ | ||
458 | err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3); | ||
459 | if (err) | ||
460 | return err; | ||
461 | |||
462 | err = tb_plug_events_active(sw, true); | ||
463 | if (err) | ||
464 | return err; | ||
465 | |||
466 | /* check for surviving downstream switches */ | ||
467 | for (i = 1; i <= sw->config.max_port_number; i++) { | ||
468 | struct tb_port *port = &sw->ports[i]; | ||
469 | if (tb_is_upstream_port(port)) | ||
470 | continue; | ||
471 | if (!port->remote) | ||
472 | continue; | ||
473 | if (tb_wait_for_port(port, true) <= 0 | ||
474 | || tb_switch_resume(port->remote->sw)) { | ||
475 | tb_port_warn(port, | ||
476 | "lost during suspend, disconnecting\n"); | ||
477 | tb_sw_set_unpplugged(port->remote->sw); | ||
478 | } | ||
479 | } | ||
480 | return 0; | ||
481 | } | ||
482 | |||
483 | void tb_switch_suspend(struct tb_switch *sw) | ||
484 | { | ||
485 | int i, err; | ||
486 | err = tb_plug_events_active(sw, false); | ||
487 | if (err) | ||
488 | return; | ||
489 | |||
490 | for (i = 1; i <= sw->config.max_port_number; i++) { | ||
491 | if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) | ||
492 | tb_switch_suspend(sw->ports[i].remote->sw); | ||
493 | } | ||
494 | /* | ||
495 | * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any | ||
496 | * effect? | ||
497 | */ | ||
498 | } | ||
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 177f61df464d..1aa6dd7dc68b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c | |||
@@ -69,6 +69,28 @@ static void tb_free_invalid_tunnels(struct tb *tb) | |||
69 | } | 69 | } |
70 | 70 | ||
71 | /** | 71 | /** |
72 | * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches | ||
73 | */ | ||
74 | static void tb_free_unplugged_children(struct tb_switch *sw) | ||
75 | { | ||
76 | int i; | ||
77 | for (i = 1; i <= sw->config.max_port_number; i++) { | ||
78 | struct tb_port *port = &sw->ports[i]; | ||
79 | if (tb_is_upstream_port(port)) | ||
80 | continue; | ||
81 | if (!port->remote) | ||
82 | continue; | ||
83 | if (port->remote->sw->is_unplugged) { | ||
84 | tb_switch_free(port->remote->sw); | ||
85 | port->remote = NULL; | ||
86 | } else { | ||
87 | tb_free_unplugged_children(port->remote->sw); | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
92 | |||
93 | /** | ||
72 | * find_pci_up_port() - return the first PCIe up port on @sw or NULL | 94 | * find_pci_up_port() - return the first PCIe up port on @sw or NULL |
73 | */ | 95 | */ |
74 | static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw) | 96 | static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw) |
@@ -368,3 +390,42 @@ err_locked: | |||
368 | return NULL; | 390 | return NULL; |
369 | } | 391 | } |
370 | 392 | ||
393 | void thunderbolt_suspend(struct tb *tb) | ||
394 | { | ||
395 | tb_info(tb, "suspending...\n"); | ||
396 | mutex_lock(&tb->lock); | ||
397 | tb_switch_suspend(tb->root_switch); | ||
398 | tb_ctl_stop(tb->ctl); | ||
399 | tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */ | ||
400 | mutex_unlock(&tb->lock); | ||
401 | tb_info(tb, "suspend finished\n"); | ||
402 | } | ||
403 | |||
404 | void thunderbolt_resume(struct tb *tb) | ||
405 | { | ||
406 | struct tb_pci_tunnel *tunnel, *n; | ||
407 | tb_info(tb, "resuming...\n"); | ||
408 | mutex_lock(&tb->lock); | ||
409 | tb_ctl_start(tb->ctl); | ||
410 | |||
411 | /* remove any pci devices the firmware might have setup */ | ||
412 | tb_switch_reset(tb, 0); | ||
413 | |||
414 | tb_switch_resume(tb->root_switch); | ||
415 | tb_free_invalid_tunnels(tb); | ||
416 | tb_free_unplugged_children(tb->root_switch); | ||
417 | list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) | ||
418 | tb_pci_restart(tunnel); | ||
419 | if (!list_empty(&tb->tunnel_list)) { | ||
420 | /* | ||
421 | * the pcie links need some time to get going. | ||
422 | * 100ms works for me... | ||
423 | */ | ||
424 | tb_info(tb, "tunnels restarted, sleeping for 100ms\n"); | ||
425 | msleep(100); | ||
426 | } | ||
427 | /* Allow tb_handle_hotplug to progress events */ | ||
428 | tb->hotplug_active = true; | ||
429 | mutex_unlock(&tb->lock); | ||
430 | tb_info(tb, "resume finished\n"); | ||
431 | } | ||
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a89087f2da71..63e89d01047c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h | |||
@@ -214,9 +214,14 @@ static inline int tb_port_write(struct tb_port *port, void *buffer, | |||
214 | 214 | ||
215 | struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); | 215 | struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); |
216 | void thunderbolt_shutdown_and_free(struct tb *tb); | 216 | void thunderbolt_shutdown_and_free(struct tb *tb); |
217 | void thunderbolt_suspend(struct tb *tb); | ||
218 | void thunderbolt_resume(struct tb *tb); | ||
217 | 219 | ||
218 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); | 220 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); |
219 | void tb_switch_free(struct tb_switch *sw); | 221 | void tb_switch_free(struct tb_switch *sw); |
222 | void tb_switch_suspend(struct tb_switch *sw); | ||
223 | int tb_switch_resume(struct tb_switch *sw); | ||
224 | int tb_switch_reset(struct tb *tb, u64 route); | ||
220 | void tb_sw_set_unpplugged(struct tb_switch *sw); | 225 | void tb_sw_set_unpplugged(struct tb_switch *sw); |
221 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); | 226 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); |
222 | 227 | ||