]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
8ceee660 | 2 | /**************************************************************************** |
f7a6d2c4 | 3 | * Driver for Solarflare network controllers and boards |
0a6f40c6 | 4 | * Copyright 2006-2011 Solarflare Communications Inc. |
8ceee660 BH |
5 | */ |
6 | /* | |
7 | * Useful functions for working with MDIO clause 45 PHYs | |
8 | */ | |
9 | #include <linux/types.h> | |
10 | #include <linux/ethtool.h> | |
11 | #include <linux/delay.h> | |
12 | #include "net_driver.h" | |
13 | #include "mdio_10g.h" | |
8b9dc8dd | 14 | #include "workarounds.h" |
8ceee660 | 15 | |
5a6681e2 | 16 | unsigned ef4_mdio_id_oui(u32 id) |
3f39a5e9 BH |
17 | { |
18 | unsigned oui = 0; | |
19 | int i; | |
20 | ||
21 | /* The bits of the OUI are designated a..x, with a=0 and b variable. | |
22 | * In the id register c is the MSB but the OUI is conventionally | |
23 | * written as bytes h..a, p..i, x..q. Reorder the bits accordingly. */ | |
24 | for (i = 0; i < 22; ++i) | |
25 | if (id & (1 << (i + 10))) | |
26 | oui |= 1 << (i ^ 7); | |
27 | ||
28 | return oui; | |
29 | } | |
30 | ||
5a6681e2 | 31 | int ef4_mdio_reset_mmd(struct ef4_nic *port, int mmd, |
8ceee660 BH |
32 | int spins, int spintime) |
33 | { | |
34 | u32 ctrl; | |
8ceee660 BH |
35 | |
36 | /* Catch callers passing values in the wrong units (or just silly) */ | |
5a6681e2 | 37 | EF4_BUG_ON_PARANOID(spins * spintime >= 5000); |
8ceee660 | 38 | |
5a6681e2 | 39 | ef4_mdio_write(port, mmd, MDIO_CTRL1, MDIO_CTRL1_RESET); |
8ceee660 BH |
40 | /* Wait for the reset bit to clear. */ |
41 | do { | |
42 | msleep(spintime); | |
5a6681e2 | 43 | ctrl = ef4_mdio_read(port, mmd, MDIO_CTRL1); |
8ceee660 BH |
44 | spins--; |
45 | ||
68e7f45e | 46 | } while (spins && (ctrl & MDIO_CTRL1_RESET)); |
8ceee660 BH |
47 | |
48 | return spins ? spins : -ETIMEDOUT; | |
49 | } | |
50 | ||
5a6681e2 | 51 | static int ef4_mdio_check_mmd(struct ef4_nic *efx, int mmd) |
8ceee660 BH |
52 | { |
53 | int status; | |
8ceee660 | 54 | |
04cc8cac BH |
55 | if (mmd != MDIO_MMD_AN) { |
56 | /* Read MMD STATUS2 to check it is responding. */ | |
5a6681e2 | 57 | status = ef4_mdio_read(efx, mmd, MDIO_STAT2); |
68e7f45e | 58 | if ((status & MDIO_STAT2_DEVPRST) != MDIO_STAT2_DEVPRST_VAL) { |
62776d03 BH |
59 | netif_err(efx, hw, efx->net_dev, |
60 | "PHY MMD %d not responding.\n", mmd); | |
04cc8cac BH |
61 | return -EIO; |
62 | } | |
8ceee660 BH |
63 | } |
64 | ||
8ceee660 BH |
65 | return 0; |
66 | } | |
67 | ||
68 | /* This ought to be ridiculous overkill. We expect it to fail rarely */ | |
69 | #define MDIO45_RESET_TIME 1000 /* ms */ | |
70 | #define MDIO45_RESET_ITERS 100 | |
71 | ||
5a6681e2 | 72 | int ef4_mdio_wait_reset_mmds(struct ef4_nic *efx, unsigned int mmd_mask) |
8ceee660 BH |
73 | { |
74 | const int spintime = MDIO45_RESET_TIME / MDIO45_RESET_ITERS; | |
75 | int tries = MDIO45_RESET_ITERS; | |
76 | int rc = 0; | |
77 | int in_reset; | |
78 | ||
79 | while (tries) { | |
80 | int mask = mmd_mask; | |
81 | int mmd = 0; | |
82 | int stat; | |
83 | in_reset = 0; | |
84 | while (mask) { | |
85 | if (mask & 1) { | |
5a6681e2 | 86 | stat = ef4_mdio_read(efx, mmd, MDIO_CTRL1); |
8ceee660 | 87 | if (stat < 0) { |
62776d03 BH |
88 | netif_err(efx, hw, efx->net_dev, |
89 | "failed to read status of" | |
90 | " MMD %d\n", mmd); | |
8ceee660 BH |
91 | return -EIO; |
92 | } | |
68e7f45e | 93 | if (stat & MDIO_CTRL1_RESET) |
8ceee660 BH |
94 | in_reset |= (1 << mmd); |
95 | } | |
96 | mask = mask >> 1; | |
97 | mmd++; | |
98 | } | |
99 | if (!in_reset) | |
100 | break; | |
101 | tries--; | |
102 | msleep(spintime); | |
103 | } | |
104 | if (in_reset != 0) { | |
62776d03 BH |
105 | netif_err(efx, hw, efx->net_dev, |
106 | "not all MMDs came out of reset in time." | |
107 | " MMDs still in reset: %x\n", in_reset); | |
8ceee660 BH |
108 | rc = -ETIMEDOUT; |
109 | } | |
110 | return rc; | |
111 | } | |
112 | ||
5a6681e2 | 113 | int ef4_mdio_check_mmds(struct ef4_nic *efx, unsigned int mmd_mask) |
8ceee660 | 114 | { |
68e7f45e | 115 | int mmd = 0, probe_mmd, devs1, devs2; |
27dd2cac | 116 | u32 devices; |
8ceee660 BH |
117 | |
118 | /* Historically we have probed the PHYXS to find out what devices are | |
119 | * present,but that doesn't work so well if the PHYXS isn't expected | |
120 | * to exist, if so just find the first item in the list supplied. */ | |
68e7f45e | 121 | probe_mmd = (mmd_mask & MDIO_DEVS_PHYXS) ? MDIO_MMD_PHYXS : |
8ceee660 | 122 | __ffs(mmd_mask); |
8ceee660 BH |
123 | |
124 | /* Check all the expected MMDs are present */ | |
5a6681e2 EC |
125 | devs1 = ef4_mdio_read(efx, probe_mmd, MDIO_DEVS1); |
126 | devs2 = ef4_mdio_read(efx, probe_mmd, MDIO_DEVS2); | |
68e7f45e | 127 | if (devs1 < 0 || devs2 < 0) { |
62776d03 BH |
128 | netif_err(efx, hw, efx->net_dev, |
129 | "failed to read devices present\n"); | |
8ceee660 BH |
130 | return -EIO; |
131 | } | |
68e7f45e | 132 | devices = devs1 | (devs2 << 16); |
8ceee660 | 133 | if ((devices & mmd_mask) != mmd_mask) { |
62776d03 BH |
134 | netif_err(efx, hw, efx->net_dev, |
135 | "required MMDs not present: got %x, wanted %x\n", | |
136 | devices, mmd_mask); | |
8ceee660 BH |
137 | return -ENODEV; |
138 | } | |
62776d03 | 139 | netif_vdbg(efx, hw, efx->net_dev, "Devices present: %x\n", devices); |
8ceee660 BH |
140 | |
141 | /* Check all required MMDs are responding and happy. */ | |
142 | while (mmd_mask) { | |
5a6681e2 | 143 | if ((mmd_mask & 1) && ef4_mdio_check_mmd(efx, mmd)) |
a461103b | 144 | return -EIO; |
8ceee660 | 145 | mmd_mask = mmd_mask >> 1; |
8ceee660 BH |
146 | mmd++; |
147 | } | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
5a6681e2 | 152 | bool ef4_mdio_links_ok(struct ef4_nic *efx, unsigned int mmd_mask) |
8ceee660 | 153 | { |
3273c2e8 BH |
154 | /* If the port is in loopback, then we should only consider a subset |
155 | * of mmd's */ | |
156 | if (LOOPBACK_INTERNAL(efx)) | |
dc8cfa55 | 157 | return true; |
e58f69f4 | 158 | else if (LOOPBACK_MASK(efx) & LOOPBACKS_WS) |
dc8cfa55 | 159 | return false; |
5a6681e2 | 160 | else if (ef4_phy_mode_disabled(efx->phy_mode)) |
f8b87c17 | 161 | return false; |
67797763 | 162 | else if (efx->loopback_mode == LOOPBACK_PHYXS) |
68e7f45e BH |
163 | mmd_mask &= ~(MDIO_DEVS_PHYXS | |
164 | MDIO_DEVS_PCS | | |
165 | MDIO_DEVS_PMAPMD | | |
166 | MDIO_DEVS_AN); | |
67797763 | 167 | else if (efx->loopback_mode == LOOPBACK_PCS) |
68e7f45e BH |
168 | mmd_mask &= ~(MDIO_DEVS_PCS | |
169 | MDIO_DEVS_PMAPMD | | |
170 | MDIO_DEVS_AN); | |
3273c2e8 | 171 | else if (efx->loopback_mode == LOOPBACK_PMAPMD) |
68e7f45e BH |
172 | mmd_mask &= ~(MDIO_DEVS_PMAPMD | |
173 | MDIO_DEVS_AN); | |
67797763 | 174 | |
68e7f45e | 175 | return mdio45_links_ok(&efx->mdio, mmd_mask); |
8ceee660 BH |
176 | } |
177 | ||
5a6681e2 | 178 | void ef4_mdio_transmit_disable(struct ef4_nic *efx) |
3273c2e8 | 179 | { |
5a6681e2 | 180 | ef4_mdio_set_flag(efx, MDIO_MMD_PMAPMD, |
68e7f45e BH |
181 | MDIO_PMA_TXDIS, MDIO_PMD_TXDIS_GLOBAL, |
182 | efx->phy_mode & PHY_MODE_TX_DISABLED); | |
3273c2e8 BH |
183 | } |
184 | ||
5a6681e2 | 185 | void ef4_mdio_phy_reconfigure(struct ef4_nic *efx) |
3273c2e8 | 186 | { |
5a6681e2 | 187 | ef4_mdio_set_flag(efx, MDIO_MMD_PMAPMD, |
68e7f45e BH |
188 | MDIO_CTRL1, MDIO_PMA_CTRL1_LOOPBACK, |
189 | efx->loopback_mode == LOOPBACK_PMAPMD); | |
5a6681e2 | 190 | ef4_mdio_set_flag(efx, MDIO_MMD_PCS, |
68e7f45e BH |
191 | MDIO_CTRL1, MDIO_PCS_CTRL1_LOOPBACK, |
192 | efx->loopback_mode == LOOPBACK_PCS); | |
5a6681e2 | 193 | ef4_mdio_set_flag(efx, MDIO_MMD_PHYXS, |
68e7f45e | 194 | MDIO_CTRL1, MDIO_PHYXS_CTRL1_LOOPBACK, |
e58f69f4 | 195 | efx->loopback_mode == LOOPBACK_PHYXS_WS); |
3273c2e8 BH |
196 | } |
197 | ||
5a6681e2 | 198 | static void ef4_mdio_set_mmd_lpower(struct ef4_nic *efx, |
68e7f45e | 199 | int lpower, int mmd) |
3e133c44 | 200 | { |
5a6681e2 | 201 | int stat = ef4_mdio_read(efx, mmd, MDIO_STAT1); |
3e133c44 | 202 | |
62776d03 | 203 | netif_vdbg(efx, drv, efx->net_dev, "Setting low power mode for MMD %d to %d\n", |
3e133c44 BH |
204 | mmd, lpower); |
205 | ||
68e7f45e | 206 | if (stat & MDIO_STAT1_LPOWERABLE) { |
5a6681e2 | 207 | ef4_mdio_set_flag(efx, mmd, MDIO_CTRL1, |
68e7f45e | 208 | MDIO_CTRL1_LPOWER, lpower); |
3e133c44 BH |
209 | } |
210 | } | |
211 | ||
5a6681e2 | 212 | void ef4_mdio_set_mmds_lpower(struct ef4_nic *efx, |
68e7f45e | 213 | int low_power, unsigned int mmd_mask) |
3e133c44 BH |
214 | { |
215 | int mmd = 0; | |
68e7f45e | 216 | mmd_mask &= ~MDIO_DEVS_AN; |
3e133c44 BH |
217 | while (mmd_mask) { |
218 | if (mmd_mask & 1) | |
5a6681e2 | 219 | ef4_mdio_set_mmd_lpower(efx, low_power, mmd); |
3e133c44 BH |
220 | mmd_mask = (mmd_mask >> 1); |
221 | mmd++; | |
222 | } | |
223 | } | |
224 | ||
04cc8cac | 225 | /** |
e938ed15 | 226 | * ef4_mdio_set_link_ksettings - Set (some of) the PHY settings over MDIO. |
8ceee660 | 227 | * @efx: Efx NIC |
e938ed15 | 228 | * @cmd: New settings |
8ceee660 | 229 | */ |
e938ed15 PR |
230 | int ef4_mdio_set_link_ksettings(struct ef4_nic *efx, |
231 | const struct ethtool_link_ksettings *cmd) | |
8ceee660 | 232 | { |
e938ed15 PR |
233 | struct ethtool_link_ksettings prev = { |
234 | .base.cmd = ETHTOOL_GLINKSETTINGS | |
235 | }; | |
236 | u32 prev_advertising, advertising; | |
237 | u32 prev_supported; | |
238 | ||
239 | efx->phy_op->get_link_ksettings(efx, &prev); | |
240 | ||
241 | ethtool_convert_link_mode_to_legacy_u32(&advertising, | |
242 | cmd->link_modes.advertising); | |
243 | ethtool_convert_link_mode_to_legacy_u32(&prev_advertising, | |
244 | prev.link_modes.advertising); | |
245 | ethtool_convert_link_mode_to_legacy_u32(&prev_supported, | |
246 | prev.link_modes.supported); | |
247 | ||
248 | if (advertising == prev_advertising && | |
249 | cmd->base.speed == prev.base.speed && | |
250 | cmd->base.duplex == prev.base.duplex && | |
251 | cmd->base.port == prev.base.port && | |
252 | cmd->base.autoneg == prev.base.autoneg) | |
8ceee660 | 253 | return 0; |
04cc8cac BH |
254 | |
255 | /* We can only change these settings for -T PHYs */ | |
e938ed15 | 256 | if (prev.base.port != PORT_TP || cmd->base.port != PORT_TP) |
04cc8cac BH |
257 | return -EINVAL; |
258 | ||
af4ad9bc | 259 | /* Check that PHY supports these settings */ |
e938ed15 PR |
260 | if (!cmd->base.autoneg || |
261 | (advertising | SUPPORTED_Autoneg) & ~prev_supported) | |
04cc8cac BH |
262 | return -EINVAL; |
263 | ||
e938ed15 | 264 | ef4_link_set_advertising(efx, advertising | ADVERTISED_Autoneg); |
5a6681e2 | 265 | ef4_mdio_an_reconfigure(efx); |
d3245b28 BH |
266 | return 0; |
267 | } | |
268 | ||
269 | /** | |
5a6681e2 | 270 | * ef4_mdio_an_reconfigure - Push advertising flags and restart autonegotiation |
d3245b28 BH |
271 | * @efx: Efx NIC |
272 | */ | |
5a6681e2 | 273 | void ef4_mdio_an_reconfigure(struct ef4_nic *efx) |
d3245b28 | 274 | { |
d3245b28 BH |
275 | int reg; |
276 | ||
277 | WARN_ON(!(efx->mdio.mmds & MDIO_DEVS_AN)); | |
fc2b5e67 BH |
278 | |
279 | /* Set up the base page */ | |
8fbca791 | 280 | reg = ADVERTISE_CSMA | ADVERTISE_RESV; |
d3245b28 BH |
281 | if (efx->link_advertising & ADVERTISED_Pause) |
282 | reg |= ADVERTISE_PAUSE_CAP; | |
283 | if (efx->link_advertising & ADVERTISED_Asym_Pause) | |
284 | reg |= ADVERTISE_PAUSE_ASYM; | |
5a6681e2 | 285 | ef4_mdio_write(efx, MDIO_MMD_AN, MDIO_AN_ADVERTISE, reg); |
fc2b5e67 | 286 | |
8fbca791 BH |
287 | /* Set up the (extended) next page */ |
288 | efx->phy_op->set_npage_adv(efx, efx->link_advertising); | |
fc2b5e67 BH |
289 | |
290 | /* Enable and restart AN */ | |
5a6681e2 | 291 | reg = ef4_mdio_read(efx, MDIO_MMD_AN, MDIO_CTRL1); |
8fbca791 | 292 | reg |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART | MDIO_AN_CTRL1_XNP; |
5a6681e2 | 293 | ef4_mdio_write(efx, MDIO_MMD_AN, MDIO_CTRL1, reg); |
04cc8cac BH |
294 | } |
295 | ||
5a6681e2 | 296 | u8 ef4_mdio_get_pause(struct ef4_nic *efx) |
04cc8cac | 297 | { |
5a6681e2 | 298 | BUILD_BUG_ON(EF4_FC_AUTO & (EF4_FC_RX | EF4_FC_TX)); |
04cc8cac | 299 | |
5a6681e2 | 300 | if (!(efx->wanted_fc & EF4_FC_AUTO)) |
04cc8cac | 301 | return efx->wanted_fc; |
18ea024f BH |
302 | |
303 | WARN_ON(!(efx->mdio.mmds & MDIO_DEVS_AN)); | |
304 | ||
305 | return mii_resolve_flowctrl_fdx( | |
306 | mii_advertise_flowctrl(efx->wanted_fc), | |
5a6681e2 | 307 | ef4_mdio_read(efx, MDIO_MMD_AN, MDIO_AN_LPA)); |
8ceee660 | 308 | } |
4f16c073 | 309 | |
5a6681e2 | 310 | int ef4_mdio_test_alive(struct ef4_nic *efx) |
4f16c073 BH |
311 | { |
312 | int rc; | |
313 | int devad = __ffs(efx->mdio.mmds); | |
314 | u16 physid1, physid2; | |
315 | ||
316 | mutex_lock(&efx->mac_lock); | |
317 | ||
5a6681e2 EC |
318 | physid1 = ef4_mdio_read(efx, devad, MDIO_DEVID1); |
319 | physid2 = ef4_mdio_read(efx, devad, MDIO_DEVID2); | |
4f16c073 BH |
320 | |
321 | if ((physid1 == 0x0000) || (physid1 == 0xffff) || | |
322 | (physid2 == 0x0000) || (physid2 == 0xffff)) { | |
62776d03 BH |
323 | netif_err(efx, hw, efx->net_dev, |
324 | "no MDIO PHY present with ID %d\n", efx->mdio.prtad); | |
4f16c073 BH |
325 | rc = -EINVAL; |
326 | } else { | |
5a6681e2 | 327 | rc = ef4_mdio_check_mmds(efx, efx->mdio.mmds); |
4f16c073 BH |
328 | } |
329 | ||
330 | mutex_unlock(&efx->mac_lock); | |
331 | return rc; | |
332 | } |