xref: /openbmc/linux/drivers/thunderbolt/nhi_ops.c (revision 59ed8dca)
13cdb9446SMika Westerberg // SPDX-License-Identifier: GPL-2.0
23cdb9446SMika Westerberg /*
33cdb9446SMika Westerberg  * NHI specific operations
43cdb9446SMika Westerberg  *
53cdb9446SMika Westerberg  * Copyright (C) 2019, Intel Corporation
63cdb9446SMika Westerberg  * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
73cdb9446SMika Westerberg  */
83cdb9446SMika Westerberg 
93cdb9446SMika Westerberg #include <linux/delay.h>
103cdb9446SMika Westerberg #include <linux/suspend.h>
113cdb9446SMika Westerberg 
123cdb9446SMika Westerberg #include "nhi.h"
133cdb9446SMika Westerberg #include "nhi_regs.h"
143cdb9446SMika Westerberg #include "tb.h"
153cdb9446SMika Westerberg 
163cdb9446SMika Westerberg /* Ice Lake specific NHI operations */
173cdb9446SMika Westerberg 
183cdb9446SMika Westerberg #define ICL_LC_MAILBOX_TIMEOUT	500 /* ms */
193cdb9446SMika Westerberg 
check_for_device(struct device * dev,void * data)203cdb9446SMika Westerberg static int check_for_device(struct device *dev, void *data)
213cdb9446SMika Westerberg {
223cdb9446SMika Westerberg 	return tb_is_switch(dev);
233cdb9446SMika Westerberg }
243cdb9446SMika Westerberg 
icl_nhi_is_device_connected(struct tb_nhi * nhi)253cdb9446SMika Westerberg static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
263cdb9446SMika Westerberg {
273cdb9446SMika Westerberg 	struct tb *tb = pci_get_drvdata(nhi->pdev);
283cdb9446SMika Westerberg 	int ret;
293cdb9446SMika Westerberg 
303cdb9446SMika Westerberg 	ret = device_for_each_child(&tb->root_switch->dev, NULL,
313cdb9446SMika Westerberg 				    check_for_device);
323cdb9446SMika Westerberg 	return ret > 0;
333cdb9446SMika Westerberg }
343cdb9446SMika Westerberg 
icl_nhi_force_power(struct tb_nhi * nhi,bool power)353cdb9446SMika Westerberg static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
363cdb9446SMika Westerberg {
373cdb9446SMika Westerberg 	u32 vs_cap;
383cdb9446SMika Westerberg 
393cdb9446SMika Westerberg 	/*
403cdb9446SMika Westerberg 	 * The Thunderbolt host controller is present always in Ice Lake
413cdb9446SMika Westerberg 	 * but the firmware may not be loaded and running (depending
423cdb9446SMika Westerberg 	 * whether there is device connected and so on). Each time the
433cdb9446SMika Westerberg 	 * controller is used we need to "Force Power" it first and wait
443cdb9446SMika Westerberg 	 * for the firmware to indicate it is up and running. This "Force
453cdb9446SMika Westerberg 	 * Power" is really not about actually powering on/off the
463cdb9446SMika Westerberg 	 * controller so it is accessible even if "Force Power" is off.
473cdb9446SMika Westerberg 	 *
483cdb9446SMika Westerberg 	 * The actual power management happens inside shared ACPI power
493cdb9446SMika Westerberg 	 * resources using standard ACPI methods.
503cdb9446SMika Westerberg 	 */
513cdb9446SMika Westerberg 	pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);
523cdb9446SMika Westerberg 	if (power) {
533cdb9446SMika Westerberg 		vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
543cdb9446SMika Westerberg 		vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
553cdb9446SMika Westerberg 		vs_cap |= VS_CAP_22_FORCE_POWER;
563cdb9446SMika Westerberg 	} else {
573cdb9446SMika Westerberg 		vs_cap &= ~VS_CAP_22_FORCE_POWER;
583cdb9446SMika Westerberg 	}
593cdb9446SMika Westerberg 	pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
603cdb9446SMika Westerberg 
613cdb9446SMika Westerberg 	if (power) {
62dc4c4bf1SRajmohan Mani 		unsigned int retries = 350;
633cdb9446SMika Westerberg 		u32 val;
643cdb9446SMika Westerberg 
653cdb9446SMika Westerberg 		/* Wait until the firmware tells it is up and running */
663cdb9446SMika Westerberg 		do {
673cdb9446SMika Westerberg 			pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
683cdb9446SMika Westerberg 			if (val & VS_CAP_9_FW_READY)
693cdb9446SMika Westerberg 				return 0;
70dc4c4bf1SRajmohan Mani 			usleep_range(3000, 3100);
713cdb9446SMika Westerberg 		} while (--retries);
723cdb9446SMika Westerberg 
733cdb9446SMika Westerberg 		return -ETIMEDOUT;
743cdb9446SMika Westerberg 	}
753cdb9446SMika Westerberg 
763cdb9446SMika Westerberg 	return 0;
773cdb9446SMika Westerberg }
783cdb9446SMika Westerberg 
icl_nhi_lc_mailbox_cmd(struct tb_nhi * nhi,enum icl_lc_mailbox_cmd cmd)793cdb9446SMika Westerberg static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
803cdb9446SMika Westerberg {
813cdb9446SMika Westerberg 	u32 data;
823cdb9446SMika Westerberg 
833cdb9446SMika Westerberg 	data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
843cdb9446SMika Westerberg 	pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
853cdb9446SMika Westerberg }
863cdb9446SMika Westerberg 
icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi * nhi,int timeout)873cdb9446SMika Westerberg static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
883cdb9446SMika Westerberg {
893cdb9446SMika Westerberg 	unsigned long end;
903cdb9446SMika Westerberg 	u32 data;
913cdb9446SMika Westerberg 
923cdb9446SMika Westerberg 	if (!timeout)
933cdb9446SMika Westerberg 		goto clear;
943cdb9446SMika Westerberg 
953cdb9446SMika Westerberg 	end = jiffies + msecs_to_jiffies(timeout);
963cdb9446SMika Westerberg 	do {
973cdb9446SMika Westerberg 		pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
983cdb9446SMika Westerberg 		if (data & VS_CAP_18_DONE)
993cdb9446SMika Westerberg 			goto clear;
1006651c91dSRajmohan Mani 		usleep_range(1000, 1100);
1013cdb9446SMika Westerberg 	} while (time_before(jiffies, end));
1023cdb9446SMika Westerberg 
1033cdb9446SMika Westerberg 	return -ETIMEDOUT;
1043cdb9446SMika Westerberg 
1053cdb9446SMika Westerberg clear:
1063cdb9446SMika Westerberg 	/* Clear the valid bit */
1073cdb9446SMika Westerberg 	pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);
1083cdb9446SMika Westerberg 	return 0;
1093cdb9446SMika Westerberg }
1103cdb9446SMika Westerberg 
icl_nhi_set_ltr(struct tb_nhi * nhi)1113cdb9446SMika Westerberg static void icl_nhi_set_ltr(struct tb_nhi *nhi)
1123cdb9446SMika Westerberg {
1133cdb9446SMika Westerberg 	u32 max_ltr, ltr;
1143cdb9446SMika Westerberg 
1153cdb9446SMika Westerberg 	pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);
1163cdb9446SMika Westerberg 	max_ltr &= 0xffff;
1173cdb9446SMika Westerberg 	/* Program the same value for both snoop and no-snoop */
1183cdb9446SMika Westerberg 	ltr = max_ltr << 16 | max_ltr;
1193cdb9446SMika Westerberg 	pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);
1203cdb9446SMika Westerberg }
1213cdb9446SMika Westerberg 
icl_nhi_suspend(struct tb_nhi * nhi)1223cdb9446SMika Westerberg static int icl_nhi_suspend(struct tb_nhi *nhi)
1233cdb9446SMika Westerberg {
12459ed8dcaSMika Westerberg 	struct tb *tb = pci_get_drvdata(nhi->pdev);
1253cdb9446SMika Westerberg 	int ret;
1263cdb9446SMika Westerberg 
1273cdb9446SMika Westerberg 	if (icl_nhi_is_device_connected(nhi))
1283cdb9446SMika Westerberg 		return 0;
1293cdb9446SMika Westerberg 
13059ed8dcaSMika Westerberg 	if (tb_switch_is_icm(tb->root_switch)) {
1313cdb9446SMika Westerberg 		/*
13259ed8dcaSMika Westerberg 		 * If there is no device connected we need to perform
13359ed8dcaSMika Westerberg 		 * both: a handshake through LC mailbox and force power
13459ed8dcaSMika Westerberg 		 * down before entering D3.
1353cdb9446SMika Westerberg 		 */
1363cdb9446SMika Westerberg 		icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
1373cdb9446SMika Westerberg 		ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
1383cdb9446SMika Westerberg 		if (ret)
1393cdb9446SMika Westerberg 			return ret;
14059ed8dcaSMika Westerberg 	}
1413cdb9446SMika Westerberg 
1423cdb9446SMika Westerberg 	return icl_nhi_force_power(nhi, false);
1433cdb9446SMika Westerberg }
1443cdb9446SMika Westerberg 
icl_nhi_suspend_noirq(struct tb_nhi * nhi,bool wakeup)1453cdb9446SMika Westerberg static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
1463cdb9446SMika Westerberg {
14759ed8dcaSMika Westerberg 	struct tb *tb = pci_get_drvdata(nhi->pdev);
1483cdb9446SMika Westerberg 	enum icl_lc_mailbox_cmd cmd;
1493cdb9446SMika Westerberg 
1503cdb9446SMika Westerberg 	if (!pm_suspend_via_firmware())
1513cdb9446SMika Westerberg 		return icl_nhi_suspend(nhi);
1523cdb9446SMika Westerberg 
15359ed8dcaSMika Westerberg 	if (!tb_switch_is_icm(tb->root_switch))
15459ed8dcaSMika Westerberg 		return 0;
15559ed8dcaSMika Westerberg 
1563cdb9446SMika Westerberg 	cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
1573cdb9446SMika Westerberg 	icl_nhi_lc_mailbox_cmd(nhi, cmd);
1583cdb9446SMika Westerberg 	return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
1593cdb9446SMika Westerberg }
1603cdb9446SMika Westerberg 
icl_nhi_resume(struct tb_nhi * nhi)1613cdb9446SMika Westerberg static int icl_nhi_resume(struct tb_nhi *nhi)
1623cdb9446SMika Westerberg {
1633cdb9446SMika Westerberg 	int ret;
1643cdb9446SMika Westerberg 
1653cdb9446SMika Westerberg 	ret = icl_nhi_force_power(nhi, true);
1663cdb9446SMika Westerberg 	if (ret)
1673cdb9446SMika Westerberg 		return ret;
1683cdb9446SMika Westerberg 
1693cdb9446SMika Westerberg 	icl_nhi_set_ltr(nhi);
1703cdb9446SMika Westerberg 	return 0;
1713cdb9446SMika Westerberg }
1723cdb9446SMika Westerberg 
icl_nhi_shutdown(struct tb_nhi * nhi)1733cdb9446SMika Westerberg static void icl_nhi_shutdown(struct tb_nhi *nhi)
1743cdb9446SMika Westerberg {
1753cdb9446SMika Westerberg 	icl_nhi_force_power(nhi, false);
1763cdb9446SMika Westerberg }
1773cdb9446SMika Westerberg 
1783cdb9446SMika Westerberg const struct tb_nhi_ops icl_nhi_ops = {
1793cdb9446SMika Westerberg 	.init = icl_nhi_resume,
1803cdb9446SMika Westerberg 	.suspend_noirq = icl_nhi_suspend_noirq,
1813cdb9446SMika Westerberg 	.resume_noirq = icl_nhi_resume,
1823cdb9446SMika Westerberg 	.runtime_suspend = icl_nhi_suspend,
1833cdb9446SMika Westerberg 	.runtime_resume = icl_nhi_resume,
1843cdb9446SMika Westerberg 	.shutdown = icl_nhi_shutdown,
1853cdb9446SMika Westerberg };
186