1 /************************************************************************** 2 3 Copyright (c) 2007, Chelsio Inc. 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions are met: 8 9 1. Redistributions of source code must retain the above copyright notice, 10 this list of conditions and the following disclaimer. 11 12 2. Neither the name of the Chelsio Corporation nor the names of its 13 contributors may be used to endorse or promote products derived from 14 this software without specific prior written permission. 15 16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 POSSIBILITY OF SUCH DAMAGE. 27 28 ***************************************************************************/ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <cxgb_include.h> 34 35 /* Marvell PHY interrupt status bits. */ 36 #define MV_INTR_JABBER 0x0001 37 #define MV_INTR_POLARITY_CHNG 0x0002 38 #define MV_INTR_ENG_DETECT_CHNG 0x0010 39 #define MV_INTR_DOWNSHIFT 0x0020 40 #define MV_INTR_MDI_XOVER_CHNG 0x0040 41 #define MV_INTR_FIFO_OVER_UNDER 0x0080 42 #define MV_INTR_FALSE_CARRIER 0x0100 43 #define MV_INTR_SYMBOL_ERROR 0x0200 44 #define MV_INTR_LINK_CHNG 0x0400 45 #define MV_INTR_AUTONEG_DONE 0x0800 46 #define MV_INTR_PAGE_RECV 0x1000 47 #define MV_INTR_DUPLEX_CHNG 0x2000 48 #define MV_INTR_SPEED_CHNG 0x4000 49 #define MV_INTR_AUTONEG_ERR 0x8000 50 51 /* Marvell PHY specific registers. */ 52 #define MV88E1XXX_SPECIFIC_CNTRL 16 53 #define MV88E1XXX_SPECIFIC_STATUS 17 54 #define MV88E1XXX_INTR_ENABLE 18 55 #define MV88E1XXX_INTR_STATUS 19 56 #define MV88E1XXX_EXT_SPECIFIC_CNTRL 20 57 #define MV88E1XXX_RECV_ERR 21 58 #define MV88E1XXX_EXT_ADDR 22 59 #define MV88E1XXX_GLOBAL_STATUS 23 60 #define MV88E1XXX_LED_CNTRL 24 61 #define MV88E1XXX_LED_OVERRIDE 25 62 #define MV88E1XXX_EXT_SPECIFIC_CNTRL2 26 63 #define MV88E1XXX_EXT_SPECIFIC_STATUS 27 64 #define MV88E1XXX_VIRTUAL_CABLE_TESTER 28 65 #define MV88E1XXX_EXTENDED_ADDR 29 66 #define MV88E1XXX_EXTENDED_DATA 30 67 68 /* PHY specific control register fields */ 69 #define S_PSCR_MDI_XOVER_MODE 5 70 #define M_PSCR_MDI_XOVER_MODE 0x3 71 #define V_PSCR_MDI_XOVER_MODE(x) ((x) << S_PSCR_MDI_XOVER_MODE) 72 73 /* Extended PHY specific control register fields */ 74 #define S_DOWNSHIFT_ENABLE 8 75 #define V_DOWNSHIFT_ENABLE (1 << S_DOWNSHIFT_ENABLE) 76 77 #define S_DOWNSHIFT_CNT 9 78 #define M_DOWNSHIFT_CNT 0x7 79 #define V_DOWNSHIFT_CNT(x) ((x) << S_DOWNSHIFT_CNT) 80 81 /* PHY specific status register fields */ 82 #define S_PSSR_JABBER 0 83 #define V_PSSR_JABBER (1 << S_PSSR_JABBER) 84 85 #define S_PSSR_POLARITY 1 86 #define V_PSSR_POLARITY (1 << S_PSSR_POLARITY) 87 88 #define S_PSSR_RX_PAUSE 2 89 #define V_PSSR_RX_PAUSE (1 << S_PSSR_RX_PAUSE) 90 91 #define S_PSSR_TX_PAUSE 3 92 #define V_PSSR_TX_PAUSE (1 << S_PSSR_TX_PAUSE) 93 94 #define S_PSSR_ENERGY_DETECT 4 95 #define V_PSSR_ENERGY_DETECT (1 << S_PSSR_ENERGY_DETECT) 96 97 #define S_PSSR_DOWNSHIFT_STATUS 5 98 #define V_PSSR_DOWNSHIFT_STATUS (1 << S_PSSR_DOWNSHIFT_STATUS) 99 100 #define S_PSSR_MDI 6 101 #define V_PSSR_MDI (1 << S_PSSR_MDI) 102 103 #define S_PSSR_CABLE_LEN 7 104 #define M_PSSR_CABLE_LEN 0x7 105 #define V_PSSR_CABLE_LEN(x) ((x) << S_PSSR_CABLE_LEN) 106 #define G_PSSR_CABLE_LEN(x) (((x) >> S_PSSR_CABLE_LEN) & M_PSSR_CABLE_LEN) 107 108 #define S_PSSR_LINK 10 109 #define V_PSSR_LINK (1 << S_PSSR_LINK) 110 111 #define S_PSSR_STATUS_RESOLVED 11 112 #define V_PSSR_STATUS_RESOLVED (1 << S_PSSR_STATUS_RESOLVED) 113 114 #define S_PSSR_PAGE_RECEIVED 12 115 #define V_PSSR_PAGE_RECEIVED (1 << S_PSSR_PAGE_RECEIVED) 116 117 #define S_PSSR_DUPLEX 13 118 #define V_PSSR_DUPLEX (1 << S_PSSR_DUPLEX) 119 120 #define S_PSSR_SPEED 14 121 #define M_PSSR_SPEED 0x3 122 #define V_PSSR_SPEED(x) ((x) << S_PSSR_SPEED) 123 #define G_PSSR_SPEED(x) (((x) >> S_PSSR_SPEED) & M_PSSR_SPEED) 124 125 /* MV88E1XXX MDI crossover register values */ 126 #define CROSSOVER_MDI 0 127 #define CROSSOVER_MDIX 1 128 #define CROSSOVER_AUTO 3 129 130 #define INTR_ENABLE_MASK (MV_INTR_SPEED_CHNG | MV_INTR_DUPLEX_CHNG | \ 131 MV_INTR_AUTONEG_DONE | MV_INTR_LINK_CHNG | MV_INTR_FIFO_OVER_UNDER | \ 132 MV_INTR_ENG_DETECT_CHNG) 133 134 /* 135 * Reset the PHY. If 'wait' is set wait until the reset completes. 136 */ 137 static int mv88e1xxx_reset(struct cphy *cphy, int wait) 138 { 139 return t3_phy_reset(cphy, 0, wait); 140 } 141 142 static int mv88e1xxx_intr_enable(struct cphy *cphy) 143 { 144 return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, INTR_ENABLE_MASK); 145 } 146 147 static int mv88e1xxx_intr_disable(struct cphy *cphy) 148 { 149 return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, 0); 150 } 151 152 static int mv88e1xxx_intr_clear(struct cphy *cphy) 153 { 154 u32 val; 155 156 /* Clear PHY interrupts by reading the register. */ 157 return mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &val); 158 } 159 160 static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover) 161 { 162 return t3_mdio_change_bits(cphy, 0, MV88E1XXX_SPECIFIC_CNTRL, 163 V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE), 164 V_PSCR_MDI_XOVER_MODE(crossover)); 165 } 166 167 static int mv88e1xxx_autoneg_enable(struct cphy *cphy) 168 { 169 mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO); 170 171 /* restart autoneg for change to take effect */ 172 return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 173 BMCR_ANENABLE | BMCR_ANRESTART); 174 } 175 176 static int mv88e1xxx_autoneg_restart(struct cphy *cphy) 177 { 178 return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 179 BMCR_ANRESTART); 180 } 181 182 static int mv88e1xxx_set_loopback(struct cphy *cphy, int mmd, int dir, int on) 183 { 184 return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_LOOPBACK, 185 on ? BMCR_LOOPBACK : 0); 186 } 187 188 static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_state, 189 int *speed, int *duplex, int *fc) 190 { 191 u32 status; 192 int sp = -1, dplx = -1, pause = 0; 193 194 mdio_read(cphy, 0, MV88E1XXX_SPECIFIC_STATUS, &status); 195 if ((status & V_PSSR_STATUS_RESOLVED) != 0) { 196 if (status & V_PSSR_RX_PAUSE) 197 pause |= PAUSE_RX; 198 if (status & V_PSSR_TX_PAUSE) 199 pause |= PAUSE_TX; 200 dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF; 201 sp = G_PSSR_SPEED(status); 202 if (sp == 0) 203 sp = SPEED_10; 204 else if (sp == 1) 205 sp = SPEED_100; 206 else 207 sp = SPEED_1000; 208 } 209 if (link_state) 210 *link_state = status & V_PSSR_LINK ? PHY_LINK_UP : 211 PHY_LINK_DOWN; 212 if (speed) 213 *speed = sp; 214 if (duplex) 215 *duplex = dplx; 216 if (fc) 217 *fc = pause; 218 return 0; 219 } 220 221 static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex) 222 { 223 int err = t3_set_phy_speed_duplex(phy, speed, duplex); 224 225 /* PHY needs reset for new settings to take effect */ 226 if (!err) 227 err = mv88e1xxx_reset(phy, 0); 228 return err; 229 } 230 231 static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable) 232 { 233 /* 234 * Set the downshift counter to 2 so we try to establish Gb link 235 * twice before downshifting. 236 */ 237 return t3_mdio_change_bits(cphy, 0, MV88E1XXX_EXT_SPECIFIC_CNTRL, 238 V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT), 239 downshift_enable ? V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2) : 0); 240 } 241 242 static int mv88e1xxx_power_down(struct cphy *cphy, int enable) 243 { 244 return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN, 245 enable ? BMCR_PDOWN : 0); 246 } 247 248 static int mv88e1xxx_intr_handler(struct cphy *cphy) 249 { 250 const u32 link_change_intrs = MV_INTR_LINK_CHNG | 251 MV_INTR_AUTONEG_DONE | MV_INTR_DUPLEX_CHNG | 252 MV_INTR_SPEED_CHNG | MV_INTR_DOWNSHIFT; 253 254 u32 cause; 255 int cphy_cause = 0; 256 257 mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &cause); 258 cause &= INTR_ENABLE_MASK; 259 if (cause & link_change_intrs) 260 cphy_cause |= cphy_cause_link_change; 261 if (cause & MV_INTR_FIFO_OVER_UNDER) 262 cphy_cause |= cphy_cause_fifo_error; 263 return cphy_cause; 264 } 265 266 #ifdef C99_NOT_SUPPORTED 267 static struct cphy_ops mv88e1xxx_ops = { 268 mv88e1xxx_reset, 269 mv88e1xxx_intr_enable, 270 mv88e1xxx_intr_disable, 271 mv88e1xxx_intr_clear, 272 mv88e1xxx_intr_handler, 273 mv88e1xxx_autoneg_enable, 274 mv88e1xxx_autoneg_restart, 275 t3_phy_advertise, 276 mv88e1xxx_set_loopback, 277 mv88e1xxx_set_speed_duplex, 278 mv88e1xxx_get_link_status, 279 mv88e1xxx_power_down, 280 }; 281 #else 282 static struct cphy_ops mv88e1xxx_ops = { 283 .reset = mv88e1xxx_reset, 284 .intr_enable = mv88e1xxx_intr_enable, 285 .intr_disable = mv88e1xxx_intr_disable, 286 .intr_clear = mv88e1xxx_intr_clear, 287 .intr_handler = mv88e1xxx_intr_handler, 288 .autoneg_enable = mv88e1xxx_autoneg_enable, 289 .autoneg_restart = mv88e1xxx_autoneg_restart, 290 .advertise = t3_phy_advertise, 291 .set_loopback = mv88e1xxx_set_loopback, 292 .set_speed_duplex = mv88e1xxx_set_speed_duplex, 293 .get_link_status = mv88e1xxx_get_link_status, 294 .power_down = mv88e1xxx_power_down, 295 }; 296 #endif 297 298 int t3_mv88e1xxx_phy_prep(pinfo_t *pinfo, int phy_addr, 299 const struct mdio_ops *mdio_ops) 300 { 301 struct cphy *phy = &pinfo->phy; 302 int err; 303 304 cphy_init(phy, pinfo->adapter, pinfo, phy_addr, &mv88e1xxx_ops, mdio_ops, 305 SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full | 306 SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII | 307 SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T"); 308 309 /* Configure copper PHY transmitter as class A to reduce EMI. */ 310 err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_ADDR, 0xb); 311 if (!err) 312 err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_DATA, 0x8004); 313 314 if (!err) 315 err = mv88e1xxx_downshift_set(phy, 1); /* Enable downshift */ 316 return err; 317 } 318