diff options
Diffstat (limited to 'drivers/net/ethernet/marvell/mvgmac.c')
-rw-r--r-- | drivers/net/ethernet/marvell/mvgmac.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/drivers/net/ethernet/marvell/mvgmac.c b/drivers/net/ethernet/marvell/mvgmac.c new file mode 100644 index 000000000000..ccce6f52a75a --- /dev/null +++ b/drivers/net/ethernet/marvell/mvgmac.c @@ -0,0 +1,360 @@ +/* + * GMAC driver for Marvell network interfaces on Armada SoCs. + * + * Copyright (C) 2012 Marvell + * + * Rami Rosen <rosenr@marvell.com> + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + * 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 <linux/export.h> +#include <linux/io.h> +#include <linux/phylink.h> + +#include "mvgmac.h" + +enum { + 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), + + 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), + + 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_AN_FLOW_CTRL_ENABLE = BIT(11), + GMAC_ANEG_FULL_DUPLEX = BIT(12), + GMAC_ANEG_AN_DUPLEX_ENABLE = BIT(13), + + 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), + + GMAC_CTRL4_REG = 0x90, + GMAC_CTRL4_SHORT_PREAMBLE_ENABLE = BIT(1), + + 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); + +/* Configure port loopback */ +void mvgmac_configure_loopback(struct mvgmac *gmac, bool gmii, bool pcs) +{ + u32 val; + + val = readl_relaxed(gmac->base + GMAC_CTRL1_REG); + val = insert(val, GMAC_CTRL1_GMII_LB_ENABLE, gmii); + val = insert(val, GMAC_CTRL1_PCS_LB_ENABLE, pcs); + writel_relaxed(val, gmac->base + GMAC_CTRL1_REG); +} +EXPORT_SYMBOL_GPL(mvgmac_configure_loopback); + +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; + + val = readl_relaxed(gmac->base + GMAC_ANEG_REG); + + 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; + } + + if (tx_pause || rx_pause) + val |= GMAC_ANEG_CONFIG_FLOW_CTRL; + + 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_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_an_restart); + +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); + u32 new_an, gmac_an = readl_relaxed(gmac->base + GMAC_ANEG_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 & ~GMAC_CTRL4_SHORT_PREAMBLE_ENABLE; + new_an = gmac_an & ~(GMAC_ANEG_INBAND_AN_ENABLE | + GMAC_ANEG_INBAND_RESTART_AN | + GMAC_ANEG_AN_SPEED_ENABLE | + GMAC_ANEG_AN_DUPLEX_ENABLE); + + /* 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; + new_an = (new_an & ~(GMAC_ANEG_MII_SPEED | + GMAC_ANEG_GMII_SPEED | + GMAC_ANEG_FULL_DUPLEX)) | + GMAC_ANEG_INBAND_AN_ENABLE | + GMAC_ANEG_AN_SPEED_ENABLE | + GMAC_ANEG_AN_DUPLEX_ENABLE; + } else { + /* 802.3z negotiation - 1000BaseX */ + new_ctrl0 |= GMAC_CTRL0_PORT_1000BASE_X; + new_an = (new_an & ~GMAC_ANEG_MII_SPEED) | + GMAC_ANEG_INBAND_AN_ENABLE | + GMAC_ANEG_GMII_SPEED | + /* The MAC only supports FD mode */ + GMAC_ANEG_FULL_DUPLEX; + } + + /* When at 2.5G, the link partner can send frames with shortened + * preambles. + */ + 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 (new_an != gmac_an) + writel_relaxed(new_an, gmac->base + GMAC_ANEG_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_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_ADVERT_SYM_FLOW_CTRL | GMAC_ANEG_AN_FLOW_CTRL_ENABLE; + val = 0; + if (phylink_test(advertising, Pause)) + val |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL; + + /* The FLOW_CTRL_ENABLE bit selects either the hardware automatically + * or the CONFIG_FLOW_CTRL manually controls the GMAC pause mode. + */ + if (phylink_autoneg_inband(mode) && + phy_interface_mode_is_8023z(interface) && + permit_pause_to_mac) + val |= GMAC_ANEG_AN_FLOW_CTRL_ENABLE; + + 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); +} +EXPORT_SYMBOL_GPL(mvgmac_pcs_config); + +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"); |