aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt/icm.c
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2018-01-16 22:19:00 +0200
committerMika Westerberg <mika.westerberg@linux.intel.com>2019-08-26 12:15:06 +0300
commit3cdb9446a117d5d63af823bde6fe6babc312e77b (patch)
tree7359140a756c47d7ac7823d855420e9155a7c274 /drivers/thunderbolt/icm.c
parentthunderbolt: Expose active parts of NVM even if upgrade is not supported (diff)
downloadlinux-dev-3cdb9446a117d5d63af823bde6fe6babc312e77b.tar.xz
linux-dev-3cdb9446a117d5d63af823bde6fe6babc312e77b.zip
thunderbolt: Add support for Intel Ice Lake
The Thunderbolt controller is integrated into the Ice Lake CPU itself and requires special flows to power it on and off using force power bit in NHI VSEC registers. Runtime PM (RTD3) and Sx flows also differ from the discrete solutions. Now the firmware notifies the driver whether RTD3 entry or exit are possible. The driver is responsible of sending Go2Sx command through link controller mailbox when system enters Sx states (suspend-to-mem/disk). Rest of the ICM firwmare flows follow Titan Ridge. Signed-off-by: Raanan Avargil <raanan.avargil@intel.com> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Reviewed-by: Yehezkel Bernat <YehezkelShB@gmail.com> Tested-by: Mario Limonciello <mario.limonciello@dell.com>
Diffstat (limited to 'drivers/thunderbolt/icm.c')
-rw-r--r--drivers/thunderbolt/icm.c176
1 files changed, 166 insertions, 10 deletions
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index e9835ab35465..245588f691e7 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -56,16 +56,19 @@
* @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported)
* @rpm: Does the controller support runtime PM (RTD3)
* @can_upgrade_nvm: Can the NVM firmware be upgrade on this controller
+ * @veto: Is RTD3 veto in effect
* @is_supported: Checks if we can support ICM on this controller
* @cio_reset: Trigger CIO reset
* @get_mode: Read and return the ICM firmware mode (optional)
* @get_route: Find a route string for given switch
* @save_devices: Ask ICM to save devices to ACL when suspending (optional)
* @driver_ready: Send driver ready message to ICM
+ * @set_uuid: Set UUID for the root switch (optional)
* @device_connected: Handle device connected ICM message
* @device_disconnected: Handle device disconnected ICM message
* @xdomain_connected - Handle XDomain connected ICM message
* @xdomain_disconnected - Handle XDomain disconnected ICM message
+ * @rtd3_veto: Handle RTD3 veto notification ICM message
*/
struct icm {
struct mutex request_lock;
@@ -76,6 +79,7 @@ struct icm {
bool safe_mode;
bool rpm;
bool can_upgrade_nvm;
+ bool veto;
bool (*is_supported)(struct tb *tb);
int (*cio_reset)(struct tb *tb);
int (*get_mode)(struct tb *tb);
@@ -84,6 +88,7 @@ struct icm {
int (*driver_ready)(struct tb *tb,
enum tb_security_level *security_level,
size_t *nboot_acl, bool *rpm);
+ void (*set_uuid)(struct tb *tb);
void (*device_connected)(struct tb *tb,
const struct icm_pkg_header *hdr);
void (*device_disconnected)(struct tb *tb,
@@ -92,6 +97,7 @@ struct icm {
const struct icm_pkg_header *hdr);
void (*xdomain_disconnected)(struct tb *tb,
const struct icm_pkg_header *hdr);
+ void (*rtd3_veto)(struct tb *tb, const struct icm_pkg_header *hdr);
};
struct icm_notification {
@@ -296,6 +302,43 @@ static int icm_request(struct tb *tb, const void *request, size_t request_size,
return -ETIMEDOUT;
}
+/*
+ * If rescan is queued to run (we are resuming), postpone it to give the
+ * firmware some more time to send device connected notifications for next
+ * devices in the chain.
+ */
+static void icm_postpone_rescan(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+
+ if (delayed_work_pending(&icm->rescan_work))
+ mod_delayed_work(tb->wq, &icm->rescan_work,
+ msecs_to_jiffies(500));
+}
+
+static void icm_veto_begin(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+
+ if (!icm->veto) {
+ icm->veto = true;
+ /* Keep the domain powered while veto is in effect */
+ pm_runtime_get(&tb->dev);
+ }
+}
+
+static void icm_veto_end(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+
+ if (icm->veto) {
+ icm->veto = false;
+ /* Allow the domain suspend now */
+ pm_runtime_mark_last_busy(&tb->dev);
+ pm_runtime_put_autosuspend(&tb->dev);
+ }
+}
+
static bool icm_fr_is_supported(struct tb *tb)
{
return !x86_apple_machine;
@@ -519,14 +562,16 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
return 0;
}
-static void add_switch(struct tb_switch *parent_sw, u64 route,
- const uuid_t *uuid, const u8 *ep_name,
- size_t ep_name_size, u8 connection_id, u8 connection_key,
- u8 link, u8 depth, enum tb_security_level security_level,
- bool authorized, bool boot)
+static struct tb_switch *add_switch(struct tb_switch *parent_sw, u64 route,
+ const uuid_t *uuid, const u8 *ep_name,
+ size_t ep_name_size, u8 connection_id,
+ u8 connection_key, u8 link, u8 depth,
+ enum tb_security_level security_level,
+ bool authorized, bool boot)
{
const struct intel_vss *vss;
struct tb_switch *sw;
+ int ret;
pm_runtime_get_sync(&parent_sw->dev);
@@ -557,14 +602,18 @@ static void add_switch(struct tb_switch *parent_sw, u64 route,
tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw);
tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw);
- if (tb_switch_add(sw)) {
+ ret = tb_switch_add(sw);
+ if (ret) {
tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
tb_switch_put(sw);
+ sw = ERR_PTR(ret);
}
out:
pm_runtime_mark_last_busy(&parent_sw->dev);
pm_runtime_put_autosuspend(&parent_sw->dev);
+
+ return sw;
}
static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw,
@@ -656,6 +705,8 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
u64 route;
int ret;
+ icm_postpone_rescan(tb);
+
link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
ICM_LINK_INFO_DEPTH_SHIFT;
@@ -1086,7 +1137,8 @@ static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
}
static void
-icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
+__icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr,
+ bool force_rtd3)
{
const struct icm_tr_event_device_connected *pkg =
(const struct icm_tr_event_device_connected *)hdr;
@@ -1096,6 +1148,8 @@ icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
bool authorized, boot;
u64 route;
+ icm_postpone_rescan(tb);
+
/*
* Currently we don't use the QoS information coming with the
* device connected message so simply just ignore that extra
@@ -1151,14 +1205,22 @@ icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
return;
}
- add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name,
- sizeof(pkg->ep_name), pkg->connection_id,
- 0, 0, 0, security_level, authorized, boot);
+ sw = add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name,
+ sizeof(pkg->ep_name), pkg->connection_id, 0, 0, 0,
+ security_level, authorized, boot);
+ if (!IS_ERR(sw) && force_rtd3)
+ sw->rpm = true;
tb_switch_put(parent_sw);
}
static void
+icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
+{
+ __icm_tr_device_connected(tb, hdr, false);
+}
+
+static void
icm_tr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
{
const struct icm_tr_event_device_disconnected *pkg =
@@ -1468,6 +1530,61 @@ static int icm_ar_set_boot_acl(struct tb *tb, const uuid_t *uuids,
return 0;
}
+static int
+icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level,
+ size_t *nboot_acl, bool *rpm)
+{
+ struct icm_tr_pkg_driver_ready_response reply;
+ struct icm_pkg_driver_ready request = {
+ .hdr.code = ICM_DRIVER_READY,
+ };
+ int ret;
+
+ memset(&reply, 0, sizeof(reply));
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, 20000);
+ if (ret)
+ return ret;
+
+ /* Ice Lake always supports RTD3 */
+ if (rpm)
+ *rpm = true;
+
+ return 0;
+}
+
+static void icm_icl_set_uuid(struct tb *tb)
+{
+ struct tb_nhi *nhi = tb->nhi;
+ u32 uuid[4];
+
+ pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]);
+ pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]);
+ uuid[2] = 0xffffffff;
+ uuid[3] = 0xffffffff;
+
+ tb->root_switch->uuid = kmemdup(uuid, sizeof(uuid), GFP_KERNEL);
+}
+
+static void
+icm_icl_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
+{
+ __icm_tr_device_connected(tb, hdr, true);
+}
+
+static void icm_icl_rtd3_veto(struct tb *tb, const struct icm_pkg_header *hdr)
+{
+ const struct icm_icl_event_rtd3_veto *pkg =
+ (const struct icm_icl_event_rtd3_veto *)hdr;
+
+ tb_dbg(tb, "ICM rtd3 veto=0x%08x\n", pkg->veto_reason);
+
+ if (pkg->veto_reason)
+ icm_veto_begin(tb);
+ else
+ icm_veto_end(tb);
+}
+
static void icm_handle_notification(struct work_struct *work)
{
struct icm_notification *n = container_of(work, typeof(*n), work);
@@ -1495,6 +1612,9 @@ static void icm_handle_notification(struct work_struct *work)
case ICM_EVENT_XDOMAIN_DISCONNECTED:
icm->xdomain_disconnected(tb, n->pkg);
break;
+ case ICM_EVENT_RTD3_VETO:
+ icm->rtd3_veto(tb, n->pkg);
+ break;
}
}
@@ -1853,6 +1973,13 @@ static void icm_complete(struct tb *tb)
if (tb->nhi->going_away)
return;
+ /*
+ * If RTD3 was vetoed before we entered system suspend allow it
+ * again now before driver ready is sent. Firmware sends a new RTD3
+ * veto if it is still the case after we have sent it driver ready
+ * command.
+ */
+ icm_veto_end(tb);
icm_unplug_children(tb->root_switch);
/*
@@ -1918,6 +2045,9 @@ static int icm_start(struct tb *tb)
tb->root_switch->no_nvm_upgrade = !icm->can_upgrade_nvm;
tb->root_switch->rpm = icm->rpm;
+ if (icm->set_uuid)
+ icm->set_uuid(tb);
+
ret = tb_switch_add(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
@@ -2002,6 +2132,19 @@ static const struct tb_cm_ops icm_tr_ops = {
.disconnect_xdomain_paths = icm_tr_disconnect_xdomain_paths,
};
+/* Ice Lake */
+static const struct tb_cm_ops icm_icl_ops = {
+ .driver_ready = icm_driver_ready,
+ .start = icm_start,
+ .stop = icm_stop,
+ .complete = icm_complete,
+ .runtime_suspend = icm_runtime_suspend,
+ .runtime_resume = icm_runtime_resume,
+ .handle_event = icm_handle_event,
+ .approve_xdomain_paths = icm_tr_approve_xdomain_paths,
+ .disconnect_xdomain_paths = icm_tr_disconnect_xdomain_paths,
+};
+
struct tb *icm_probe(struct tb_nhi *nhi)
{
struct icm *icm;
@@ -2070,6 +2213,19 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->xdomain_disconnected = icm_tr_xdomain_disconnected;
tb->cm_ops = &icm_tr_ops;
break;
+
+ case PCI_DEVICE_ID_INTEL_ICL_NHI0:
+ case PCI_DEVICE_ID_INTEL_ICL_NHI1:
+ icm->is_supported = icm_ar_is_supported;
+ icm->driver_ready = icm_icl_driver_ready;
+ icm->set_uuid = icm_icl_set_uuid;
+ icm->device_connected = icm_icl_device_connected;
+ icm->device_disconnected = icm_tr_device_disconnected;
+ icm->xdomain_connected = icm_tr_xdomain_connected;
+ icm->xdomain_disconnected = icm_tr_xdomain_disconnected;
+ icm->rtd3_veto = icm_icl_rtd3_veto;
+ tb->cm_ops = &icm_icl_ops;
+ break;
}
if (!icm->is_supported || !icm->is_supported(tb)) {