summaryrefslogtreecommitdiff
path: root/drivers/pci/pcie_dw_mvebu.c
blob: a19885501cab6a82d8f5e781e3b21d26041b7cf0 (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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
/*
 * Copyright (C) 2015 Marvell International Ltd.
 *
 * Copyright (C) 2016 Stefan Roese <sr@denx.de>
 *
 * Based on:
 *   - drivers/pci/pcie_imx.c
 *   - drivers/pci/pci_mvebu.c
 *   - drivers/pci/pcie_xilinx.c
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <dm.h>
#include <pci.h>
#include <asm/io.h>
#include <asm-generic/gpio.h>

DECLARE_GLOBAL_DATA_PTR;

/* PCI Config space registers */
#define PCIE_CONFIG_BAR0		0x10
#define PCIE_LINK_STATUS_REG		0x80
#define PCIE_LINK_STATUS_SPEED_OFF	16
#define PCIE_LINK_STATUS_SPEED_MASK	(0xf << PCIE_LINK_STATUS_SPEED_OFF)
#define PCIE_LINK_STATUS_WIDTH_OFF	20
#define PCIE_LINK_STATUS_WIDTH_MASK	(0xf << PCIE_LINK_STATUS_WIDTH_OFF)

/* Resizable bar capability registers */
#define RESIZABLE_BAR_CAP		0x250
#define RESIZABLE_BAR_CTL0		0x254
#define RESIZABLE_BAR_CTL1		0x258

/* iATU registers */
#define PCIE_ATU_VIEWPORT		0x900
#define PCIE_ATU_REGION_INBOUND		(0x1 << 31)
#define PCIE_ATU_REGION_OUTBOUND	(0x0 << 31)
#define PCIE_ATU_REGION_INDEX1		(0x1 << 0)
#define PCIE_ATU_REGION_INDEX0		(0x0 << 0)
#define PCIE_ATU_CR1			0x904
#define PCIE_ATU_TYPE_MEM		(0x0 << 0)
#define PCIE_ATU_TYPE_IO		(0x2 << 0)
#define PCIE_ATU_TYPE_CFG0		(0x4 << 0)
#define PCIE_ATU_TYPE_CFG1		(0x5 << 0)
#define PCIE_ATU_CR2			0x908
#define PCIE_ATU_ENABLE			(0x1 << 31)
#define PCIE_ATU_BAR_MODE_ENABLE	(0x1 << 30)
#define PCIE_ATU_LOWER_BASE		0x90C
#define PCIE_ATU_UPPER_BASE		0x910
#define PCIE_ATU_LIMIT			0x914
#define PCIE_ATU_LOWER_TARGET		0x918
#define PCIE_ATU_BUS(x)			(((x) & 0xff) << 24)
#define PCIE_ATU_DEV(x)			(((x) & 0x1f) << 19)
#define PCIE_ATU_FUNC(x)		(((x) & 0x7) << 16)
#define PCIE_ATU_UPPER_TARGET		0x91C

#define PCIE_LINK_CAPABILITY		0x7C
#define PCIE_LINK_CTL_2			0xA0
#define TARGET_LINK_SPEED_MASK		0xF
#define LINK_SPEED_GEN_1		0x1
#define LINK_SPEED_GEN_2		0x2
#define LINK_SPEED_GEN_3		0x3

#define PCIE_GEN3_RELATED		0x890
#define GEN3_EQU_DISABLE		(1 << 16)
#define GEN3_ZRXDC_NON_COMP		(1 << 0)

#define PCIE_GEN3_EQU_CTRL		0x8A8
#define GEN3_EQU_EVAL_2MS_DISABLE	(1 << 5)

#define PCIE_ROOT_COMPLEX_MODE_MASK	(0xF << 4)

#define PCIE_LINK_UP_TIMEOUT_MS		100

#define PCIE_GLOBAL_CONTROL		0x8000
#define PCIE_APP_LTSSM_EN		(1 << 2)
#define PCIE_DEVICE_TYPE_OFFSET		(4)
#define PCIE_DEVICE_TYPE_MASK		(0xF)
#define PCIE_DEVICE_TYPE_EP		(0x0) /* Endpoint */
#define PCIE_DEVICE_TYPE_LEP		(0x1) /* Legacy endpoint */
#define PCIE_DEVICE_TYPE_RC		(0x4) /* Root complex */

#define PCIE_GLOBAL_STATUS		0x8008
#define PCIE_GLB_STS_RDLH_LINK_UP	(1 << 1)
#define PCIE_GLB_STS_PHY_LINK_UP	(1 << 9)

#define PCIE_ARCACHE_TRC		0x8050
#define PCIE_AWCACHE_TRC		0x8054
#define ARCACHE_SHAREABLE_CACHEABLE	0x3511
#define AWCACHE_SHAREABLE_CACHEABLE	0x5311

#define LINK_SPEED_GEN_1                0x1
#define LINK_SPEED_GEN_2                0x2
#define LINK_SPEED_GEN_3                0x3

/**
 * struct pcie_dw_mvebu - MVEBU DW PCIe controller state
 *
 * @ctrl_base: The base address of the register space
 * @cfg_base: The base address of the configuration space
 * @cfg_size: The size of the configuration space which is needed
 *            as it gets written into the PCIE_ATU_LIMIT register
 * @first_busno: This driver supports multiple PCIe controllers.
 *               first_busno stores the bus number of the PCIe root-port
 *               number which may vary depending on the PCIe setup
 *               (PEX switches etc).
 */
struct pcie_dw_mvebu {
	void *ctrl_base;
	void *cfg_base;
	fdt_size_t cfg_size;
	int first_busno;
};

static int pcie_dw_get_link_speed(const void *regs_base)
{
	return (readl(regs_base + PCIE_LINK_STATUS_REG) &
		PCIE_LINK_STATUS_SPEED_MASK) >> PCIE_LINK_STATUS_SPEED_OFF;
}

static int pcie_dw_get_link_width(const void *regs_base)
{
	return (readl(regs_base + PCIE_LINK_STATUS_REG) &
		PCIE_LINK_STATUS_WIDTH_MASK) >> PCIE_LINK_STATUS_WIDTH_OFF;
}

/**
 * set_cfg_address() - Configure the PCIe controller config space access
 *
 * @pcie: Pointer to the PCI controller state
 * @d: PCI device to access
 * @where: Offset in the configuration space
 *
 * Configures the PCIe controller to access the configuration space of
 * a specific PCIe device and returns the address to use for this
 * access.
 *
 * Return: Address that can be used to access the configation space
 *         of the requested device / offset
 */
static uintptr_t set_cfg_address(struct pcie_dw_mvebu *pcie,
				 pci_dev_t d, uint where)
{
	uintptr_t va_address;

	/*
	 * Region #0 is used for Outbound CFG space access.
	 * Direction = Outbound
	 * Region Index = 0
	 */
	writel(0, pcie->ctrl_base + PCIE_ATU_VIEWPORT);

	if (PCI_BUS(d) == (pcie->first_busno + 1))
		/* For local bus, change TLP Type field to 4. */
		writel(PCIE_ATU_TYPE_CFG0, pcie->ctrl_base + PCIE_ATU_CR1);
	else
		/* Otherwise, change TLP Type field to 5. */
		writel(PCIE_ATU_TYPE_CFG1, pcie->ctrl_base + PCIE_ATU_CR1);

	if (PCI_BUS(d) == pcie->first_busno) {
		/* Accessing root port configuration space. */
		va_address = (uintptr_t)pcie->ctrl_base;
	} else {
		d = PCI_MASK_BUS(d) | (PCI_BUS(d) - pcie->first_busno);
		writel(d << 8, pcie->ctrl_base + PCIE_ATU_LOWER_TARGET);
		va_address = (uintptr_t)pcie->cfg_base;
	}

	va_address += where & ~0x3;

	return va_address;
}

/**
 * pcie_dw_addr_valid() - Check for valid bus address
 *
 * @d: The PCI device to access
 * @first_busno: Bus number of the PCIe controller root complex
 *
 * Return 1 (true) if the PCI device can be accessed by this controller.
 *
 * Return: 1 on valid, 0 on invalid
 */
static int pcie_dw_addr_valid(pci_dev_t d, int first_busno)
{
	if ((PCI_BUS(d) == first_busno) && (PCI_DEV(d) > 0))
		return 0;
	if ((PCI_BUS(d) == first_busno + 1) && (PCI_DEV(d) > 0))
		return 0;

	return 1;
}

/**
 * pcie_dw_mvebu_read_config() - Read from configuration space
 *
 * @bus: Pointer to the PCI bus
 * @bdf: Identifies the PCIe device to access
 * @offset: The offset into the device's configuration space
 * @valuep: A pointer at which to store the read value
 * @size: Indicates the size of access to perform
 *
 * Read a value of size @size from offset @offset within the configuration
 * space of the device identified by the bus, device & function numbers in @bdf
 * on the PCI bus @bus.
 *
 * Return: 0 on success
 */
static int pcie_dw_mvebu_read_config(struct udevice *bus, pci_dev_t bdf,
				     uint offset, ulong *valuep,
				     enum pci_size_t size)
{
	struct pcie_dw_mvebu *pcie = dev_get_priv(bus);
	uintptr_t va_address;
	ulong value;

	debug("PCIE CFG read:  (b,d,f)=(%2d,%2d,%2d) ",
	      PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf));

	if (!pcie_dw_addr_valid(bdf, pcie->first_busno)) {
		debug("- out of range\n");
		*valuep = pci_get_ff(size);
		return 0;
	}

	va_address = set_cfg_address(pcie, bdf, offset);

	value = readl(va_address);

	debug("(addr,val)=(0x%04x, 0x%08lx)\n", offset, value);
	*valuep = pci_conv_32_to_size(value, offset, size);

	return 0;
}

/**
 * pcie_dw_mvebu_write_config() - Write to configuration space
 *
 * @bus: Pointer to the PCI bus
 * @bdf: Identifies the PCIe device to access
 * @offset: The offset into the device's configuration space
 * @value: The value to write
 * @size: Indicates the size of access to perform
 *
 * Write the value @value of size @size from offset @offset within the
 * configuration space of the device identified by the bus, device & function
 * numbers in @bdf on the PCI bus @bus.
 *
 * Return: 0 on success
 */
static int pcie_dw_mvebu_write_config(struct udevice *bus, pci_dev_t bdf,
				      uint offset, ulong value,
				      enum pci_size_t size)
{
	struct pcie_dw_mvebu *pcie = dev_get_priv(bus);
	uintptr_t va_address;
	ulong old;

	debug("PCIE CFG write: (b,d,f)=(%2d,%2d,%2d) ",
	      PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf));
	debug("(addr,val)=(0x%04x, 0x%08lx)\n", offset, value);

	if (!pcie_dw_addr_valid(bdf, pcie->first_busno)) {
		debug("- out of range\n");
		return 0;
	}

	va_address = set_cfg_address(pcie, bdf, offset);

	old = readl(va_address);
	value = pci_conv_size_to_32(old, value, offset, size);
	writel(value, va_address);

	return 0;
}

/**
 * pcie_dw_configure() - Configure link capabilities and speed
 *
 * @regs_base: A pointer to the PCIe controller registers
 * @cap_speed: The capabilities and speed to configure
 *
 * Configure the link capabilities and speed in the PCIe root complex.
 */
static void pcie_dw_configure(const void *regs_base, u32 cap_speed)
{
	/*
	 * TODO (shadi@marvell.com, sr@denx.de):
	 * Need to read the serdes speed from the dts and according to it
	 * configure the PCIe gen
	 */

	/* Set link to GEN 3 */
	clrsetbits_le32(regs_base + PCIE_LINK_CTL_2,
			TARGET_LINK_SPEED_MASK, cap_speed);
	clrsetbits_le32(regs_base + PCIE_LINK_CAPABILITY,
			TARGET_LINK_SPEED_MASK, cap_speed);
	setbits_le32(regs_base + PCIE_GEN3_EQU_CTRL, GEN3_EQU_EVAL_2MS_DISABLE);
}

/**
 * is_link_up() - Return the link state
 *
 * @regs_base: A pointer to the PCIe controller registers
 *
 * Return: 1 (true) for active line and 0 (false) for no link
 */
static int is_link_up(const void *regs_base)
{
	u32 mask = PCIE_GLB_STS_RDLH_LINK_UP | PCIE_GLB_STS_PHY_LINK_UP;
	u32 reg;

	reg = readl(regs_base + PCIE_GLOBAL_STATUS);
	if ((reg & mask) == mask)
		return 1;

	return 0;
}

/**
 * wait_link_up() - Wait for the link to come up
 *
 * @regs_base: A pointer to the PCIe controller registers
 *
 * Return: 1 (true) for active line and 0 (false) for no link (timeout)
 */
static int wait_link_up(const void *regs_base)
{
	unsigned long timeout;

	timeout = get_timer(0) + PCIE_LINK_UP_TIMEOUT_MS;
	while (!is_link_up(regs_base)) {
		if (get_timer(0) > timeout)
			return 0;
	};

	return 1;
}

/**
 * pcie_dw_mvebu_pcie_link_up() - Configure the PCIe root port
 *
 * @regs_base: A pointer to the PCIe controller registers
 * @cap_speed: The capabilities and speed to configure
 *
 * Configure the PCIe controller root complex depending on the
 * requested link capabilities and speed.
 *
 * Return: 1 (true) for active line and 0 (false) for no link
 */
static int pcie_dw_mvebu_pcie_link_up(const void *regs_base, u32 cap_speed)
{
	if (!is_link_up(regs_base)) {
		/* Disable LTSSM state machine to enable configuration */
		clrbits_le32(regs_base + PCIE_GLOBAL_CONTROL,
			     PCIE_APP_LTSSM_EN);
	}

	clrsetbits_le32(regs_base + PCIE_GLOBAL_CONTROL,
			PCIE_DEVICE_TYPE_MASK << PCIE_DEVICE_TYPE_OFFSET,
			PCIE_DEVICE_TYPE_RC << PCIE_DEVICE_TYPE_OFFSET);

	/* Set the PCIe master AXI attributes */
	writel(ARCACHE_SHAREABLE_CACHEABLE, regs_base + PCIE_ARCACHE_TRC);
	writel(AWCACHE_SHAREABLE_CACHEABLE, regs_base + PCIE_AWCACHE_TRC);

	/* DW pre link configurations */
	pcie_dw_configure(regs_base, cap_speed);

	if (!is_link_up(regs_base)) {
		/* Configuration done. Start LTSSM */
		setbits_le32(regs_base + PCIE_GLOBAL_CONTROL,
			     PCIE_APP_LTSSM_EN);
	}

	/* Check that link was established */
	if (!wait_link_up(regs_base))
		return 0;

	/*
	 * Link can be established in Gen 1. still need to wait
	 * till MAC nagaotiation is completed
	 */
	udelay(100);

	return 1;
}

/**
 * pcie_dw_regions_setup() - iATU region setup
 *
 * @pcie: Pointer to the PCI controller state
 *
 * Configure the iATU regions in the PCIe controller for outbound access.
 */
static void pcie_dw_regions_setup(struct pcie_dw_mvebu *pcie)
{
	/*
	 * Region #0 is used for Outbound CFG space access.
	 * Direction = Outbound
	 * Region Index = 0
	 */
	writel(0, pcie->ctrl_base + PCIE_ATU_VIEWPORT);

	writel((u32)(uintptr_t)pcie->cfg_base, pcie->ctrl_base
	       + PCIE_ATU_LOWER_BASE);
	writel(0, pcie->ctrl_base + PCIE_ATU_UPPER_BASE);
	writel((u32)(uintptr_t)pcie->cfg_base + pcie->cfg_size,
	       pcie->ctrl_base + PCIE_ATU_LIMIT);

	writel(0, pcie->ctrl_base + PCIE_ATU_LOWER_TARGET);
	writel(0, pcie->ctrl_base + PCIE_ATU_UPPER_TARGET);
	writel(PCIE_ATU_TYPE_CFG0, pcie->ctrl_base + PCIE_ATU_CR1);
	writel(PCIE_ATU_ENABLE, pcie->ctrl_base + PCIE_ATU_CR2);
}

/**
 * pcie_dw_set_host_bars() - Configure the host BARs
 *
 * @regs_base: A pointer to the PCIe controller registers
 *
 * Configure the host BARs of the PCIe controller root port so that
 * PCI(e) devices may access the system memory.
 */
static void pcie_dw_set_host_bars(const void *regs_base)
{
	u32 size = gd->ram_size;
	u64 max_size;
	u32 reg;
	u32 bar0;

	/* Verify the maximal BAR size */
	reg = readl(regs_base + RESIZABLE_BAR_CAP);
	max_size = 1ULL << (5 + (reg + (1 << 4)));

	if (size > max_size) {
		size = max_size;
		printf("Warning: PCIe BARs can't map all DRAM space\n");
	}

	/* Set the BAR base and size towards DDR */
	bar0 = CONFIG_SYS_SDRAM_BASE & ~0xf;
	bar0 |= PCI_BASE_ADDRESS_MEM_TYPE_32;
	writel(CONFIG_SYS_SDRAM_BASE, regs_base + PCIE_CONFIG_BAR0);

	reg = ((size >> 20) - 1) << 12;
	writel(size, regs_base + RESIZABLE_BAR_CTL0);
}

/**
 * pcie_dw_mvebu_probe() - Probe the PCIe bus for active link
 *
 * @dev: A pointer to the device being operated on
 *
 * Probe for an active link on the PCIe bus and configure the controller
 * to enable this port.
 *
 * Return: 0 on success, else -ENODEV
 */
static int pcie_dw_mvebu_probe(struct udevice *dev)
{
	struct pcie_dw_mvebu *pcie = dev_get_priv(dev);
	struct udevice *ctlr = pci_get_controller(dev);
	struct pci_controller *hose = dev_get_uclass_priv(ctlr);
#ifdef CONFIG_DM_GPIO
	struct gpio_desc reset_gpio;

	gpio_request_by_name(dev, "marvell,reset-gpio", 0, &reset_gpio,
			     GPIOD_IS_OUT);
	/*
	 * Issue reset to add-in card trough the dedicated GPIO.
	 * Some boards are connecting the card reset pin to common system
	 * reset wire and others are using separate GPIO port.
	 * In the last case we have to release a reset of the addon card
	 * using this GPIO.
	 */
	if (dm_gpio_is_valid(&reset_gpio)) {
		dm_gpio_set_value(&reset_gpio, 1);
		mdelay(200);
	}
#else
	debug("PCIE Reset on GPIO support is missing\n");
#endif /* CONFIG_DM_GPIO */

	pcie->first_busno = dev->seq;

	/* Don't register host if link is down */
	if (!pcie_dw_mvebu_pcie_link_up(pcie->ctrl_base, LINK_SPEED_GEN_3)) {
		printf("PCIE-%d: Link down\n", dev->seq);
	} else {
		printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", dev->seq,
		       pcie_dw_get_link_speed(pcie->ctrl_base),
		       pcie_dw_get_link_width(pcie->ctrl_base),
		       hose->first_busno);
	}

	pcie_dw_regions_setup(pcie);

	/* Set the CLASS_REV of RC CFG header to PCI_CLASS_BRIDGE_PCI */
	clrsetbits_le32(pcie->ctrl_base + PCI_CLASS_REVISION,
			0xffff << 16, PCI_CLASS_BRIDGE_PCI << 16);

	pcie_dw_set_host_bars(pcie->ctrl_base);

	return 0;
}

/**
 * pcie_dw_mvebu_ofdata_to_platdata() - Translate from DT to device state
 *
 * @dev: A pointer to the device being operated on
 *
 * Translate relevant data from the device tree pertaining to device @dev into
 * state that the driver will later make use of. This state is stored in the
 * device's private data structure.
 *
 * Return: 0 on success, else -EINVAL
 */
static int pcie_dw_mvebu_ofdata_to_platdata(struct udevice *dev)
{
	struct pcie_dw_mvebu *pcie = dev_get_priv(dev);

	/* Get the controller base address */
	pcie->ctrl_base = (void *)devfdt_get_addr_index(dev, 0);
	if ((fdt_addr_t)pcie->ctrl_base == FDT_ADDR_T_NONE)
		return -EINVAL;

	/* Get the config space base address and size */
	pcie->cfg_base = (void *)devfdt_get_addr_size_index(dev, 1,
							 &pcie->cfg_size);
	if ((fdt_addr_t)pcie->cfg_base == FDT_ADDR_T_NONE)
		return -EINVAL;

	return 0;
}

static const struct dm_pci_ops pcie_dw_mvebu_ops = {
	.read_config	= pcie_dw_mvebu_read_config,
	.write_config	= pcie_dw_mvebu_write_config,
};

static const struct udevice_id pcie_dw_mvebu_ids[] = {
	{ .compatible = "marvell,armada8k-pcie" },
	{ }
};

U_BOOT_DRIVER(pcie_dw_mvebu) = {
	.name			= "pcie_dw_mvebu",
	.id			= UCLASS_PCI,
	.of_match		= pcie_dw_mvebu_ids,
	.ops			= &pcie_dw_mvebu_ops,
	.ofdata_to_platdata	= pcie_dw_mvebu_ofdata_to_platdata,
	.probe			= pcie_dw_mvebu_probe,
	.priv_auto_alloc_size	= sizeof(struct pcie_dw_mvebu),
};