diff options
author | Ben Hutchings <bhutchings@solarflare.com> | 2008-04-27 15:55:59 +0400 |
---|---|---|
committer | Jeff Garzik <jgarzik@redhat.com> | 2008-04-29 09:42:43 +0400 |
commit | 8ceee660aacb29721e26f08e336c58dc4847d1bd (patch) | |
tree | 158122642e6f21fe85d072c50d6185a0d0cf6834 /drivers/net/sfc/mdio_10g.c | |
parent | 358c12953b88c5a06a57c33eb27c753b2e7934d1 (diff) | |
download | linux-8ceee660aacb29721e26f08e336c58dc4847d1bd.tar.xz |
New driver "sfc" for Solarstorm SFC4000 controller.
The driver supports the 10Xpress PHY and XFP modules on our reference
designs SFE4001 and SFE4002 and the SMC models SMC10GPCIe-XFP and
SMC10GPCIe-10BT.
Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Diffstat (limited to 'drivers/net/sfc/mdio_10g.c')
-rw-r--r-- | drivers/net/sfc/mdio_10g.c | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/drivers/net/sfc/mdio_10g.c b/drivers/net/sfc/mdio_10g.c new file mode 100644 index 000000000000..dc06bb0aa575 --- /dev/null +++ b/drivers/net/sfc/mdio_10g.c @@ -0,0 +1,282 @@ +/**************************************************************************** + * Driver for Solarflare Solarstorm network controllers and boards + * Copyright 2006-2008 Solarflare Communications Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ +/* + * Useful functions for working with MDIO clause 45 PHYs + */ +#include <linux/types.h> +#include <linux/ethtool.h> +#include <linux/delay.h> +#include "net_driver.h" +#include "mdio_10g.h" +#include "boards.h" + +int mdio_clause45_reset_mmd(struct efx_nic *port, int mmd, + int spins, int spintime) +{ + u32 ctrl; + int phy_id = port->mii.phy_id; + + /* Catch callers passing values in the wrong units (or just silly) */ + EFX_BUG_ON_PARANOID(spins * spintime >= 5000); + + mdio_clause45_write(port, phy_id, mmd, MDIO_MMDREG_CTRL1, + (1 << MDIO_MMDREG_CTRL1_RESET_LBN)); + /* Wait for the reset bit to clear. */ + do { + msleep(spintime); + ctrl = mdio_clause45_read(port, phy_id, mmd, MDIO_MMDREG_CTRL1); + spins--; + + } while (spins && (ctrl & (1 << MDIO_MMDREG_CTRL1_RESET_LBN))); + + return spins ? spins : -ETIMEDOUT; +} + +static int mdio_clause45_check_mmd(struct efx_nic *efx, int mmd, + int fault_fatal) +{ + int status; + int phy_id = efx->mii.phy_id; + + /* Read MMD STATUS2 to check it is responding. */ + status = mdio_clause45_read(efx, phy_id, mmd, MDIO_MMDREG_STAT2); + if (((status >> MDIO_MMDREG_STAT2_PRESENT_LBN) & + ((1 << MDIO_MMDREG_STAT2_PRESENT_WIDTH) - 1)) != + MDIO_MMDREG_STAT2_PRESENT_VAL) { + EFX_ERR(efx, "PHY MMD %d not responding.\n", mmd); + return -EIO; + } + + /* Read MMD STATUS 1 to check for fault. */ + status = mdio_clause45_read(efx, phy_id, mmd, MDIO_MMDREG_STAT1); + if ((status & (1 << MDIO_MMDREG_STAT1_FAULT_LBN)) != 0) { + if (fault_fatal) { + EFX_ERR(efx, "PHY MMD %d reporting fatal" + " fault: status %x\n", mmd, status); + return -EIO; + } else { + EFX_LOG(efx, "PHY MMD %d reporting status" + " %x (expected)\n", mmd, status); + } + } + return 0; +} + +/* This ought to be ridiculous overkill. We expect it to fail rarely */ +#define MDIO45_RESET_TIME 1000 /* ms */ +#define MDIO45_RESET_ITERS 100 + +int mdio_clause45_wait_reset_mmds(struct efx_nic *efx, + unsigned int mmd_mask) +{ + const int spintime = MDIO45_RESET_TIME / MDIO45_RESET_ITERS; + int tries = MDIO45_RESET_ITERS; + int rc = 0; + int in_reset; + + while (tries) { + int mask = mmd_mask; + int mmd = 0; + int stat; + in_reset = 0; + while (mask) { + if (mask & 1) { + stat = mdio_clause45_read(efx, + efx->mii.phy_id, + mmd, + MDIO_MMDREG_CTRL1); + if (stat < 0) { + EFX_ERR(efx, "failed to read status of" + " MMD %d\n", mmd); + return -EIO; + } + if (stat & (1 << MDIO_MMDREG_CTRL1_RESET_LBN)) + in_reset |= (1 << mmd); + } + mask = mask >> 1; + mmd++; + } + if (!in_reset) + break; + tries--; + msleep(spintime); + } + if (in_reset != 0) { + EFX_ERR(efx, "not all MMDs came out of reset in time." + " MMDs still in reset: %x\n", in_reset); + rc = -ETIMEDOUT; + } + return rc; +} + +int mdio_clause45_check_mmds(struct efx_nic *efx, + unsigned int mmd_mask, unsigned int fatal_mask) +{ + int devices, mmd = 0; + int probe_mmd; + + /* Historically we have probed the PHYXS to find out what devices are + * present,but that doesn't work so well if the PHYXS isn't expected + * to exist, if so just find the first item in the list supplied. */ + probe_mmd = (mmd_mask & MDIO_MMDREG_DEVS0_PHYXS) ? MDIO_MMD_PHYXS : + __ffs(mmd_mask); + devices = mdio_clause45_read(efx, efx->mii.phy_id, + probe_mmd, MDIO_MMDREG_DEVS0); + + /* Check all the expected MMDs are present */ + if (devices < 0) { + EFX_ERR(efx, "failed to read devices present\n"); + return -EIO; + } + if ((devices & mmd_mask) != mmd_mask) { + EFX_ERR(efx, "required MMDs not present: got %x, " + "wanted %x\n", devices, mmd_mask); + return -ENODEV; + } + EFX_TRACE(efx, "Devices present: %x\n", devices); + + /* Check all required MMDs are responding and happy. */ + while (mmd_mask) { + if (mmd_mask & 1) { + int fault_fatal = fatal_mask & 1; + if (mdio_clause45_check_mmd(efx, mmd, fault_fatal)) + return -EIO; + } + mmd_mask = mmd_mask >> 1; + fatal_mask = fatal_mask >> 1; + mmd++; + } + + return 0; +} + +int mdio_clause45_links_ok(struct efx_nic *efx, unsigned int mmd_mask) +{ + int phy_id = efx->mii.phy_id; + int status; + int ok = 1; + int mmd = 0; + int good; + + while (mmd_mask) { + if (mmd_mask & 1) { + /* Double reads because link state is latched, and a + * read moves the current state into the register */ + status = mdio_clause45_read(efx, phy_id, + mmd, MDIO_MMDREG_STAT1); + status = mdio_clause45_read(efx, phy_id, + mmd, MDIO_MMDREG_STAT1); + + good = status & (1 << MDIO_MMDREG_STAT1_LINK_LBN); + ok = ok && good; + } + mmd_mask = (mmd_mask >> 1); + mmd++; + } + return ok; +} + +/** + * mdio_clause45_get_settings - Read (some of) the PHY settings over MDIO. + * @efx: Efx NIC + * @ecmd: Buffer for settings + * + * On return the 'port', 'speed', 'supported' and 'advertising' fields of + * ecmd have been filled out based on the PMA type. + */ +void mdio_clause45_get_settings(struct efx_nic *efx, + struct ethtool_cmd *ecmd) +{ + int pma_type; + + /* If no PMA is present we are presumably talking something XAUI-ish + * like CX4. Which we report as FIBRE (see below) */ + if ((efx->phy_op->mmds & DEV_PRESENT_BIT(MDIO_MMD_PMAPMD)) == 0) { + ecmd->speed = SPEED_10000; + ecmd->port = PORT_FIBRE; + ecmd->supported = SUPPORTED_FIBRE; + ecmd->advertising = ADVERTISED_FIBRE; + return; + } + + pma_type = mdio_clause45_read(efx, efx->mii.phy_id, + MDIO_MMD_PMAPMD, MDIO_MMDREG_CTRL2); + pma_type &= MDIO_PMAPMD_CTRL2_TYPE_MASK; + + switch (pma_type) { + /* We represent CX4 as fibre in the absence of anything + better. */ + case MDIO_PMAPMD_CTRL2_10G_CX4: + ecmd->speed = SPEED_10000; + ecmd->port = PORT_FIBRE; + ecmd->supported = SUPPORTED_FIBRE; + ecmd->advertising = ADVERTISED_FIBRE; + break; + /* 10G Base-T */ + case MDIO_PMAPMD_CTRL2_10G_BT: + ecmd->speed = SPEED_10000; + ecmd->port = PORT_TP; + ecmd->supported = SUPPORTED_TP | SUPPORTED_10000baseT_Full; + ecmd->advertising = (ADVERTISED_FIBRE + | ADVERTISED_10000baseT_Full); + break; + case MDIO_PMAPMD_CTRL2_1G_BT: + ecmd->speed = SPEED_1000; + ecmd->port = PORT_TP; + ecmd->supported = SUPPORTED_TP | SUPPORTED_1000baseT_Full; + ecmd->advertising = (ADVERTISED_FIBRE + | ADVERTISED_1000baseT_Full); + break; + case MDIO_PMAPMD_CTRL2_100_BT: + ecmd->speed = SPEED_100; + ecmd->port = PORT_TP; + ecmd->supported = SUPPORTED_TP | SUPPORTED_100baseT_Full; + ecmd->advertising = (ADVERTISED_FIBRE + | ADVERTISED_100baseT_Full); + break; + case MDIO_PMAPMD_CTRL2_10_BT: + ecmd->speed = SPEED_10; + ecmd->port = PORT_TP; + ecmd->supported = SUPPORTED_TP | SUPPORTED_10baseT_Full; + ecmd->advertising = ADVERTISED_FIBRE | ADVERTISED_10baseT_Full; + break; + /* All the other defined modes are flavours of + * 10G optical */ + default: + ecmd->speed = SPEED_10000; + ecmd->port = PORT_FIBRE; + ecmd->supported = SUPPORTED_FIBRE; + ecmd->advertising = ADVERTISED_FIBRE; + break; + } +} + +/** + * mdio_clause45_set_settings - Set (some of) the PHY settings over MDIO. + * @efx: Efx NIC + * @ecmd: New settings + * + * Currently this just enforces that we are _not_ changing the + * 'port', 'speed', 'supported' or 'advertising' settings as these + * cannot be changed on any currently supported PHY. + */ +int mdio_clause45_set_settings(struct efx_nic *efx, + struct ethtool_cmd *ecmd) +{ + struct ethtool_cmd tmpcmd; + mdio_clause45_get_settings(efx, &tmpcmd); + /* None of the current PHYs support more than one mode + * of operation (and only 10GBT ever will), so keep things + * simple for now */ + if ((ecmd->speed == tmpcmd.speed) && (ecmd->port == tmpcmd.port) && + (ecmd->supported == tmpcmd.supported) && + (ecmd->advertising == tmpcmd.advertising)) + return 0; + return -EOPNOTSUPP; +} |