diff options
author | Andreas Noever <andreas.noever@gmail.com> | 2014-06-03 16:04:12 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-06-19 17:13:00 -0400 |
commit | 23dd5bb49d986f37977ed80dd2ca65040ead4392 (patch) | |
tree | 390db91ea55659f22ca93a15ac41bf584bd3a9b9 /drivers/thunderbolt/tb.c | |
parent | c90553b3c4ac2389a71a5c012b6e5bb1160d48a7 (diff) |
thunderbolt: Add suspend/hibernate support
We use _noirq since we have to restore the pci tunnels before the pci
core wakes the tunneled devices.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/thunderbolt/tb.c')
-rw-r--r-- | drivers/thunderbolt/tb.c | 61 |
1 files changed, 61 insertions, 0 deletions
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 | } | ||