summaryrefslogtreecommitdiffstats
path: root/drivers/pci/pci-mvebu-phy.c
blob: 55a1d39f62b97c7fa6dc0bb27838e2304a7f921f (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
/*
 * SoC specific PCIe PHY setup for Marvell MVEBU SoCs
 *
 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
 *
 * based on Marvell BSP code (C) Marvell International Ltd.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include <common.h>
#include <of.h>
#include <of_address.h>

#include "pci-mvebu.h"

static u32 mvebu_pcie_phy_indirect(void __iomem *phybase, u8 lane,
				   u8 off, u16 val, bool is_read)
{
	u32 reg = (lane << 24) | (off << 16) | val;

	if (is_read)
		reg |= BIT(31);
	writel(reg, phybase);

	return (is_read) ? readl(phybase) & 0xffff : 0;
}

static inline u32 mvebu_pcie_phy_read(void __iomem *phybase, u8 lane,
				      u8 off)
{
	return mvebu_pcie_phy_indirect(phybase, lane, off, 0, true);
}

static inline void mvebu_pcie_phy_write(void __iomem *phybase, u8 lane,
					u8 off, u16 val)
{
	mvebu_pcie_phy_indirect(phybase, lane, off, val, false);
}

/* PCIe registers */
#define ARMADA_370_XP_PCIE_LINK_CAPS	0x6c
#define  MAX_LINK_WIDTH_MASK		MAX_LINK_WIDTH(0x3f)
#define  MAX_LINK_WIDTH(x)		((x) << 4)
#define  MAX_LINK_SPEED_MASK		0xf
#define  MAX_LINK_SPEED_5G0		0x2
#define  MAX_LINK_SPEED_2G5		0x1
#define ARMADA_370_XP_PHY_OFFSET	0x1b00
/* System Control registers */
#define ARMADA_370_XP_SOC_CTRL		0x04
#define  PCIE1_QUADX1_EN		BIT(8) /* Armada XP */
#define  PCIE0_QUADX1_EN		BIT(7) /* Armada XP */
#define  PCIE0_EN			BIT(0)
#define ARMADA_370_XP_SERDES03_SEL	0x70
#define ARMADA_370_XP_SERDES47_SEL	0x74
#define  SERDES(x, v)			((v) << ((x) * 0x4))
#define  SERDES_MASK(x)			SERDES((x), 0xf)

int armada_370_phy_setup(struct mvebu_pcie *pcie)
{
	struct device_node *np = of_find_compatible_node(NULL, NULL,
				 "marvell,armada-370-xp-system-controller");
	void __iomem *sysctrl = of_iomap(np, 0);
	void __iomem *phybase = pcie->base + ARMADA_370_XP_PHY_OFFSET;
	u32 reg;

	if (!sysctrl)
		return -ENODEV;

	/* Enable PEX */
	reg = readl(sysctrl + ARMADA_370_XP_SOC_CTRL);
	reg |= PCIE0_EN << pcie->port;
	writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);

	/* Set SERDES selector */
	reg = readl(sysctrl + ARMADA_370_XP_SERDES03_SEL);
	reg &= ~SERDES_MASK(pcie->port);
	reg |= SERDES(pcie->port, 0x1);
	writel(reg, sysctrl + ARMADA_370_XP_SERDES03_SEL);

	/* BTS #232 - PCIe clock (undocumented) */
	writel(0x00000077, sysctrl + 0x2f0);

	/* Set x1 Link Capability */
	reg = readl(pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
	reg &= ~(MAX_LINK_WIDTH_MASK | MAX_LINK_SPEED_MASK);
	reg |= MAX_LINK_WIDTH(0x1) | MAX_LINK_SPEED_5G0;
	writel(reg, pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);

	/* PEX pipe configuration */
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0025);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x000f);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc8, 0x0005);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xd0, 0x0100);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xd1, 0x3014);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc5, 0x011f);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x80, 0x1000);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x81, 0x0011);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x0f, 0x2a21);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x45, 0x00df);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x4f, 0x6219);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x01, 0xfc60);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x46, 0x0000);

	reg = mvebu_pcie_phy_read(phybase, pcie->lane, 0x48) & ~0x4;
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, reg & 0xffff);

	mvebu_pcie_phy_write(phybase, pcie->lane, 0x02, 0x0040);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0024);

	mdelay(15);

	return 0;
}

/*
 * MV78230: 2 PCIe units Gen2.0, one unit 1x4 or 4x1, one unit 1x1
 * MV78260: 3 PCIe units Gen2.0, two units 1x4 or 4x1, one unit 1x1/1x4
 * MV78460: 4 PCIe units Gen2.0, two units 1x4 or 4x1, two units 1x1/1x4
 */
#define ARMADA_XP_COMM_PHY_REFCLK_ALIGN	0xf8
#define  REFCLK_ALIGN(x)	(0xf << ((x) * 0x4))
int armada_xp_phy_setup(struct mvebu_pcie *pcie)
{
	struct device_node *np = of_find_compatible_node(NULL, NULL,
				 "marvell,armada-370-xp-system-controller");
	void __iomem *sysctrl = of_iomap(np, 0);
	void __iomem *phybase = pcie->base + ARMADA_370_XP_PHY_OFFSET;
	u32 serdes_off = (pcie->port < 2) ? ARMADA_370_XP_SERDES03_SEL :
		ARMADA_370_XP_SERDES47_SEL;
	bool single_x4 = (pcie->lane_mask == 0xf);
	u32 reg, mask;

	if (!sysctrl)
		return -ENODEV;

	/* Prepare PEX */
	reg = readl(sysctrl + ARMADA_370_XP_SOC_CTRL);
	reg &= ~(PCIE0_EN << pcie->port);
	writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);

	if (pcie->port < 2) {
		mask = PCIE0_QUADX1_EN << pcie->port;
		if (single_x4)
			reg &= ~mask;
		else
			reg |= mask;
	}
	reg |= PCIE0_EN << pcie->port;
	writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);

	/* Set SERDES selector */
	reg = readl(sysctrl + serdes_off);
	for (mask = pcie->lane_mask; mask;) {
		u32 l = ffs(mask)-1;
		u32 off = 4 * (pcie->port % 2);
		reg &= ~SERDES_MASK(off + l);
		reg |= SERDES(off + l, 0x1);
		mask &= ~BIT(l);
	}
	reg &= ~SERDES_MASK(pcie->port % 2);
	reg |= SERDES(pcie->port % 2, 0x1);
	writel(reg, sysctrl + serdes_off);

	/* Reference Clock Alignment for 1x4 */
	reg = readl(sysctrl + ARMADA_XP_COMM_PHY_REFCLK_ALIGN);
	if (single_x4)
		reg |= REFCLK_ALIGN(pcie->port);
	else
		reg &= ~REFCLK_ALIGN(pcie->port);
	writel(reg, sysctrl + ARMADA_XP_COMM_PHY_REFCLK_ALIGN);

	/* Set x1/x4 Link Capability */
	reg = readl(pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
	reg &= ~(MAX_LINK_WIDTH_MASK | MAX_LINK_SPEED_MASK);
	if (single_x4)
		reg |= MAX_LINK_WIDTH(0x4);
	else
		reg |= MAX_LINK_WIDTH(0x1);
	reg |= MAX_LINK_SPEED_5G0;
	writel(reg, pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);

	/* PEX pipe configuration */
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0025);
	if (single_x4) {
		mvebu_pcie_phy_write(phybase, pcie->lane, 0xc2, 0x0200);
		mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x0001);
	} else {
		mvebu_pcie_phy_write(phybase, pcie->lane, 0xc2, 0x0000);
		mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x000f);
	}
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc8, 0x0005);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x01, 0xfc60);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0x46, 0x0000);

	mvebu_pcie_phy_write(phybase, pcie->lane, 0x02, 0x0040);
	mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0024);
	if (single_x4)
		mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, 0x1080);
	else
		mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, 0x9080);

	mdelay(15);

	return 0;
}