1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * T-HEAD DWMAC platform driver 4 * 5 * Copyright (C) 2021 Alibaba Group Holding Limited. 6 * Copyright (C) 2023 Jisheng Zhang <jszhang@kernel.org> 7 * 8 */ 9 10 #include <linux/bitfield.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/of_device.h> 14 #include <linux/of_net.h> 15 #include <linux/platform_device.h> 16 17 #include "stmmac_platform.h" 18 19 #define GMAC_CLK_EN 0x00 20 #define GMAC_TX_CLK_EN BIT(1) 21 #define GMAC_TX_CLK_N_EN BIT(2) 22 #define GMAC_TX_CLK_OUT_EN BIT(3) 23 #define GMAC_RX_CLK_EN BIT(4) 24 #define GMAC_RX_CLK_N_EN BIT(5) 25 #define GMAC_EPHY_REF_CLK_EN BIT(6) 26 #define GMAC_RXCLK_DELAY_CTRL 0x04 27 #define GMAC_RXCLK_BYPASS BIT(15) 28 #define GMAC_RXCLK_INVERT BIT(14) 29 #define GMAC_RXCLK_DELAY GENMASK(4, 0) 30 #define GMAC_TXCLK_DELAY_CTRL 0x08 31 #define GMAC_TXCLK_BYPASS BIT(15) 32 #define GMAC_TXCLK_INVERT BIT(14) 33 #define GMAC_TXCLK_DELAY GENMASK(4, 0) 34 #define GMAC_PLLCLK_DIV 0x0c 35 #define GMAC_PLLCLK_DIV_EN BIT(31) 36 #define GMAC_PLLCLK_DIV_NUM GENMASK(7, 0) 37 #define GMAC_GTXCLK_SEL 0x18 38 #define GMAC_GTXCLK_SEL_PLL BIT(0) 39 #define GMAC_INTF_CTRL 0x1c 40 #define PHY_INTF_MASK BIT(0) 41 #define PHY_INTF_RGMII FIELD_PREP(PHY_INTF_MASK, 1) 42 #define PHY_INTF_MII_GMII FIELD_PREP(PHY_INTF_MASK, 0) 43 #define GMAC_TXCLK_OEN 0x20 44 #define TXCLK_DIR_MASK BIT(0) 45 #define TXCLK_DIR_OUTPUT FIELD_PREP(TXCLK_DIR_MASK, 0) 46 #define TXCLK_DIR_INPUT FIELD_PREP(TXCLK_DIR_MASK, 1) 47 48 struct thead_dwmac { 49 struct plat_stmmacenet_data *plat; 50 void __iomem *apb_base; 51 struct device *dev; 52 }; 53 54 static int thead_dwmac_set_phy_if(struct plat_stmmacenet_data *plat) 55 { 56 struct thead_dwmac *dwmac = plat->bsp_priv; 57 u32 phyif; 58 59 switch (plat->mac_interface) { 60 case PHY_INTERFACE_MODE_MII: 61 phyif = PHY_INTF_MII_GMII; 62 break; 63 case PHY_INTERFACE_MODE_RGMII: 64 case PHY_INTERFACE_MODE_RGMII_ID: 65 case PHY_INTERFACE_MODE_RGMII_TXID: 66 case PHY_INTERFACE_MODE_RGMII_RXID: 67 phyif = PHY_INTF_RGMII; 68 break; 69 default: 70 dev_err(dwmac->dev, "unsupported phy interface %d\n", 71 plat->mac_interface); 72 return -EINVAL; 73 } 74 75 writel(phyif, dwmac->apb_base + GMAC_INTF_CTRL); 76 return 0; 77 } 78 79 static int thead_dwmac_set_txclk_dir(struct plat_stmmacenet_data *plat) 80 { 81 struct thead_dwmac *dwmac = plat->bsp_priv; 82 u32 txclk_dir; 83 84 switch (plat->mac_interface) { 85 case PHY_INTERFACE_MODE_MII: 86 txclk_dir = TXCLK_DIR_INPUT; 87 break; 88 case PHY_INTERFACE_MODE_RGMII: 89 case PHY_INTERFACE_MODE_RGMII_ID: 90 case PHY_INTERFACE_MODE_RGMII_TXID: 91 case PHY_INTERFACE_MODE_RGMII_RXID: 92 txclk_dir = TXCLK_DIR_OUTPUT; 93 break; 94 default: 95 dev_err(dwmac->dev, "unsupported phy interface %d\n", 96 plat->mac_interface); 97 return -EINVAL; 98 } 99 100 writel(txclk_dir, dwmac->apb_base + GMAC_TXCLK_OEN); 101 return 0; 102 } 103 104 static int thead_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i, 105 phy_interface_t interface, int speed) 106 { 107 struct thead_dwmac *dwmac = bsp_priv; 108 struct plat_stmmacenet_data *plat; 109 unsigned long rate; 110 long tx_rate; 111 u32 div, reg; 112 113 plat = dwmac->plat; 114 115 switch (plat->mac_interface) { 116 /* For MII, rxc/txc is provided by phy */ 117 case PHY_INTERFACE_MODE_MII: 118 return 0; 119 120 case PHY_INTERFACE_MODE_RGMII: 121 case PHY_INTERFACE_MODE_RGMII_ID: 122 case PHY_INTERFACE_MODE_RGMII_RXID: 123 case PHY_INTERFACE_MODE_RGMII_TXID: 124 rate = clk_get_rate(plat->stmmac_clk); 125 126 writel(0, dwmac->apb_base + GMAC_PLLCLK_DIV); 127 128 tx_rate = rgmii_clock(speed); 129 if (tx_rate < 0) { 130 dev_err(dwmac->dev, "invalid speed %d\n", speed); 131 return tx_rate; 132 } 133 134 div = rate / tx_rate; 135 if (rate != tx_rate * div) { 136 dev_err(dwmac->dev, "invalid gmac rate %lu\n", rate); 137 return -EINVAL; 138 } 139 140 reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) | 141 FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div); 142 writel(reg, dwmac->apb_base + GMAC_PLLCLK_DIV); 143 return 0; 144 145 default: 146 dev_err(dwmac->dev, "unsupported phy interface %d\n", 147 plat->mac_interface); 148 return -EINVAL; 149 } 150 } 151 152 static int thead_dwmac_enable_clk(struct plat_stmmacenet_data *plat) 153 { 154 struct thead_dwmac *dwmac = plat->bsp_priv; 155 u32 reg, div; 156 157 switch (plat->mac_interface) { 158 case PHY_INTERFACE_MODE_MII: 159 reg = GMAC_RX_CLK_EN | GMAC_TX_CLK_EN; 160 break; 161 162 case PHY_INTERFACE_MODE_RGMII: 163 case PHY_INTERFACE_MODE_RGMII_ID: 164 case PHY_INTERFACE_MODE_RGMII_RXID: 165 case PHY_INTERFACE_MODE_RGMII_TXID: 166 /* use pll */ 167 div = clk_get_rate(plat->stmmac_clk) / rgmii_clock(SPEED_1000); 168 reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) | 169 FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div); 170 171 writel(0, dwmac->apb_base + GMAC_PLLCLK_DIV); 172 writel(reg, dwmac->apb_base + GMAC_PLLCLK_DIV); 173 174 writel(GMAC_GTXCLK_SEL_PLL, dwmac->apb_base + GMAC_GTXCLK_SEL); 175 reg = GMAC_TX_CLK_EN | GMAC_TX_CLK_N_EN | GMAC_TX_CLK_OUT_EN | 176 GMAC_RX_CLK_EN | GMAC_RX_CLK_N_EN; 177 break; 178 179 default: 180 dev_err(dwmac->dev, "unsupported phy interface %d\n", 181 plat->mac_interface); 182 return -EINVAL; 183 } 184 185 writel(reg, dwmac->apb_base + GMAC_CLK_EN); 186 return 0; 187 } 188 189 static int thead_dwmac_init(struct platform_device *pdev, void *priv) 190 { 191 struct thead_dwmac *dwmac = priv; 192 unsigned int reg; 193 int ret; 194 195 ret = thead_dwmac_set_phy_if(dwmac->plat); 196 if (ret) 197 return ret; 198 199 ret = thead_dwmac_set_txclk_dir(dwmac->plat); 200 if (ret) 201 return ret; 202 203 reg = readl(dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL); 204 reg &= ~(GMAC_RXCLK_DELAY); 205 reg |= FIELD_PREP(GMAC_RXCLK_DELAY, 0); 206 writel(reg, dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL); 207 208 reg = readl(dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL); 209 reg &= ~(GMAC_TXCLK_DELAY); 210 reg |= FIELD_PREP(GMAC_TXCLK_DELAY, 0); 211 writel(reg, dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL); 212 213 return thead_dwmac_enable_clk(dwmac->plat); 214 } 215 216 static int thead_dwmac_probe(struct platform_device *pdev) 217 { 218 struct stmmac_resources stmmac_res; 219 struct plat_stmmacenet_data *plat; 220 struct thead_dwmac *dwmac; 221 struct clk *apb_clk; 222 void __iomem *apb; 223 int ret; 224 225 ret = stmmac_get_platform_resources(pdev, &stmmac_res); 226 if (ret) 227 return dev_err_probe(&pdev->dev, ret, 228 "failed to get resources\n"); 229 230 plat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac); 231 if (IS_ERR(plat)) 232 return dev_err_probe(&pdev->dev, PTR_ERR(plat), 233 "dt configuration failed\n"); 234 235 /* 236 * The APB clock is essential for accessing glue registers. However, 237 * old devicetrees don't describe it correctly. We continue to probe 238 * and emit a warning if it isn't present. 239 */ 240 apb_clk = devm_clk_get_enabled(&pdev->dev, "apb"); 241 if (PTR_ERR(apb_clk) == -ENOENT) 242 dev_warn(&pdev->dev, 243 "cannot get apb clock, link may break after speed changes\n"); 244 else if (IS_ERR(apb_clk)) 245 return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk), 246 "failed to get apb clock\n"); 247 248 dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL); 249 if (!dwmac) 250 return -ENOMEM; 251 252 apb = devm_platform_ioremap_resource(pdev, 1); 253 if (IS_ERR(apb)) 254 return dev_err_probe(&pdev->dev, PTR_ERR(apb), 255 "failed to remap gmac apb registers\n"); 256 257 dwmac->dev = &pdev->dev; 258 dwmac->plat = plat; 259 dwmac->apb_base = apb; 260 plat->bsp_priv = dwmac; 261 plat->set_clk_tx_rate = thead_set_clk_tx_rate; 262 plat->init = thead_dwmac_init; 263 264 return devm_stmmac_pltfr_probe(pdev, plat, &stmmac_res); 265 } 266 267 static const struct of_device_id thead_dwmac_match[] = { 268 { .compatible = "thead,th1520-gmac" }, 269 { /* sentinel */ } 270 }; 271 MODULE_DEVICE_TABLE(of, thead_dwmac_match); 272 273 static struct platform_driver thead_dwmac_driver = { 274 .probe = thead_dwmac_probe, 275 .driver = { 276 .name = "thead-dwmac", 277 .pm = &stmmac_pltfr_pm_ops, 278 .of_match_table = thead_dwmac_match, 279 }, 280 }; 281 module_platform_driver(thead_dwmac_driver); 282 283 MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>"); 284 MODULE_AUTHOR("Drew Fustini <drew@pdp7.com>"); 285 MODULE_DESCRIPTION("T-HEAD DWMAC platform driver"); 286 MODULE_LICENSE("GPL"); 287