/* * GMAC driver for Marvell network interfaces on Armada SoCs. * * Copyright (C) 2012 Marvell * * Rami Rosen * Thomas Petazzoni * * Split from mvneta and mvpp2 by Russell King. * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include #include #include #include "mvgmac.h" enum { /* N = Neta, 21 = PPV2.1, 22 = PPV2.2 */ /* N: 0-14 21: 0,2-15 22: 0-14 */ GMAC_CTRL0_REG = 0x00, GMAC_CTRL0_PORT_ENABLE = BIT(0), GMAC_CTRL0_PORT_1000BASE_X = BIT(1), GMAC_CTRL0_MAX_RX_SIZE_SHIFT = 2, GMAC_CTRL0_MAX_RX_SIZE_MASK = 0x1fff << GMAC_CTRL0_MAX_RX_SIZE_SHIFT, GMAC_CTRL0_MIB_CNTR_ENABLE = BIT(15), /* N: 21: 1,5,6 22: */ GMAC_CTRL1_REG = 0x04, GMAC_CTRL1_PERIODIC_XON_ENABLE = BIT(1), GMAC_CTRL1_GMII_LB_ENABLE = BIT(5), GMAC_CTRL1_PCS_LB_ENABLE = BIT(6), /* ALL: 0,3,4,6 */ GMAC_CTRL2_REG = 0x08, GMAC_CTRL2_INBAND_AN_SGMII = BIT(0), GMAC_CTRL2_PCS_ENABLE = BIT(3), GMAC_CTRL2_PORT_RGMII = BIT(4), GMAC_CTRL2_PORT_RESET = BIT(6), /* N:0-9,11-13 21:0,1,5-7,9,12,13 22:0-7,9-15 */ /* 22 bit 2 - EN_PCS_AN */ GMAC_ANEG_REG = 0x0c, GMAC_ANEG_FORCE_LINK_DOWN = BIT(0), GMAC_ANEG_FORCE_LINK_PASS = BIT(1), GMAC_ANEG_INBAND_AN_ENABLE = BIT(2), GMAC_ANEG_AN_BYPASS_ENABLE = BIT(3), GMAC_ANEG_INBAND_RESTART_AN = BIT(4), GMAC_ANEG_MII_SPEED = BIT(5), GMAC_ANEG_GMII_SPEED = BIT(6), GMAC_ANEG_AN_SPEED_ENABLE = BIT(7), GMAC_ANEG_CONFIG_FLOW_CTRL = BIT(8), GMAC_ANEG_ADVERT_SYM_FLOW_CTRL = BIT(9), GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL = BIT(10), GMAC_ANEG_AN_FLOW_CTRL_ENABLE = BIT(11), GMAC_ANEG_FULL_DUPLEX = BIT(12), GMAC_ANEG_AN_DUPLEX_ENABLE = BIT(13), /* pp22: bit 14 - phy mode */ /* pp22: bit 15 - choose sample tx config */ GMAC_STATUS_REG = 0x10, MVGMAC_LINK_UP = BIT(0), MVGMAC_SPEED_1000 = BIT(1), MVGMAC_SPEED_100 = BIT(2), MVGMAC_FULL_DUPLEX = BIT(3), MVGMAC_RX_FLOW_CTRL_ENABLE = BIT(4), MVGMAC_TX_FLOW_CTRL_ENABLE = BIT(5), MVGMAC_RX_FLOW_CTRL_ACTIVE = BIT(6), MVGMAC_TX_FLOW_CTRL_ACTIVE = BIT(7), MVGMAC_AN_COMPLETE = BIT(11), MVGMAC_SYNC_OK = BIT(14), /* N: 21:6-13 22: */ GMAC_FIFO_CFG1_REG = 0x1c, GMAC_FIFO_CFG1_TX_MIN_TH_SHIFT = 6, GMAC_FIFO_CFG1_TX_MIN_TH_MASK = 0x7f << GMAC_FIFO_CFG1_TX_MIN_TH_SHIFT, /* N:1 21: 22:0,3-7 */ GMAC_CTRL4_REG = 0x90, GMAC_CTRL4_EXT_PIN_GMII_SEL = BIT(0), GMAC_CTRL4_SHORT_PREAMBLE_ENABLE = BIT(1), GMAC_CTRL4_FC_RX_ENABLE = BIT(3), GMAC_CTRL4_FC_TX_ENABLE = BIT(4), GMAC_CTRL4_DP_CLK_SEL = BIT(5), GMAC_CTRL4_SYNC_BYPASS = BIT(6), GMAC_CTRL4_QSGMII_BYPASS = BIT(7), GMAC_LPI_CTRL0_REG = 0xc0, GMAC_LPI_CTRL0_TS = 0xff << 8, GMAC_LPI_CTRL1_REG = 0xc4, GMAC_LPI_CTRL1_REQ_EN = BIT(0), GMAC_LPI_CTRL2_REG = 0xc8, GMAC_LPI_STATUS_REG = 0xcc, GMAC_LPI_CNTR_REG = 0xd0, }; #define insert(var, mask, val) ({ \ u32 __mask = mask; \ ((var) & ~(__mask)) | (((val) << __ffs(__mask)) & (__mask)); \ }) /* Change maximum receive size of the port. */ void mvgmac_set_max_rx_size(struct mvgmac *gmac, size_t max_rx_size) { int size = (max_rx_size - MARVELL_HEADER_SIZE) / 2; u32 val; val = readl_relaxed(gmac->base + GMAC_CTRL0_REG); val = insert(val, GMAC_CTRL0_MAX_RX_SIZE_MASK, size); writel_relaxed(val, gmac->base + GMAC_CTRL0_REG); } EXPORT_SYMBOL_GPL(mvgmac_set_max_rx_size); /* Enable the port by setting the port enable bit of the MAC control register */ void mvgmac_enable(struct mvgmac *gmac) { u32 val; val = readl_relaxed(gmac->base + GMAC_CTRL0_REG); val |= GMAC_CTRL0_PORT_ENABLE; val |= GMAC_CTRL0_MIB_CNTR_ENABLE; writel_relaxed(val, gmac->base + GMAC_CTRL0_REG); } EXPORT_SYMBOL_GPL(mvgmac_enable); /* Disable the port */ void mvgmac_disable(struct mvgmac *gmac) { u32 val; val = readl_relaxed(gmac->base + GMAC_CTRL0_REG); val &= ~GMAC_CTRL0_PORT_ENABLE; writel_relaxed(val, gmac->base + GMAC_CTRL0_REG); } EXPORT_SYMBOL_GPL(mvgmac_disable); int mvgmac_configure(struct mvgmac *gmac, phy_interface_t phy_mode) { bool ext_pin_gmii; u32 val; switch (phy_mode) { case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_SGMII: ext_pin_gmii = false; break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_ID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_TXID: ext_pin_gmii = true; break; default: return -EINVAL; } if (gmac->version == MVGMAC_PP21) { /* Min. TX threshold must be less than minimum packet length */ val = readl_relaxed(gmac->base + GMAC_FIFO_CFG1_REG); val = insert(val, GMAC_FIFO_CFG1_TX_MIN_TH_MASK, 64 - 4 - 2); writel_relaxed(val, gmac->base + GMAC_FIFO_CFG1_REG); } else if (gmac->version == MVGMAC_PP22) { val = readl_relaxed(gmac->base + GMAC_CTRL4_REG); val &= ~GMAC_CTRL4_DP_CLK_SEL; val |= GMAC_CTRL4_SYNC_BYPASS; val = insert(val, GMAC_CTRL4_QSGMII_BYPASS, phy_mode != PHY_INTERFACE_MODE_QSGMII); val = insert(val, GMAC_CTRL4_EXT_PIN_GMII_SEL, ext_pin_gmii); writel_relaxed(val, gmac->base + GMAC_CTRL4_REG); } return 0; } EXPORT_SYMBOL_GPL(mvgmac_configure); void mvgmac_link_unforce(struct mvgmac *gmac) { u32 val; val = readl_relaxed(gmac->base + GMAC_ANEG_REG); val &= ~(GMAC_ANEG_FORCE_LINK_PASS | GMAC_ANEG_FORCE_LINK_DOWN); writel_relaxed(val, gmac->base + GMAC_ANEG_REG); } EXPORT_SYMBOL_GPL(mvgmac_link_unforce); void mvgmac_link_force_down(struct mvgmac *gmac) { u32 val; val = readl_relaxed(gmac->base + GMAC_ANEG_REG); val &= ~GMAC_ANEG_FORCE_LINK_PASS; val |= GMAC_ANEG_FORCE_LINK_DOWN; writel_relaxed(val, gmac->base + GMAC_ANEG_REG); } EXPORT_SYMBOL_GPL(mvgmac_link_force_down); void mvgmac_link_down(struct mvgmac *gmac, int mode) { if (!phylink_autoneg_inband(mode)) mvgmac_link_force_down(gmac); } EXPORT_SYMBOL_GPL(mvgmac_link_down); void mvgmac_link_up(struct mvgmac *gmac, int mode, int speed, int duplex, bool tx_pause, bool rx_pause) { u32 val, ctrl4; val = readl_relaxed(gmac->base + GMAC_ANEG_REG); val &= ~GMAC_ANEG_CONFIG_FLOW_CTRL; if (!phylink_autoneg_inband(mode)) { val &= ~(GMAC_ANEG_FORCE_LINK_DOWN | GMAC_ANEG_MII_SPEED | GMAC_ANEG_GMII_SPEED | GMAC_ANEG_FULL_DUPLEX); val |= GMAC_ANEG_FORCE_LINK_PASS; if (speed == SPEED_1000 || speed == SPEED_2500) val |= GMAC_ANEG_GMII_SPEED; else if (speed == SPEED_100) val |= GMAC_ANEG_MII_SPEED; if (duplex == DUPLEX_FULL) val |= GMAC_ANEG_FULL_DUPLEX; } switch (gmac->version) { case MVGMAC_NETA: val = insert(val, GMAC_ANEG_CONFIG_FLOW_CTRL, tx_pause || rx_pause); break; case MVGMAC_PP22: ctrl4 = readl_relaxed(gmac->base + GMAC_CTRL4_REG); ctrl4 = insert(ctrl4, GMAC_CTRL4_FC_TX_ENABLE, tx_pause); ctrl4 = insert(ctrl4, GMAC_CTRL4_FC_RX_ENABLE, rx_pause); writel_relaxed(ctrl4, gmac->base + GMAC_CTRL4_REG); break; } writel_relaxed(val, gmac->base + GMAC_ANEG_REG); } EXPORT_SYMBOL_GPL(mvgmac_link_up); bool mvgmac_link_is_up(struct mvgmac *gmac) { u32 gmac_stat = readl_relaxed(gmac->base + GMAC_STATUS_REG); return !!(gmac_stat & MVGMAC_LINK_UP); } EXPORT_SYMBOL_GPL(mvgmac_link_is_up); void mvgmac_pcs_get_state(struct mvgmac *gmac, struct phylink_link_state *state) { u32 gmac_stat = readl_relaxed(gmac->base + GMAC_STATUS_REG); if (gmac_stat & MVGMAC_SPEED_1000) state->speed = state->interface == PHY_INTERFACE_MODE_2500BASEX ? SPEED_2500 : SPEED_1000; else if (gmac_stat & MVGMAC_SPEED_100) state->speed = SPEED_100; else state->speed = SPEED_10; state->an_complete = !!(gmac_stat & MVGMAC_AN_COMPLETE); state->link = !!(gmac_stat & MVGMAC_LINK_UP); state->duplex = !!(gmac_stat & MVGMAC_FULL_DUPLEX); if (gmac_stat & MVGMAC_RX_FLOW_CTRL_ENABLE) state->pause |= MLO_PAUSE_RX; if (gmac_stat & MVGMAC_TX_FLOW_CTRL_ENABLE) state->pause |= MLO_PAUSE_TX; } EXPORT_SYMBOL_GPL(mvgmac_pcs_get_state); void mvgmac_pcs_an_restart(struct mvgmac *gmac) { u32 gmac_an = readl_relaxed(gmac->base + GMAC_ANEG_REG); writel_relaxed(gmac_an | GMAC_ANEG_INBAND_RESTART_AN, gmac->base + GMAC_ANEG_REG); writel_relaxed(gmac_an & ~GMAC_ANEG_INBAND_RESTART_AN, gmac->base + GMAC_ANEG_REG); } EXPORT_SYMBOL_GPL(mvgmac_pcs_an_restart); int mvgmac_pcs_config(struct mvgmac *gmac, unsigned int mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac) { u32 mask, val, an, old_an, changed; mask = GMAC_ANEG_INBAND_AN_ENABLE | GMAC_ANEG_INBAND_RESTART_AN | GMAC_ANEG_AN_SPEED_ENABLE | GMAC_ANEG_AN_FLOW_CTRL_ENABLE | GMAC_ANEG_AN_DUPLEX_ENABLE; if (phylink_autoneg_inband(mode)) { mask |= GMAC_ANEG_MII_SPEED | GMAC_ANEG_GMII_SPEED | GMAC_ANEG_FULL_DUPLEX; val = GMAC_ANEG_INBAND_AN_ENABLE; if (interface == PHY_INTERFACE_MODE_SGMII) { /* SGMII mode receives the speed and duplex from PHY */ val |= GMAC_ANEG_AN_SPEED_ENABLE | GMAC_ANEG_AN_DUPLEX_ENABLE; } else { /* 802.3z mode has fixed speed and duplex */ val |= GMAC_ANEG_GMII_SPEED | GMAC_ANEG_FULL_DUPLEX; /* The FLOW_CTRL_ENABLE bit selects either the hardware * automatically or the GMAC_ANEG_FLOW_CTRL manually * controls the GMAC pause mode. */ if (permit_pause_to_mac) val |= GMAC_ANEG_AN_FLOW_CTRL_ENABLE; /* Update the advertisement bits */ mask |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL; if (phylink_test(advertising, Pause)) val |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL; if (gmac->version == MVGMAC_PP22) { mask |= GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL; if (phylink_test(advertising, Asym_Pause)) val |= GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL; } } } else { /* Phy or fixed speed - disable in-band AN modes */ val = 0; } old_an = an = readl_relaxed(gmac->base + GMAC_ANEG_REG); an = (an & ~mask) | val; changed = old_an ^ an; if (changed) writel_relaxed(an, gmac->base + GMAC_ANEG_REG); /* We are only interested in the advertisement bits changing */ return !!(changed & (GMAC_ANEG_ADVERT_SYM_FLOW_CTRL | GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL)); } EXPORT_SYMBOL_GPL(mvgmac_pcs_config); void mvgmac_config_mac(struct mvgmac *gmac, unsigned int mode, const struct phylink_link_state *state) { u32 new_ctrl0, gmac_ctrl0 = readl_relaxed(gmac->base + GMAC_CTRL0_REG); u32 new_ctrl2, gmac_ctrl2 = readl_relaxed(gmac->base + GMAC_CTRL2_REG); u32 new_ctrl4, gmac_ctrl4 = readl_relaxed(gmac->base + GMAC_CTRL4_REG); new_ctrl0 = gmac_ctrl0 & ~GMAC_CTRL0_PORT_1000BASE_X; new_ctrl2 = gmac_ctrl2 & ~(GMAC_CTRL2_INBAND_AN_SGMII | GMAC_CTRL2_PORT_RESET); new_ctrl4 = gmac_ctrl4; /* Even though it might look weird, when we're configured in * SGMII or QSGMII mode, the RGMII bit needs to be set. */ new_ctrl2 |= GMAC_CTRL2_PORT_RGMII; if (state->interface == PHY_INTERFACE_MODE_QSGMII || state->interface == PHY_INTERFACE_MODE_SGMII || phy_interface_mode_is_8023z(state->interface)) new_ctrl2 |= GMAC_CTRL2_PCS_ENABLE; if (!phylink_autoneg_inband(mode)) { /* Phy or fixed speed - nothing to do, leave the * configured speed, duplex and flow control as-is. */ } else if (state->interface == PHY_INTERFACE_MODE_SGMII) { /* SGMII mode receives the state from the PHY */ new_ctrl2 |= GMAC_CTRL2_INBAND_AN_SGMII; } else { /* 802.3z negotiation - 1000BaseX */ new_ctrl0 |= GMAC_CTRL0_PORT_1000BASE_X; } if (gmac->version == MVGMAC_NETA) { /* When at 2.5G, the link partner can send frames with * shortened preambles. */ new_ctrl4 &= ~GMAC_CTRL4_SHORT_PREAMBLE_ENABLE; if (state->interface == PHY_INTERFACE_MODE_2500BASEX) new_ctrl4 |= GMAC_CTRL4_SHORT_PREAMBLE_ENABLE; } if (new_ctrl0 != gmac_ctrl0) writel_relaxed(new_ctrl0, gmac->base + GMAC_CTRL0_REG); if (new_ctrl2 != gmac_ctrl2) writel_relaxed(new_ctrl2, gmac->base + GMAC_CTRL2_REG); if (new_ctrl4 != gmac_ctrl4) writel_relaxed(new_ctrl4, gmac->base + GMAC_CTRL4_REG); if (gmac_ctrl2 & GMAC_CTRL2_PORT_RESET) { while ((readl_relaxed(gmac->base + GMAC_CTRL2_REG) & GMAC_CTRL2_PORT_RESET) != 0) continue; } } EXPORT_SYMBOL_GPL(mvgmac_config_mac); int mvgmac_set_lpi_ts(struct mvgmac *gmac, unsigned int ts) { u32 val; if (!(readl_relaxed(gmac->base + GMAC_STATUS_REG) & MVGMAC_SPEED_1000)) ts = DIV_ROUND_UP(ts, 10); if (ts > 255) ts = 255; val = readl_relaxed(gmac->base + GMAC_LPI_CTRL0_REG); val = insert(val, GMAC_LPI_CTRL0_TS, ts); writel_relaxed(val, gmac->base + GMAC_LPI_CTRL0_REG); return 0; } EXPORT_SYMBOL_GPL(mvgmac_set_lpi_ts); void mvgmac_set_eee(struct mvgmac *gmac, bool enable) { u32 val; val = readl_relaxed(gmac->base + GMAC_LPI_CTRL1_REG); val = insert(val, GMAC_LPI_CTRL1_REQ_EN, enable); writel_relaxed(val, gmac->base + GMAC_LPI_CTRL1_REG); } EXPORT_SYMBOL_GPL(mvgmac_set_eee); MODULE_DESCRIPTION("Marvell GMAC driver"); MODULE_LICENSE("GPL");