diff options
Diffstat (limited to 'drivers/net/sfc/mcdi_phy.c')
| -rw-r--r-- | drivers/net/sfc/mcdi_phy.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/drivers/net/sfc/mcdi_phy.c b/drivers/net/sfc/mcdi_phy.c new file mode 100644 index 00000000000..6c63ab0710a --- /dev/null +++ b/drivers/net/sfc/mcdi_phy.c | |||
| @@ -0,0 +1,754 @@ | |||
| 1 | /**************************************************************************** | ||
| 2 | * Driver for Solarflare Solarstorm network controllers and boards | ||
| 3 | * Copyright 2009-2010 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 | /* | ||
| 11 | * Driver for PHY related operations via MCDI. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/slab.h> | ||
| 15 | #include "efx.h" | ||
| 16 | #include "phy.h" | ||
| 17 | #include "mcdi.h" | ||
| 18 | #include "mcdi_pcol.h" | ||
| 19 | #include "nic.h" | ||
| 20 | #include "selftest.h" | ||
| 21 | |||
| 22 | struct efx_mcdi_phy_data { | ||
| 23 | u32 flags; | ||
| 24 | u32 type; | ||
| 25 | u32 supported_cap; | ||
| 26 | u32 channel; | ||
| 27 | u32 port; | ||
| 28 | u32 stats_mask; | ||
| 29 | u8 name[20]; | ||
| 30 | u32 media; | ||
| 31 | u32 mmd_mask; | ||
| 32 | u8 revision[20]; | ||
| 33 | u32 forced_cap; | ||
| 34 | }; | ||
| 35 | |||
| 36 | static int | ||
| 37 | efx_mcdi_get_phy_cfg(struct efx_nic *efx, struct efx_mcdi_phy_data *cfg) | ||
| 38 | { | ||
| 39 | u8 outbuf[MC_CMD_GET_PHY_CFG_OUT_LEN]; | ||
| 40 | size_t outlen; | ||
| 41 | int rc; | ||
| 42 | |||
| 43 | BUILD_BUG_ON(MC_CMD_GET_PHY_CFG_IN_LEN != 0); | ||
| 44 | BUILD_BUG_ON(MC_CMD_GET_PHY_CFG_OUT_NAME_LEN != sizeof(cfg->name)); | ||
| 45 | |||
| 46 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_PHY_CFG, NULL, 0, | ||
| 47 | outbuf, sizeof(outbuf), &outlen); | ||
| 48 | if (rc) | ||
| 49 | goto fail; | ||
| 50 | |||
| 51 | if (outlen < MC_CMD_GET_PHY_CFG_OUT_LEN) { | ||
| 52 | rc = -EIO; | ||
| 53 | goto fail; | ||
| 54 | } | ||
| 55 | |||
| 56 | cfg->flags = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_FLAGS); | ||
| 57 | cfg->type = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_TYPE); | ||
| 58 | cfg->supported_cap = | ||
| 59 | MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_SUPPORTED_CAP); | ||
| 60 | cfg->channel = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_CHANNEL); | ||
| 61 | cfg->port = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_PRT); | ||
| 62 | cfg->stats_mask = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_STATS_MASK); | ||
| 63 | memcpy(cfg->name, MCDI_PTR(outbuf, GET_PHY_CFG_OUT_NAME), | ||
| 64 | sizeof(cfg->name)); | ||
| 65 | cfg->media = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_MEDIA_TYPE); | ||
| 66 | cfg->mmd_mask = MCDI_DWORD(outbuf, GET_PHY_CFG_OUT_MMD_MASK); | ||
| 67 | memcpy(cfg->revision, MCDI_PTR(outbuf, GET_PHY_CFG_OUT_REVISION), | ||
| 68 | sizeof(cfg->revision)); | ||
| 69 | |||
| 70 | return 0; | ||
| 71 | |||
| 72 | fail: | ||
| 73 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); | ||
| 74 | return rc; | ||
| 75 | } | ||
| 76 | |||
| 77 | static int efx_mcdi_set_link(struct efx_nic *efx, u32 capabilities, | ||
| 78 | u32 flags, u32 loopback_mode, | ||
| 79 | u32 loopback_speed) | ||
| 80 | { | ||
| 81 | u8 inbuf[MC_CMD_SET_LINK_IN_LEN]; | ||
| 82 | int rc; | ||
| 83 | |||
| 84 | BUILD_BUG_ON(MC_CMD_SET_LINK_OUT_LEN != 0); | ||
| 85 | |||
| 86 | MCDI_SET_DWORD(inbuf, SET_LINK_IN_CAP, capabilities); | ||
| 87 | MCDI_SET_DWORD(inbuf, SET_LINK_IN_FLAGS, flags); | ||
| 88 | MCDI_SET_DWORD(inbuf, SET_LINK_IN_LOOPBACK_MODE, loopback_mode); | ||
| 89 | MCDI_SET_DWORD(inbuf, SET_LINK_IN_LOOPBACK_SPEED, loopback_speed); | ||
| 90 | |||
| 91 | rc = efx_mcdi_rpc(efx, MC_CMD_SET_LINK, inbuf, sizeof(inbuf), | ||
| 92 | NULL, 0, NULL); | ||
| 93 | if (rc) | ||
| 94 | goto fail; | ||
| 95 | |||
| 96 | return 0; | ||
| 97 | |||
| 98 | fail: | ||
| 99 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); | ||
| 100 | return rc; | ||
| 101 | } | ||
| 102 | |||
| 103 | static int efx_mcdi_loopback_modes(struct efx_nic *efx, u64 *loopback_modes) | ||
| 104 | { | ||
| 105 | u8 outbuf[MC_CMD_GET_LOOPBACK_MODES_OUT_LEN]; | ||
| 106 | size_t outlen; | ||
| 107 | int rc; | ||
| 108 | |||
| 109 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_LOOPBACK_MODES, NULL, 0, | ||
| 110 | outbuf, sizeof(outbuf), &outlen); | ||
| 111 | if (rc) | ||
| 112 | goto fail; | ||
| 113 | |||
| 114 | if (outlen < MC_CMD_GET_LOOPBACK_MODES_OUT_LEN) { | ||
| 115 | rc = -EIO; | ||
| 116 | goto fail; | ||
| 117 | } | ||
| 118 | |||
| 119 | *loopback_modes = MCDI_QWORD(outbuf, GET_LOOPBACK_MODES_SUGGESTED); | ||
| 120 | |||
| 121 | return 0; | ||
| 122 | |||
| 123 | fail: | ||
| 124 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); | ||
| 125 | return rc; | ||
| 126 | } | ||
| 127 | |||
| 128 | int efx_mcdi_mdio_read(struct efx_nic *efx, unsigned int bus, | ||
| 129 | unsigned int prtad, unsigned int devad, u16 addr, | ||
| 130 | u16 *value_out, u32 *status_out) | ||
| 131 | { | ||
| 132 | u8 inbuf[MC_CMD_MDIO_READ_IN_LEN]; | ||
| 133 | u8 outbuf[MC_CMD_MDIO_READ_OUT_LEN]; | ||
| 134 | size_t outlen; | ||
| 135 | int rc; | ||
| 136 | |||
| 137 | MCDI_SET_DWORD(inbuf, MDIO_READ_IN_BUS, bus); | ||
| 138 | MCDI_SET_DWORD(inbuf, MDIO_READ_IN_PRTAD, prtad); | ||
| 139 | MCDI_SET_DWORD(inbuf, MDIO_READ_IN_DEVAD, devad); | ||
| 140 | MCDI_SET_DWORD(inbuf, MDIO_READ_IN_ADDR, addr); | ||
| 141 | |||
| 142 | rc = efx_mcdi_rpc(efx, MC_CMD_MDIO_READ, inbuf, sizeof(inbuf), | ||
| 143 | outbuf, sizeof(outbuf), &outlen); | ||
| 144 | if (rc) | ||
| 145 | goto fail; | ||
| 146 | |||
| 147 | *value_out = (u16)MCDI_DWORD(outbuf, MDIO_READ_OUT_VALUE); | ||
| 148 | *status_out = MCDI_DWORD(outbuf, MDIO_READ_OUT_STATUS); | ||
| 149 | return 0; | ||
| 150 | |||
| 151 | fail: | ||
| 152 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); | ||
| 153 | return rc; | ||
| 154 | } | ||
| 155 | |||
| 156 | int efx_mcdi_mdio_write(struct efx_nic *efx, unsigned int bus, | ||
| 157 | unsigned int prtad, unsigned int devad, u16 addr, | ||
| 158 | u16 value, u32 *status_out) | ||
| 159 | { | ||
| 160 | u8 inbuf[MC_CMD_MDIO_WRITE_IN_LEN]; | ||
| 161 | u8 outbuf[MC_CMD_MDIO_WRITE_OUT_LEN]; | ||
| 162 | size_t outlen; | ||
| 163 | int rc; | ||
| 164 | |||
| 165 | MCDI_SET_DWORD(inbuf, MDIO_WRITE_IN_BUS, bus); | ||
| 166 | MCDI_SET_DWORD(inbuf, MDIO_WRITE_IN_PRTAD, prtad); | ||
| 167 | MCDI_SET_DWORD(inbuf, MDIO_WRITE_IN_DEVAD, devad); | ||
| 168 | MCDI_SET_DWORD(inbuf, MDIO_WRITE_IN_ADDR, addr); | ||
| 169 | MCDI_SET_DWORD(inbuf, MDIO_WRITE_IN_VALUE, value); | ||
| 170 | |||
| 171 | rc = efx_mcdi_rpc(efx, MC_CMD_MDIO_WRITE, inbuf, sizeof(inbuf), | ||
| 172 | outbuf, sizeof(outbuf), &outlen); | ||
| 173 | if (rc) | ||
| 174 | goto fail; | ||
| 175 | |||
| 176 | *status_out = MCDI_DWORD(outbuf, MDIO_WRITE_OUT_STATUS); | ||
| 177 | return 0; | ||
| 178 | |||
| 179 | fail: | ||
| 180 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); | ||
| 181 | return rc; | ||
| 182 | } | ||
| 183 | |||
| 184 | static u32 mcdi_to_ethtool_cap(u32 media, u32 cap) | ||
| 185 | { | ||
| 186 | u32 result = 0; | ||
| 187 | |||
| 188 | switch (media) { | ||
| 189 | case MC_CMD_MEDIA_KX4: | ||
| 190 | result |= SUPPORTED_Backplane; | ||
| 191 | if (cap & (1 << MC_CMD_PHY_CAP_1000FDX_LBN)) | ||
| 192 | result |= SUPPORTED_1000baseKX_Full; | ||
| 193 | if (cap & (1 << MC_CMD_PHY_CAP_10000FDX_LBN)) | ||
| 194 | result |= SUPPORTED_10000baseKX4_Full; | ||
| 195 | break; | ||
| 196 | |||
| 197 | case MC_CMD_MEDIA_XFP: | ||
| 198 | case MC_CMD_MEDIA_SFP_PLUS: | ||
| 199 | result |= SUPPORTED_FIBRE; | ||
| 200 | break; | ||
| 201 | |||
| 202 | case MC_CMD_MEDIA_BASE_T: | ||
| 203 | result |= SUPPORTED_TP; | ||
| 204 | if (cap & (1 << MC_CMD_PHY_CAP_10HDX_LBN)) | ||
| 205 | result |= SUPPORTED_10baseT_Half; | ||
| 206 | if (cap & (1 << MC_CMD_PHY_CAP_10FDX_LBN)) | ||
| 207 | result |= SUPPORTED_10baseT_Full; | ||
| 208 | if (cap & (1 << MC_CMD_PHY_CAP_100HDX_LBN)) | ||
| 209 | result |= SUPPORTED_100baseT_Half; | ||
| 210 | if (cap & (1 << MC_CMD_PHY_CAP_100FDX_LBN)) | ||
| 211 | result |= SUPPORTED_100baseT_Full; | ||
| 212 | if (cap & (1 << MC_CMD_PHY_CAP_1000HDX_LBN)) | ||
| 213 | result |= SUPPORTED_1000baseT_Half; | ||
| 214 | if (cap & (1 << MC_CMD_PHY_CAP_1000FDX_LBN)) | ||
| 215 | result |= SUPPORTED_1000baseT_Full; | ||
| 216 | if (cap & (1 << MC_CMD_PHY_CAP_10000FDX_LBN)) | ||
| 217 | result |= SUPPORTED_10000baseT_Full; | ||
| 218 | break; | ||
| 219 | } | ||
| 220 | |||
| 221 | if (cap & (1 << MC_CMD_PHY_CAP_PAUSE_LBN)) | ||
| 222 | result |= SUPPORTED_Pause; | ||
| 223 | if (cap & (1 << MC_CMD_PHY_CAP_ASYM_LBN)) | ||
| 224 | result |= SUPPORTED_Asym_Pause; | ||
| 225 | if (cap & (1 << MC_CMD_PHY_CAP_AN_LBN)) | ||
| 226 | result |= SUPPORTED_Autoneg; | ||
| 227 | |||
| 228 | return result; | ||
| 229 | } | ||
| 230 | |||
| 231 | static u32 ethtool_to_mcdi_cap(u32 cap) | ||
| 232 | { | ||
| 233 | u32 result = 0; | ||
| 234 | |||
| 235 | if (cap & SUPPORTED_10baseT_Half) | ||
| 236 | result |= (1 << MC_CMD_PHY_CAP_10HDX_LBN); | ||
| 237 | if (cap & SUPPORTED_10baseT_Full) | ||
| 238 | result |= (1 << MC_CMD_PHY_CAP_10FDX_LBN); | ||
| 239 | if (cap & SUPPORTED_100baseT_Half) | ||
| 240 | result |= (1 << MC_CMD_PHY_CAP_100HDX_LBN); | ||
| 241 | if (cap & SUPPORTED_100baseT_Full) | ||
| 242 | result |= (1 << MC_CMD_PHY_CAP_100FDX_LBN); | ||
| 243 | if (cap & SUPPORTED_1000baseT_Half) | ||
| 244 | result |= (1 << MC_CMD_PHY_CAP_1000HDX_LBN); | ||
| 245 | if (cap & (SUPPORTED_1000baseT_Full | SUPPORTED_1000baseKX_Full)) | ||
| 246 | result |= (1 << MC_CMD_PHY_CAP_1000FDX_LBN); | ||
| 247 | if (cap & (SUPPORTED_10000baseT_Full | SUPPORTED_10000baseKX4_Full)) | ||
| 248 | result |= (1 << MC_CMD_PHY_CAP_10000FDX_LBN); | ||
| 249 | if (cap & SUPPORTED_Pause) | ||
| 250 | result |= (1 << MC_CMD_PHY_CAP_PAUSE_LBN); | ||
| 251 | if (cap & SUPPORTED_Asym_Pause) | ||
| 252 | result |= (1 << MC_CMD_PHY_CAP_ASYM_LBN); | ||
| 253 | if (cap & SUPPORTED_Autoneg) | ||
| 254 | result |= (1 << MC_CMD_PHY_CAP_AN_LBN); | ||
| 255 | |||
| 256 | return result; | ||
| 257 | } | ||
| 258 | |||
| 259 | static u32 efx_get_mcdi_phy_flags(struct efx_nic *efx) | ||
| 260 | { | ||
| 261 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 262 | enum efx_phy_mode mode, supported; | ||
| 263 | u32 flags; | ||
| 264 | |||
| 265 | /* TODO: Advertise the capabilities supported by this PHY */ | ||
| 266 | supported = 0; | ||
| 267 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_TXDIS_LBN)) | ||
| 268 | supported |= PHY_MODE_TX_DISABLED; | ||
| 269 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_LOWPOWER_LBN)) | ||
| 270 | supported |= PHY_MODE_LOW_POWER; | ||
| 271 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_POWEROFF_LBN)) | ||
| 272 | supported |= PHY_MODE_OFF; | ||
| 273 | |||
| 274 | mode = efx->phy_mode & supported; | ||
| 275 | |||
| 276 | flags = 0; | ||
| 277 | if (mode & PHY_MODE_TX_DISABLED) | ||
| 278 | flags |= (1 << MC_CMD_SET_LINK_TXDIS_LBN); | ||
| 279 | if (mode & PHY_MODE_LOW_POWER) | ||
| 280 | flags |= (1 << MC_CMD_SET_LINK_LOWPOWER_LBN); | ||
| 281 | if (mode & PHY_MODE_OFF) | ||
| 282 | flags |= (1 << MC_CMD_SET_LINK_POWEROFF_LBN); | ||
| 283 | |||
| 284 | return flags; | ||
| 285 | } | ||
| 286 | |||
| 287 | static u32 mcdi_to_ethtool_media(u32 media) | ||
| 288 | { | ||
| 289 | switch (media) { | ||
| 290 | case MC_CMD_MEDIA_XAUI: | ||
| 291 | case MC_CMD_MEDIA_CX4: | ||
| 292 | case MC_CMD_MEDIA_KX4: | ||
| 293 | return PORT_OTHER; | ||
| 294 | |||
| 295 | case MC_CMD_MEDIA_XFP: | ||
| 296 | case MC_CMD_MEDIA_SFP_PLUS: | ||
| 297 | return PORT_FIBRE; | ||
| 298 | |||
| 299 | case MC_CMD_MEDIA_BASE_T: | ||
| 300 | return PORT_TP; | ||
| 301 | |||
| 302 | default: | ||
| 303 | return PORT_OTHER; | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | static int efx_mcdi_phy_probe(struct efx_nic *efx) | ||
| 308 | { | ||
| 309 | struct efx_mcdi_phy_data *phy_data; | ||
| 310 | u8 outbuf[MC_CMD_GET_LINK_OUT_LEN]; | ||
| 311 | u32 caps; | ||
| 312 | int rc; | ||
| 313 | |||
| 314 | /* Initialise and populate phy_data */ | ||
| 315 | phy_data = kzalloc(sizeof(*phy_data), GFP_KERNEL); | ||
| 316 | if (phy_data == NULL) | ||
| 317 | return -ENOMEM; | ||
| 318 | |||
| 319 | rc = efx_mcdi_get_phy_cfg(efx, phy_data); | ||
| 320 | if (rc != 0) | ||
| 321 | goto fail; | ||
| 322 | |||
| 323 | /* Read initial link advertisement */ | ||
| 324 | BUILD_BUG_ON(MC_CMD_GET_LINK_IN_LEN != 0); | ||
| 325 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_LINK, NULL, 0, | ||
| 326 | outbuf, sizeof(outbuf), NULL); | ||
| 327 | if (rc) | ||
| 328 | goto fail; | ||
| 329 | |||
| 330 | /* Fill out nic state */ | ||
| 331 | efx->phy_data = phy_data; | ||
| 332 | efx->phy_type = phy_data->type; | ||
| 333 | |||
| 334 | efx->mdio_bus = phy_data->channel; | ||
| 335 | efx->mdio.prtad = phy_data->port; | ||
| 336 | efx->mdio.mmds = phy_data->mmd_mask & ~(1 << MC_CMD_MMD_CLAUSE22); | ||
| 337 | efx->mdio.mode_support = 0; | ||
| 338 | if (phy_data->mmd_mask & (1 << MC_CMD_MMD_CLAUSE22)) | ||
| 339 | efx->mdio.mode_support |= MDIO_SUPPORTS_C22; | ||
| 340 | if (phy_data->mmd_mask & ~(1 << MC_CMD_MMD_CLAUSE22)) | ||
| 341 | efx->mdio.mode_support |= MDIO_SUPPORTS_C45 | MDIO_EMULATE_C22; | ||
| 342 | |||
| 343 | caps = MCDI_DWORD(outbuf, GET_LINK_OUT_CAP); | ||
| 344 | if (caps & (1 << MC_CMD_PHY_CAP_AN_LBN)) | ||
| 345 | efx->link_advertising = | ||
| 346 | mcdi_to_ethtool_cap(phy_data->media, caps); | ||
| 347 | else | ||
| 348 | phy_data->forced_cap = caps; | ||
| 349 | |||
| 350 | /* Assert that we can map efx -> mcdi loopback modes */ | ||
| 351 | BUILD_BUG_ON(LOOPBACK_NONE != MC_CMD_LOOPBACK_NONE); | ||
| 352 | BUILD_BUG_ON(LOOPBACK_DATA != MC_CMD_LOOPBACK_DATA); | ||
| 353 | BUILD_BUG_ON(LOOPBACK_GMAC != MC_CMD_LOOPBACK_GMAC); | ||
| 354 | BUILD_BUG_ON(LOOPBACK_XGMII != MC_CMD_LOOPBACK_XGMII); | ||
| 355 | BUILD_BUG_ON(LOOPBACK_XGXS != MC_CMD_LOOPBACK_XGXS); | ||
| 356 | BUILD_BUG_ON(LOOPBACK_XAUI != MC_CMD_LOOPBACK_XAUI); | ||
| 357 | BUILD_BUG_ON(LOOPBACK_GMII != MC_CMD_LOOPBACK_GMII); | ||
| 358 | BUILD_BUG_ON(LOOPBACK_SGMII != MC_CMD_LOOPBACK_SGMII); | ||
| 359 | BUILD_BUG_ON(LOOPBACK_XGBR != MC_CMD_LOOPBACK_XGBR); | ||
| 360 | BUILD_BUG_ON(LOOPBACK_XFI != MC_CMD_LOOPBACK_XFI); | ||
| 361 | BUILD_BUG_ON(LOOPBACK_XAUI_FAR != MC_CMD_LOOPBACK_XAUI_FAR); | ||
| 362 | BUILD_BUG_ON(LOOPBACK_GMII_FAR != MC_CMD_LOOPBACK_GMII_FAR); | ||
| 363 | BUILD_BUG_ON(LOOPBACK_SGMII_FAR != MC_CMD_LOOPBACK_SGMII_FAR); | ||
| 364 | BUILD_BUG_ON(LOOPBACK_XFI_FAR != MC_CMD_LOOPBACK_XFI_FAR); | ||
| 365 | BUILD_BUG_ON(LOOPBACK_GPHY != MC_CMD_LOOPBACK_GPHY); | ||
| 366 | BUILD_BUG_ON(LOOPBACK_PHYXS != MC_CMD_LOOPBACK_PHYXS); | ||
| 367 | BUILD_BUG_ON(LOOPBACK_PCS != MC_CMD_LOOPBACK_PCS); | ||
| 368 | BUILD_BUG_ON(LOOPBACK_PMAPMD != MC_CMD_LOOPBACK_PMAPMD); | ||
| 369 | BUILD_BUG_ON(LOOPBACK_XPORT != MC_CMD_LOOPBACK_XPORT); | ||
| 370 | BUILD_BUG_ON(LOOPBACK_XGMII_WS != MC_CMD_LOOPBACK_XGMII_WS); | ||
| 371 | BUILD_BUG_ON(LOOPBACK_XAUI_WS != MC_CMD_LOOPBACK_XAUI_WS); | ||
| 372 | BUILD_BUG_ON(LOOPBACK_XAUI_WS_FAR != MC_CMD_LOOPBACK_XAUI_WS_FAR); | ||
| 373 | BUILD_BUG_ON(LOOPBACK_XAUI_WS_NEAR != MC_CMD_LOOPBACK_XAUI_WS_NEAR); | ||
| 374 | BUILD_BUG_ON(LOOPBACK_GMII_WS != MC_CMD_LOOPBACK_GMII_WS); | ||
| 375 | BUILD_BUG_ON(LOOPBACK_XFI_WS != MC_CMD_LOOPBACK_XFI_WS); | ||
| 376 | BUILD_BUG_ON(LOOPBACK_XFI_WS_FAR != MC_CMD_LOOPBACK_XFI_WS_FAR); | ||
| 377 | BUILD_BUG_ON(LOOPBACK_PHYXS_WS != MC_CMD_LOOPBACK_PHYXS_WS); | ||
| 378 | |||
| 379 | rc = efx_mcdi_loopback_modes(efx, &efx->loopback_modes); | ||
| 380 | if (rc != 0) | ||
| 381 | goto fail; | ||
| 382 | /* The MC indicates that LOOPBACK_NONE is a valid loopback mode, | ||
| 383 | * but by convention we don't */ | ||
| 384 | efx->loopback_modes &= ~(1 << LOOPBACK_NONE); | ||
| 385 | |||
| 386 | /* Set the initial link mode */ | ||
| 387 | efx_mcdi_phy_decode_link( | ||
| 388 | efx, &efx->link_state, | ||
| 389 | MCDI_DWORD(outbuf, GET_LINK_OUT_LINK_SPEED), | ||
| 390 | MCDI_DWORD(outbuf, GET_LINK_OUT_FLAGS), | ||
| 391 | MCDI_DWORD(outbuf, GET_LINK_OUT_FCNTL)); | ||
| 392 | |||
| 393 | /* Default to Autonegotiated flow control if the PHY supports it */ | ||
| 394 | efx->wanted_fc = EFX_FC_RX | EFX_FC_TX; | ||
| 395 | if (phy_data->supported_cap & (1 << MC_CMD_PHY_CAP_AN_LBN)) | ||
| 396 | efx->wanted_fc |= EFX_FC_AUTO; | ||
| 397 | efx_link_set_wanted_fc(efx, efx->wanted_fc); | ||
| 398 | |||
| 399 | return 0; | ||
| 400 | |||
| 401 | fail: | ||
| 402 | kfree(phy_data); | ||
| 403 | return rc; | ||
| 404 | } | ||
| 405 | |||
| 406 | int efx_mcdi_phy_reconfigure(struct efx_nic *efx) | ||
| 407 | { | ||
| 408 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 409 | u32 caps = (efx->link_advertising ? | ||
| 410 | ethtool_to_mcdi_cap(efx->link_advertising) : | ||
| 411 | phy_cfg->forced_cap); | ||
| 412 | |||
| 413 | return efx_mcdi_set_link(efx, caps, efx_get_mcdi_phy_flags(efx), | ||
| 414 | efx->loopback_mode, 0); | ||
| 415 | } | ||
| 416 | |||
| 417 | void efx_mcdi_phy_decode_link(struct efx_nic *efx, | ||
| 418 | struct efx_link_state *link_state, | ||
| 419 | u32 speed, u32 flags, u32 fcntl) | ||
| 420 | { | ||
| 421 | switch (fcntl) { | ||
| 422 | case MC_CMD_FCNTL_AUTO: | ||
| 423 | WARN_ON(1); /* This is not a link mode */ | ||
| 424 | link_state->fc = EFX_FC_AUTO | EFX_FC_TX | EFX_FC_RX; | ||
| 425 | break; | ||
| 426 | case MC_CMD_FCNTL_BIDIR: | ||
| 427 | link_state->fc = EFX_FC_TX | EFX_FC_RX; | ||
| 428 | break; | ||
| 429 | case MC_CMD_FCNTL_RESPOND: | ||
| 430 | link_state->fc = EFX_FC_RX; | ||
| 431 | break; | ||
| 432 | default: | ||
| 433 | WARN_ON(1); | ||
| 434 | case MC_CMD_FCNTL_OFF: | ||
| 435 | link_state->fc = 0; | ||
| 436 | break; | ||
| 437 | } | ||
| 438 | |||
| 439 | link_state->up = !!(flags & (1 << MC_CMD_GET_LINK_LINK_UP_LBN)); | ||
| 440 | link_state->fd = !!(flags & (1 << MC_CMD_GET_LINK_FULL_DUPLEX_LBN)); | ||
| 441 | link_state->speed = speed; | ||
| 442 | } | ||
| 443 | |||
| 444 | /* Verify that the forced flow control settings (!EFX_FC_AUTO) are | ||
| 445 | * supported by the link partner. Warn the user if this isn't the case | ||
| 446 | */ | ||
| 447 | void efx_mcdi_phy_check_fcntl(struct efx_nic *efx, u32 lpa) | ||
| 448 | { | ||
| 449 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 450 | u32 rmtadv; | ||
| 451 | |||
| 452 | /* The link partner capabilities are only relevant if the | ||
| 453 | * link supports flow control autonegotiation */ | ||
| 454 | if (~phy_cfg->supported_cap & (1 << MC_CMD_PHY_CAP_AN_LBN)) | ||
| 455 | return; | ||
| 456 | |||
| 457 | /* If flow control autoneg is supported and enabled, then fine */ | ||
| 458 | if (efx->wanted_fc & EFX_FC_AUTO) | ||
| 459 | return; | ||
| 460 | |||
| 461 | rmtadv = 0; | ||
| 462 | if (lpa & (1 << MC_CMD_PHY_CAP_PAUSE_LBN)) | ||
| 463 | rmtadv |= ADVERTISED_Pause; | ||
| 464 | if (lpa & (1 << MC_CMD_PHY_CAP_ASYM_LBN)) | ||
| 465 | rmtadv |= ADVERTISED_Asym_Pause; | ||
| 466 | |||
| 467 | if ((efx->wanted_fc & EFX_FC_TX) && rmtadv == ADVERTISED_Asym_Pause) | ||
| 468 | netif_err(efx, link, efx->net_dev, | ||
| 469 | "warning: link partner doesn't support pause frames"); | ||
| 470 | } | ||
| 471 | |||
| 472 | static bool efx_mcdi_phy_poll(struct efx_nic *efx) | ||
| 473 | { | ||
| 474 | struct efx_link_state old_state = efx->link_state; | ||
| 475 | u8 outbuf[MC_CMD_GET_LINK_OUT_LEN]; | ||
| 476 | int rc; | ||
| 477 | |||
| 478 | WARN_ON(!mutex_is_locked(&efx->mac_lock)); | ||
| 479 | |||
| 480 | BUILD_BUG_ON(MC_CMD_GET_LINK_IN_LEN != 0); | ||
| 481 | |||
| 482 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_LINK, NULL, 0, | ||
| 483 | outbuf, sizeof(outbuf), NULL); | ||
| 484 | if (rc) { | ||
| 485 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", | ||
| 486 | __func__, rc); | ||
| 487 | efx->link_state.up = false; | ||
| 488 | } else { | ||
| 489 | efx_mcdi_phy_decode_link( | ||
| 490 | efx, &efx->link_state, | ||
| 491 | MCDI_DWORD(outbuf, GET_LINK_OUT_LINK_SPEED), | ||
| 492 | MCDI_DWORD(outbuf, GET_LINK_OUT_FLAGS), | ||
| 493 | MCDI_DWORD(outbuf, GET_LINK_OUT_FCNTL)); | ||
| 494 | } | ||
| 495 | |||
| 496 | return !efx_link_state_equal(&efx->link_state, &old_state); | ||
| 497 | } | ||
| 498 | |||
| 499 | static void efx_mcdi_phy_remove(struct efx_nic *efx) | ||
| 500 | { | ||
| 501 | struct efx_mcdi_phy_data *phy_data = efx->phy_data; | ||
| 502 | |||
| 503 | efx->phy_data = NULL; | ||
| 504 | kfree(phy_data); | ||
| 505 | } | ||
| 506 | |||
| 507 | static void efx_mcdi_phy_get_settings(struct efx_nic *efx, struct ethtool_cmd *ecmd) | ||
| 508 | { | ||
| 509 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 510 | u8 outbuf[MC_CMD_GET_LINK_OUT_LEN]; | ||
| 511 | int rc; | ||
| 512 | |||
| 513 | ecmd->supported = | ||
| 514 | mcdi_to_ethtool_cap(phy_cfg->media, phy_cfg->supported_cap); | ||
| 515 | ecmd->advertising = efx->link_advertising; | ||
| 516 | ethtool_cmd_speed_set(ecmd, efx->link_state.speed); | ||
| 517 | ecmd->duplex = efx->link_state.fd; | ||
| 518 | ecmd->port = mcdi_to_ethtool_media(phy_cfg->media); | ||
| 519 | ecmd->phy_address = phy_cfg->port; | ||
| 520 | ecmd->transceiver = XCVR_INTERNAL; | ||
| 521 | ecmd->autoneg = !!(efx->link_advertising & ADVERTISED_Autoneg); | ||
| 522 | ecmd->mdio_support = (efx->mdio.mode_support & | ||
| 523 | (MDIO_SUPPORTS_C45 | MDIO_SUPPORTS_C22)); | ||
| 524 | |||
| 525 | BUILD_BUG_ON(MC_CMD_GET_LINK_IN_LEN != 0); | ||
| 526 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_LINK, NULL, 0, | ||
| 527 | outbuf, sizeof(outbuf), NULL); | ||
| 528 | if (rc) { | ||
| 529 | netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", | ||
| 530 | __func__, rc); | ||
| 531 | return; | ||
| 532 | } | ||
| 533 | ecmd->lp_advertising = | ||
| 534 | mcdi_to_ethtool_cap(phy_cfg->media, | ||
| 535 | MCDI_DWORD(outbuf, GET_LINK_OUT_LP_CAP)); | ||
| 536 | } | ||
| 537 | |||
| 538 | static int efx_mcdi_phy_set_settings(struct efx_nic *efx, struct ethtool_cmd *ecmd) | ||
| 539 | { | ||
| 540 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 541 | u32 caps; | ||
| 542 | int rc; | ||
| 543 | |||
| 544 | if (ecmd->autoneg) { | ||
| 545 | caps = (ethtool_to_mcdi_cap(ecmd->advertising) | | ||
| 546 | 1 << MC_CMD_PHY_CAP_AN_LBN); | ||
| 547 | } else if (ecmd->duplex) { | ||
| 548 | switch (ethtool_cmd_speed(ecmd)) { | ||
| 549 | case 10: caps = 1 << MC_CMD_PHY_CAP_10FDX_LBN; break; | ||
| 550 | case 100: caps = 1 << MC_CMD_PHY_CAP_100FDX_LBN; break; | ||
| 551 | case 1000: caps = 1 << MC_CMD_PHY_CAP_1000FDX_LBN; break; | ||
| 552 | case 10000: caps = 1 << MC_CMD_PHY_CAP_10000FDX_LBN; break; | ||
| 553 | default: return -EINVAL; | ||
| 554 | } | ||
| 555 | } else { | ||
| 556 | switch (ethtool_cmd_speed(ecmd)) { | ||
| 557 | case 10: caps = 1 << MC_CMD_PHY_CAP_10HDX_LBN; break; | ||
| 558 | case 100: caps = 1 << MC_CMD_PHY_CAP_100HDX_LBN; break; | ||
| 559 | case 1000: caps = 1 << MC_CMD_PHY_CAP_1000HDX_LBN; break; | ||
| 560 | default: return -EINVAL; | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | rc = efx_mcdi_set_link(efx, caps, efx_get_mcdi_phy_flags(efx), | ||
| 565 | efx->loopback_mode, 0); | ||
| 566 | if (rc) | ||
| 567 | return rc; | ||
| 568 | |||
| 569 | if (ecmd->autoneg) { | ||
| 570 | efx_link_set_advertising( | ||
| 571 | efx, ecmd->advertising | ADVERTISED_Autoneg); | ||
| 572 | phy_cfg->forced_cap = 0; | ||
| 573 | } else { | ||
| 574 | efx_link_set_advertising(efx, 0); | ||
| 575 | phy_cfg->forced_cap = caps; | ||
| 576 | } | ||
| 577 | return 0; | ||
| 578 | } | ||
| 579 | |||
| 580 | static int efx_mcdi_phy_test_alive(struct efx_nic *efx) | ||
| 581 | { | ||
| 582 | u8 outbuf[MC_CMD_GET_PHY_STATE_OUT_LEN]; | ||
| 583 | size_t outlen; | ||
| 584 | int rc; | ||
| 585 | |||
| 586 | BUILD_BUG_ON(MC_CMD_GET_PHY_STATE_IN_LEN != 0); | ||
| 587 | |||
| 588 | rc = efx_mcdi_rpc(efx, MC_CMD_GET_PHY_STATE, NULL, 0, | ||
| 589 | outbuf, sizeof(outbuf), &outlen); | ||
| 590 | if (rc) | ||
| 591 | return rc; | ||
| 592 | |||
| 593 | if (outlen < MC_CMD_GET_PHY_STATE_OUT_LEN) | ||
| 594 | return -EIO; | ||
| 595 | if (MCDI_DWORD(outbuf, GET_PHY_STATE_STATE) != MC_CMD_PHY_STATE_OK) | ||
| 596 | return -EINVAL; | ||
| 597 | |||
| 598 | return 0; | ||
| 599 | } | ||
| 600 | |||
| 601 | static const char *const mcdi_sft9001_cable_diag_names[] = { | ||
| 602 | "cable.pairA.length", | ||
| 603 | "cable.pairB.length", | ||
| 604 | "cable.pairC.length", | ||
| 605 | "cable.pairD.length", | ||
| 606 | "cable.pairA.status", | ||
| 607 | "cable.pairB.status", | ||
| 608 | "cable.pairC.status", | ||
| 609 | "cable.pairD.status", | ||
| 610 | }; | ||
| 611 | |||
| 612 | static int efx_mcdi_bist(struct efx_nic *efx, unsigned int bist_mode, | ||
| 613 | int *results) | ||
| 614 | { | ||
| 615 | unsigned int retry, i, count = 0; | ||
| 616 | size_t outlen; | ||
| 617 | u32 status; | ||
| 618 | u8 *buf, *ptr; | ||
| 619 | int rc; | ||
| 620 | |||
| 621 | buf = kzalloc(0x100, GFP_KERNEL); | ||
| 622 | if (buf == NULL) | ||
| 623 | return -ENOMEM; | ||
| 624 | |||
| 625 | BUILD_BUG_ON(MC_CMD_START_BIST_OUT_LEN != 0); | ||
| 626 | MCDI_SET_DWORD(buf, START_BIST_IN_TYPE, bist_mode); | ||
| 627 | rc = efx_mcdi_rpc(efx, MC_CMD_START_BIST, buf, MC_CMD_START_BIST_IN_LEN, | ||
| 628 | NULL, 0, NULL); | ||
| 629 | if (rc) | ||
| 630 | goto out; | ||
| 631 | |||
| 632 | /* Wait up to 10s for BIST to finish */ | ||
| 633 | for (retry = 0; retry < 100; ++retry) { | ||
| 634 | BUILD_BUG_ON(MC_CMD_POLL_BIST_IN_LEN != 0); | ||
| 635 | rc = efx_mcdi_rpc(efx, MC_CMD_POLL_BIST, NULL, 0, | ||
| 636 | buf, 0x100, &outlen); | ||
| 637 | if (rc) | ||
| 638 | goto out; | ||
| 639 | |||
| 640 | status = MCDI_DWORD(buf, POLL_BIST_OUT_RESULT); | ||
| 641 | if (status != MC_CMD_POLL_BIST_RUNNING) | ||
| 642 | goto finished; | ||
| 643 | |||
| 644 | msleep(100); | ||
| 645 | } | ||
| 646 | |||
| 647 | rc = -ETIMEDOUT; | ||
| 648 | goto out; | ||
| 649 | |||
| 650 | finished: | ||
| 651 | results[count++] = (status == MC_CMD_POLL_BIST_PASSED) ? 1 : -1; | ||
| 652 | |||
| 653 | /* SFT9001 specific cable diagnostics output */ | ||
| 654 | if (efx->phy_type == PHY_TYPE_SFT9001B && | ||
| 655 | (bist_mode == MC_CMD_PHY_BIST_CABLE_SHORT || | ||
| 656 | bist_mode == MC_CMD_PHY_BIST_CABLE_LONG)) { | ||
| 657 | ptr = MCDI_PTR(buf, POLL_BIST_OUT_SFT9001_CABLE_LENGTH_A); | ||
| 658 | if (status == MC_CMD_POLL_BIST_PASSED && | ||
| 659 | outlen >= MC_CMD_POLL_BIST_OUT_SFT9001_LEN) { | ||
| 660 | for (i = 0; i < 8; i++) { | ||
| 661 | results[count + i] = | ||
| 662 | EFX_DWORD_FIELD(((efx_dword_t *)ptr)[i], | ||
| 663 | EFX_DWORD_0); | ||
| 664 | } | ||
| 665 | } | ||
| 666 | count += 8; | ||
| 667 | } | ||
| 668 | rc = count; | ||
| 669 | |||
| 670 | out: | ||
| 671 | kfree(buf); | ||
| 672 | |||
| 673 | return rc; | ||
| 674 | } | ||
| 675 | |||
| 676 | static int efx_mcdi_phy_run_tests(struct efx_nic *efx, int *results, | ||
| 677 | unsigned flags) | ||
| 678 | { | ||
| 679 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 680 | u32 mode; | ||
| 681 | int rc; | ||
| 682 | |||
| 683 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_BIST_LBN)) { | ||
| 684 | rc = efx_mcdi_bist(efx, MC_CMD_PHY_BIST, results); | ||
| 685 | if (rc < 0) | ||
| 686 | return rc; | ||
| 687 | |||
| 688 | results += rc; | ||
| 689 | } | ||
| 690 | |||
| 691 | /* If we support both LONG and SHORT, then run each in response to | ||
| 692 | * break or not. Otherwise, run the one we support */ | ||
| 693 | mode = 0; | ||
| 694 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_BIST_CABLE_SHORT_LBN)) { | ||
| 695 | if ((flags & ETH_TEST_FL_OFFLINE) && | ||
| 696 | (phy_cfg->flags & | ||
| 697 | (1 << MC_CMD_GET_PHY_CFG_BIST_CABLE_LONG_LBN))) | ||
| 698 | mode = MC_CMD_PHY_BIST_CABLE_LONG; | ||
| 699 | else | ||
| 700 | mode = MC_CMD_PHY_BIST_CABLE_SHORT; | ||
| 701 | } else if (phy_cfg->flags & | ||
| 702 | (1 << MC_CMD_GET_PHY_CFG_BIST_CABLE_LONG_LBN)) | ||
| 703 | mode = MC_CMD_PHY_BIST_CABLE_LONG; | ||
| 704 | |||
| 705 | if (mode != 0) { | ||
| 706 | rc = efx_mcdi_bist(efx, mode, results); | ||
| 707 | if (rc < 0) | ||
| 708 | return rc; | ||
| 709 | results += rc; | ||
| 710 | } | ||
| 711 | |||
| 712 | return 0; | ||
| 713 | } | ||
| 714 | |||
| 715 | static const char *efx_mcdi_phy_test_name(struct efx_nic *efx, | ||
| 716 | unsigned int index) | ||
| 717 | { | ||
| 718 | struct efx_mcdi_phy_data *phy_cfg = efx->phy_data; | ||
| 719 | |||
| 720 | if (phy_cfg->flags & (1 << MC_CMD_GET_PHY_CFG_BIST_LBN)) { | ||
| 721 | if (index == 0) | ||
| 722 | return "bist"; | ||
| 723 | --index; | ||
| 724 | } | ||
| 725 | |||
| 726 | if (phy_cfg->flags & ((1 << MC_CMD_GET_PHY_CFG_BIST_CABLE_SHORT_LBN) | | ||
| 727 | (1 << MC_CMD_GET_PHY_CFG_BIST_CABLE_LONG_LBN))) { | ||
| 728 | if (index == 0) | ||
| 729 | return "cable"; | ||
| 730 | --index; | ||
| 731 | |||
| 732 | if (efx->phy_type == PHY_TYPE_SFT9001B) { | ||
| 733 | if (index < ARRAY_SIZE(mcdi_sft9001_cable_diag_names)) | ||
| 734 | return mcdi_sft9001_cable_diag_names[index]; | ||
| 735 | index -= ARRAY_SIZE(mcdi_sft9001_cable_diag_names); | ||
| 736 | } | ||
| 737 | } | ||
| 738 | |||
| 739 | return NULL; | ||
| 740 | } | ||
| 741 | |||
| 742 | const struct efx_phy_operations efx_mcdi_phy_ops = { | ||
| 743 | .probe = efx_mcdi_phy_probe, | ||
| 744 | .init = efx_port_dummy_op_int, | ||
| 745 | .reconfigure = efx_mcdi_phy_reconfigure, | ||
| 746 | .poll = efx_mcdi_phy_poll, | ||
| 747 | .fini = efx_port_dummy_op_void, | ||
| 748 | .remove = efx_mcdi_phy_remove, | ||
| 749 | .get_settings = efx_mcdi_phy_get_settings, | ||
| 750 | .set_settings = efx_mcdi_phy_set_settings, | ||
| 751 | .test_alive = efx_mcdi_phy_test_alive, | ||
| 752 | .run_tests = efx_mcdi_phy_run_tests, | ||
| 753 | .test_name = efx_mcdi_phy_test_name, | ||
| 754 | }; | ||
