root/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018-19, Linaro Limited

#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>

#include "stmmac.h"
#include "stmmac_platform.h"

#define RGMII_IO_MACRO_CONFIG           0x0
#define SDCC_HC_REG_DLL_CONFIG          0x4
#define SDCC_TEST_CTL                   0x8
#define SDCC_HC_REG_DDR_CONFIG          0xC
#define SDCC_HC_REG_DLL_CONFIG2         0x10
#define SDC4_STATUS                     0x14
#define SDCC_USR_CTL                    0x18
#define RGMII_IO_MACRO_CONFIG2          0x1C
#define RGMII_IO_MACRO_DEBUG1           0x20
#define EMAC_SYSTEM_LOW_POWER_DEBUG     0x28
#define EMAC_WRAPPER_SGMII_PHY_CNTRL1   0xf4

/* RGMII_IO_MACRO_CONFIG fields */
#define RGMII_CONFIG_FUNC_CLK_EN                BIT(30)
#define RGMII_CONFIG_POS_NEG_DATA_SEL           BIT(23)
#define RGMII_CONFIG_GPIO_CFG_RX_INT            GENMASK(21, 20)
#define RGMII_CONFIG_GPIO_CFG_TX_INT            GENMASK(19, 17)
#define RGMII_CONFIG_MAX_SPD_PRG_9              GENMASK(16, 8)
#define RGMII_CONFIG_MAX_SPD_PRG_2              GENMASK(7, 6)
#define RGMII_CONFIG_INTF_SEL                   GENMASK(5, 4)
#define RGMII_CONFIG_BYPASS_TX_ID_EN            BIT(3)
#define RGMII_CONFIG_LOOPBACK_EN                BIT(2)
#define RGMII_CONFIG_PROG_SWAP                  BIT(1)
#define RGMII_CONFIG_DDR_MODE                   BIT(0)
#define RGMII_CONFIG_SGMII_CLK_DVDR             GENMASK(18, 10)

/* SDCC_HC_REG_DLL_CONFIG fields */
#define SDCC_DLL_CONFIG_DLL_RST                 BIT(30)
#define SDCC_DLL_CONFIG_PDN                     BIT(29)
#define SDCC_DLL_CONFIG_MCLK_FREQ               GENMASK(26, 24)
#define SDCC_DLL_CONFIG_CDR_SELEXT              GENMASK(23, 20)
#define SDCC_DLL_CONFIG_CDR_EXT_EN              BIT(19)
#define SDCC_DLL_CONFIG_CK_OUT_EN               BIT(18)
#define SDCC_DLL_CONFIG_CDR_EN                  BIT(17)
#define SDCC_DLL_CONFIG_DLL_EN                  BIT(16)
#define SDCC_DLL_MCLK_GATING_EN                 BIT(5)
#define SDCC_DLL_CDR_FINE_PHASE                 GENMASK(3, 2)

/* SDCC_HC_REG_DDR_CONFIG fields */
#define SDCC_DDR_CONFIG_PRG_DLY_EN              BIT(31)
#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY        GENMASK(26, 21)
#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE   GENMASK(29, 27)
#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN     BIT(30)
#define SDCC_DDR_CONFIG_TCXO_CYCLES_CNT         GENMASK(11, 9)
#define SDCC_DDR_CONFIG_PRG_RCLK_DLY            GENMASK(8, 0)

/* SDCC_HC_REG_DLL_CONFIG2 fields */
#define SDCC_DLL_CONFIG2_DLL_CLOCK_DIS          BIT(21)
#define SDCC_DLL_CONFIG2_MCLK_FREQ_CALC         GENMASK(17, 10)
#define SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SEL   GENMASK(3, 2)
#define SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW    BIT(1)
#define SDCC_DLL_CONFIG2_DDR_CAL_EN             BIT(0)

/* SDC4_STATUS bits */
#define SDC4_STATUS_DLL_LOCK                    BIT(7)

/* RGMII_IO_MACRO_CONFIG2 fields */
#define RGMII_CONFIG2_RSVD_CONFIG15             GENMASK(31, 17)
#define RGMII_CONFIG2_RGMII_CLK_SEL_CFG         BIT(16)
#define RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN      BIT(13)
#define RGMII_CONFIG2_CLK_DIVIDE_SEL            BIT(12)
#define RGMII_CONFIG2_RX_PROG_SWAP              BIT(7)
#define RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL       BIT(6)
#define RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN     BIT(5)

/* EMAC_WRAPPER_SGMII_PHY_CNTRL1 bits */
#define SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN     BIT(3)

#define SGMII_10M_RX_CLK_DVDR                   0x31

struct ethqos_emac_por {
        unsigned int offset;
        unsigned int value;
};

struct ethqos_emac_driver_data {
        const struct ethqos_emac_por *por;
        unsigned int num_por;
        bool rgmii_config_loopback_en;
        bool has_emac_ge_3;
        const char *link_clk_name;
        u32 dma_addr_width;
        struct dwmac4_addrs dwmac4_addrs;
        bool needs_sgmii_loopback;
};

struct qcom_ethqos {
        struct platform_device *pdev;
        void __iomem *rgmii_base;
        int (*configure_func)(struct qcom_ethqos *ethqos, int speed);

        unsigned int link_clk_rate;
        struct clk *link_clk;
        struct phy *serdes_phy;
        int serdes_speed;
        phy_interface_t phy_mode;

        const struct ethqos_emac_por *por;
        unsigned int num_por;
        bool rgmii_config_loopback_en;
        bool has_emac_ge_3;
        bool needs_sgmii_loopback;
};

static u32 rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset)
{
        return readl(ethqos->rgmii_base + offset);
}

static void rgmii_writel(struct qcom_ethqos *ethqos, u32 value,
                         unsigned int offset)
{
        writel(value, ethqos->rgmii_base + offset);
}

static void rgmii_updatel(struct qcom_ethqos *ethqos, u32 mask, u32 val,
                          unsigned int offset)
{
        u32 temp;

        temp = rgmii_readl(ethqos, offset);
        temp = (temp & ~(mask)) | val;
        rgmii_writel(ethqos, temp, offset);
}

static void rgmii_setmask(struct qcom_ethqos *ethqos, u32 mask,
                          unsigned int offset)
{
        rgmii_updatel(ethqos, mask, mask, offset);
}

static void rgmii_clrmask(struct qcom_ethqos *ethqos, u32 mask,
                          unsigned int offset)
{
        rgmii_updatel(ethqos, mask, 0, offset);
}

static void rgmii_dump(void *priv)
{
        struct qcom_ethqos *ethqos = priv;
        struct device *dev = &ethqos->pdev->dev;

        dev_dbg(dev, "Rgmii register dump\n");
        dev_dbg(dev, "RGMII_IO_MACRO_CONFIG: %x\n",
                rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG));
        dev_dbg(dev, "SDCC_HC_REG_DLL_CONFIG: %x\n",
                rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG));
        dev_dbg(dev, "SDCC_HC_REG_DDR_CONFIG: %x\n",
                rgmii_readl(ethqos, SDCC_HC_REG_DDR_CONFIG));
        dev_dbg(dev, "SDCC_HC_REG_DLL_CONFIG2: %x\n",
                rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG2));
        dev_dbg(dev, "SDC4_STATUS: %x\n",
                rgmii_readl(ethqos, SDC4_STATUS));
        dev_dbg(dev, "SDCC_USR_CTL: %x\n",
                rgmii_readl(ethqos, SDCC_USR_CTL));
        dev_dbg(dev, "RGMII_IO_MACRO_CONFIG2: %x\n",
                rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG2));
        dev_dbg(dev, "RGMII_IO_MACRO_DEBUG1: %x\n",
                rgmii_readl(ethqos, RGMII_IO_MACRO_DEBUG1));
        dev_dbg(dev, "EMAC_SYSTEM_LOW_POWER_DEBUG: %x\n",
                rgmii_readl(ethqos, EMAC_SYSTEM_LOW_POWER_DEBUG));
}

static void
ethqos_update_link_clk(struct qcom_ethqos *ethqos, int speed)
{
        long rate;

        if (!phy_interface_mode_is_rgmii(ethqos->phy_mode))
                return;

        rate = rgmii_clock(speed);
        if (rate > 0)
                ethqos->link_clk_rate = rate * 2;

        clk_set_rate(ethqos->link_clk, ethqos->link_clk_rate);
}

static void
qcom_ethqos_set_sgmii_loopback(struct qcom_ethqos *ethqos, bool enable)
{
        if (!ethqos->needs_sgmii_loopback ||
            ethqos->phy_mode != PHY_INTERFACE_MODE_2500BASEX)
                return;

        rgmii_updatel(ethqos,
                      SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN,
                      enable ? SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN : 0,
                      EMAC_WRAPPER_SGMII_PHY_CNTRL1);
}

static void ethqos_set_func_clk_en(struct qcom_ethqos *ethqos)
{
        qcom_ethqos_set_sgmii_loopback(ethqos, true);
        rgmii_setmask(ethqos, RGMII_CONFIG_FUNC_CLK_EN, RGMII_IO_MACRO_CONFIG);
}

static const struct ethqos_emac_por emac_v2_3_0_por[] = {
        { .offset = RGMII_IO_MACRO_CONFIG,      .value = 0x00C01343 },
        { .offset = SDCC_HC_REG_DLL_CONFIG,     .value = 0x2004642C },
        { .offset = SDCC_HC_REG_DDR_CONFIG,     .value = 0x00000000 },
        { .offset = SDCC_HC_REG_DLL_CONFIG2,    .value = 0x00200000 },
        { .offset = SDCC_USR_CTL,               .value = 0x00010800 },
        { .offset = RGMII_IO_MACRO_CONFIG2,     .value = 0x00002060 },
};

static const struct ethqos_emac_driver_data emac_v2_3_0_data = {
        .por = emac_v2_3_0_por,
        .num_por = ARRAY_SIZE(emac_v2_3_0_por),
        .rgmii_config_loopback_en = true,
        .has_emac_ge_3 = false,
};

static const struct ethqos_emac_por emac_v2_1_0_por[] = {
        { .offset = RGMII_IO_MACRO_CONFIG,      .value = 0x40C01343 },
        { .offset = SDCC_HC_REG_DLL_CONFIG,     .value = 0x2004642C },
        { .offset = SDCC_HC_REG_DDR_CONFIG,     .value = 0x00000000 },
        { .offset = SDCC_HC_REG_DLL_CONFIG2,    .value = 0x00200000 },
        { .offset = SDCC_USR_CTL,               .value = 0x00010800 },
        { .offset = RGMII_IO_MACRO_CONFIG2,     .value = 0x00002060 },
};

static const struct ethqos_emac_driver_data emac_v2_1_0_data = {
        .por = emac_v2_1_0_por,
        .num_por = ARRAY_SIZE(emac_v2_1_0_por),
        .rgmii_config_loopback_en = false,
        .has_emac_ge_3 = false,
};

static const struct ethqos_emac_por emac_v3_0_0_por[] = {
        { .offset = RGMII_IO_MACRO_CONFIG,      .value = 0x40c01343 },
        { .offset = SDCC_HC_REG_DLL_CONFIG,     .value = 0x2004642c },
        { .offset = SDCC_HC_REG_DDR_CONFIG,     .value = 0x80040800 },
        { .offset = SDCC_HC_REG_DLL_CONFIG2,    .value = 0x00200000 },
        { .offset = SDCC_USR_CTL,               .value = 0x00010800 },
        { .offset = RGMII_IO_MACRO_CONFIG2,     .value = 0x00002060 },
};

static const struct ethqos_emac_driver_data emac_v3_0_0_data = {
        .por = emac_v3_0_0_por,
        .num_por = ARRAY_SIZE(emac_v3_0_0_por),
        .rgmii_config_loopback_en = false,
        .has_emac_ge_3 = true,
        .dwmac4_addrs = {
                .dma_chan = 0x00008100,
                .dma_chan_offset = 0x1000,
                .mtl_chan = 0x00008000,
                .mtl_chan_offset = 0x1000,
                .mtl_ets_ctrl = 0x00008010,
                .mtl_ets_ctrl_offset = 0x1000,
                .mtl_txq_weight = 0x00008018,
                .mtl_txq_weight_offset = 0x1000,
                .mtl_send_slp_cred = 0x0000801c,
                .mtl_send_slp_cred_offset = 0x1000,
                .mtl_high_cred = 0x00008020,
                .mtl_high_cred_offset = 0x1000,
                .mtl_low_cred = 0x00008024,
                .mtl_low_cred_offset = 0x1000,
        },
};

static const struct ethqos_emac_por emac_v4_0_0_por[] = {
        { .offset = RGMII_IO_MACRO_CONFIG,      .value = 0x40c01343 },
        { .offset = SDCC_HC_REG_DLL_CONFIG,     .value = 0x2004642c },
        { .offset = SDCC_HC_REG_DDR_CONFIG,     .value = 0x80040800 },
        { .offset = SDCC_HC_REG_DLL_CONFIG2,    .value = 0x00200000 },
        { .offset = SDCC_USR_CTL,               .value = 0x00010800 },
        { .offset = RGMII_IO_MACRO_CONFIG2,     .value = 0x00002060 },
};

static const struct ethqos_emac_driver_data emac_v4_0_0_data = {
        .por = emac_v4_0_0_por,
        .num_por = ARRAY_SIZE(emac_v4_0_0_por),
        .rgmii_config_loopback_en = false,
        .has_emac_ge_3 = true,
        .link_clk_name = "phyaux",
        .needs_sgmii_loopback = true,
        .dma_addr_width = 36,
        .dwmac4_addrs = {
                .dma_chan = 0x00008100,
                .dma_chan_offset = 0x1000,
                .mtl_chan = 0x00008000,
                .mtl_chan_offset = 0x1000,
                .mtl_ets_ctrl = 0x00008010,
                .mtl_ets_ctrl_offset = 0x1000,
                .mtl_txq_weight = 0x00008018,
                .mtl_txq_weight_offset = 0x1000,
                .mtl_send_slp_cred = 0x0000801c,
                .mtl_send_slp_cred_offset = 0x1000,
                .mtl_high_cred = 0x00008020,
                .mtl_high_cred_offset = 0x1000,
                .mtl_low_cred = 0x00008024,
                .mtl_low_cred_offset = 0x1000,
        },
};

static int ethqos_dll_configure(struct qcom_ethqos *ethqos)
{
        struct device *dev = &ethqos->pdev->dev;
        u32 val;

        /* Set CDR_EN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CDR_EN, SDCC_HC_REG_DLL_CONFIG);

        /* Set CDR_EXT_EN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CDR_EXT_EN,
                      SDCC_HC_REG_DLL_CONFIG);

        /* Clear CK_OUT_EN */
        rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
                      SDCC_HC_REG_DLL_CONFIG);

        /* Set DLL_EN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_EN, SDCC_HC_REG_DLL_CONFIG);

        if (!ethqos->has_emac_ge_3) {
                rgmii_clrmask(ethqos, SDCC_DLL_MCLK_GATING_EN,
                              SDCC_HC_REG_DLL_CONFIG);

                rgmii_clrmask(ethqos, SDCC_DLL_CDR_FINE_PHASE,
                              SDCC_HC_REG_DLL_CONFIG);
        }

        /* Wait for CK_OUT_EN clear */
        if (read_poll_timeout_atomic(rgmii_readl, val,
                                     !(val & SDCC_DLL_CONFIG_CK_OUT_EN),
                                     1000, 1000000, false,
                                     ethqos, SDCC_HC_REG_DLL_CONFIG))
                dev_err(dev, "Clear CK_OUT_EN timedout\n");

        /* Set CK_OUT_EN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
                      SDCC_HC_REG_DLL_CONFIG);

        /* Wait for CK_OUT_EN set */
        if (read_poll_timeout_atomic(rgmii_readl, val,
                                     val & SDCC_DLL_CONFIG_CK_OUT_EN,
                                     1000, 1000000, false,
                                     ethqos, SDCC_HC_REG_DLL_CONFIG))
                dev_err(dev, "Set CK_OUT_EN timedout\n");

        /* Set DDR_CAL_EN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG2_DDR_CAL_EN,
                      SDCC_HC_REG_DLL_CONFIG2);

        if (!ethqos->has_emac_ge_3) {
                rgmii_clrmask(ethqos, SDCC_DLL_CONFIG2_DLL_CLOCK_DIS,
                              SDCC_HC_REG_DLL_CONFIG2);

                rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_MCLK_FREQ_CALC,
                              0x1A << 10, SDCC_HC_REG_DLL_CONFIG2);

                rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SEL,
                              BIT(2), SDCC_HC_REG_DLL_CONFIG2);

                rgmii_setmask(ethqos, SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW,
                              SDCC_HC_REG_DLL_CONFIG2);
        }

        return 0;
}

static int ethqos_rgmii_macro_init(struct qcom_ethqos *ethqos, int speed)
{
        struct device *dev = &ethqos->pdev->dev;
        int phase_shift;
        int loopback;

        /* Determine if the PHY adds a 2 ns TX delay or the MAC handles it */
        if (ethqos->phy_mode == PHY_INTERFACE_MODE_RGMII_ID ||
            ethqos->phy_mode == PHY_INTERFACE_MODE_RGMII_TXID)
                phase_shift = 0;
        else
                phase_shift = RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN;

        /* Disable loopback mode */
        rgmii_clrmask(ethqos, RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN,
                      RGMII_IO_MACRO_CONFIG2);

        /* Determine if this platform wants loopback enabled after programming */
        if (ethqos->rgmii_config_loopback_en)
                loopback = RGMII_CONFIG_LOOPBACK_EN;
        else
                loopback = 0;

        /* Select RGMII, write 0 to interface select */
        rgmii_clrmask(ethqos, RGMII_CONFIG_INTF_SEL, RGMII_IO_MACRO_CONFIG);

        switch (speed) {
        case SPEED_1000:
                rgmii_setmask(ethqos, RGMII_CONFIG_DDR_MODE,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_setmask(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_setmask(ethqos, RGMII_CONFIG_PROG_SWAP,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL,
                              RGMII_IO_MACRO_CONFIG2);

                rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN,
                              phase_shift, RGMII_IO_MACRO_CONFIG2);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_RSVD_CONFIG15,
                              RGMII_IO_MACRO_CONFIG2);
                rgmii_setmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
                              RGMII_IO_MACRO_CONFIG2);

                /* PRG_RCLK_DLY = TCXO period * TCXO_CYCLES_CNT / 2 * RX delay ns,
                 * in practice this becomes PRG_RCLK_DLY = 52 * 4 / 2 * RX delay ns
                 */
                if (ethqos->has_emac_ge_3) {
                        /* 0.9 ns */
                        rgmii_updatel(ethqos, SDCC_DDR_CONFIG_PRG_RCLK_DLY,
                                      115, SDCC_HC_REG_DDR_CONFIG);
                } else {
                        /* 1.8 ns */
                        rgmii_updatel(ethqos, SDCC_DDR_CONFIG_PRG_RCLK_DLY,
                                      57, SDCC_HC_REG_DDR_CONFIG);
                }
                rgmii_setmask(ethqos, SDCC_DDR_CONFIG_PRG_DLY_EN,
                              SDCC_HC_REG_DDR_CONFIG);
                rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
                              loopback, RGMII_IO_MACRO_CONFIG);
                break;

        case SPEED_100:
                rgmii_setmask(ethqos, RGMII_CONFIG_DDR_MODE,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_setmask(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG_PROG_SWAP,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL,
                              RGMII_IO_MACRO_CONFIG2);
                rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN,
                              phase_shift, RGMII_IO_MACRO_CONFIG2);
                rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_2,
                              BIT(6), RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_RSVD_CONFIG15,
                              RGMII_IO_MACRO_CONFIG2);

                if (ethqos->has_emac_ge_3)
                        rgmii_setmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
                                      RGMII_IO_MACRO_CONFIG2);
                else
                        rgmii_clrmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
                                      RGMII_IO_MACRO_CONFIG2);

                /* Write 0x5 to PRG_RCLK_DLY_CODE */
                rgmii_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE,
                              (BIT(29) | BIT(27)), SDCC_HC_REG_DDR_CONFIG);
                rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY,
                              SDCC_HC_REG_DDR_CONFIG);
                rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN,
                              SDCC_HC_REG_DDR_CONFIG);
                rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
                              loopback, RGMII_IO_MACRO_CONFIG);
                break;

        case SPEED_10:
                rgmii_setmask(ethqos, RGMII_CONFIG_DDR_MODE,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_setmask(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG_PROG_SWAP,
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL,
                              RGMII_IO_MACRO_CONFIG2);
                rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN,
                              phase_shift, RGMII_IO_MACRO_CONFIG2);
                rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_9,
                              BIT(12) | GENMASK(9, 8),
                              RGMII_IO_MACRO_CONFIG);
                rgmii_clrmask(ethqos, RGMII_CONFIG2_RSVD_CONFIG15,
                              RGMII_IO_MACRO_CONFIG2);
                if (ethqos->has_emac_ge_3)
                        rgmii_setmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
                                      RGMII_IO_MACRO_CONFIG2);
                else
                        rgmii_clrmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
                                      RGMII_IO_MACRO_CONFIG2);
                /* Write 0x5 to PRG_RCLK_DLY_CODE */
                rgmii_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE,
                              (BIT(29) | BIT(27)), SDCC_HC_REG_DDR_CONFIG);
                rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY,
                              SDCC_HC_REG_DDR_CONFIG);
                rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN,
                              SDCC_HC_REG_DDR_CONFIG);
                rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
                              loopback, RGMII_IO_MACRO_CONFIG);
                break;
        default:
                dev_err(dev, "Invalid speed %d\n", speed);
                return -EINVAL;
        }

        return 0;
}

static int ethqos_configure_rgmii(struct qcom_ethqos *ethqos, int speed)
{
        struct device *dev = &ethqos->pdev->dev;
        unsigned int i;
        u32 val;

        /* Reset to POR values and enable clk */
        for (i = 0; i < ethqos->num_por; i++)
                rgmii_writel(ethqos, ethqos->por[i].value,
                             ethqos->por[i].offset);
        ethqos_set_func_clk_en(ethqos);

        /* Initialize the DLL first */

        /* Set DLL_RST */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_RST,
                      SDCC_HC_REG_DLL_CONFIG);

        /* Set PDN */
        rgmii_setmask(ethqos, SDCC_DLL_CONFIG_PDN,
                      SDCC_HC_REG_DLL_CONFIG);

        if (ethqos->has_emac_ge_3) {
                if (speed == SPEED_1000) {
                        rgmii_writel(ethqos, 0x1800000, SDCC_TEST_CTL);
                        rgmii_writel(ethqos, 0x2C010800, SDCC_USR_CTL);
                        rgmii_writel(ethqos, 0xA001, SDCC_HC_REG_DLL_CONFIG2);
                } else {
                        rgmii_writel(ethqos, 0x40010800, SDCC_USR_CTL);
                        rgmii_writel(ethqos, 0xA001, SDCC_HC_REG_DLL_CONFIG2);
                }
        }

        /* Clear DLL_RST */
        rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_DLL_RST, SDCC_HC_REG_DLL_CONFIG);

        /* Clear PDN */
        rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_PDN, SDCC_HC_REG_DLL_CONFIG);

        if (speed != SPEED_100 && speed != SPEED_10) {
                /* Set DLL_EN */
                rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_EN,
                              SDCC_HC_REG_DLL_CONFIG);

                /* Set CK_OUT_EN */
                rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
                              SDCC_HC_REG_DLL_CONFIG);

                /* Set USR_CTL bit 26 with mask of 3 bits */
                if (!ethqos->has_emac_ge_3)
                        rgmii_updatel(ethqos, GENMASK(26, 24), BIT(26),
                                      SDCC_USR_CTL);

                /* wait for DLL LOCK */
                if (read_poll_timeout_atomic(rgmii_readl, val,
                                             val & SDC4_STATUS_DLL_LOCK,
                                             1000, 1000000, true,
                                             ethqos, SDC4_STATUS))
                        dev_err(dev, "Timeout while waiting for DLL lock\n");
        }

        if (speed == SPEED_1000)
                ethqos_dll_configure(ethqos);

        ethqos_rgmii_macro_init(ethqos, speed);

        return 0;
}

static void ethqos_set_serdes_speed(struct qcom_ethqos *ethqos, int speed)
{
        if (ethqos->serdes_speed != speed) {
                phy_set_speed(ethqos->serdes_phy, speed);
                ethqos->serdes_speed = speed;
        }
}

static void ethqos_pcs_set_inband(struct stmmac_priv *priv, bool enable)
{
        stmmac_pcs_ctrl_ane(priv, enable, 0);
}

/* On interface toggle MAC registers gets reset.
 * Configure MAC block for SGMII on ethernet phy link up
 */
static int ethqos_configure_sgmii(struct qcom_ethqos *ethqos, int speed)
{
        struct net_device *dev = platform_get_drvdata(ethqos->pdev);
        struct stmmac_priv *priv = netdev_priv(dev);

        switch (speed) {
        case SPEED_2500:
                rgmii_setmask(ethqos, RGMII_CONFIG2_RGMII_CLK_SEL_CFG,
                              RGMII_IO_MACRO_CONFIG2);
                ethqos_set_serdes_speed(ethqos, SPEED_2500);
                ethqos_pcs_set_inband(priv, false);
                break;
        case SPEED_1000:
                rgmii_setmask(ethqos, RGMII_CONFIG2_RGMII_CLK_SEL_CFG,
                              RGMII_IO_MACRO_CONFIG2);
                ethqos_set_serdes_speed(ethqos, SPEED_1000);
                ethqos_pcs_set_inband(priv, true);
                break;
        case SPEED_100:
                ethqos_set_serdes_speed(ethqos, SPEED_1000);
                ethqos_pcs_set_inband(priv, true);
                break;
        case SPEED_10:
                rgmii_updatel(ethqos, RGMII_CONFIG_SGMII_CLK_DVDR,
                              FIELD_PREP(RGMII_CONFIG_SGMII_CLK_DVDR,
                                         SGMII_10M_RX_CLK_DVDR),
                              RGMII_IO_MACRO_CONFIG);
                ethqos_set_serdes_speed(ethqos, SPEED_1000);
                ethqos_pcs_set_inband(priv, true);
                break;
        }

        return 0;
}

static int ethqos_configure(struct qcom_ethqos *ethqos, int speed)
{
        return ethqos->configure_func(ethqos, speed);
}

static void ethqos_fix_mac_speed(void *priv, int speed, unsigned int mode)
{
        struct qcom_ethqos *ethqos = priv;

        qcom_ethqos_set_sgmii_loopback(ethqos, false);
        ethqos_update_link_clk(ethqos, speed);
        ethqos_configure(ethqos, speed);
}

static int qcom_ethqos_serdes_powerup(struct net_device *ndev, void *priv)
{
        struct qcom_ethqos *ethqos = priv;
        int ret;

        ret = phy_init(ethqos->serdes_phy);
        if (ret)
                return ret;

        ret = phy_power_on(ethqos->serdes_phy);
        if (ret) {
                phy_exit(ethqos->serdes_phy);
                return ret;
        }

        ret = phy_set_speed(ethqos->serdes_phy, ethqos->serdes_speed);
        if (ret) {
                phy_power_off(ethqos->serdes_phy);
                phy_exit(ethqos->serdes_phy);
        }

        return ret;
}

static void qcom_ethqos_serdes_powerdown(struct net_device *ndev, void *priv)
{
        struct qcom_ethqos *ethqos = priv;

        phy_power_off(ethqos->serdes_phy);
        phy_exit(ethqos->serdes_phy);
}

static int ethqos_clks_config(void *priv, bool enabled)
{
        struct qcom_ethqos *ethqos = priv;
        int ret = 0;

        if (enabled) {
                ret = clk_prepare_enable(ethqos->link_clk);
                if (ret) {
                        dev_err(&ethqos->pdev->dev, "link_clk enable failed\n");
                        return ret;
                }

                /* Enable functional clock to prevent DMA reset to timeout due
                 * to lacking PHY clock after the hardware block has been power
                 * cycled. The actual configuration will be adjusted once
                 * ethqos_fix_mac_speed() is invoked.
                 */
                ethqos_set_func_clk_en(ethqos);
        } else {
                clk_disable_unprepare(ethqos->link_clk);
        }

        return ret;
}

static void ethqos_clks_disable(void *data)
{
        ethqos_clks_config(data, false);
}

static void ethqos_ptp_clk_freq_config(struct stmmac_priv *priv)
{
        struct plat_stmmacenet_data *plat_dat = priv->plat;
        int err;

        if (!plat_dat->clk_ptp_ref)
                return;

        /* Max the PTP ref clock out to get the best resolution possible */
        err = clk_set_rate(plat_dat->clk_ptp_ref, ULONG_MAX);
        if (err)
                netdev_err(priv->dev, "Failed to max out clk_ptp_ref: %d\n", err);
        plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->clk_ptp_ref);

        netdev_dbg(priv->dev, "PTP rate %lu\n", plat_dat->clk_ptp_rate);
}

static int qcom_ethqos_probe(struct platform_device *pdev)
{
        struct device_node *np = pdev->dev.of_node;
        const struct ethqos_emac_driver_data *data;
        struct plat_stmmacenet_data *plat_dat;
        struct stmmac_resources stmmac_res;
        struct device *dev = &pdev->dev;
        struct qcom_ethqos *ethqos;
        int ret, i;

        ret = stmmac_get_platform_resources(pdev, &stmmac_res);
        if (ret)
                return dev_err_probe(dev, ret,
                                     "Failed to get platform resources\n");

        plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
        if (IS_ERR(plat_dat)) {
                return dev_err_probe(dev, PTR_ERR(plat_dat),
                                     "dt configuration failed\n");
        }

        plat_dat->clks_config = ethqos_clks_config;

        ethqos = devm_kzalloc(dev, sizeof(*ethqos), GFP_KERNEL);
        if (!ethqos)
                return -ENOMEM;

        ethqos->phy_mode = plat_dat->phy_interface;
        switch (ethqos->phy_mode) {
        case PHY_INTERFACE_MODE_RGMII:
        case PHY_INTERFACE_MODE_RGMII_ID:
        case PHY_INTERFACE_MODE_RGMII_RXID:
        case PHY_INTERFACE_MODE_RGMII_TXID:
                ethqos->configure_func = ethqos_configure_rgmii;
                break;
        case PHY_INTERFACE_MODE_2500BASEX:
        case PHY_INTERFACE_MODE_SGMII:
                ethqos->configure_func = ethqos_configure_sgmii;
                break;
        default:
                dev_err(dev, "Unsupported phy mode %s\n",
                        phy_modes(ethqos->phy_mode));
                return -EINVAL;
        }

        ethqos->pdev = pdev;
        ethqos->rgmii_base = devm_platform_ioremap_resource_byname(pdev, "rgmii");
        if (IS_ERR(ethqos->rgmii_base))
                return dev_err_probe(dev, PTR_ERR(ethqos->rgmii_base),
                                     "Failed to map rgmii resource\n");

        data = of_device_get_match_data(dev);
        ethqos->por = data->por;
        ethqos->num_por = data->num_por;
        ethqos->rgmii_config_loopback_en = data->rgmii_config_loopback_en;
        ethqos->has_emac_ge_3 = data->has_emac_ge_3;
        ethqos->needs_sgmii_loopback = data->needs_sgmii_loopback;

        ethqos->link_clk = devm_clk_get(dev, data->link_clk_name ?: "rgmii");
        if (IS_ERR(ethqos->link_clk))
                return dev_err_probe(dev, PTR_ERR(ethqos->link_clk),
                                     "Failed to get link_clk\n");

        ret = ethqos_clks_config(ethqos, true);
        if (ret)
                return ret;

        ret = devm_add_action_or_reset(dev, ethqos_clks_disable, ethqos);
        if (ret)
                return ret;

        ethqos->serdes_phy = devm_phy_optional_get(dev, "serdes");
        if (IS_ERR(ethqos->serdes_phy))
                return dev_err_probe(dev, PTR_ERR(ethqos->serdes_phy),
                                     "Failed to get serdes phy\n");

        ethqos->serdes_speed = SPEED_1000;
        ethqos_update_link_clk(ethqos, SPEED_1000);
        ethqos_set_func_clk_en(ethqos);

        plat_dat->bsp_priv = ethqos;
        plat_dat->fix_mac_speed = ethqos_fix_mac_speed;
        plat_dat->dump_debug_regs = rgmii_dump;
        plat_dat->ptp_clk_freq_config = ethqos_ptp_clk_freq_config;
        plat_dat->core_type = DWMAC_CORE_GMAC4;
        if (ethqos->has_emac_ge_3)
                plat_dat->dwmac4_addrs = &data->dwmac4_addrs;
        plat_dat->pmt = 1;
        if (of_property_read_bool(np, "snps,tso"))
                plat_dat->flags |= STMMAC_FLAG_TSO_EN;
        if (of_device_is_compatible(np, "qcom,qcs404-ethqos"))
                plat_dat->flags |= STMMAC_FLAG_RX_CLK_RUNS_IN_LPI;
        if (data->dma_addr_width)
                plat_dat->host_dma_width = data->dma_addr_width;

        if (ethqos->serdes_phy) {
                plat_dat->serdes_powerup = qcom_ethqos_serdes_powerup;
                plat_dat->serdes_powerdown  = qcom_ethqos_serdes_powerdown;
        }

        /* Enable TSO on queue0 and enable TBS on rest of the queues */
        for (i = 1; i < plat_dat->tx_queues_to_use; i++)
                plat_dat->tx_queues_cfg[i].tbs_en = 1;

        return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
}

static const struct of_device_id qcom_ethqos_match[] = {
        { .compatible = "qcom,qcs404-ethqos", .data = &emac_v2_3_0_data},
        { .compatible = "qcom,sa8775p-ethqos", .data = &emac_v4_0_0_data},
        { .compatible = "qcom,sc8280xp-ethqos", .data = &emac_v3_0_0_data},
        { .compatible = "qcom,sm8150-ethqos", .data = &emac_v2_1_0_data},
        { }
};
MODULE_DEVICE_TABLE(of, qcom_ethqos_match);

static struct platform_driver qcom_ethqos_driver = {
        .probe  = qcom_ethqos_probe,
        .driver = {
                .name           = "qcom-ethqos",
                .pm             = &stmmac_pltfr_pm_ops,
                .of_match_table = qcom_ethqos_match,
        },
};
module_platform_driver(qcom_ethqos_driver);

MODULE_DESCRIPTION("Qualcomm ETHQOS driver");
MODULE_LICENSE("GPL v2");