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>
This commit is contained in:
Andreas Noever
2014-06-03 22:04:12 +02:00
committed by Greg Kroah-Hartman
parent c90553b3c4
commit 23dd5bb49d
4 changed files with 183 additions and 0 deletions

View File

@@ -229,6 +229,30 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
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; /*
@@ -412,3 +436,63 @@ void tb_sw_set_unpplugged(struct tb_switch *sw)
}
}
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?
*/
}