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 #define GMAC_GMII_RGMII_RATE 125000000 49 #define GMAC_MII_RATE 25000000 50 51 struct thead_dwmac { 52 struct plat_stmmacenet_data *plat; 53 void __iomem *apb_base; 54 struct device *dev; 55 }; 56 57 static int thead_dwmac_set_phy_if(struct plat_stmmacenet_data *plat) 58 { 59 struct thead_dwmac *dwmac = plat->bsp_priv; 60 u32 phyif; 61 62 switch (plat->mac_interface) { 63 case PHY_INTERFACE_MODE_MII: 64 phyif = PHY_INTF_MII_GMII; 65 break; 66 case PHY_INTERFACE_MODE_RGMII: 67 case PHY_INTERFACE_MODE_RGMII_ID: 68 case PHY_INTERFACE_MODE_RGMII_TXID: 69 case PHY_INTERFACE_MODE_RGMII_RXID: 70 phyif = PHY_INTF_RGMII; 71 break; 72 default: 73 dev_err(dwmac->dev, "unsupported phy interface %d\n", 74 plat->mac_interface); 75 return -EINVAL; 76 } 77 78 writel(phyif, dwmac->apb_base + GMAC_INTF_CTRL); 79 return 0; 80 } 81 82 static int thead_dwmac_set_txclk_dir(struct plat_stmmacenet_data *plat) 83 { 84 struct thead_dwmac *dwmac = plat->bsp_priv; 85 u32 txclk_dir; 86 87 switch (plat->mac_interface) { 88 case PHY_INTERFACE_MODE_MII: 89 txclk_dir = TXCLK_DIR_INPUT; 90 break; 91 case PHY_INTERFACE_MODE_RGMII: 92 case PHY_INTERFACE_MODE_RGMII_ID: 93 case PHY_INTERFACE_MODE_RGMII_TXID: 94 case PHY_INTERFACE_MODE_RGMII_RXID: 95 txclk_dir = TXCLK_DIR_OUTPUT; 96 break; 97 default: 98 dev_err(dwmac->dev, "unsupported phy interface %d\n", 99 plat->mac_interface); 100 return -EINVAL; 101 } 102 103 writel(txclk_dir, dwmac->apb_base + GMAC_TXCLK_OEN); 104 return 0; 105 } 106 107 static void thead_dwmac_fix_speed(void *priv, unsigned int speed, unsigned int mode) 108 { 109 struct plat_stmmacenet_data *plat; 110 struct thead_dwmac *dwmac = priv; 111 unsigned long rate; 112 u32 div, reg; 113 114 plat = dwmac->plat; 115 116 switch (plat->mac_interface) { 117 /* For MII, rxc/txc is provided by phy */ 118 case PHY_INTERFACE_MODE_MII: 119 return; 120 121 case PHY_INTERFACE_MODE_RGMII: 122 case PHY_INTERFACE_MODE_RGMII_ID: 123 case PHY_INTERFACE_MODE_RGMII_RXID: 124 case PHY_INTERFACE_MODE_RGMII_TXID: 125 rate = clk_get_rate(plat->stmmac_clk); 126 if (!rate || rate % GMAC_GMII_RGMII_RATE != 0 || 127 rate % GMAC_MII_RATE != 0) { 128 dev_err(dwmac->dev, "invalid gmac rate %ld\n", rate); 129 return; 130 } 131 132 writel(0, dwmac->apb_base + GMAC_PLLCLK_DIV); 133 134 switch (speed) { 135 case SPEED_1000: 136 div = rate / GMAC_GMII_RGMII_RATE; 137 break; 138 case SPEED_100: 139 div = rate / GMAC_MII_RATE; 140 break; 141 case SPEED_10: 142 div = rate * 10 / GMAC_MII_RATE; 143 break; 144 default: 145 dev_err(dwmac->dev, "invalid speed %u\n", speed); 146 return; 147 } 148 149 reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) | 150 FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div); 151 writel(reg, dwmac->apb_base + GMAC_PLLCLK_DIV); 152 break; 153 default: 154 dev_err(dwmac->dev, "unsupported phy interface %d\n", 155 plat->mac_interface); 156 return; 157 } 158 } 159 160 static int thead_dwmac_enable_clk(struct plat_stmmacenet_data *plat) 161 { 162 struct thead_dwmac *dwmac = plat->bsp_priv; 163 u32 reg; 164 165 switch (plat->mac_interface) { 166 case PHY_INTERFACE_MODE_MII: 167 reg = GMAC_RX_CLK_EN | GMAC_TX_CLK_EN; 168 break; 169 170 case PHY_INTERFACE_MODE_RGMII: 171 case PHY_INTERFACE_MODE_RGMII_ID: 172 case PHY_INTERFACE_MODE_RGMII_RXID: 173 case PHY_INTERFACE_MODE_RGMII_TXID: 174 /* use pll */ 175 writel(GMAC_GTXCLK_SEL_PLL, dwmac->apb_base + GMAC_GTXCLK_SEL); 176 reg = GMAC_TX_CLK_EN | GMAC_TX_CLK_N_EN | GMAC_TX_CLK_OUT_EN | 177 GMAC_RX_CLK_EN | GMAC_RX_CLK_N_EN; 178 break; 179 180 default: 181 dev_err(dwmac->dev, "unsupported phy interface %d\n", 182 plat->mac_interface); 183 return -EINVAL; 184 } 185 186 writel(reg, dwmac->apb_base + GMAC_CLK_EN); 187 return 0; 188 } 189 190 static int thead_dwmac_init(struct platform_device *pdev, void *priv) 191 { 192 struct thead_dwmac *dwmac = priv; 193 unsigned int reg; 194 int ret; 195 196 ret = thead_dwmac_set_phy_if(dwmac->plat); 197 if (ret) 198 return ret; 199 200 ret = thead_dwmac_set_txclk_dir(dwmac->plat); 201 if (ret) 202 return ret; 203 204 reg = readl(dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL); 205 reg &= ~(GMAC_RXCLK_DELAY); 206 reg |= FIELD_PREP(GMAC_RXCLK_DELAY, 0); 207 writel(reg, dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL); 208 209 reg = readl(dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL); 210 reg &= ~(GMAC_TXCLK_DELAY); 211 reg |= FIELD_PREP(GMAC_TXCLK_DELAY, 0); 212 writel(reg, dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL); 213 214 return thead_dwmac_enable_clk(dwmac->plat); 215 } 216 217 static int thead_dwmac_probe(struct platform_device *pdev) 218 { 219 struct stmmac_resources stmmac_res; 220 struct plat_stmmacenet_data *plat; 221 struct thead_dwmac *dwmac; 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 dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL); 236 if (!dwmac) 237 return -ENOMEM; 238 239 apb = devm_platform_ioremap_resource(pdev, 1); 240 if (IS_ERR(apb)) 241 return dev_err_probe(&pdev->dev, PTR_ERR(apb), 242 "failed to remap gmac apb registers\n"); 243 244 dwmac->dev = &pdev->dev; 245 dwmac->plat = plat; 246 dwmac->apb_base = apb; 247 plat->bsp_priv = dwmac; 248 plat->fix_mac_speed = thead_dwmac_fix_speed; 249 plat->init = thead_dwmac_init; 250 251 return devm_stmmac_pltfr_probe(pdev, plat, &stmmac_res); 252 } 253 254 static const struct of_device_id thead_dwmac_match[] = { 255 { .compatible = "thead,th1520-gmac" }, 256 { /* sentinel */ } 257 }; 258 MODULE_DEVICE_TABLE(of, thead_dwmac_match); 259 260 static struct platform_driver thead_dwmac_driver = { 261 .probe = thead_dwmac_probe, 262 .driver = { 263 .name = "thead-dwmac", 264 .pm = &stmmac_pltfr_pm_ops, 265 .of_match_table = thead_dwmac_match, 266 }, 267 }; 268 module_platform_driver(thead_dwmac_driver); 269 270 MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>"); 271 MODULE_AUTHOR("Drew Fustini <drew@pdp7.com>"); 272 MODULE_DESCRIPTION("T-HEAD DWMAC platform driver"); 273 MODULE_LICENSE("GPL"); 274