summaryrefslogtreecommitdiffstats
path: root/src/soc/intel/common/block/pcie/rtd3/rtd3.c
blob: 5a043334990d17fab8a71a1b621e368d898c4a96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <acpi/acpigen.h>
#include <acpi/acpi_device.h>
#include <console/console.h>
#include <device/device.h>
#include <device/pci_ids.h>
#include <device/pci_ops.h>
#include <device/pci.h>
#include <intelblocks/pmc.h>
#include <intelblocks/pmc_ipc.h>
#include "chip.h"

/*
 * The "ExternalFacingPort" and "HotPlugSupportInD3" properties are defined at
 * https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports
 */
#define PCIE_EXTERNAL_PORT_UUID "EFCC06CC-73AC-4BC3-BFF0-76143807C389"
#define PCIE_EXTERNAL_PORT_PROPERTY "ExternalFacingPort"

#define PCIE_HOTPLUG_IN_D3_UUID "6211E2C0-58A3-4AF3-90E1-927A4E0C55A4"
#define PCIE_HOTPLUG_IN_D3_PROPERTY "HotPlugSupportInD3"

/*
 * This UUID and the resulting ACPI Device Property is defined by the
 * Power Management for Storage Hardware Devices:
 *
 * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro
 */
#define PCIE_RTD3_STORAGE_UUID "5025030F-842F-4AB4-A561-99A5189762D0"
#define PCIE_RTD3_STORAGE_PROPERTY "StorageD3Enable"

/* PCIe Root Port registers for link status and L23 control. */
#define PCH_PCIE_CFG_LSTS 0x52	  /* Link Status Register */
#define PCH_PCIE_CFG_SPR 0xe0	  /* Scratchpad */
#define PCH_PCIE_CFG_RPPGEN 0xe2  /* Root Port Power Gating Enable */
#define PCH_PCIE_CFG_LCAP_PN 0x4f /* Root Port Number */

/* ACPI register names corresponding to PCIe root port registers. */
#define ACPI_REG_PCI_LINK_ACTIVE "LASX"	   /* Link active status */
#define ACPI_REG_PCI_L23_RDY_ENTRY "L23E"  /* L23_Rdy Entry Request */
#define ACPI_REG_PCI_L23_RDY_DETECT "L23R" /* L23_Rdy Detect Transition */
#define ACPI_REG_PCI_L23_SAVE_STATE "NCB7" /* Scratch bit to save L23 state */

/* Called from _ON to get PCIe link back to active state. */
static void pcie_rtd3_acpi_l23_exit(void)
{
	/* Skip if port is not in L2/L3. */
	acpigen_write_if_lequal_namestr_int(ACPI_REG_PCI_L23_SAVE_STATE, 1);

	/* Initiate L2/L3 Ready To Detect transition. */
	acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_RDY_DETECT);

	/* Wait for transition to detect. */
	acpigen_write_delay_until_namestr_int(320, ACPI_REG_PCI_L23_RDY_DETECT, 0);

	acpigen_write_store_int_to_namestr(0, ACPI_REG_PCI_L23_SAVE_STATE);

	/* Once in detect, wait for link active. */
	acpigen_write_delay_until_namestr_int(128, ACPI_REG_PCI_LINK_ACTIVE, 1);

	acpigen_pop_len(); /* If */
}

/* Called from _OFF to put PCIe link into L2/L3 state. */
static void pcie_rtd3_acpi_l23_entry(void)
{
	/* Initiate L2/L3 Entry request. */
	acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_RDY_ENTRY);

	/* Wait for L2/L3 Entry request to clear. */
	acpigen_write_delay_until_namestr_int(128, ACPI_REG_PCI_L23_RDY_ENTRY, 0);

	acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_SAVE_STATE);
}

static void
pcie_rtd3_acpi_method_on(unsigned int pcie_rp,
			 const struct soc_intel_common_block_pcie_rtd3_config *config)
{
	acpigen_write_method_serialized("_ON", 0);

	/* Assert enable GPIO to turn on device power. */
	if (config->enable_gpio.pin_count) {
		acpigen_enable_tx_gpio(&config->enable_gpio);
		if (config->enable_delay_ms)
			acpigen_write_sleep(config->enable_delay_ms);
	}

	/* Enable SRCCLK for root port if pin is defined. */
	if (config->srcclk_pin >= 0)
		pmc_ipc_acpi_set_pci_clock(pcie_rp, config->srcclk_pin, true);

	/* De-assert reset GPIO to bring device out of reset. */
	if (config->reset_gpio.pin_count) {
		acpigen_disable_tx_gpio(&config->reset_gpio);
		if (config->reset_delay_ms)
			acpigen_write_sleep(config->reset_delay_ms);
	}

	/* Trigger L23 ready exit flow unless disabld by config. */
	if (!config->disable_l23)
		pcie_rtd3_acpi_l23_exit();

	acpigen_pop_len(); /* Method */
}

static void
pcie_rtd3_acpi_method_off(int pcie_rp,
			  const struct soc_intel_common_block_pcie_rtd3_config *config)
{
	acpigen_write_method_serialized("_OFF", 0);

	/* Trigger L23 ready entry flow unless disabled by config. */
	if (!config->disable_l23)
		pcie_rtd3_acpi_l23_entry();

	/* Assert reset GPIO to place device into reset. */
	if (config->reset_gpio.pin_count) {
		acpigen_enable_tx_gpio(&config->reset_gpio);
		if (config->reset_off_delay_ms)
			acpigen_write_sleep(config->reset_off_delay_ms);
	}

	/* Disable SRCCLK for this root port if pin is defined. */
	if (config->srcclk_pin >= 0)
		pmc_ipc_acpi_set_pci_clock(pcie_rp, config->srcclk_pin, false);

	/* De-assert enable GPIO to turn off device power. */
	if (config->enable_gpio.pin_count) {
		acpigen_disable_tx_gpio(&config->enable_gpio);
		if (config->enable_off_delay_ms)
			acpigen_write_sleep(config->enable_off_delay_ms);
	}

	acpigen_pop_len(); /* Method */
}

static void
pcie_rtd3_acpi_method_status(int pcie_rp,
			     const struct soc_intel_common_block_pcie_rtd3_config *config)
{
	const struct acpi_gpio *gpio;

	acpigen_write_method("_STA", 0);

	/* Use enable GPIO for status if provided, otherwise use reset GPIO. */
	if (config->enable_gpio.pin_count)
		gpio = &config->enable_gpio;
	else
		gpio = &config->reset_gpio;

	/* Read current GPIO value into Local0. */
	acpigen_get_tx_gpio(gpio);

	/* Ensure check works for both active low and active high GPIOs. */
	acpigen_write_store_int_to_op(gpio->active_low, LOCAL1_OP);

	acpigen_write_if_lequal_op_op(LOCAL0_OP, LOCAL1_OP);
	acpigen_write_return_op(ZERO_OP);
	acpigen_write_else();
	acpigen_write_return_op(ONE_OP);
	acpigen_pop_len(); /* Else */

	acpigen_pop_len(); /* Method */
}

static void pcie_rtd3_acpi_fill_ssdt(const struct device *dev)
{
	const struct soc_intel_common_block_pcie_rtd3_config *config = config_of(dev);
	static const char *const power_res_states[] = {"_PR0"};
	const struct device *parent = dev->bus->dev;
	const char *scope = acpi_device_path(parent);
	const struct opregion opregion = OPREGION("PXCS", PCI_CONFIG, 0, 0xff);
	const struct fieldlist fieldlist[] = {
		FIELDLIST_OFFSET(PCH_PCIE_CFG_LSTS),
		FIELDLIST_RESERVED(13),
		FIELDLIST_NAMESTR(ACPI_REG_PCI_LINK_ACTIVE, 1),
		FIELDLIST_OFFSET(PCH_PCIE_CFG_SPR),
		FIELDLIST_RESERVED(7),
		FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_SAVE_STATE, 1),
		FIELDLIST_OFFSET(PCH_PCIE_CFG_RPPGEN),
		FIELDLIST_RESERVED(2),
		FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_RDY_ENTRY, 1),
		FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_RDY_DETECT, 1),
	};
	uint8_t pcie_rp;
	struct acpi_dp *dsd, *pkg;

	if (!is_dev_enabled(parent)) {
		printk(BIOS_ERR, "%s: root port not enabled\n", __func__);
		return;
	}
	if (!scope) {
		printk(BIOS_ERR, "%s: root port scope not found\n", __func__);
		return;
	}
	if (!config->enable_gpio.pin_count && !config->reset_gpio.pin_count) {
		printk(BIOS_ERR, "%s: Enable and/or Reset GPIO required for %s.\n",
		       __func__, scope);
		return;
	}
	if (config->srcclk_pin > CONFIG_MAX_PCIE_CLOCKS) {
		printk(BIOS_ERR, "%s: Invalid clock pin %u for %s.\n", __func__,
		       config->srcclk_pin, scope);
		return;
	}

	/* Read port number of root port that this device is attached to. */
	pcie_rp = pci_read_config8(parent, PCH_PCIE_CFG_LCAP_PN);
	if (pcie_rp == 0 || pcie_rp > CONFIG_MAX_ROOT_PORTS) {
		printk(BIOS_ERR, "%s: Invalid root port number: %u\n", __func__, pcie_rp);
		return;
	}
	/* Port number is 1-based, PMC IPC method expects 0-based. */
	pcie_rp--;

	printk(BIOS_INFO, "%s: Enable RTD3 for %s (%s)\n", scope, dev_path(parent),
	       config->desc ?: dev->chip_ops->name);

	/* The RTD3 power resource is added to the root port, not the device. */
	acpigen_write_scope(scope);

	if (config->desc)
		acpigen_write_name_string("_DDN", config->desc);

	acpigen_write_opregion(&opregion);
	acpigen_write_field("PXCS", fieldlist, ARRAY_SIZE(fieldlist),
			    FIELD_ANYACC | FIELD_NOLOCK | FIELD_PRESERVE);

	/* ACPI Power Resource for controlling the attached device power. */
	acpigen_write_power_res("RTD3", 0, 0, power_res_states, ARRAY_SIZE(power_res_states));
	pcie_rtd3_acpi_method_status(pcie_rp, config);
	pcie_rtd3_acpi_method_on(pcie_rp, config);
	pcie_rtd3_acpi_method_off(pcie_rp, config);
	acpigen_pop_len(); /* PowerResource */

	/* Indicate to the OS that device supports hotplug in D3. */
	dsd = acpi_dp_new_table("_DSD");
	pkg = acpi_dp_new_table(PCIE_HOTPLUG_IN_D3_UUID);
	acpi_dp_add_integer(pkg, PCIE_HOTPLUG_IN_D3_PROPERTY, 1);
	acpi_dp_add_package(dsd, pkg);

	/* Indicate to the OS if the device provides an External facing port. */
	if (config->is_external) {
		pkg = acpi_dp_new_table(PCIE_EXTERNAL_PORT_UUID);
		acpi_dp_add_integer(pkg, PCIE_EXTERNAL_PORT_PROPERTY, 1);
		acpi_dp_add_package(dsd, pkg);
	}
	acpi_dp_write(dsd);

	/*
	 * Check the sibling device on the root port to see if it is storage class and add the
	 * property for the OS to enable storage D3, or allow it to be enabled by config.
	 */
	if (config->is_storage
	    || (dev->sibling && (dev->sibling->class >> 16) == PCI_BASE_CLASS_STORAGE)) {
		acpigen_write_device(acpi_device_name(dev));
		acpigen_write_ADR(0);
		acpigen_write_STA(ACPI_STATUS_DEVICE_ALL_ON);
		acpigen_write_name_integer("_S0W", 4);

		dsd = acpi_dp_new_table("_DSD");
		pkg = acpi_dp_new_table(PCIE_RTD3_STORAGE_UUID);
		acpi_dp_add_integer(pkg, PCIE_RTD3_STORAGE_PROPERTY, 1);
		acpi_dp_add_package(dsd, pkg);
		acpi_dp_write(dsd);

		acpigen_pop_len(); /* Device */

		printk(BIOS_INFO, "%s: Added StorageD3Enable property\n", scope);
	}

	acpigen_pop_len(); /* Scope */
}

static const char *pcie_rtd3_acpi_name(const struct device *dev)
{
	/* Attached device name must be "PXSX" for the Linux Kernel to recognize it. */
	return "PXSX";
}

static struct device_operations pcie_rtd3_ops = {
	.read_resources = noop_read_resources,
	.set_resources = noop_set_resources,
	.acpi_fill_ssdt = pcie_rtd3_acpi_fill_ssdt,
	.acpi_name = pcie_rtd3_acpi_name,
};

static void pcie_rtd3_acpi_enable(struct device *dev)
{
	dev->ops = &pcie_rtd3_ops;
}

struct chip_operations soc_intel_common_block_pcie_rtd3_ops = {
	CHIP_NAME("Intel PCIe Runtime D3")
	.enable_dev = pcie_rtd3_acpi_enable
};