summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/thunderbolt/nhi.c33
-rw-r--r--drivers/thunderbolt/switch.c84
-rw-r--r--drivers/thunderbolt/tb.c61
-rw-r--r--drivers/thunderbolt/tb.h5
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
496static 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
504static 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
495static void nhi_shutdown(struct tb_nhi *nhi) 512static 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 */
625static 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
603struct pci_device_id nhi_ids[] = { 635struct 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
631static int __init nhi_init(void) 664static 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 */
237int 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
232struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) 256struct 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
439int 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
483void 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 */
74static 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 */
74static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw) 96static 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
393void 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
404void 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
215struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); 215struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi);
216void thunderbolt_shutdown_and_free(struct tb *tb); 216void thunderbolt_shutdown_and_free(struct tb *tb);
217void thunderbolt_suspend(struct tb *tb);
218void thunderbolt_resume(struct tb *tb);
217 219
218struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); 220struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
219void tb_switch_free(struct tb_switch *sw); 221void tb_switch_free(struct tb_switch *sw);
222void tb_switch_suspend(struct tb_switch *sw);
223int tb_switch_resume(struct tb_switch *sw);
224int tb_switch_reset(struct tb *tb, u64 route);
220void tb_sw_set_unpplugged(struct tb_switch *sw); 225void tb_sw_set_unpplugged(struct tb_switch *sw);
221struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); 226struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);
222 227