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