summaryrefslogtreecommitdiff
path: root/drivers/pci/pcie_dw_amd.c
blob: 81c6d8f2817ab58b96dd31995c65e31fc29114ec (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
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD Versal2 DesignWare PCIe host controller driver
 *
 * Copyright (C) 2025 - 2026, Advanced Micro Devices, Inc.
 * Author: Pranav Sanwal <[email protected]>
 */

#include <dm.h>
#include <log.h>
#include <pci.h>
#include <wait_bit.h>

#include <asm/gpio.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <dm/ofnode.h>
#include <linux/delay.h>

#include "pcie_dw_common.h"

/*
 * SLCR (System Level Control Register) Interrupt Register Offsets
 * These are relative to the SLCR base address from device tree
 */
#define AMD_DW_TLP_IR_STATUS_MISC	0x4c0
#define AMD_DW_TLP_IR_DISABLE_MISC	0x4cc

/* Interrupt bit definitions */
#define AMD_DW_PCIE_INTR_CMPL_TIMEOUT		15
#define AMD_DW_PCIE_INTR_PM_PME_RCVD		24
#define AMD_DW_PCIE_INTR_PME_TO_ACK_RCVD	25
#define AMD_DW_PCIE_INTR_MISC_CORRECTABLE	26
#define AMD_DW_PCIE_INTR_NONFATAL		27
#define AMD_DW_PCIE_INTR_FATAL			28

#define AMD_DW_PCIE_INTR_INTX_MASK		GENMASK(23, 16)

#define AMD_DW_PCIE_IMR_ALL_MASK	\
	(BIT(AMD_DW_PCIE_INTR_CMPL_TIMEOUT)	| \
	 BIT(AMD_DW_PCIE_INTR_PM_PME_RCVD)	| \
	 BIT(AMD_DW_PCIE_INTR_PME_TO_ACK_RCVD)	| \
	 BIT(AMD_DW_PCIE_INTR_MISC_CORRECTABLE)	| \
	 BIT(AMD_DW_PCIE_INTR_NONFATAL)		| \
	 BIT(AMD_DW_PCIE_INTR_FATAL)		| \
	 AMD_DW_PCIE_INTR_INTX_MASK)

/* DW PCIe Debug Registers (in DBI space) */
#define AMD_DW_PCIE_PORT_DEBUG1			0x72c
#define AMD_DW_PCIE_PORT_DEBUG1_LINK_UP		BIT(4)
#define AMD_DW_PCIE_PORT_DEBUG1_LINK_IN_TRAINING	BIT(29)
#define AMD_DW_PCIE_DBI_64BIT_MEM_DECODE		BIT(0)

/* Link training timeout */
#define LINK_WAIT_MSLEEP_MAX		1000

/* PCIe spec timing requirements */
#define PCIE_RESET_CONFIG_WAIT_MS	100
#define PCIE_T_PERST_WAIT_MS		1

/**
 * struct amd_dw_pcie - AMD DesignWare PCIe controller private data
 * @dw: DesignWare PCIe common structure
 * @slcr_base: System Level Control Register base (for interrupts)
 */
struct amd_dw_pcie {
	struct pcie_dw dw;
	void __iomem *slcr_base;
};

static void amd_dw_pcie_init_port(struct amd_dw_pcie *pcie)
{
	u32 val;

	if (!pcie->slcr_base)
		return;

	/* Disable all TLP interrupts */
	writel(AMD_DW_PCIE_IMR_ALL_MASK,
	       pcie->slcr_base + AMD_DW_TLP_IR_DISABLE_MISC);

	/* Clear any pending TLP interrupts */
	val = readl(pcie->slcr_base + AMD_DW_TLP_IR_STATUS_MISC);
	val &= AMD_DW_PCIE_IMR_ALL_MASK;
	writel(val, pcie->slcr_base + AMD_DW_TLP_IR_STATUS_MISC);
}

static void amd_dw_pcie_start_link(struct amd_dw_pcie *pcie)
{
	void __iomem *reg = pcie->dw.dbi_base + AMD_DW_PCIE_PORT_DEBUG1;
	struct udevice *dev = pcie->dw.dev;
	struct pcie_dw *pci = &pcie->dw;
	int ret;

	ret = wait_for_bit_le32(reg, AMD_DW_PCIE_PORT_DEBUG1_LINK_UP,
				true, LINK_WAIT_MSLEEP_MAX,
				false);
	if (!ret)
		ret = wait_for_bit_le32(reg,
					AMD_DW_PCIE_PORT_DEBUG1_LINK_IN_TRAINING,
					false, LINK_WAIT_MSLEEP_MAX, false);
	if (ret)
		dev_warn(dev, "PCIE-%d: Link down\n", dev_seq(dev));
	else
		dev_dbg(dev, "PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
			dev_seq(dev), pcie_dw_get_link_speed(pci),
			pcie_dw_get_link_width(pci), pci->first_busno);
}

static void amd_dw_pcie_host_init(struct amd_dw_pcie *pcie)
{
	struct pcie_dw *pci = &pcie->dw;

	/*
	 * Set 64-bit prefetchable memory decode capability. U-Boot's pci_auto.c
	 * reads this bit before assigning prefetchable BARs. If cleared, it skips
	 * PCI_PREF_BASE_UPPER32 programming, causing 64-bit BAR assignment to fail.
	 */
	dw_pcie_dbi_write_enable(pci, true);
	setbits_le32(pci->dbi_base + PCI_PREF_MEMORY_BASE,
		     AMD_DW_PCIE_DBI_64BIT_MEM_DECODE);
	dw_pcie_dbi_write_enable(pci, false);

	amd_dw_pcie_init_port(pcie);
	pcie_dw_setup_host(pci);
}

static void amd_dw_pcie_request_gpio(struct udevice *dev)
{
	struct gpio_desc perst_gpio;
	ofnode child_node;
	int ret;

	/*
	 * PERST# reset GPIO is optional. Child PCI endpoint nodes may carry a
	 * 'reset-gpios' property to toggle the endpoint reset signal during
	 * initialization. If absent, the endpoint is assumed to be already
	 * released from reset.
	 */
	ofnode_for_each_subnode(child_node, dev_ofnode(dev)) {
		ret = gpio_request_by_name_nodev(child_node, "reset-gpios", 0,
						 &perst_gpio, GPIOD_IS_OUT);
		if (!ret) {
			dev_dbg(dev, "Found reset-gpios in child node %s\n",
				ofnode_get_name(child_node));
			dm_gpio_set_value(&perst_gpio, 1);
			mdelay(PCIE_T_PERST_WAIT_MS);
			dm_gpio_set_value(&perst_gpio, 0);
			mdelay(PCIE_RESET_CONFIG_WAIT_MS);
			dm_gpio_free(dev, &perst_gpio);
		}
	}
}

static int amd_dw_pcie_of_to_plat(struct udevice *dev)
{
	struct pci_region *io_region, *mem_region, *pref_region;
	struct amd_dw_pcie *pcie = dev_get_priv(dev);
	struct pcie_dw *pci = &pcie->dw;
	int ret;

	pci->dev = dev;

	pci->dbi_base = dev_read_addr_name_ptr(dev, "dbi");
	if (!pci->dbi_base) {
		dev_err(dev, "Missing 'dbi' register region\n");
		return -EINVAL;
	}

	pci->cfg_base = dev_read_addr_size_name_ptr(dev, "config", &pci->cfg_size);
	if (!pci->cfg_base) {
		dev_err(dev, "Missing 'config' register region\n");
		return -EINVAL;
	}

	pci->atu_base = dev_read_addr_name_ptr(dev, "atu");
	if (!pci->atu_base) {
		dev_dbg(dev, "No 'atu' region, using default offset from DBI\n");
		pci->atu_base = pci->dbi_base + DEFAULT_DBI_ATU_OFFSET;
	}

	pcie->slcr_base = dev_read_addr_name_ptr(dev, "slcr");
	if (!pcie->slcr_base)
		dev_dbg(dev, "No 'slcr' region, interrupt features disabled\n");

	ret = pci_get_regions(dev, &io_region, &mem_region, &pref_region);
	if (ret < 0) {
		dev_err(dev, "Failed to get PCI regions: %d\n", ret);
		return ret;
	}

	if (mem_region)
		pci->mem = *mem_region;

	return 0;
}

static int amd_dw_pcie_probe(struct udevice *dev)
{
	struct amd_dw_pcie *pcie = dev_get_priv(dev);
	struct pcie_dw *pci = &pcie->dw;

	/* Set first bus number */
	pci->first_busno = dev_seq(dev);

	amd_dw_pcie_request_gpio(dev);
	amd_dw_pcie_host_init(pcie);
	amd_dw_pcie_start_link(pcie);

	if (pci->mem.size) {
		dev_dbg(dev, "Programming ATU region 0 for MEM: phys=0x%llx bus=0x%llx size=0x%llx\n",
			(unsigned long long)pci->mem.phys_start,
			(unsigned long long)pci->mem.bus_start,
			(unsigned long long)pci->mem.size);
		pcie_dw_prog_outbound_atu_unroll(pci,
						 PCIE_ATU_REGION_INDEX0,
						 PCIE_ATU_TYPE_MEM,
						 pci->mem.phys_start,
						 pci->mem.bus_start,
						 pci->mem.size);
	} else {
		dev_warn(dev, "No MEM region configured!\n");
	}

	dev_dbg(dev, "dbi: 0x%lx | config: 0x%lx | atu: 0x%lx | slcr: 0x%lx\n",
		(long)pci->dbi_base, (long)pci->cfg_base,
		(long)pci->atu_base, (long)pcie->slcr_base);

	return 0;
}

static const struct dm_pci_ops amd_dw_pcie_ops = {
	.read_config	= pcie_dw_read_config,
	.write_config	= pcie_dw_write_config,
};

static const struct udevice_id amd_dw_pcie_ids[] = {
	{ .compatible = "amd,versal2-mdb-host" },
	{ }
};

U_BOOT_DRIVER(pcie_dw_amd) = {
	.name		= "pcie_dw_amd",
	.id		= UCLASS_PCI,
	.of_match	= amd_dw_pcie_ids,
	.ops		= &amd_dw_pcie_ops,
	.of_to_plat	= amd_dw_pcie_of_to_plat,
	.probe		= amd_dw_pcie_probe,
	.priv_auto	= sizeof(struct amd_dw_pcie),
};