diff options
Diffstat (limited to 'drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c')
-rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c new file mode 100644 index 000000000..732774645 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c @@ -0,0 +1,615 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-19, Linaro Limited + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/phy.h> +#include "stmmac.h" +#include "stmmac_platform.h" + +#define RGMII_IO_MACRO_CONFIG 0x0 +#define SDCC_HC_REG_DLL_CONFIG 0x4 +#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 + +/* 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) + +/* 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_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) + +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_looback_en; +}; + +struct qcom_ethqos { + struct platform_device *pdev; + void __iomem *rgmii_base; + + unsigned int rgmii_clk_rate; + struct clk *rgmii_clk; + unsigned int speed; + + const struct ethqos_emac_por *por; + unsigned int num_por; + bool rgmii_config_looback_en; +}; + +static int rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset) +{ + return readl(ethqos->rgmii_base + offset); +} + +static void rgmii_writel(struct qcom_ethqos *ethqos, + int value, unsigned int offset) +{ + writel(value, ethqos->rgmii_base + offset); +} + +static void rgmii_updatel(struct qcom_ethqos *ethqos, + int mask, int val, unsigned int offset) +{ + unsigned int temp; + + temp = rgmii_readl(ethqos, offset); + temp = (temp & ~(mask)) | val; + rgmii_writel(ethqos, temp, offset); +} + +static void rgmii_dump(void *priv) +{ + struct qcom_ethqos *ethqos = priv; + + dev_dbg(ðqos->pdev->dev, "Rgmii register dump\n"); + dev_dbg(ðqos->pdev->dev, "RGMII_IO_MACRO_CONFIG: %x\n", + rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG)); + dev_dbg(ðqos->pdev->dev, "SDCC_HC_REG_DLL_CONFIG: %x\n", + rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG)); + dev_dbg(ðqos->pdev->dev, "SDCC_HC_REG_DDR_CONFIG: %x\n", + rgmii_readl(ethqos, SDCC_HC_REG_DDR_CONFIG)); + dev_dbg(ðqos->pdev->dev, "SDCC_HC_REG_DLL_CONFIG2: %x\n", + rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG2)); + dev_dbg(ðqos->pdev->dev, "SDC4_STATUS: %x\n", + rgmii_readl(ethqos, SDC4_STATUS)); + dev_dbg(ðqos->pdev->dev, "SDCC_USR_CTL: %x\n", + rgmii_readl(ethqos, SDCC_USR_CTL)); + dev_dbg(ðqos->pdev->dev, "RGMII_IO_MACRO_CONFIG2: %x\n", + rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG2)); + dev_dbg(ðqos->pdev->dev, "RGMII_IO_MACRO_DEBUG1: %x\n", + rgmii_readl(ethqos, RGMII_IO_MACRO_DEBUG1)); + dev_dbg(ðqos->pdev->dev, "EMAC_SYSTEM_LOW_POWER_DEBUG: %x\n", + rgmii_readl(ethqos, EMAC_SYSTEM_LOW_POWER_DEBUG)); +} + +/* Clock rates */ +#define RGMII_1000_NOM_CLK_FREQ (250 * 1000 * 1000UL) +#define RGMII_ID_MODE_100_LOW_SVS_CLK_FREQ (50 * 1000 * 1000UL) +#define RGMII_ID_MODE_10_LOW_SVS_CLK_FREQ (5 * 1000 * 1000UL) + +static void +ethqos_update_rgmii_clk(struct qcom_ethqos *ethqos, unsigned int speed) +{ + switch (speed) { + case SPEED_1000: + ethqos->rgmii_clk_rate = RGMII_1000_NOM_CLK_FREQ; + break; + + case SPEED_100: + ethqos->rgmii_clk_rate = RGMII_ID_MODE_100_LOW_SVS_CLK_FREQ; + break; + + case SPEED_10: + ethqos->rgmii_clk_rate = RGMII_ID_MODE_10_LOW_SVS_CLK_FREQ; + break; + } + + clk_set_rate(ethqos->rgmii_clk, ethqos->rgmii_clk_rate); +} + +static void ethqos_set_func_clk_en(struct qcom_ethqos *ethqos) +{ + rgmii_updatel(ethqos, RGMII_CONFIG_FUNC_CLK_EN, + 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_looback_en = true, +}; + +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_looback_en = false, +}; + +static int ethqos_dll_configure(struct qcom_ethqos *ethqos) +{ + unsigned int val; + int retry = 1000; + + /* Set CDR_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_CDR_EN, + SDCC_DLL_CONFIG_CDR_EN, SDCC_HC_REG_DLL_CONFIG); + + /* Set CDR_EXT_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_CDR_EXT_EN, + SDCC_DLL_CONFIG_CDR_EXT_EN, SDCC_HC_REG_DLL_CONFIG); + + /* Clear CK_OUT_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN, + 0, SDCC_HC_REG_DLL_CONFIG); + + /* Set DLL_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_DLL_EN, + SDCC_DLL_CONFIG_DLL_EN, SDCC_HC_REG_DLL_CONFIG); + + rgmii_updatel(ethqos, SDCC_DLL_MCLK_GATING_EN, + 0, SDCC_HC_REG_DLL_CONFIG); + + rgmii_updatel(ethqos, SDCC_DLL_CDR_FINE_PHASE, + 0, SDCC_HC_REG_DLL_CONFIG); + + /* Wait for CK_OUT_EN clear */ + do { + val = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG); + val &= SDCC_DLL_CONFIG_CK_OUT_EN; + if (!val) + break; + mdelay(1); + retry--; + } while (retry > 0); + if (!retry) + dev_err(ðqos->pdev->dev, "Clear CK_OUT_EN timedout\n"); + + /* Set CK_OUT_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN, + SDCC_DLL_CONFIG_CK_OUT_EN, SDCC_HC_REG_DLL_CONFIG); + + /* Wait for CK_OUT_EN set */ + retry = 1000; + do { + val = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG); + val &= SDCC_DLL_CONFIG_CK_OUT_EN; + if (val) + break; + mdelay(1); + retry--; + } while (retry > 0); + if (!retry) + dev_err(ðqos->pdev->dev, "Set CK_OUT_EN timedout\n"); + + /* Set DDR_CAL_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_DDR_CAL_EN, + SDCC_DLL_CONFIG2_DDR_CAL_EN, SDCC_HC_REG_DLL_CONFIG2); + + rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_DLL_CLOCK_DIS, + 0, 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_updatel(ethqos, SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW, + SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW, + SDCC_HC_REG_DLL_CONFIG2); + + return 0; +} + +static int ethqos_rgmii_macro_init(struct qcom_ethqos *ethqos) +{ + /* Disable loopback mode */ + rgmii_updatel(ethqos, RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN, + 0, RGMII_IO_MACRO_CONFIG2); + + /* Select RGMII, write 0 to interface select */ + rgmii_updatel(ethqos, RGMII_CONFIG_INTF_SEL, + 0, RGMII_IO_MACRO_CONFIG); + + switch (ethqos->speed) { + case SPEED_1000: + rgmii_updatel(ethqos, RGMII_CONFIG_DDR_MODE, + RGMII_CONFIG_DDR_MODE, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN, + 0, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL, + RGMII_CONFIG_POS_NEG_DATA_SEL, + RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_PROG_SWAP, + RGMII_CONFIG_PROG_SWAP, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, + RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, + RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_RSVD_CONFIG15, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_RX_PROG_SWAP, + RGMII_CONFIG2_RX_PROG_SWAP, + RGMII_IO_MACRO_CONFIG2); + + /* Set PRG_RCLK_DLY to 57 for 1.8 ns delay */ + rgmii_updatel(ethqos, SDCC_DDR_CONFIG_PRG_RCLK_DLY, + 57, SDCC_HC_REG_DDR_CONFIG); + rgmii_updatel(ethqos, SDCC_DDR_CONFIG_PRG_DLY_EN, + SDCC_DDR_CONFIG_PRG_DLY_EN, + SDCC_HC_REG_DDR_CONFIG); + if (ethqos->rgmii_config_looback_en) + rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, + RGMII_CONFIG_LOOPBACK_EN, RGMII_IO_MACRO_CONFIG); + else + rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, + 0, RGMII_IO_MACRO_CONFIG); + break; + + case SPEED_100: + rgmii_updatel(ethqos, RGMII_CONFIG_DDR_MODE, + RGMII_CONFIG_DDR_MODE, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN, + RGMII_CONFIG_BYPASS_TX_ID_EN, + RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL, + 0, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_PROG_SWAP, + 0, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, + RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, + RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_2, + BIT(6), RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG2_RSVD_CONFIG15, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_RX_PROG_SWAP, + 0, 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_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY, + SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY, + SDCC_HC_REG_DDR_CONFIG); + rgmii_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN, + SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN, + SDCC_HC_REG_DDR_CONFIG); + if (ethqos->rgmii_config_looback_en) + rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, + RGMII_CONFIG_LOOPBACK_EN, RGMII_IO_MACRO_CONFIG); + else + rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, + 0, RGMII_IO_MACRO_CONFIG); + + break; + + case SPEED_10: + rgmii_updatel(ethqos, RGMII_CONFIG_DDR_MODE, + RGMII_CONFIG_DDR_MODE, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN, + RGMII_CONFIG_BYPASS_TX_ID_EN, + RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL, + 0, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_PROG_SWAP, + 0, RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_9, + BIT(12) | GENMASK(9, 8), + RGMII_IO_MACRO_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG2_RSVD_CONFIG15, + 0, RGMII_IO_MACRO_CONFIG2); + rgmii_updatel(ethqos, RGMII_CONFIG2_RX_PROG_SWAP, + 0, 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_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY, + SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY, + SDCC_HC_REG_DDR_CONFIG); + rgmii_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN, + SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN, + SDCC_HC_REG_DDR_CONFIG); + rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, + RGMII_CONFIG_LOOPBACK_EN, RGMII_IO_MACRO_CONFIG); + break; + default: + dev_err(ðqos->pdev->dev, + "Invalid speed %d\n", ethqos->speed); + return -EINVAL; + } + + return 0; +} + +static int ethqos_configure(struct qcom_ethqos *ethqos) +{ + volatile unsigned int dll_lock; + unsigned int i, retry = 1000; + + /* 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_updatel(ethqos, SDCC_DLL_CONFIG_DLL_RST, + SDCC_DLL_CONFIG_DLL_RST, SDCC_HC_REG_DLL_CONFIG); + + /* Set PDN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_PDN, + SDCC_DLL_CONFIG_PDN, SDCC_HC_REG_DLL_CONFIG); + + /* Clear DLL_RST */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_DLL_RST, 0, + SDCC_HC_REG_DLL_CONFIG); + + /* Clear PDN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_PDN, 0, + SDCC_HC_REG_DLL_CONFIG); + + if (ethqos->speed != SPEED_100 && ethqos->speed != SPEED_10) { + /* Set DLL_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_DLL_EN, + SDCC_DLL_CONFIG_DLL_EN, SDCC_HC_REG_DLL_CONFIG); + + /* Set CK_OUT_EN */ + rgmii_updatel(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN, + SDCC_DLL_CONFIG_CK_OUT_EN, + SDCC_HC_REG_DLL_CONFIG); + + /* Set USR_CTL bit 26 with mask of 3 bits */ + rgmii_updatel(ethqos, GENMASK(26, 24), BIT(26), SDCC_USR_CTL); + + /* wait for DLL LOCK */ + do { + mdelay(1); + dll_lock = rgmii_readl(ethqos, SDC4_STATUS); + if (dll_lock & SDC4_STATUS_DLL_LOCK) + break; + retry--; + } while (retry > 0); + if (!retry) + dev_err(ðqos->pdev->dev, + "Timeout while waiting for DLL lock\n"); + } + + if (ethqos->speed == SPEED_1000) + ethqos_dll_configure(ethqos); + + ethqos_rgmii_macro_init(ethqos); + + return 0; +} + +static void ethqos_fix_mac_speed(void *priv, unsigned int speed) +{ + struct qcom_ethqos *ethqos = priv; + + ethqos->speed = speed; + ethqos_update_rgmii_clk(ethqos, speed); + ethqos_configure(ethqos); +} + +static int ethqos_clks_config(void *priv, bool enabled) +{ + struct qcom_ethqos *ethqos = priv; + int ret = 0; + + if (enabled) { + ret = clk_prepare_enable(ethqos->rgmii_clk); + if (ret) { + dev_err(ðqos->pdev->dev, "rgmii_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->rgmii_clk); + } + + return ret; +} + +static int qcom_ethqos_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct plat_stmmacenet_data *plat_dat; + struct stmmac_resources stmmac_res; + const struct ethqos_emac_driver_data *data; + struct qcom_ethqos *ethqos; + int ret; + + ret = stmmac_get_platform_resources(pdev, &stmmac_res); + if (ret) + return ret; + + plat_dat = stmmac_probe_config_dt(pdev, stmmac_res.mac); + if (IS_ERR(plat_dat)) { + dev_err(&pdev->dev, "dt configuration failed\n"); + return PTR_ERR(plat_dat); + } + + plat_dat->clks_config = ethqos_clks_config; + + ethqos = devm_kzalloc(&pdev->dev, sizeof(*ethqos), GFP_KERNEL); + if (!ethqos) { + ret = -ENOMEM; + goto err_mem; + } + + ethqos->pdev = pdev; + ethqos->rgmii_base = devm_platform_ioremap_resource_byname(pdev, "rgmii"); + if (IS_ERR(ethqos->rgmii_base)) { + ret = PTR_ERR(ethqos->rgmii_base); + goto err_mem; + } + + data = of_device_get_match_data(&pdev->dev); + ethqos->por = data->por; + ethqos->num_por = data->num_por; + ethqos->rgmii_config_looback_en = data->rgmii_config_looback_en; + + ethqos->rgmii_clk = devm_clk_get(&pdev->dev, "rgmii"); + if (IS_ERR(ethqos->rgmii_clk)) { + ret = PTR_ERR(ethqos->rgmii_clk); + goto err_mem; + } + + ret = ethqos_clks_config(ethqos, true); + if (ret) + goto err_mem; + + ethqos->speed = SPEED_1000; + ethqos_update_rgmii_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->has_gmac4 = 1; + plat_dat->pmt = 1; + plat_dat->tso_en = of_property_read_bool(np, "snps,tso"); + if (of_device_is_compatible(np, "qcom,qcs404-ethqos")) + plat_dat->rx_clk_runs_in_lpi = 1; + + ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); + if (ret) + goto err_clk; + + return ret; + +err_clk: + ethqos_clks_config(ethqos, false); + +err_mem: + stmmac_remove_config_dt(pdev, plat_dat); + + return ret; +} + +static int qcom_ethqos_remove(struct platform_device *pdev) +{ + struct qcom_ethqos *ethqos; + int ret; + + ethqos = get_stmmac_bsp_priv(&pdev->dev); + if (!ethqos) + return -ENODEV; + + ret = stmmac_pltfr_remove(pdev); + ethqos_clks_config(ethqos, false); + + return ret; +} + +static const struct of_device_id qcom_ethqos_match[] = { + { .compatible = "qcom,qcs404-ethqos", .data = &emac_v2_3_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, + .remove = qcom_ethqos_remove, + .driver = { + .name = "qcom-ethqos", + .pm = &stmmac_pltfr_pm_ops, + .of_match_table = of_match_ptr(qcom_ethqos_match), + }, +}; +module_platform_driver(qcom_ethqos_driver); + +MODULE_DESCRIPTION("Qualcomm ETHQOS driver"); +MODULE_LICENSE("GPL v2"); |