summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
blob: bcb8eefeb93c0c45961e9e7f0fefda88bbe96c9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * NXP NETC Blocks Control Driver
 *
 * Copyright 2024 NXP
 *
 * This driver is used for pre-initialization of NETC, such as PCS and MII
 * protocols, LDID, warm reset, etc. Therefore, all NETC device drivers can
 * only be probed after the netc-blk-crtl driver has completed initialization.
 * In addition, when the system enters suspend mode, IERB, PRB, and NETCMIX
 * will be powered off, except for WOL. Therefore, when the system resumes,
 * these blocks need to be reinitialized.
 */

#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/fsl/netc_global.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>

/* NETCMIX registers */
#define IMX95_CFG_LINK_IO_VAR		0x0
#define  IO_VAR_16FF_16G_SERDES		0x1
#define  IO_VAR(port, var)		(((var) & 0xf) << ((port) << 2))

#define IMX95_CFG_LINK_MII_PROT		0x4
#define CFG_LINK_MII_PORT_0		GENMASK(3, 0)
#define CFG_LINK_MII_PORT_1		GENMASK(7, 4)
#define  MII_PROT_MII			0x0
#define  MII_PROT_RMII			0x1
#define  MII_PROT_RGMII			0x2
#define  MII_PROT_SERIAL		0x3
#define  MII_PROT(port, prot)		(((prot) & 0xf) << ((port) << 2))

#define IMX95_CFG_LINK_PCS_PROT(a)	(0x8 + (a) * 4)
#define PCS_PROT_1G_SGMII		BIT(0)
#define PCS_PROT_2500M_SGMII		BIT(1)
#define PCS_PROT_XFI			BIT(3)
#define PCS_PROT_SFI			BIT(4)
#define PCS_PROT_10G_SXGMII		BIT(6)

/* NETC privileged register block register */
#define PRB_NETCRR			0x100
#define  NETCRR_SR			BIT(0)
#define  NETCRR_LOCK			BIT(1)

#define PRB_NETCSR			0x104
#define  NETCSR_ERROR			BIT(0)
#define  NETCSR_STATE			BIT(1)

/* NETC integrated endpoint register block register */
#define IERB_EMDIOFAUXR			0x344
#define IERB_T0FAUXR			0x444
#define IERB_EFAUXR(a)			(0x3044 + 0x100 * (a))
#define IERB_VFAUXR(a)			(0x4004 + 0x40 * (a))
#define FAUXR_LDID			GENMASK(3, 0)

/* Platform information */
#define IMX95_ENETC0_BUS_DEVFN		0x0
#define IMX95_ENETC1_BUS_DEVFN		0x40
#define IMX95_ENETC2_BUS_DEVFN		0x80

/* Flags for different platforms */
#define NETC_HAS_NETCMIX		BIT(0)

struct netc_devinfo {
	u32 flags;
	int (*netcmix_init)(struct platform_device *pdev);
	int (*ierb_init)(struct platform_device *pdev);
};

struct netc_blk_ctrl {
	void __iomem *prb;
	void __iomem *ierb;
	void __iomem *netcmix;

	const struct netc_devinfo *devinfo;
	struct platform_device *pdev;
	struct dentry *debugfs_root;
};

static void netc_reg_write(void __iomem *base, u32 offset, u32 val)
{
	netc_write(base + offset, val);
}

static u32 netc_reg_read(void __iomem *base, u32 offset)
{
	return netc_read(base + offset);
}

static int netc_of_pci_get_bus_devfn(struct device_node *np)
{
	u32 reg[5];
	int error;

	error = of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg));
	if (error)
		return error;

	return (reg[0] >> 8) & 0xffff;
}

static int netc_get_link_mii_protocol(phy_interface_t interface)
{
	switch (interface) {
	case PHY_INTERFACE_MODE_MII:
		return MII_PROT_MII;
	case PHY_INTERFACE_MODE_RMII:
		return MII_PROT_RMII;
	case PHY_INTERFACE_MODE_RGMII:
	case PHY_INTERFACE_MODE_RGMII_ID:
	case PHY_INTERFACE_MODE_RGMII_RXID:
	case PHY_INTERFACE_MODE_RGMII_TXID:
		return MII_PROT_RGMII;
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_2500BASEX:
	case PHY_INTERFACE_MODE_10GBASER:
	case PHY_INTERFACE_MODE_XGMII:
	case PHY_INTERFACE_MODE_USXGMII:
		return MII_PROT_SERIAL;
	default:
		return -EINVAL;
	}
}

static int imx95_netcmix_init(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
	struct device_node *np = pdev->dev.of_node;
	phy_interface_t interface;
	int bus_devfn, mii_proto;
	u32 val;
	int err;

	/* Default setting of MII protocol */
	val = MII_PROT(0, MII_PROT_RGMII) | MII_PROT(1, MII_PROT_RGMII) |
	      MII_PROT(2, MII_PROT_SERIAL);

	/* Update the link MII protocol through parsing phy-mode */
	for_each_available_child_of_node_scoped(np, child) {
		for_each_available_child_of_node_scoped(child, gchild) {
			if (!of_device_is_compatible(gchild, "pci1131,e101"))
				continue;

			bus_devfn = netc_of_pci_get_bus_devfn(gchild);
			if (bus_devfn < 0)
				return -EINVAL;

			if (bus_devfn == IMX95_ENETC2_BUS_DEVFN)
				continue;

			err = of_get_phy_mode(gchild, &interface);
			if (err)
				continue;

			mii_proto = netc_get_link_mii_protocol(interface);
			if (mii_proto < 0)
				return -EINVAL;

			switch (bus_devfn) {
			case IMX95_ENETC0_BUS_DEVFN:
				val = u32_replace_bits(val, mii_proto,
						       CFG_LINK_MII_PORT_0);
				break;
			case IMX95_ENETC1_BUS_DEVFN:
				val = u32_replace_bits(val, mii_proto,
						       CFG_LINK_MII_PORT_1);
				break;
			default:
				return -EINVAL;
			}
		}
	}

	/* Configure Link I/O variant */
	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_IO_VAR,
		       IO_VAR(2, IO_VAR_16FF_16G_SERDES));
	/* Configure Link 2 PCS protocol */
	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_PCS_PROT(2),
		       PCS_PROT_10G_SXGMII);
	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_MII_PROT, val);

	return 0;
}

static bool netc_ierb_is_locked(struct netc_blk_ctrl *priv)
{
	return !!(netc_reg_read(priv->prb, PRB_NETCRR) & NETCRR_LOCK);
}

static int netc_lock_ierb(struct netc_blk_ctrl *priv)
{
	u32 val;

	netc_reg_write(priv->prb, PRB_NETCRR, NETCRR_LOCK);

	return read_poll_timeout(netc_reg_read, val, !(val & NETCSR_STATE),
				 100, 2000, false, priv->prb, PRB_NETCSR);
}

static int netc_unlock_ierb_with_warm_reset(struct netc_blk_ctrl *priv)
{
	u32 val;

	netc_reg_write(priv->prb, PRB_NETCRR, 0);

	return read_poll_timeout(netc_reg_read, val, !(val & NETCRR_LOCK),
				 1000, 100000, true, priv->prb, PRB_NETCRR);
}

static int imx95_ierb_init(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);

	/* EMDIO : No MSI-X intterupt */
	netc_reg_write(priv->ierb, IERB_EMDIOFAUXR, 0);
	/* ENETC0 PF */
	netc_reg_write(priv->ierb, IERB_EFAUXR(0), 0);
	/* ENETC0 VF0 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(0), 1);
	/* ENETC0 VF1 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(1), 2);
	/* ENETC1 PF */
	netc_reg_write(priv->ierb, IERB_EFAUXR(1), 3);
	/* ENETC1 VF0 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(2), 5);
	/* ENETC1 VF1 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(3), 6);
	/* ENETC2 PF */
	netc_reg_write(priv->ierb, IERB_EFAUXR(2), 4);
	/* ENETC2 VF0 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(4), 5);
	/* ENETC2 VF1 */
	netc_reg_write(priv->ierb, IERB_VFAUXR(5), 6);
	/* NETC TIMER */
	netc_reg_write(priv->ierb, IERB_T0FAUXR, 7);

	return 0;
}

static int netc_ierb_init(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
	const struct netc_devinfo *devinfo = priv->devinfo;
	int err;

	if (netc_ierb_is_locked(priv)) {
		err = netc_unlock_ierb_with_warm_reset(priv);
		if (err) {
			dev_err(&pdev->dev, "Unlock IERB failed.\n");
			return err;
		}
	}

	if (devinfo->ierb_init) {
		err = devinfo->ierb_init(pdev);
		if (err)
			return err;
	}

	err = netc_lock_ierb(priv);
	if (err) {
		dev_err(&pdev->dev, "Lock IERB failed.\n");
		return err;
	}

	return 0;
}

#if IS_ENABLED(CONFIG_DEBUG_FS)
static int netc_prb_show(struct seq_file *s, void *data)
{
	struct netc_blk_ctrl *priv = s->private;
	u32 val;

	val = netc_reg_read(priv->prb, PRB_NETCRR);
	seq_printf(s, "[PRB NETCRR] Lock:%d SR:%d\n",
		   (val & NETCRR_LOCK) ? 1 : 0,
		   (val & NETCRR_SR) ? 1 : 0);

	val = netc_reg_read(priv->prb, PRB_NETCSR);
	seq_printf(s, "[PRB NETCSR] State:%d Error:%d\n",
		   (val & NETCSR_STATE) ? 1 : 0,
		   (val & NETCSR_ERROR) ? 1 : 0);

	return 0;
}
DEFINE_SHOW_ATTRIBUTE(netc_prb);

static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
{
	struct dentry *root;

	root = debugfs_create_dir("netc_blk_ctrl", NULL);
	if (IS_ERR(root))
		return;

	priv->debugfs_root = root;

	debugfs_create_file("prb", 0444, root, priv, &netc_prb_fops);
}

static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
{
	debugfs_remove_recursive(priv->debugfs_root);
	priv->debugfs_root = NULL;
}

#else

static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
{
}

static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
{
}
#endif

static int netc_prb_check_error(struct netc_blk_ctrl *priv)
{
	if (netc_reg_read(priv->prb, PRB_NETCSR) & NETCSR_ERROR)
		return -1;

	return 0;
}

static const struct netc_devinfo imx95_devinfo = {
	.flags = NETC_HAS_NETCMIX,
	.netcmix_init = imx95_netcmix_init,
	.ierb_init = imx95_ierb_init,
};

static const struct of_device_id netc_blk_ctrl_match[] = {
	{ .compatible = "nxp,imx95-netc-blk-ctrl", .data = &imx95_devinfo },
	{},
};
MODULE_DEVICE_TABLE(of, netc_blk_ctrl_match);

static int netc_blk_ctrl_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	const struct netc_devinfo *devinfo;
	struct device *dev = &pdev->dev;
	const struct of_device_id *id;
	struct netc_blk_ctrl *priv;
	struct clk *ipg_clk;
	void __iomem *regs;
	int err;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->pdev = pdev;
	ipg_clk = devm_clk_get_optional_enabled(dev, "ipg");
	if (IS_ERR(ipg_clk))
		return dev_err_probe(dev, PTR_ERR(ipg_clk),
				     "Set ipg clock failed\n");

	id = of_match_device(netc_blk_ctrl_match, dev);
	if (!id)
		return dev_err_probe(dev, -EINVAL, "Cannot match device\n");

	devinfo = (struct netc_devinfo *)id->data;
	if (!devinfo)
		return dev_err_probe(dev, -EINVAL, "No device information\n");

	priv->devinfo = devinfo;
	regs = devm_platform_ioremap_resource_byname(pdev, "ierb");
	if (IS_ERR(regs))
		return dev_err_probe(dev, PTR_ERR(regs),
				     "Missing IERB resource\n");

	priv->ierb = regs;
	regs = devm_platform_ioremap_resource_byname(pdev, "prb");
	if (IS_ERR(regs))
		return dev_err_probe(dev, PTR_ERR(regs),
				     "Missing PRB resource\n");

	priv->prb = regs;
	if (devinfo->flags & NETC_HAS_NETCMIX) {
		regs = devm_platform_ioremap_resource_byname(pdev, "netcmix");
		if (IS_ERR(regs))
			return dev_err_probe(dev, PTR_ERR(regs),
					     "Missing NETCMIX resource\n");
		priv->netcmix = regs;
	}

	platform_set_drvdata(pdev, priv);
	if (devinfo->netcmix_init) {
		err = devinfo->netcmix_init(pdev);
		if (err)
			return dev_err_probe(dev, err,
					     "Initializing NETCMIX failed\n");
	}

	err = netc_ierb_init(pdev);
	if (err)
		return dev_err_probe(dev, err, "Initializing IERB failed\n");

	if (netc_prb_check_error(priv) < 0)
		dev_warn(dev, "The current IERB configuration is invalid\n");

	netc_blk_ctrl_create_debugfs(priv);

	err = of_platform_populate(node, NULL, NULL, dev);
	if (err) {
		netc_blk_ctrl_remove_debugfs(priv);
		return dev_err_probe(dev, err, "of_platform_populate failed\n");
	}

	return 0;
}

static void netc_blk_ctrl_remove(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);

	of_platform_depopulate(&pdev->dev);
	netc_blk_ctrl_remove_debugfs(priv);
}

static struct platform_driver netc_blk_ctrl_driver = {
	.driver = {
		.name = "nxp-netc-blk-ctrl",
		.of_match_table = netc_blk_ctrl_match,
	},
	.probe = netc_blk_ctrl_probe,
	.remove = netc_blk_ctrl_remove,
};

module_platform_driver(netc_blk_ctrl_driver);

MODULE_DESCRIPTION("NXP NETC Blocks Control Driver");
MODULE_LICENSE("Dual BSD/GPL");