1// SPDX-License-Identifier: GPL-2.0
2/*
3 * dwmac-imx.c - DWMAC Specific Glue layer for NXP imx8
4 *
5 * Copyright 2020 NXP
6 *
7 */
8
9#include <linux/clk.h>
10#include <linux/gpio/consumer.h>
11#include <linux/kernel.h>
12#include <linux/mfd/syscon.h>
13#include <linux/module.h>
14#include <linux/of.h>
15#include <linux/of_device.h>
16#include <linux/of_net.h>
17#include <linux/phy.h>
18#include <linux/platform_device.h>
19#include <linux/pm_wakeirq.h>
20#include <linux/regmap.h>
21#include <linux/slab.h>
22#include <linux/stmmac.h>
23
24#include "stmmac_platform.h"
25
26#define GPR_ENET_QOS_INTF_MODE_MASK	GENMASK(21, 16)
27#define GPR_ENET_QOS_INTF_SEL_MII	(0x0 << 16)
28#define GPR_ENET_QOS_INTF_SEL_RMII	(0x4 << 16)
29#define GPR_ENET_QOS_INTF_SEL_RGMII	(0x1 << 16)
30#define GPR_ENET_QOS_CLK_GEN_EN		(0x1 << 19)
31#define GPR_ENET_QOS_CLK_TX_CLK_SEL	(0x1 << 20)
32#define GPR_ENET_QOS_RGMII_EN		(0x1 << 21)
33
34struct imx_dwmac_ops {
35	u32 addr_width;
36	bool mac_rgmii_txclk_auto_adj;
37
38	int (*set_intf_mode)(struct plat_stmmacenet_data *plat_dat);
39};
40
41struct imx_priv_data {
42	struct device *dev;
43	struct clk *clk_tx;
44	struct clk *clk_mem;
45	struct regmap *intf_regmap;
46	u32 intf_reg_off;
47	bool rmii_refclk_ext;
48
49	const struct imx_dwmac_ops *ops;
50	struct plat_stmmacenet_data *plat_dat;
51};
52
53static int imx8mp_set_intf_mode(struct plat_stmmacenet_data *plat_dat)
54{
55	struct imx_priv_data *dwmac = plat_dat->bsp_priv;
56	int val;
57
58	switch (plat_dat->interface) {
59	case PHY_INTERFACE_MODE_MII:
60		val = GPR_ENET_QOS_INTF_SEL_MII;
61		break;
62	case PHY_INTERFACE_MODE_RMII:
63		val = GPR_ENET_QOS_INTF_SEL_RMII;
64		val |= (dwmac->rmii_refclk_ext ? 0 : GPR_ENET_QOS_CLK_TX_CLK_SEL);
65		break;
66	case PHY_INTERFACE_MODE_RGMII:
67	case PHY_INTERFACE_MODE_RGMII_ID:
68	case PHY_INTERFACE_MODE_RGMII_RXID:
69	case PHY_INTERFACE_MODE_RGMII_TXID:
70		val = GPR_ENET_QOS_INTF_SEL_RGMII |
71		      GPR_ENET_QOS_RGMII_EN;
72		break;
73	default:
74		pr_debug("imx dwmac doesn't support %d interface\n",
75			 plat_dat->interface);
76		return -EINVAL;
77	}
78
79	val |= GPR_ENET_QOS_CLK_GEN_EN;
80	return regmap_update_bits(dwmac->intf_regmap, dwmac->intf_reg_off,
81				  GPR_ENET_QOS_INTF_MODE_MASK, val);
82};
83
84static int
85imx8dxl_set_intf_mode(struct plat_stmmacenet_data *plat_dat)
86{
87	int ret = 0;
88
89	/* TBD: depends on imx8dxl scu interfaces to be upstreamed */
90	return ret;
91}
92
93static int imx_dwmac_init(struct platform_device *pdev, void *priv)
94{
95	struct plat_stmmacenet_data *plat_dat;
96	struct imx_priv_data *dwmac = priv;
97	int ret;
98
99	plat_dat = dwmac->plat_dat;
100
101	ret = clk_prepare_enable(dwmac->clk_mem);
102	if (ret) {
103		dev_err(&pdev->dev, "mem clock enable failed\n");
104		return ret;
105	}
106
107	ret = clk_prepare_enable(dwmac->clk_tx);
108	if (ret) {
109		dev_err(&pdev->dev, "tx clock enable failed\n");
110		goto clk_tx_en_failed;
111	}
112
113	if (dwmac->ops->set_intf_mode) {
114		ret = dwmac->ops->set_intf_mode(plat_dat);
115		if (ret)
116			goto intf_mode_failed;
117	}
118
119	return 0;
120
121intf_mode_failed:
122	clk_disable_unprepare(dwmac->clk_tx);
123clk_tx_en_failed:
124	clk_disable_unprepare(dwmac->clk_mem);
125	return ret;
126}
127
128static void imx_dwmac_exit(struct platform_device *pdev, void *priv)
129{
130	struct imx_priv_data *dwmac = priv;
131
132	clk_disable_unprepare(dwmac->clk_tx);
133	clk_disable_unprepare(dwmac->clk_mem);
134}
135
136static void imx_dwmac_fix_speed(void *priv, unsigned int speed)
137{
138	struct plat_stmmacenet_data *plat_dat;
139	struct imx_priv_data *dwmac = priv;
140	unsigned long rate;
141	int err;
142
143	plat_dat = dwmac->plat_dat;
144
145	if (dwmac->ops->mac_rgmii_txclk_auto_adj ||
146	    (plat_dat->interface == PHY_INTERFACE_MODE_RMII) ||
147	    (plat_dat->interface == PHY_INTERFACE_MODE_MII))
148		return;
149
150	switch (speed) {
151	case SPEED_1000:
152		rate = 125000000;
153		break;
154	case SPEED_100:
155		rate = 25000000;
156		break;
157	case SPEED_10:
158		rate = 2500000;
159		break;
160	default:
161		dev_err(dwmac->dev, "invalid speed %u\n", speed);
162		return;
163	}
164
165	err = clk_set_rate(dwmac->clk_tx, rate);
166	if (err < 0)
167		dev_err(dwmac->dev, "failed to set tx rate %lu\n", rate);
168}
169
170static int
171imx_dwmac_parse_dt(struct imx_priv_data *dwmac, struct device *dev)
172{
173	struct device_node *np = dev->of_node;
174	int err = 0;
175
176	if (of_get_property(np, "snps,rmii_refclk_ext", NULL))
177		dwmac->rmii_refclk_ext = true;
178
179	dwmac->clk_tx = devm_clk_get(dev, "tx");
180	if (IS_ERR(dwmac->clk_tx)) {
181		dev_err(dev, "failed to get tx clock\n");
182		return PTR_ERR(dwmac->clk_tx);
183	}
184
185	dwmac->clk_mem = NULL;
186	if (of_machine_is_compatible("fsl,imx8dxl")) {
187		dwmac->clk_mem = devm_clk_get(dev, "mem");
188		if (IS_ERR(dwmac->clk_mem)) {
189			dev_err(dev, "failed to get mem clock\n");
190			return PTR_ERR(dwmac->clk_mem);
191		}
192	}
193
194	if (of_machine_is_compatible("fsl,imx8mp")) {
195		/* Binding doc describes the propety:
196		   is required by i.MX8MP.
197		   is optinoal for i.MX8DXL.
198		 */
199		dwmac->intf_regmap = syscon_regmap_lookup_by_phandle(np, "intf_mode");
200		if (IS_ERR(dwmac->intf_regmap))
201			return PTR_ERR(dwmac->intf_regmap);
202
203		err = of_property_read_u32_index(np, "intf_mode", 1, &dwmac->intf_reg_off);
204		if (err) {
205			dev_err(dev, "Can't get intf mode reg offset (%d)\n", err);
206			return err;
207		}
208	}
209
210	return err;
211}
212
213static int imx_dwmac_probe(struct platform_device *pdev)
214{
215	struct plat_stmmacenet_data *plat_dat;
216	struct stmmac_resources stmmac_res;
217	struct imx_priv_data *dwmac;
218	const struct imx_dwmac_ops *data;
219	int ret;
220
221	ret = stmmac_get_platform_resources(pdev, &stmmac_res);
222	if (ret)
223		return ret;
224
225	dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
226	if (!dwmac)
227		return -ENOMEM;
228
229	plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
230	if (IS_ERR(plat_dat))
231		return PTR_ERR(plat_dat);
232
233	data = of_device_get_match_data(&pdev->dev);
234	if (!data) {
235		dev_err(&pdev->dev, "failed to get match data\n");
236		ret = -EINVAL;
237		goto err_match_data;
238	}
239
240	dwmac->ops = data;
241	dwmac->dev = &pdev->dev;
242
243	ret = imx_dwmac_parse_dt(dwmac, &pdev->dev);
244	if (ret) {
245		dev_err(&pdev->dev, "failed to parse OF data\n");
246		goto err_parse_dt;
247	}
248
249	plat_dat->addr64 = dwmac->ops->addr_width;
250	plat_dat->init = imx_dwmac_init;
251	plat_dat->exit = imx_dwmac_exit;
252	plat_dat->fix_mac_speed = imx_dwmac_fix_speed;
253	plat_dat->bsp_priv = dwmac;
254	dwmac->plat_dat = plat_dat;
255
256	ret = imx_dwmac_init(pdev, dwmac);
257	if (ret)
258		goto err_dwmac_init;
259
260	ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
261	if (ret)
262		goto err_drv_probe;
263
264	return 0;
265
266err_dwmac_init:
267err_drv_probe:
268	imx_dwmac_exit(pdev, plat_dat->bsp_priv);
269err_parse_dt:
270err_match_data:
271	stmmac_remove_config_dt(pdev, plat_dat);
272	return ret;
273}
274
275static struct imx_dwmac_ops imx8mp_dwmac_data = {
276	.addr_width = 34,
277	.mac_rgmii_txclk_auto_adj = false,
278	.set_intf_mode = imx8mp_set_intf_mode,
279};
280
281static struct imx_dwmac_ops imx8dxl_dwmac_data = {
282	.addr_width = 32,
283	.mac_rgmii_txclk_auto_adj = true,
284	.set_intf_mode = imx8dxl_set_intf_mode,
285};
286
287static const struct of_device_id imx_dwmac_match[] = {
288	{ .compatible = "nxp,imx8mp-dwmac-eqos", .data = &imx8mp_dwmac_data },
289	{ .compatible = "nxp,imx8dxl-dwmac-eqos", .data = &imx8dxl_dwmac_data },
290	{ }
291};
292MODULE_DEVICE_TABLE(of, imx_dwmac_match);
293
294static struct platform_driver imx_dwmac_driver = {
295	.probe  = imx_dwmac_probe,
296	.remove = stmmac_pltfr_remove,
297	.driver = {
298		.name           = "imx-dwmac",
299		.pm		= &stmmac_pltfr_pm_ops,
300		.of_match_table = imx_dwmac_match,
301	},
302};
303module_platform_driver(imx_dwmac_driver);
304
305MODULE_AUTHOR("NXP");
306MODULE_DESCRIPTION("NXP imx8 DWMAC Specific Glue layer");
307MODULE_LICENSE("GPL v2");
308