1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Toshiba Visconti Ethernet Support |
3 | * |
4 | * (C) Copyright 2020 TOSHIBA CORPORATION |
5 | * (C) Copyright 2020 Toshiba Electronic Devices & Storage Corporation |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/of_net.h> |
12 | #include <linux/stmmac.h> |
13 | |
14 | #include "stmmac_platform.h" |
15 | #include "dwmac4.h" |
16 | |
17 | #define REG_ETHER_CONTROL 0x52D4 |
18 | #define ETHER_ETH_CONTROL_RESET BIT(17) |
19 | |
20 | #define REG_ETHER_CLOCK_SEL 0x52D0 |
21 | #define ETHER_CLK_SEL_TX_CLK_EN BIT(0) |
22 | #define ETHER_CLK_SEL_RX_CLK_EN BIT(1) |
23 | #define ETHER_CLK_SEL_RMII_CLK_EN BIT(2) |
24 | #define ETHER_CLK_SEL_RMII_CLK_RST BIT(3) |
25 | #define ETHER_CLK_SEL_DIV_SEL_2 BIT(4) |
26 | #define ETHER_CLK_SEL_DIV_SEL_20 0 |
27 | #define ETHER_CLK_SEL_FREQ_SEL_125M (BIT(9) | BIT(8)) |
28 | #define ETHER_CLK_SEL_FREQ_SEL_50M BIT(9) |
29 | #define ETHER_CLK_SEL_FREQ_SEL_25M BIT(8) |
30 | #define ETHER_CLK_SEL_FREQ_SEL_2P5M 0 |
31 | #define ETHER_CLK_SEL_TX_CLK_EXT_SEL_IN 0 |
32 | #define ETHER_CLK_SEL_TX_CLK_EXT_SEL_TXC BIT(10) |
33 | #define ETHER_CLK_SEL_TX_CLK_EXT_SEL_DIV BIT(11) |
34 | #define ETHER_CLK_SEL_RX_CLK_EXT_SEL_IN 0 |
35 | #define ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC BIT(12) |
36 | #define ETHER_CLK_SEL_RX_CLK_EXT_SEL_DIV BIT(13) |
37 | #define ETHER_CLK_SEL_TX_CLK_O_TX_I 0 |
38 | #define ETHER_CLK_SEL_TX_CLK_O_RMII_I BIT(14) |
39 | #define ETHER_CLK_SEL_TX_O_E_N_IN BIT(15) |
40 | #define ETHER_CLK_SEL_RMII_CLK_SEL_IN 0 |
41 | #define ETHER_CLK_SEL_RMII_CLK_SEL_RX_C BIT(16) |
42 | |
43 | #define ETHER_CLK_SEL_RX_TX_CLK_EN (ETHER_CLK_SEL_RX_CLK_EN | ETHER_CLK_SEL_TX_CLK_EN) |
44 | |
45 | #define ETHER_CONFIG_INTF_MII 0 |
46 | #define ETHER_CONFIG_INTF_RGMII BIT(0) |
47 | #define ETHER_CONFIG_INTF_RMII BIT(2) |
48 | |
49 | struct visconti_eth { |
50 | void __iomem *reg; |
51 | u32 phy_intf_sel; |
52 | struct clk *phy_ref_clk; |
53 | struct device *dev; |
54 | spinlock_t lock; /* lock to protect register update */ |
55 | }; |
56 | |
57 | static void visconti_eth_fix_mac_speed(void *priv, unsigned int speed, unsigned int mode) |
58 | { |
59 | struct visconti_eth *dwmac = priv; |
60 | struct net_device *netdev = dev_get_drvdata(dev: dwmac->dev); |
61 | unsigned int val, clk_sel_val = 0; |
62 | unsigned long flags; |
63 | |
64 | spin_lock_irqsave(&dwmac->lock, flags); |
65 | |
66 | /* adjust link */ |
67 | val = readl(addr: dwmac->reg + MAC_CTRL_REG); |
68 | val &= ~(GMAC_CONFIG_PS | GMAC_CONFIG_FES); |
69 | |
70 | switch (speed) { |
71 | case SPEED_1000: |
72 | if (dwmac->phy_intf_sel == ETHER_CONFIG_INTF_RGMII) |
73 | clk_sel_val = ETHER_CLK_SEL_FREQ_SEL_125M; |
74 | break; |
75 | case SPEED_100: |
76 | if (dwmac->phy_intf_sel == ETHER_CONFIG_INTF_RGMII) |
77 | clk_sel_val = ETHER_CLK_SEL_FREQ_SEL_25M; |
78 | if (dwmac->phy_intf_sel == ETHER_CONFIG_INTF_RMII) |
79 | clk_sel_val = ETHER_CLK_SEL_DIV_SEL_2; |
80 | val |= GMAC_CONFIG_PS | GMAC_CONFIG_FES; |
81 | break; |
82 | case SPEED_10: |
83 | if (dwmac->phy_intf_sel == ETHER_CONFIG_INTF_RGMII) |
84 | clk_sel_val = ETHER_CLK_SEL_FREQ_SEL_2P5M; |
85 | if (dwmac->phy_intf_sel == ETHER_CONFIG_INTF_RMII) |
86 | clk_sel_val = ETHER_CLK_SEL_DIV_SEL_20; |
87 | val |= GMAC_CONFIG_PS; |
88 | break; |
89 | default: |
90 | /* No bit control */ |
91 | netdev_err(dev: netdev, format: "Unsupported speed request (%d)" , speed); |
92 | spin_unlock_irqrestore(lock: &dwmac->lock, flags); |
93 | return; |
94 | } |
95 | |
96 | writel(val, addr: dwmac->reg + MAC_CTRL_REG); |
97 | |
98 | /* Stop internal clock */ |
99 | val = readl(addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
100 | val &= ~(ETHER_CLK_SEL_RMII_CLK_EN | ETHER_CLK_SEL_RX_TX_CLK_EN); |
101 | val |= ETHER_CLK_SEL_TX_O_E_N_IN; |
102 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
103 | |
104 | /* Set Clock-Mux, Start clock, Set TX_O direction */ |
105 | switch (dwmac->phy_intf_sel) { |
106 | case ETHER_CONFIG_INTF_RGMII: |
107 | val = clk_sel_val | ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC; |
108 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
109 | |
110 | val |= ETHER_CLK_SEL_RX_TX_CLK_EN; |
111 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
112 | |
113 | val &= ~ETHER_CLK_SEL_TX_O_E_N_IN; |
114 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
115 | break; |
116 | case ETHER_CONFIG_INTF_RMII: |
117 | val = clk_sel_val | ETHER_CLK_SEL_RX_CLK_EXT_SEL_DIV | |
118 | ETHER_CLK_SEL_TX_CLK_EXT_SEL_DIV | ETHER_CLK_SEL_TX_O_E_N_IN | |
119 | ETHER_CLK_SEL_RMII_CLK_SEL_RX_C; |
120 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
121 | |
122 | val |= ETHER_CLK_SEL_RMII_CLK_RST; |
123 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
124 | |
125 | val |= ETHER_CLK_SEL_RMII_CLK_EN | ETHER_CLK_SEL_RX_TX_CLK_EN; |
126 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
127 | break; |
128 | case ETHER_CONFIG_INTF_MII: |
129 | default: |
130 | val = clk_sel_val | ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC | |
131 | ETHER_CLK_SEL_TX_CLK_EXT_SEL_TXC | ETHER_CLK_SEL_TX_O_E_N_IN; |
132 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
133 | |
134 | val |= ETHER_CLK_SEL_RX_TX_CLK_EN; |
135 | writel(val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
136 | break; |
137 | } |
138 | |
139 | spin_unlock_irqrestore(lock: &dwmac->lock, flags); |
140 | } |
141 | |
142 | static int visconti_eth_init_hw(struct platform_device *pdev, struct plat_stmmacenet_data *plat_dat) |
143 | { |
144 | struct visconti_eth *dwmac = plat_dat->bsp_priv; |
145 | unsigned int reg_val, clk_sel_val; |
146 | |
147 | switch (plat_dat->phy_interface) { |
148 | case PHY_INTERFACE_MODE_RGMII: |
149 | case PHY_INTERFACE_MODE_RGMII_ID: |
150 | case PHY_INTERFACE_MODE_RGMII_RXID: |
151 | case PHY_INTERFACE_MODE_RGMII_TXID: |
152 | dwmac->phy_intf_sel = ETHER_CONFIG_INTF_RGMII; |
153 | break; |
154 | case PHY_INTERFACE_MODE_MII: |
155 | dwmac->phy_intf_sel = ETHER_CONFIG_INTF_MII; |
156 | break; |
157 | case PHY_INTERFACE_MODE_RMII: |
158 | dwmac->phy_intf_sel = ETHER_CONFIG_INTF_RMII; |
159 | break; |
160 | default: |
161 | dev_err(&pdev->dev, "Unsupported phy-mode (%d)\n" , plat_dat->phy_interface); |
162 | return -EOPNOTSUPP; |
163 | } |
164 | |
165 | reg_val = dwmac->phy_intf_sel; |
166 | writel(val: reg_val, addr: dwmac->reg + REG_ETHER_CONTROL); |
167 | |
168 | /* Enable TX/RX clock */ |
169 | clk_sel_val = ETHER_CLK_SEL_FREQ_SEL_125M; |
170 | writel(val: clk_sel_val, addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
171 | |
172 | writel(val: (clk_sel_val | ETHER_CLK_SEL_RMII_CLK_EN | ETHER_CLK_SEL_RX_TX_CLK_EN), |
173 | addr: dwmac->reg + REG_ETHER_CLOCK_SEL); |
174 | |
175 | /* release internal-reset */ |
176 | reg_val |= ETHER_ETH_CONTROL_RESET; |
177 | writel(val: reg_val, addr: dwmac->reg + REG_ETHER_CONTROL); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | static int visconti_eth_clock_probe(struct platform_device *pdev, |
183 | struct plat_stmmacenet_data *plat_dat) |
184 | { |
185 | struct visconti_eth *dwmac = plat_dat->bsp_priv; |
186 | int err; |
187 | |
188 | dwmac->phy_ref_clk = devm_clk_get(dev: &pdev->dev, id: "phy_ref_clk" ); |
189 | if (IS_ERR(ptr: dwmac->phy_ref_clk)) |
190 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: dwmac->phy_ref_clk), |
191 | fmt: "phy_ref_clk clock not found.\n" ); |
192 | |
193 | err = clk_prepare_enable(clk: dwmac->phy_ref_clk); |
194 | if (err < 0) { |
195 | dev_err(&pdev->dev, "failed to enable phy_ref clock: %d\n" , err); |
196 | return err; |
197 | } |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static void visconti_eth_clock_remove(struct platform_device *pdev) |
203 | { |
204 | struct visconti_eth *dwmac = get_stmmac_bsp_priv(dev: &pdev->dev); |
205 | struct net_device *ndev = platform_get_drvdata(pdev); |
206 | struct stmmac_priv *priv = netdev_priv(dev: ndev); |
207 | |
208 | clk_disable_unprepare(clk: dwmac->phy_ref_clk); |
209 | clk_disable_unprepare(clk: priv->plat->stmmac_clk); |
210 | } |
211 | |
212 | static int visconti_eth_dwmac_probe(struct platform_device *pdev) |
213 | { |
214 | struct plat_stmmacenet_data *plat_dat; |
215 | struct stmmac_resources stmmac_res; |
216 | struct visconti_eth *dwmac; |
217 | int ret; |
218 | |
219 | ret = stmmac_get_platform_resources(pdev, stmmac_res: &stmmac_res); |
220 | if (ret) |
221 | return ret; |
222 | |
223 | plat_dat = devm_stmmac_probe_config_dt(pdev, mac: stmmac_res.mac); |
224 | if (IS_ERR(ptr: plat_dat)) |
225 | return PTR_ERR(ptr: plat_dat); |
226 | |
227 | dwmac = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dwmac), GFP_KERNEL); |
228 | if (!dwmac) |
229 | return -ENOMEM; |
230 | |
231 | spin_lock_init(&dwmac->lock); |
232 | dwmac->reg = stmmac_res.addr; |
233 | dwmac->dev = &pdev->dev; |
234 | plat_dat->bsp_priv = dwmac; |
235 | plat_dat->fix_mac_speed = visconti_eth_fix_mac_speed; |
236 | |
237 | ret = visconti_eth_clock_probe(pdev, plat_dat); |
238 | if (ret) |
239 | return ret; |
240 | |
241 | visconti_eth_init_hw(pdev, plat_dat); |
242 | |
243 | plat_dat->dma_cfg->aal = 1; |
244 | |
245 | ret = stmmac_dvr_probe(device: &pdev->dev, plat_dat, res: &stmmac_res); |
246 | if (ret) |
247 | goto remove; |
248 | |
249 | return ret; |
250 | |
251 | remove: |
252 | visconti_eth_clock_remove(pdev); |
253 | |
254 | return ret; |
255 | } |
256 | |
257 | static void visconti_eth_dwmac_remove(struct platform_device *pdev) |
258 | { |
259 | stmmac_pltfr_remove(pdev); |
260 | visconti_eth_clock_remove(pdev); |
261 | } |
262 | |
263 | static const struct of_device_id visconti_eth_dwmac_match[] = { |
264 | { .compatible = "toshiba,visconti-dwmac" }, |
265 | { } |
266 | }; |
267 | MODULE_DEVICE_TABLE(of, visconti_eth_dwmac_match); |
268 | |
269 | static struct platform_driver visconti_eth_dwmac_driver = { |
270 | .probe = visconti_eth_dwmac_probe, |
271 | .remove_new = visconti_eth_dwmac_remove, |
272 | .driver = { |
273 | .name = "visconti-eth-dwmac" , |
274 | .of_match_table = visconti_eth_dwmac_match, |
275 | }, |
276 | }; |
277 | module_platform_driver(visconti_eth_dwmac_driver); |
278 | |
279 | MODULE_AUTHOR("Toshiba" ); |
280 | MODULE_DESCRIPTION("Toshiba Visconti Ethernet DWMAC glue driver" ); |
281 | MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp" ); |
282 | MODULE_LICENSE("GPL v2" ); |
283 | |