summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/marvell/mvgmac.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/marvell/mvgmac.c')
-rw-r--r--drivers/net/ethernet/marvell/mvgmac.c360
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");