* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
  */
 
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 #include <linux/errno.h>
 #include <linux/pci.h>
        return IRQ_HANDLED;
 }
 
+static int nhi_suspend_noirq(struct device *dev)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       struct tb *tb = pci_get_drvdata(pdev);
+       thunderbolt_suspend(tb);
+       return 0;
+}
+
+static int nhi_resume_noirq(struct device *dev)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       struct tb *tb = pci_get_drvdata(pdev);
+       thunderbolt_resume(tb);
+       return 0;
+}
+
 static void nhi_shutdown(struct tb_nhi *nhi)
 {
        int i;
        nhi_shutdown(nhi);
 }
 
+/*
+ * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable
+ * the tunnels asap. A corresponding pci quirk blocks the downstream bridges
+ * resume_noirq until we are done.
+ */
+static const struct dev_pm_ops nhi_pm_ops = {
+       .suspend_noirq = nhi_suspend_noirq,
+       .resume_noirq = nhi_resume_noirq,
+       .freeze_noirq = nhi_suspend_noirq, /*
+                                           * we just disable hotplug, the
+                                           * pci-tunnels stay alive.
+                                           */
+       .restore_noirq = nhi_resume_noirq,
+};
+
 struct pci_device_id nhi_ids[] = {
        /*
         * We have to specify class, the TB bridges use the same device and
        .id_table = nhi_ids,
        .probe = nhi_probe,
        .remove = nhi_remove,
+       .driver.pm = &nhi_pm_ops,
 };
 
 static int __init nhi_init(void)
 
                sw->__unknown1, sw->__unknown4);
 }
 
+/**
+ * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_switch_reset(struct tb *tb, u64 route)
+{
+       struct tb_cfg_result res;
+       struct tb_regs_switch_header header = {
+               header.route_hi = route >> 32,
+               header.route_lo = route,
+               header.enabled = true,
+       };
+       tb_info(tb, "resetting switch at %llx\n", route);
+       res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route,
+                       0, 2, 2, 2);
+       if (res.err)
+               return res.err;
+       res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT);
+       if (res.err > 0)
+               return -EIO;
+       return res.err;
+}
+
 struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route)
 {
        u8 next_port = route; /*
        }
 }
 
+int tb_switch_resume(struct tb_switch *sw)
+{
+       int i, err;
+       u64 uid;
+       tb_sw_info(sw, "resuming switch\n");
+
+       err = tb_eeprom_read_uid(sw, &uid);
+       if (err) {
+               tb_sw_warn(sw, "uid read failed\n");
+               return err;
+       }
+       if (sw->uid != uid) {
+               tb_sw_info(sw,
+                       "changed while suspended (uid %#llx -> %#llx)\n",
+                       sw->uid, uid);
+               return -ENODEV;
+       }
+
+       /* upload configuration */
+       err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3);
+       if (err)
+               return err;
+
+       err = tb_plug_events_active(sw, true);
+       if (err)
+               return err;
+
+       /* check for surviving downstream switches */
+       for (i = 1; i <= sw->config.max_port_number; i++) {
+               struct tb_port *port = &sw->ports[i];
+               if (tb_is_upstream_port(port))
+                       continue;
+               if (!port->remote)
+                       continue;
+               if (tb_wait_for_port(port, true) <= 0
+                       || tb_switch_resume(port->remote->sw)) {
+                       tb_port_warn(port,
+                                    "lost during suspend, disconnecting\n");
+                       tb_sw_set_unpplugged(port->remote->sw);
+               }
+       }
+       return 0;
+}
+
+void tb_switch_suspend(struct tb_switch *sw)
+{
+       int i, err;
+       err = tb_plug_events_active(sw, false);
+       if (err)
+               return;
+
+       for (i = 1; i <= sw->config.max_port_number; i++) {
+               if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote)
+                       tb_switch_suspend(sw->ports[i].remote->sw);
+       }
+       /*
+        * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any
+        * effect?
+        */
+}
 
        }
 }
 
+/**
+ * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches
+ */
+static void tb_free_unplugged_children(struct tb_switch *sw)
+{
+       int i;
+       for (i = 1; i <= sw->config.max_port_number; i++) {
+               struct tb_port *port = &sw->ports[i];
+               if (tb_is_upstream_port(port))
+                       continue;
+               if (!port->remote)
+                       continue;
+               if (port->remote->sw->is_unplugged) {
+                       tb_switch_free(port->remote->sw);
+                       port->remote = NULL;
+               } else {
+                       tb_free_unplugged_children(port->remote->sw);
+               }
+       }
+}
+
+
 /**
  * find_pci_up_port() - return the first PCIe up port on @sw or NULL
  */
        return NULL;
 }
 
+void thunderbolt_suspend(struct tb *tb)
+{
+       tb_info(tb, "suspending...\n");
+       mutex_lock(&tb->lock);
+       tb_switch_suspend(tb->root_switch);
+       tb_ctl_stop(tb->ctl);
+       tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */
+       mutex_unlock(&tb->lock);
+       tb_info(tb, "suspend finished\n");
+}
+
+void thunderbolt_resume(struct tb *tb)
+{
+       struct tb_pci_tunnel *tunnel, *n;
+       tb_info(tb, "resuming...\n");
+       mutex_lock(&tb->lock);
+       tb_ctl_start(tb->ctl);
+
+       /* remove any pci devices the firmware might have setup */
+       tb_switch_reset(tb, 0);
+
+       tb_switch_resume(tb->root_switch);
+       tb_free_invalid_tunnels(tb);
+       tb_free_unplugged_children(tb->root_switch);
+       list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list)
+               tb_pci_restart(tunnel);
+       if (!list_empty(&tb->tunnel_list)) {
+               /*
+                * the pcie links need some time to get going.
+                * 100ms works for me...
+                */
+               tb_info(tb, "tunnels restarted, sleeping for 100ms\n");
+               msleep(100);
+       }
+        /* Allow tb_handle_hotplug to progress events */
+       tb->hotplug_active = true;
+       mutex_unlock(&tb->lock);
+       tb_info(tb, "resume finished\n");
+}
 
 
 struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi);
 void thunderbolt_shutdown_and_free(struct tb *tb);
+void thunderbolt_suspend(struct tb *tb);
+void thunderbolt_resume(struct tb *tb);
 
 struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
 void tb_switch_free(struct tb_switch *sw);
+void tb_switch_suspend(struct tb_switch *sw);
+int tb_switch_resume(struct tb_switch *sw);
+int tb_switch_reset(struct tb *tb, u64 route);
 void tb_sw_set_unpplugged(struct tb_switch *sw);
 struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);