// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include "stmmac.h" #include "stmmac_platform.h" /* Peri Configuration register for mt2712 */ #define PERI_ETH_PHY_INTF_SEL 0x418 #define PHY_INTF_MII 0 #define PHY_INTF_RGMII 1 #define PHY_INTF_RMII 4 #define RMII_CLK_SRC_RXC BIT(4) #define RMII_CLK_SRC_INTERNAL BIT(5) #define PERI_ETH_DLY 0x428 #define ETH_DLY_GTXC_INV BIT(6) #define ETH_DLY_GTXC_ENABLE BIT(5) #define ETH_DLY_GTXC_STAGES GENMASK(4, 0) #define ETH_DLY_TXC_INV BIT(20) #define ETH_DLY_TXC_ENABLE BIT(19) #define ETH_DLY_TXC_STAGES GENMASK(18, 14) #define ETH_DLY_RXC_INV BIT(13) #define ETH_DLY_RXC_ENABLE BIT(12) #define ETH_DLY_RXC_STAGES GENMASK(11, 7) #define PERI_ETH_DLY_FINE 0x800 #define ETH_RMII_DLY_TX_INV BIT(2) #define ETH_FINE_DLY_GTXC BIT(1) #define ETH_FINE_DLY_RXC BIT(0) struct mac_delay_struct { u32 tx_delay; u32 rx_delay; bool tx_inv; bool rx_inv; }; struct mediatek_dwmac_plat_data { const struct mediatek_dwmac_variant *variant; struct mac_delay_struct mac_delay; struct clk_bulk_data *clks; struct device_node *np; struct regmap *peri_regmap; struct device *dev; phy_interface_t phy_mode; bool rmii_rxc; }; struct mediatek_dwmac_variant { int (*dwmac_set_phy_interface)(struct mediatek_dwmac_plat_data *plat); int (*dwmac_set_delay)(struct mediatek_dwmac_plat_data *plat); /* clock ids to be requested */ const char * const *clk_list; int num_clks; u32 dma_bit_mask; u32 rx_delay_max; u32 tx_delay_max; }; /* list of clocks required for mac */ static const char * const mt2712_dwmac_clk_l[] = { "axi", "apb", "mac_main", "ptp_ref" }; static int mt2712_set_interface(struct mediatek_dwmac_plat_data *plat) { int rmii_rxc = plat->rmii_rxc ? RMII_CLK_SRC_RXC : 0; u32 intf_val = 0; /* select phy interface in top control domain */ switch (plat->phy_mode) { case PHY_INTERFACE_MODE_MII: intf_val |= PHY_INTF_MII; break; case PHY_INTERFACE_MODE_RMII: intf_val |= (PHY_INTF_RMII | rmii_rxc); break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_TXID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_ID: intf_val |= PHY_INTF_RGMII; break; default: dev_err(plat->dev, "phy interface not supported\n"); return -EINVAL; } regmap_write(plat->peri_regmap, PERI_ETH_PHY_INTF_SEL, intf_val); return 0; } static void mt2712_delay_ps2stage(struct mediatek_dwmac_plat_data *plat) { struct mac_delay_struct *mac_delay = &plat->mac_delay; switch (plat->phy_mode) { case PHY_INTERFACE_MODE_MII: case PHY_INTERFACE_MODE_RMII: /* 550ps per stage for MII/RMII */ mac_delay->tx_delay /= 550; mac_delay->rx_delay /= 550; break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_TXID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_ID: /* 170ps per stage for RGMII */ mac_delay->tx_delay /= 170; mac_delay->rx_delay /= 170; break; default: dev_err(plat->dev, "phy interface not supported\n"); break; } } static void mt2712_delay_stage2ps(struct mediatek_dwmac_plat_data *plat) { struct mac_delay_struct *mac_delay = &plat->mac_delay; switch (plat->phy_mode) { case PHY_INTERFACE_MODE_MII: case PHY_INTERFACE_MODE_RMII: /* 550ps per stage for MII/RMII */ mac_delay->tx_delay *= 550; mac_delay->rx_delay *= 550; break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_TXID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_ID: /* 170ps per stage for RGMII */ mac_delay->tx_delay *= 170; mac_delay->rx_delay *= 170; break; default: dev_err(plat->dev, "phy interface not supported\n"); break; } } static int mt2712_set_delay(struct mediatek_dwmac_plat_data *plat) { struct mac_delay_struct *mac_delay = &plat->mac_delay; u32 delay_val = 0, fine_val = 0; mt2712_delay_ps2stage(plat); switch (plat->phy_mode) { case PHY_INTERFACE_MODE_MII: delay_val |= FIELD_PREP(ETH_DLY_TXC_ENABLE, !!mac_delay->tx_delay); delay_val |= FIELD_PREP(ETH_DLY_TXC_STAGES, mac_delay->tx_delay); delay_val |= FIELD_PREP(ETH_DLY_TXC_INV, mac_delay->tx_inv); delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv); break; case PHY_INTERFACE_MODE_RMII: /* the rmii reference clock is from external phy, * and the property "rmii_rxc" indicates which pin(TXC/RXC) * the reference clk is connected to. The reference clock is a * received signal, so rx_delay/rx_inv are used to indicate * the reference clock timing adjustment */ if (plat->rmii_rxc) { /* the rmii reference clock from outside is connected * to RXC pin, the reference clock will be adjusted * by RXC delay macro circuit. */ delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv); } else { /* the rmii reference clock from outside is connected * to TXC pin, the reference clock will be adjusted * by TXC delay macro circuit. */ delay_val |= FIELD_PREP(ETH_DLY_TXC_ENABLE, !!mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_TXC_STAGES, mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_TXC_INV, mac_delay->rx_inv); } /* tx_inv will inverse the tx clock inside mac relateive to * reference clock from external phy, * and this bit is located in the same register with fine-tune */ if (mac_delay->tx_inv) fine_val = ETH_RMII_DLY_TX_INV; break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_TXID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_ID: fine_val = ETH_FINE_DLY_GTXC | ETH_FINE_DLY_RXC; delay_val |= FIELD_PREP(ETH_DLY_GTXC_ENABLE, !!mac_delay->tx_delay); delay_val |= FIELD_PREP(ETH_DLY_GTXC_STAGES, mac_delay->tx_delay); delay_val |= FIELD_PREP(ETH_DLY_GTXC_INV, mac_delay->tx_inv); delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay); delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv); break; default: dev_err(plat->dev, "phy interface not supported\n"); return -EINVAL; } regmap_write(plat->peri_regmap, PERI_ETH_DLY, delay_val); regmap_write(plat->peri_regmap, PERI_ETH_DLY_FINE, fine_val); mt2712_delay_stage2ps(plat); return 0; } static const struct mediatek_dwmac_variant mt2712_gmac_variant = { .dwmac_set_phy_interface = mt2712_set_interface, .dwmac_set_delay = mt2712_set_delay, .clk_list = mt2712_dwmac_clk_l, .num_clks = ARRAY_SIZE(mt2712_dwmac_clk_l), .dma_bit_mask = 33, .rx_delay_max = 17600, .tx_delay_max = 17600, }; static int mediatek_dwmac_config_dt(struct mediatek_dwmac_plat_data *plat) { struct mac_delay_struct *mac_delay = &plat->mac_delay; u32 tx_delay_ps, rx_delay_ps; int err; plat->peri_regmap = syscon_regmap_lookup_by_phandle(plat->np, "mediatek,pericfg"); if (IS_ERR(plat->peri_regmap)) { dev_err(plat->dev, "Failed to get pericfg syscon\n"); return PTR_ERR(plat->peri_regmap); } err = of_get_phy_mode(plat->np, &plat->phy_mode); if (err) { dev_err(plat->dev, "not find phy-mode\n"); return err; } if (!of_property_read_u32(plat->np, "mediatek,tx-delay-ps", &tx_delay_ps)) { if (tx_delay_ps < plat->variant->tx_delay_max) { mac_delay->tx_delay = tx_delay_ps; } else { dev_err(plat->dev, "Invalid TX clock delay: %dps\n", tx_delay_ps); return -EINVAL; } } if (!of_property_read_u32(plat->np, "mediatek,rx-delay-ps", &rx_delay_ps)) { if (rx_delay_ps < plat->variant->rx_delay_max) { mac_delay->rx_delay = rx_delay_ps; } else { dev_err(plat->dev, "Invalid RX clock delay: %dps\n", rx_delay_ps); return -EINVAL; } } mac_delay->tx_inv = of_property_read_bool(plat->np, "mediatek,txc-inverse"); mac_delay->rx_inv = of_property_read_bool(plat->np, "mediatek,rxc-inverse"); plat->rmii_rxc = of_property_read_bool(plat->np, "mediatek,rmii-rxc"); return 0; } static int mediatek_dwmac_clk_init(struct mediatek_dwmac_plat_data *plat) { const struct mediatek_dwmac_variant *variant = plat->variant; int i, num = variant->num_clks; plat->clks = devm_kcalloc(plat->dev, num, sizeof(*plat->clks), GFP_KERNEL); if (!plat->clks) return -ENOMEM; for (i = 0; i < num; i++) plat->clks[i].id = variant->clk_list[i]; return devm_clk_bulk_get(plat->dev, num, plat->clks); } static int mediatek_dwmac_init(struct platform_device *pdev, void *priv) { struct mediatek_dwmac_plat_data *plat = priv; const struct mediatek_dwmac_variant *variant = plat->variant; int ret; ret = dma_set_mask_and_coherent(plat->dev, DMA_BIT_MASK(variant->dma_bit_mask)); if (ret) { dev_err(plat->dev, "No suitable DMA available, err = %d\n", ret); return ret; } ret = variant->dwmac_set_phy_interface(plat); if (ret) { dev_err(plat->dev, "failed to set phy interface, err = %d\n", ret); return ret; } ret = variant->dwmac_set_delay(plat); if (ret) { dev_err(plat->dev, "failed to set delay value, err = %d\n", ret); return ret; } ret = clk_bulk_prepare_enable(variant->num_clks, plat->clks); if (ret) { dev_err(plat->dev, "failed to enable clks, err = %d\n", ret); return ret; } pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); return 0; } static void mediatek_dwmac_exit(struct platform_device *pdev, void *priv) { struct mediatek_dwmac_plat_data *plat = priv; const struct mediatek_dwmac_variant *variant = plat->variant; clk_bulk_disable_unprepare(variant->num_clks, plat->clks); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); } static int mediatek_dwmac_probe(struct platform_device *pdev) { struct mediatek_dwmac_plat_data *priv_plat; struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; int ret; priv_plat = devm_kzalloc(&pdev->dev, sizeof(*priv_plat), GFP_KERNEL); if (!priv_plat) return -ENOMEM; priv_plat->variant = of_device_get_match_data(&pdev->dev); if (!priv_plat->variant) { dev_err(&pdev->dev, "Missing dwmac-mediatek variant\n"); return -EINVAL; } priv_plat->dev = &pdev->dev; priv_plat->np = pdev->dev.of_node; ret = mediatek_dwmac_config_dt(priv_plat); if (ret) return ret; ret = mediatek_dwmac_clk_init(priv_plat); if (ret) return 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)) return PTR_ERR(plat_dat); plat_dat->interface = priv_plat->phy_mode; plat_dat->has_gmac4 = 1; plat_dat->has_gmac = 0; plat_dat->pmt = 0; plat_dat->riwt_off = 1; plat_dat->maxmtu = ETH_DATA_LEN; plat_dat->bsp_priv = priv_plat; plat_dat->init = mediatek_dwmac_init; plat_dat->exit = mediatek_dwmac_exit; mediatek_dwmac_init(pdev, priv_plat); ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); if (ret) { stmmac_remove_config_dt(pdev, plat_dat); return ret; } return 0; } static const struct of_device_id mediatek_dwmac_match[] = { { .compatible = "mediatek,mt2712-gmac", .data = &mt2712_gmac_variant }, { } }; MODULE_DEVICE_TABLE(of, mediatek_dwmac_match); static struct platform_driver mediatek_dwmac_driver = { .probe = mediatek_dwmac_probe, .remove = stmmac_pltfr_remove, .driver = { .name = "dwmac-mediatek", .pm = &stmmac_pltfr_pm_ops, .of_match_table = mediatek_dwmac_match, }, }; module_platform_driver(mediatek_dwmac_driver); MODULE_AUTHOR("Biao Huang "); MODULE_DESCRIPTION("MediaTek DWMAC specific glue layer"); MODULE_LICENSE("GPL v2");