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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
|
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for MaxLinear MxL862xx switch family
*
* Copyright (C) 2024 MaxLinear Inc.
* Copyright (C) 2025 John Crispin <john@phrozen.org>
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <net/dsa.h>
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
#define MXL862XX_API_WRITE(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
#define MXL862XX_API_READ(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6))
#define MXL862XX_SDMA_PCTRL_EN BIT(0)
#define MXL862XX_FDMA_PCTRLP(p) (0xa80 + ((p) * 0x6))
#define MXL862XX_FDMA_PCTRL_EN BIT(0)
#define MXL862XX_READY_TIMEOUT_MS 10000
#define MXL862XX_READY_POLL_MS 100
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
{
return DSA_TAG_PROTO_MXL862;
}
/* PHY access via firmware relay */
static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port,
int devadd, int reg)
{
struct mdio_relay_data param = {
.phy = port,
.mmd = devadd,
.reg = cpu_to_le16(reg),
};
int ret;
ret = MXL862XX_API_READ(priv, INT_GPHY_READ, param);
if (ret)
return ret;
return le16_to_cpu(param.data);
}
static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port,
int devadd, int reg, u16 data)
{
struct mdio_relay_data param = {
.phy = port,
.mmd = devadd,
.reg = cpu_to_le16(reg),
.data = cpu_to_le16(data),
};
return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param);
}
static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum)
{
return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum);
}
static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port,
int regnum, u16 val)
{
return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val);
}
static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port,
int devadd, int regnum)
{
return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum);
}
static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port,
int devadd, int regnum, u16 val)
{
return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val);
}
static int mxl862xx_wait_ready(struct dsa_switch *ds)
{
struct mxl862xx_sys_fw_image_version ver = {};
unsigned long start = jiffies, timeout;
struct mxl862xx_priv *priv = ds->priv;
struct mxl862xx_cfg cfg = {};
int ret;
timeout = start + msecs_to_jiffies(MXL862XX_READY_TIMEOUT_MS);
msleep(2000); /* it always takes at least 2 seconds */
do {
ret = MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver);
if (ret || !ver.iv_major)
goto not_ready_yet;
/* being able to perform CFGGET indicates that
* the firmware is ready
*/
ret = MXL862XX_API_READ_QUIET(priv,
MXL862XX_COMMON_CFGGET,
cfg);
if (ret)
goto not_ready_yet;
dev_info(ds->dev, "switch ready after %ums, firmware %u.%u.%u (build %u)\n",
jiffies_to_msecs(jiffies - start),
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
return 0;
not_ready_yet:
msleep(MXL862XX_READY_POLL_MS);
} while (time_before(jiffies, timeout));
dev_err(ds->dev, "switch not responding after reset\n");
return -ETIMEDOUT;
}
static int mxl862xx_setup_mdio(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
struct device *dev = ds->dev;
struct device_node *mdio_np;
struct mii_bus *bus;
int ret;
bus = devm_mdiobus_alloc(dev);
if (!bus)
return -ENOMEM;
bus->priv = priv;
bus->name = KBUILD_MODNAME "-mii";
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
bus->read_c45 = mxl862xx_phy_read_c45_mii_bus;
bus->write_c45 = mxl862xx_phy_write_c45_mii_bus;
bus->read = mxl862xx_phy_read_mii_bus;
bus->write = mxl862xx_phy_write_mii_bus;
bus->parent = dev;
bus->phy_mask = ~ds->phys_mii_mask;
mdio_np = of_get_child_by_name(dev->of_node, "mdio");
if (!mdio_np)
return -ENODEV;
ret = devm_of_mdiobus_register(dev, bus, mdio_np);
of_node_put(mdio_np);
return ret;
}
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
int ret;
ret = mxl862xx_reset(priv);
if (ret)
return ret;
ret = mxl862xx_wait_ready(ds);
if (ret)
return ret;
return mxl862xx_setup_mdio(ds);
}
static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable)
{
struct mxl862xx_register_mod sdma = {
.addr = cpu_to_le16(MXL862XX_SDMA_PCTRLP(port)),
.data = cpu_to_le16(enable ? MXL862XX_SDMA_PCTRL_EN : 0),
.mask = cpu_to_le16(MXL862XX_SDMA_PCTRL_EN),
};
struct mxl862xx_register_mod fdma = {
.addr = cpu_to_le16(MXL862XX_FDMA_PCTRLP(port)),
.data = cpu_to_le16(enable ? MXL862XX_FDMA_PCTRL_EN : 0),
.mask = cpu_to_le16(MXL862XX_FDMA_PCTRL_EN),
};
int ret;
ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, sdma);
if (ret)
return ret;
return MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, fdma);
}
static int mxl862xx_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phydev)
{
return mxl862xx_port_state(ds, port, true);
}
static void mxl862xx_port_disable(struct dsa_switch *ds, int port)
{
if (mxl862xx_port_state(ds, port, false))
dev_err(ds->dev, "failed to disable port %d\n", port);
}
static void mxl862xx_port_fast_age(struct dsa_switch *ds, int port)
{
struct mxl862xx_mac_table_clear param = {
.type = MXL862XX_MAC_CLEAR_PHY_PORT,
.port_id = port,
};
if (MXL862XX_API_WRITE(ds->priv, MXL862XX_MAC_TABLECLEARCOND, param))
dev_err(ds->dev, "failed to clear fdb on port %d\n", port);
}
static int mxl862xx_configure_ctp_port(struct dsa_switch *ds, int port,
u16 first_ctp_port_id,
u16 number_of_ctp_ports)
{
struct mxl862xx_ctp_port_assignment ctp_assign = {
.logical_port_id = port,
.first_ctp_port_id = cpu_to_le16(first_ctp_port_id),
.number_of_ctp_port = cpu_to_le16(number_of_ctp_ports),
.mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET),
};
return MXL862XX_API_WRITE(ds->priv, MXL862XX_CTP_PORTASSIGNMENTSET,
ctp_assign);
}
static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port,
bool enable)
{
struct mxl862xx_ss_sp_tag tag = {
.pid = port,
.mask = MXL862XX_SS_SP_TAG_MASK_RX | MXL862XX_SS_SP_TAG_MASK_TX,
.rx = enable ? MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT :
MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT,
.tx = enable ? MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE :
MXL862XX_SS_SP_TAG_TX_TAG_REMOVE,
};
return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
}
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
struct mxl862xx_priv *priv = ds->priv;
u16 bridge_port_map = 0;
struct dsa_port *dp;
/* CPU port bridge setup */
br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
br_port_cfg.bridge_port_id = cpu_to_le16(port);
br_port_cfg.src_mac_learning_disable = false;
br_port_cfg.vlan_src_mac_vid_enable = true;
br_port_cfg.vlan_dst_mac_vid_enable = true;
/* include all assigned user ports in the CPU portmap */
dsa_switch_for_each_user_port(dp, ds) {
/* it's safe to rely on cpu_dp being valid for user ports */
if (dp->cpu_dp->index != port)
continue;
bridge_port_map |= BIT(dp->index);
}
br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
}
static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
struct dsa_port *dp = dsa_to_port(ds, port);
struct mxl862xx_bridge_alloc br_alloc = {};
int ret;
ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
if (ret) {
dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
return ret;
}
br_port_cfg.bridge_id = br_alloc.bridge_id;
br_port_cfg.bridge_port_id = cpu_to_le16(port);
br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
br_port_cfg.src_mac_learning_disable = true;
br_port_cfg.vlan_src_mac_vid_enable = false;
br_port_cfg.vlan_dst_mac_vid_enable = false;
/* As this function is only called for user ports it is safe to rely on
* cpu_dp being valid
*/
br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
}
static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
{
struct dsa_port *dp = dsa_to_port(ds, port);
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
/* disable port and flush MAC entries */
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
mxl862xx_port_fast_age(ds, port);
/* skip setup for unused and DSA ports */
if (dsa_port_is_unused(dp) ||
dsa_port_is_dsa(dp))
return 0;
/* configure tag protocol */
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
/* assign CTP port IDs */
ret = mxl862xx_configure_ctp_port(ds, port, port,
is_cpu_port ? 32 - port : 1);
if (ret)
return ret;
if (is_cpu_port)
/* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
/* setup single-port bridge for user ports */
return mxl862xx_add_single_port_bridge(ds, port);
}
static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
MAC_100 | MAC_1000 | MAC_2500FD;
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
}
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
.port_setup = mxl862xx_port_setup,
.phylink_get_caps = mxl862xx_phylink_get_caps,
.port_enable = mxl862xx_port_enable,
.port_disable = mxl862xx_port_disable,
.port_fast_age = mxl862xx_port_fast_age,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
}
static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
}
static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phydev,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
}
static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
.mac_config = mxl862xx_phylink_mac_config,
.mac_link_down = mxl862xx_phylink_mac_link_down,
.mac_link_up = mxl862xx_phylink_mac_link_up,
};
static int mxl862xx_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct mxl862xx_priv *priv;
struct dsa_switch *ds;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->mdiodev = mdiodev;
ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
if (!ds)
return -ENOMEM;
priv->ds = ds;
ds->dev = dev;
ds->priv = priv;
ds->ops = &mxl862xx_switch_ops;
ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
ds->num_ports = MXL862XX_MAX_PORTS;
dev_set_drvdata(dev, ds);
return dsa_register_switch(ds);
}
static void mxl862xx_remove(struct mdio_device *mdiodev)
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
if (!ds)
return;
dsa_unregister_switch(ds);
}
static void mxl862xx_shutdown(struct mdio_device *mdiodev)
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
if (!ds)
return;
dsa_switch_shutdown(ds);
dev_set_drvdata(&mdiodev->dev, NULL);
}
static const struct of_device_id mxl862xx_of_match[] = {
{ .compatible = "maxlinear,mxl86282" },
{ .compatible = "maxlinear,mxl86252" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxl862xx_of_match);
static struct mdio_driver mxl862xx_driver = {
.probe = mxl862xx_probe,
.remove = mxl862xx_remove,
.shutdown = mxl862xx_shutdown,
.mdiodrv.driver = {
.name = "mxl862xx",
.of_match_table = mxl862xx_of_match,
},
};
mdio_module_driver(mxl862xx_driver);
MODULE_DESCRIPTION("Driver for MaxLinear MxL862xx switch family");
MODULE_LICENSE("GPL");
|