1
linux/drivers/net/sfc/mdio_10g.c
Ben Hutchings 8ceee660aa 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>
2008-04-29 01:42:43 -04:00

283 lines
7.8 KiB
C

/****************************************************************************
* 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;
}