| /* |
| * This file is part of the Chelsio T3 Ethernet driver. |
| * |
| * Copyright (C) 2009 Chelsio Communications. All rights reserved. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this |
| * release for licensing terms and conditions. |
| */ |
| |
| #include "common.h" |
| |
| /* TN1010 PHY specific registers. */ |
| enum { |
| TN1010_VEND1_STAT = 1, |
| }; |
| |
| /* IEEE auto-negotiation 10GBASE-T registers */ |
| enum { |
| ANEG_ADVER = 16, |
| ANEG_LPA = 19, |
| ANEG_10G_CTRL = 32, |
| ANEG_10G_STAT = 33 |
| }; |
| |
| #define ADVERTISE_ENPAGE (1 << 12) |
| #define ADVERTISE_10000FULL (1 << 12) |
| #define ADVERTISE_LOOP_TIMING (1 << 0) |
| |
| /* vendor specific status register fields */ |
| #define F_XS_LANE_ALIGN_STAT (1 << 0) |
| #define F_PCS_BLK_LOCK (1 << 1) |
| #define F_PMD_SIGNAL_OK (1 << 2) |
| #define F_LINK_STAT (1 << 3) |
| #define F_ANEG_SPEED_1G (1 << 4) |
| #define F_ANEG_MASTER (1 << 5) |
| |
| #define S_ANEG_STAT 6 |
| #define M_ANEG_STAT 0x3 |
| #define G_ANEG_STAT(x) (((x) >> S_ANEG_STAT) & M_ANEG_STAT) |
| |
| enum { /* autonegotiation status */ |
| ANEG_IN_PROGR = 0, |
| ANEG_COMPLETE = 1, |
| ANEG_FAILED = 3 |
| }; |
| |
| /* |
| * Reset the PHY. May take up to 500ms to complete. |
| */ |
| static int tn1010_reset(struct cphy *phy, int wait) |
| { |
| int err = t3_phy_reset(phy, MDIO_DEV_PMA_PMD, wait); |
| msleep(500); |
| return err; |
| } |
| |
| static int tn1010_power_down(struct cphy *phy, int enable) |
| { |
| return t3_mdio_change_bits(phy, MDIO_DEV_PMA_PMD, MII_BMCR, |
| BMCR_PDOWN, enable ? BMCR_PDOWN : 0); |
| } |
| |
| static int tn1010_autoneg_enable(struct cphy *phy) |
| { |
| int err; |
| |
| err = tn1010_power_down(phy, 0); |
| if (!err) |
| err = t3_mdio_change_bits(phy, MDIO_DEV_ANEG, MII_BMCR, 0, |
| BMCR_ANENABLE | BMCR_ANRESTART); |
| return err; |
| } |
| |
| static int tn1010_autoneg_restart(struct cphy *phy) |
| { |
| int err; |
| |
| err = tn1010_power_down(phy, 0); |
| if (!err) |
| err = t3_mdio_change_bits(phy, MDIO_DEV_ANEG, MII_BMCR, 0, |
| BMCR_ANRESTART); |
| return err; |
| } |
| |
| static int tn1010_advertise(struct cphy *phy, unsigned int advert) |
| { |
| int err, val; |
| |
| if (!(advert & ADVERTISED_1000baseT_Full)) |
| return -EINVAL; /* PHY can't disable 1000BASE-T */ |
| |
| val = ADVERTISE_CSMA | ADVERTISE_ENPAGE | ADVERTISE_NPAGE; |
| if (advert & ADVERTISED_Pause) |
| val |= ADVERTISE_PAUSE_CAP; |
| if (advert & ADVERTISED_Asym_Pause) |
| val |= ADVERTISE_PAUSE_ASYM; |
| err = mdio_write(phy, MDIO_DEV_ANEG, ANEG_ADVER, val); |
| if (err) |
| return err; |
| |
| val = (advert & ADVERTISED_10000baseT_Full) ? ADVERTISE_10000FULL : 0; |
| return mdio_write(phy, MDIO_DEV_ANEG, ANEG_10G_CTRL, val | |
| ADVERTISE_LOOP_TIMING); |
| } |
| |
| static int tn1010_get_link_status(struct cphy *phy, int *link_ok, |
| int *speed, int *duplex, int *fc) |
| { |
| unsigned int status, lpa, adv; |
| int err, sp = -1, pause = 0; |
| |
| err = mdio_read(phy, MDIO_DEV_VEND1, TN1010_VEND1_STAT, &status); |
| if (err) |
| return err; |
| |
| if (link_ok) |
| *link_ok = (status & F_LINK_STAT) != 0; |
| |
| if (G_ANEG_STAT(status) == ANEG_COMPLETE) { |
| sp = (status & F_ANEG_SPEED_1G) ? SPEED_1000 : SPEED_10000; |
| |
| if (fc) { |
| err = mdio_read(phy, MDIO_DEV_ANEG, ANEG_LPA, &lpa); |
| if (!err) |
| err = mdio_read(phy, MDIO_DEV_ANEG, ANEG_ADVER, |
| &adv); |
| if (err) |
| return err; |
| |
| if (lpa & adv & ADVERTISE_PAUSE_CAP) |
| pause = PAUSE_RX | PAUSE_TX; |
| else if ((lpa & ADVERTISE_PAUSE_CAP) && |
| (lpa & ADVERTISE_PAUSE_ASYM) && |
| (adv & ADVERTISE_PAUSE_ASYM)) |
| pause = PAUSE_TX; |
| else if ((lpa & ADVERTISE_PAUSE_ASYM) && |
| (adv & ADVERTISE_PAUSE_CAP)) |
| pause = PAUSE_RX; |
| } |
| } |
| if (speed) |
| *speed = sp; |
| if (duplex) |
| *duplex = DUPLEX_FULL; |
| if (fc) |
| *fc = pause; |
| return 0; |
| } |
| |
| int tn1010_set_speed_duplex(struct cphy *phy, int speed, int duplex) |
| { |
| return -EINVAL; /* require autoneg */ |
| } |
| |
| #ifdef C99_NOT_SUPPORTED |
| static struct cphy_ops tn1010_ops = { |
| tn1010_reset, |
| t3_phy_lasi_intr_enable, |
| t3_phy_lasi_intr_disable, |
| t3_phy_lasi_intr_clear, |
| t3_phy_lasi_intr_handler, |
| tn1010_autoneg_enable, |
| tn1010_autoneg_restart, |
| tn1010_advertise, |
| NULL, |
| tn1010_set_speed_duplex, |
| tn1010_get_link_status, |
| tn1010_power_down, |
| }; |
| #else |
| static struct cphy_ops tn1010_ops = { |
| .reset = tn1010_reset, |
| .intr_enable = t3_phy_lasi_intr_enable, |
| .intr_disable = t3_phy_lasi_intr_disable, |
| .intr_clear = t3_phy_lasi_intr_clear, |
| .intr_handler = t3_phy_lasi_intr_handler, |
| .autoneg_enable = tn1010_autoneg_enable, |
| .autoneg_restart = tn1010_autoneg_restart, |
| .advertise = tn1010_advertise, |
| .set_speed_duplex = tn1010_set_speed_duplex, |
| .get_link_status = tn1010_get_link_status, |
| .power_down = tn1010_power_down, |
| }; |
| #endif |
| |
| int t3_tn1010_phy_prep(pinfo_t *pinfo, int phy_addr, |
| const struct mdio_ops *mdio_ops) |
| { |
| cphy_init(&pinfo->phy, pinfo->adapter, pinfo, phy_addr, &tn1010_ops, mdio_ops, |
| SUPPORTED_1000baseT_Full | SUPPORTED_10000baseT_Full | |
| SUPPORTED_Autoneg | SUPPORTED_AUI | SUPPORTED_TP, |
| "1000/10GBASE-T"); |
| msleep(500); /* PHY needs up to 500ms to start responding to MDIO */ |
| return 0; |
| } |