| /* |
| * This file is part of the Chelsio T3 Ethernet driver. |
| * |
| * Copyright (C) 2003-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" |
| |
| /* Marvell PHY interrupt status bits. */ |
| #define MV_INTR_JABBER 0x0001 |
| #define MV_INTR_POLARITY_CHNG 0x0002 |
| #define MV_INTR_ENG_DETECT_CHNG 0x0010 |
| #define MV_INTR_DOWNSHIFT 0x0020 |
| #define MV_INTR_MDI_XOVER_CHNG 0x0040 |
| #define MV_INTR_FIFO_OVER_UNDER 0x0080 |
| #define MV_INTR_FALSE_CARRIER 0x0100 |
| #define MV_INTR_SYMBOL_ERROR 0x0200 |
| #define MV_INTR_LINK_CHNG 0x0400 |
| #define MV_INTR_AUTONEG_DONE 0x0800 |
| #define MV_INTR_PAGE_RECV 0x1000 |
| #define MV_INTR_DUPLEX_CHNG 0x2000 |
| #define MV_INTR_SPEED_CHNG 0x4000 |
| #define MV_INTR_AUTONEG_ERR 0x8000 |
| |
| /* Marvell PHY specific registers. */ |
| #define MV88E1XXX_SPECIFIC_CNTRL 16 |
| #define MV88E1XXX_SPECIFIC_STATUS 17 |
| #define MV88E1XXX_INTR_ENABLE 18 |
| #define MV88E1XXX_INTR_STATUS 19 |
| #define MV88E1XXX_EXT_SPECIFIC_CNTRL 20 |
| #define MV88E1XXX_RECV_ERR 21 |
| #define MV88E1XXX_EXT_ADDR 22 |
| #define MV88E1XXX_GLOBAL_STATUS 23 |
| #define MV88E1XXX_LED_CNTRL 24 |
| #define MV88E1XXX_LED_OVERRIDE 25 |
| #define MV88E1XXX_EXT_SPECIFIC_CNTRL2 26 |
| #define MV88E1XXX_EXT_SPECIFIC_STATUS 27 |
| #define MV88E1XXX_VIRTUAL_CABLE_TESTER 28 |
| #define MV88E1XXX_EXTENDED_ADDR 29 |
| #define MV88E1XXX_EXTENDED_DATA 30 |
| |
| /* PHY specific control register fields */ |
| #define S_PSCR_MDI_XOVER_MODE 5 |
| #define M_PSCR_MDI_XOVER_MODE 0x3 |
| #define V_PSCR_MDI_XOVER_MODE(x) ((x) << S_PSCR_MDI_XOVER_MODE) |
| |
| /* Extended PHY specific control register fields */ |
| #define S_DOWNSHIFT_ENABLE 8 |
| #define V_DOWNSHIFT_ENABLE (1 << S_DOWNSHIFT_ENABLE) |
| |
| #define S_DOWNSHIFT_CNT 9 |
| #define M_DOWNSHIFT_CNT 0x7 |
| #define V_DOWNSHIFT_CNT(x) ((x) << S_DOWNSHIFT_CNT) |
| |
| /* PHY specific status register fields */ |
| #define S_PSSR_JABBER 0 |
| #define V_PSSR_JABBER (1 << S_PSSR_JABBER) |
| |
| #define S_PSSR_POLARITY 1 |
| #define V_PSSR_POLARITY (1 << S_PSSR_POLARITY) |
| |
| #define S_PSSR_RX_PAUSE 2 |
| #define V_PSSR_RX_PAUSE (1 << S_PSSR_RX_PAUSE) |
| |
| #define S_PSSR_TX_PAUSE 3 |
| #define V_PSSR_TX_PAUSE (1 << S_PSSR_TX_PAUSE) |
| |
| #define S_PSSR_ENERGY_DETECT 4 |
| #define V_PSSR_ENERGY_DETECT (1 << S_PSSR_ENERGY_DETECT) |
| |
| #define S_PSSR_DOWNSHIFT_STATUS 5 |
| #define V_PSSR_DOWNSHIFT_STATUS (1 << S_PSSR_DOWNSHIFT_STATUS) |
| |
| #define S_PSSR_MDI 6 |
| #define V_PSSR_MDI (1 << S_PSSR_MDI) |
| |
| #define S_PSSR_CABLE_LEN 7 |
| #define M_PSSR_CABLE_LEN 0x7 |
| #define V_PSSR_CABLE_LEN(x) ((x) << S_PSSR_CABLE_LEN) |
| #define G_PSSR_CABLE_LEN(x) (((x) >> S_PSSR_CABLE_LEN) & M_PSSR_CABLE_LEN) |
| |
| #define S_PSSR_LINK 10 |
| #define V_PSSR_LINK (1 << S_PSSR_LINK) |
| |
| #define S_PSSR_STATUS_RESOLVED 11 |
| #define V_PSSR_STATUS_RESOLVED (1 << S_PSSR_STATUS_RESOLVED) |
| |
| #define S_PSSR_PAGE_RECEIVED 12 |
| #define V_PSSR_PAGE_RECEIVED (1 << S_PSSR_PAGE_RECEIVED) |
| |
| #define S_PSSR_DUPLEX 13 |
| #define V_PSSR_DUPLEX (1 << S_PSSR_DUPLEX) |
| |
| #define S_PSSR_SPEED 14 |
| #define M_PSSR_SPEED 0x3 |
| #define V_PSSR_SPEED(x) ((x) << S_PSSR_SPEED) |
| #define G_PSSR_SPEED(x) (((x) >> S_PSSR_SPEED) & M_PSSR_SPEED) |
| |
| /* MV88E1XXX MDI crossover register values */ |
| #define CROSSOVER_MDI 0 |
| #define CROSSOVER_MDIX 1 |
| #define CROSSOVER_AUTO 3 |
| |
| #define INTR_ENABLE_MASK (MV_INTR_SPEED_CHNG | MV_INTR_DUPLEX_CHNG | \ |
| MV_INTR_AUTONEG_DONE | MV_INTR_LINK_CHNG | MV_INTR_FIFO_OVER_UNDER | \ |
| MV_INTR_ENG_DETECT_CHNG) |
| |
| /* |
| * Reset the PHY. If 'wait' is set wait until the reset completes. |
| */ |
| static int mv88e1xxx_reset(struct cphy *cphy, int wait) |
| { |
| return t3_phy_reset(cphy, 0, wait); |
| } |
| |
| static int mv88e1xxx_intr_enable(struct cphy *cphy) |
| { |
| return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, INTR_ENABLE_MASK); |
| } |
| |
| static int mv88e1xxx_intr_disable(struct cphy *cphy) |
| { |
| return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, 0); |
| } |
| |
| static int mv88e1xxx_intr_clear(struct cphy *cphy) |
| { |
| u32 val; |
| |
| /* Clear PHY interrupts by reading the register. */ |
| return mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &val); |
| } |
| |
| static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover) |
| { |
| return t3_mdio_change_bits(cphy, 0, MV88E1XXX_SPECIFIC_CNTRL, |
| V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE), |
| V_PSCR_MDI_XOVER_MODE(crossover)); |
| } |
| |
| static int mv88e1xxx_autoneg_enable(struct cphy *cphy) |
| { |
| mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO); |
| |
| /* restart autoneg for change to take effect */ |
| return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, |
| BMCR_ANENABLE | BMCR_ANRESTART); |
| } |
| |
| static int mv88e1xxx_autoneg_restart(struct cphy *cphy) |
| { |
| return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, |
| BMCR_ANRESTART); |
| } |
| |
| static int mv88e1xxx_set_loopback(struct cphy *cphy, int mmd, int dir, int on) |
| { |
| return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_LOOPBACK, |
| on ? BMCR_LOOPBACK : 0); |
| } |
| |
| static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_ok, |
| int *speed, int *duplex, int *fc) |
| { |
| u32 status; |
| int sp = -1, dplx = -1, pause = 0; |
| |
| mdio_read(cphy, 0, MV88E1XXX_SPECIFIC_STATUS, &status); |
| if ((status & V_PSSR_STATUS_RESOLVED) != 0) { |
| if (status & V_PSSR_RX_PAUSE) |
| pause |= PAUSE_RX; |
| if (status & V_PSSR_TX_PAUSE) |
| pause |= PAUSE_TX; |
| dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF; |
| sp = G_PSSR_SPEED(status); |
| if (sp == 0) |
| sp = SPEED_10; |
| else if (sp == 1) |
| sp = SPEED_100; |
| else |
| sp = SPEED_1000; |
| } |
| if (link_ok) |
| *link_ok = (status & V_PSSR_LINK) != 0; |
| if (speed) |
| *speed = sp; |
| if (duplex) |
| *duplex = dplx; |
| if (fc) |
| *fc = pause; |
| return 0; |
| } |
| |
| static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex) |
| { |
| int err = t3_set_phy_speed_duplex(phy, speed, duplex); |
| |
| /* PHY needs reset for new settings to take effect */ |
| if (!err) |
| err = mv88e1xxx_reset(phy, 0); |
| return err; |
| } |
| |
| static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable) |
| { |
| /* |
| * Set the downshift counter to 2 so we try to establish Gb link |
| * twice before downshifting. |
| */ |
| return t3_mdio_change_bits(cphy, 0, MV88E1XXX_EXT_SPECIFIC_CNTRL, |
| V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT), |
| downshift_enable ? V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2) : 0); |
| } |
| |
| static int mv88e1xxx_power_down(struct cphy *cphy, int enable) |
| { |
| return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN, |
| enable ? BMCR_PDOWN : 0); |
| } |
| |
| static int mv88e1xxx_intr_handler(struct cphy *cphy) |
| { |
| const u32 link_change_intrs = MV_INTR_LINK_CHNG | |
| MV_INTR_AUTONEG_DONE | MV_INTR_DUPLEX_CHNG | |
| MV_INTR_SPEED_CHNG | MV_INTR_DOWNSHIFT; |
| |
| u32 cause; |
| int cphy_cause = 0; |
| |
| mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &cause); |
| cause &= INTR_ENABLE_MASK; |
| if (cause & link_change_intrs) |
| cphy_cause |= cphy_cause_link_change; |
| if (cause & MV_INTR_FIFO_OVER_UNDER) |
| cphy_cause |= cphy_cause_fifo_error; |
| return cphy_cause; |
| } |
| |
| #ifdef C99_NOT_SUPPORTED |
| static struct cphy_ops mv88e1xxx_ops = { |
| mv88e1xxx_reset, |
| mv88e1xxx_intr_enable, |
| mv88e1xxx_intr_disable, |
| mv88e1xxx_intr_clear, |
| mv88e1xxx_intr_handler, |
| mv88e1xxx_autoneg_enable, |
| mv88e1xxx_autoneg_restart, |
| t3_phy_advertise, |
| mv88e1xxx_set_loopback, |
| mv88e1xxx_set_speed_duplex, |
| mv88e1xxx_get_link_status, |
| mv88e1xxx_power_down, |
| }; |
| #else |
| static struct cphy_ops mv88e1xxx_ops = { |
| .reset = mv88e1xxx_reset, |
| .intr_enable = mv88e1xxx_intr_enable, |
| .intr_disable = mv88e1xxx_intr_disable, |
| .intr_clear = mv88e1xxx_intr_clear, |
| .intr_handler = mv88e1xxx_intr_handler, |
| .autoneg_enable = mv88e1xxx_autoneg_enable, |
| .autoneg_restart = mv88e1xxx_autoneg_restart, |
| .advertise = t3_phy_advertise, |
| .set_loopback = mv88e1xxx_set_loopback, |
| .set_speed_duplex = mv88e1xxx_set_speed_duplex, |
| .get_link_status = mv88e1xxx_get_link_status, |
| .power_down = mv88e1xxx_power_down, |
| }; |
| #endif |
| |
| int t3_mv88e1xxx_phy_prep(pinfo_t *pinfo, int phy_addr, |
| const struct mdio_ops *mdio_ops) |
| { |
| struct cphy *phy = &pinfo->phy; |
| int err; |
| |
| cphy_init(phy, pinfo->adapter, pinfo, phy_addr, &mv88e1xxx_ops, mdio_ops, |
| SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full | |
| SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII | |
| SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T"); |
| |
| /* Configure copper PHY transmitter as class A to reduce EMI. */ |
| err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_ADDR, 0xb); |
| if (!err) |
| err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_DATA, 0x8004); |
| |
| if (!err) |
| err = mv88e1xxx_downshift_set(phy, 1); /* Enable downshift */ |
| return err; |
| } |