summaryrefslogtreecommitdiff
path: root/board/highbank/ahci.c
blob: 9c057278ace15f50176c950174f8701070b7916d (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2012 Calxeda, Inc.
 */

#include <common.h>
#include <ahci.h>
#include <asm/io.h>
#include <linux/delay.h>

#define CPHY_MAP(dev, addr) ((((dev) & 0x1f) << 7) | (((addr) >> 9) & 0x7f))
#define CPHY_ADDR(base, dev, addr) ((base) | (((addr) & 0x1ff) << 2))
#define CPHY_BASE			0xfff58000
#define CPHY_WIDTH			0x1000
#define CPHY_DTE_XS			5
#define CPHY_MII			31
#define SERDES_CR_CTL			0x80a0
#define SERDES_CR_ADDR			0x80a1
#define SERDES_CR_DATA			0x80a2
#define CR_BUSY				0x0001
#define CR_START			0x0001
#define CR_WR_RDN			0x0002
#define CPHY_TX_INPUT_STS		0x2001
#define CPHY_RX_INPUT_STS		0x2002
#define CPHY_SATA_TX_OVERRIDE_BIT	0x8000
#define CPHY_SATA_RX_OVERRIDE_BIT	0x4000
#define CPHY_TX_INPUT_OVERRIDE		0x2004
#define CPHY_RX_INPUT_OVERRIDE		0x2005
#define SPHY_LANE			0x100
#define SPHY_HALF_RATE			0x0001
#define CPHY_SATA_DPLL_MODE		0x0700
#define CPHY_SATA_DPLL_SHIFT		8
#define CPHY_SATA_TX_ATTEN		0x1c00
#define CPHY_SATA_TX_ATTEN_SHIFT	10

#define HB_SREG_SATA_ATTEN		0xfff3cf24

#define SATA_PORT_BASE			0xffe08000
#define SATA_VERSIONR			0xf8
#define SATA_HB_VERSION			0x3332302a

static u32 __combo_phy_reg_read(u8 phy, u8 dev, u32 addr)
{
	u32 data;
	writel(CPHY_MAP(dev, addr), CPHY_BASE + 0x800 + CPHY_WIDTH * phy);
	data = readl(CPHY_ADDR(CPHY_BASE + CPHY_WIDTH * phy, dev, addr));
	return data;
}

static void __combo_phy_reg_write(u8 phy, u8 dev, u32 addr, u32 data)
{
	writel(CPHY_MAP(dev, addr), CPHY_BASE + 0x800 + CPHY_WIDTH * phy);
	writel(data, CPHY_ADDR(CPHY_BASE + CPHY_WIDTH * phy, dev, addr));
}

static u32 combo_phy_read(u8 phy, u32 addr)
{
	u8 dev = CPHY_DTE_XS;
	if (phy == 5)
		dev = CPHY_MII;
	while (__combo_phy_reg_read(phy, dev, SERDES_CR_CTL) & CR_BUSY)
		udelay(5);
	__combo_phy_reg_write(phy, dev, SERDES_CR_ADDR, addr);
	__combo_phy_reg_write(phy, dev, SERDES_CR_CTL, CR_START);
	while (__combo_phy_reg_read(phy, dev, SERDES_CR_CTL) & CR_BUSY)
		udelay(5);
	return __combo_phy_reg_read(phy, dev, SERDES_CR_DATA);
}

static void combo_phy_write(u8 phy, u32 addr, u32 data)
{
	u8 dev = CPHY_DTE_XS;
	if (phy == 5)
		dev = CPHY_MII;
	while (__combo_phy_reg_read(phy, dev, SERDES_CR_CTL) & CR_BUSY)
		udelay(5);
	__combo_phy_reg_write(phy, dev, SERDES_CR_ADDR, addr);
	__combo_phy_reg_write(phy, dev, SERDES_CR_DATA, data);
	__combo_phy_reg_write(phy, dev, SERDES_CR_CTL, CR_WR_RDN | CR_START);
}

static void cphy_spread_spectrum_override(u8 phy, u8 lane, u32 val)
{
	u32 tmp;
	tmp = combo_phy_read(phy, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
	tmp &= ~CPHY_SATA_RX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_RX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);

	tmp |= CPHY_SATA_RX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_RX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);

	tmp &= ~CPHY_SATA_DPLL_MODE;
	tmp |= (val << CPHY_SATA_DPLL_SHIFT) & CPHY_SATA_DPLL_MODE;
	combo_phy_write(phy, CPHY_RX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);
}

static void cphy_tx_attenuation_override(u8 phy, u8 lane)
{
	u32 val;
	u32 tmp;
	u8  shift;

	shift = ((phy == 5) ? 4 : lane) * 4;

	val = (readl(HB_SREG_SATA_ATTEN) >> shift) & 0xf;

	if (val & 0x8)
		return;

	tmp = combo_phy_read(phy, CPHY_TX_INPUT_STS + lane * SPHY_LANE);
	tmp &= ~CPHY_SATA_TX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_TX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);

	tmp |= CPHY_SATA_TX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_TX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);

	tmp |= (val << CPHY_SATA_TX_ATTEN_SHIFT) & CPHY_SATA_TX_ATTEN;
	combo_phy_write(phy, CPHY_TX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);
}

static void cphy_disable_port_overrides(u8 port)
{
	u32 tmp;
	u8 lane = 0, phy = 0;

	if (port == 0)
		phy = 5;
	else if (port < 5)
		lane = port - 1;
	else
		return;
	tmp = combo_phy_read(phy, CPHY_RX_INPUT_STS + lane * SPHY_LANE);
	tmp &= ~CPHY_SATA_RX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_RX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);

	tmp = combo_phy_read(phy, CPHY_TX_INPUT_OVERRIDE + lane * SPHY_LANE);
	tmp &= ~CPHY_SATA_TX_OVERRIDE_BIT;
	combo_phy_write(phy, CPHY_TX_INPUT_OVERRIDE + lane * SPHY_LANE, tmp);
}

void cphy_disable_overrides(void)
{
	int i;
	u32 port_map;

	port_map = readl(0xffe08000 + HOST_PORTS_IMPL);
	for (i = 0; i < 5; i++) {
		if (port_map & (1 << i))
			cphy_disable_port_overrides(i);
	}
}

static void cphy_override_lane(u8 port)
{
	u32 tmp, k = 0;
	u8 lane = 0, phy = 0;

	if (port == 0)
		phy = 5;
	else if (port < 5)
		lane = port - 1;
	else
		return;

	do {
		tmp = combo_phy_read(0, CPHY_RX_INPUT_STS +
					lane * SPHY_LANE);
	} while ((tmp & SPHY_HALF_RATE) && (k++ < 1000));
	cphy_spread_spectrum_override(phy, lane, 3);
	cphy_tx_attenuation_override(phy, lane);
}

#define WAIT_MS_LINKUP	4

int ahci_link_up(struct ahci_uc_priv *probe_ent, int port)
{
	u32 tmp;
	int j = 0;
	u8 *port_mmio = (u8 *)probe_ent->port[port].port_mmio;
	u32 is_highbank = readl(SATA_PORT_BASE + SATA_VERSIONR) ==
				SATA_HB_VERSION ? 1 : 0;

	/* Bring up SATA link.
	 * SATA link bringup time is usually less than 1 ms; only very
	 * rarely has it taken between 1-2 ms. Never seen it above 2 ms.
	 */
	while (j < WAIT_MS_LINKUP) {
		if (is_highbank && (j == 0)) {
			cphy_disable_port_overrides(port);
			writel(0x301, port_mmio + PORT_SCR_CTL);
			udelay(1000);
			writel(0x300, port_mmio + PORT_SCR_CTL);
			udelay(1000);
			cphy_override_lane(port);
		}

		tmp = readl(port_mmio + PORT_SCR_STAT);
		if ((tmp & 0xf) == 0x3)
			return 0;
		udelay(1000);
		j++;

		if ((j == WAIT_MS_LINKUP) && (tmp & 0xf))
			j = 0;	/* retry phy reset */
	}
	return 1;
}