1
1
// SPDX-License-Identifier: GPL-2.0
2
2
/*
3
3
* Marvell 88Q2XXX automotive 100BASE-T1/1000BASE-T1 PHY driver
4
+ *
5
+ * Derived from Marvell Q222x API
6
+ *
7
+ * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
4
8
*/
5
9
#include <linux/ethtool_netlink.h>
6
10
#include <linux/marvell_phy.h>
7
11
#include <linux/phy.h>
8
12
13
+ #define PHY_ID_88Q2220_REVB0 (MARVELL_PHY_ID_88Q2220 | 0x1)
14
+
9
15
#define MDIO_MMD_AN_MV_STAT 32769
10
16
#define MDIO_MMD_AN_MV_STAT_ANEG 0x0100
11
17
#define MDIO_MMD_AN_MV_STAT_LOCAL_RX 0x1000
12
18
#define MDIO_MMD_AN_MV_STAT_REMOTE_RX 0x2000
13
19
#define MDIO_MMD_AN_MV_STAT_LOCAL_MASTER 0x4000
14
20
#define MDIO_MMD_AN_MV_STAT_MS_CONF_FAULT 0x8000
15
21
22
+ #define MDIO_MMD_AN_MV_STAT2 32794
23
+ #define MDIO_MMD_AN_MV_STAT2_AN_RESOLVED 0x0800
24
+ #define MDIO_MMD_AN_MV_STAT2_100BT1 0x2000
25
+ #define MDIO_MMD_AN_MV_STAT2_1000BT1 0x4000
26
+
16
27
#define MDIO_MMD_PCS_MV_100BT1_STAT1 33032
17
28
#define MDIO_MMD_PCS_MV_100BT1_STAT1_IDLE_ERROR 0x00ff
18
29
#define MDIO_MMD_PCS_MV_100BT1_STAT1_JABBER 0x0100
29
40
30
41
#define MDIO_MMD_PCS_MV_RX_STAT 33328
31
42
43
+ struct mmd_val {
44
+ int devad ;
45
+ u32 regnum ;
46
+ u16 val ;
47
+ };
48
+
49
+ static const struct mmd_val mv88q222x_revb0_init_seq0 [] = {
50
+ { MDIO_MMD_PCS , 0x8033 , 0x6801 },
51
+ { MDIO_MMD_AN , MDIO_AN_T1_CTRL , 0x0 },
52
+ { MDIO_MMD_PMAPMD , MDIO_CTRL1 ,
53
+ MDIO_CTRL1_LPOWER | MDIO_PMA_CTRL1_SPEED1000 },
54
+ { MDIO_MMD_PCS , 0xfe1b , 0x48 },
55
+ { MDIO_MMD_PCS , 0xffe4 , 0x6b6 },
56
+ { MDIO_MMD_PMAPMD , MDIO_CTRL1 , 0x0 },
57
+ { MDIO_MMD_PCS , MDIO_CTRL1 , 0x0 },
58
+ };
59
+
60
+ static const struct mmd_val mv88q222x_revb0_init_seq1 [] = {
61
+ { MDIO_MMD_PCS , 0xfe79 , 0x0 },
62
+ { MDIO_MMD_PCS , 0xfe07 , 0x125a },
63
+ { MDIO_MMD_PCS , 0xfe09 , 0x1288 },
64
+ { MDIO_MMD_PCS , 0xfe08 , 0x2588 },
65
+ { MDIO_MMD_PCS , 0xfe11 , 0x1105 },
66
+ { MDIO_MMD_PCS , 0xfe72 , 0x042c },
67
+ { MDIO_MMD_PCS , 0xfbba , 0xcb2 },
68
+ { MDIO_MMD_PCS , 0xfbbb , 0xc4a },
69
+ { MDIO_MMD_AN , 0x8032 , 0x2020 },
70
+ { MDIO_MMD_AN , 0x8031 , 0xa28 },
71
+ { MDIO_MMD_AN , 0x8031 , 0xc28 },
72
+ { MDIO_MMD_PCS , 0xffdb , 0xfc10 },
73
+ { MDIO_MMD_PCS , 0xfe1b , 0x58 },
74
+ { MDIO_MMD_PCS , 0xfe79 , 0x4 },
75
+ { MDIO_MMD_PCS , 0xfe5f , 0xe8 },
76
+ { MDIO_MMD_PCS , 0xfe05 , 0x755c },
77
+ };
78
+
32
79
static int mv88q2xxx_soft_reset (struct phy_device * phydev )
33
80
{
34
81
int ret ;
@@ -125,24 +172,90 @@ static int mv88q2xxx_read_link_100m(struct phy_device *phydev)
125
172
126
173
static int mv88q2xxx_read_link (struct phy_device * phydev )
127
174
{
128
- int ret ;
129
-
130
175
/* The 88Q2XXX PHYs do not have the PMA/PMD status register available,
131
176
* therefore we need to read the link status from the vendor specific
132
177
* registers depending on the speed.
133
178
*/
179
+
134
180
if (phydev -> speed == SPEED_1000 )
135
- ret = mv88q2xxx_read_link_gbit (phydev );
181
+ return mv88q2xxx_read_link_gbit (phydev );
182
+ else if (phydev -> speed == SPEED_100 )
183
+ return mv88q2xxx_read_link_100m (phydev );
184
+
185
+ phydev -> link = false;
186
+ return 0 ;
187
+ }
188
+
189
+ static int mv88q2xxx_read_master_slave_state (struct phy_device * phydev )
190
+ {
191
+ int ret ;
192
+
193
+ phydev -> master_slave_state = MASTER_SLAVE_STATE_UNKNOWN ;
194
+ ret = phy_read_mmd (phydev , MDIO_MMD_AN , MDIO_MMD_AN_MV_STAT );
195
+ if (ret < 0 )
196
+ return ret ;
197
+
198
+ if (ret & MDIO_MMD_AN_MV_STAT_LOCAL_MASTER )
199
+ phydev -> master_slave_state = MASTER_SLAVE_STATE_MASTER ;
136
200
else
137
- ret = mv88q2xxx_read_link_100m ( phydev ) ;
201
+ phydev -> master_slave_state = MASTER_SLAVE_STATE_SLAVE ;
138
202
139
- return ret ;
203
+ return 0 ;
204
+ }
205
+
206
+ static int mv88q2xxx_read_aneg_speed (struct phy_device * phydev )
207
+ {
208
+ int ret ;
209
+
210
+ phydev -> speed = SPEED_UNKNOWN ;
211
+ ret = phy_read_mmd (phydev , MDIO_MMD_AN , MDIO_MMD_AN_MV_STAT2 );
212
+ if (ret < 0 )
213
+ return ret ;
214
+
215
+ if (!(ret & MDIO_MMD_AN_MV_STAT2_AN_RESOLVED ))
216
+ return 0 ;
217
+
218
+ if (ret & MDIO_MMD_AN_MV_STAT2_100BT1 )
219
+ phydev -> speed = SPEED_100 ;
220
+ else if (ret & MDIO_MMD_AN_MV_STAT2_1000BT1 )
221
+ phydev -> speed = SPEED_1000 ;
222
+
223
+ return 0 ;
140
224
}
141
225
142
226
static int mv88q2xxx_read_status (struct phy_device * phydev )
143
227
{
144
228
int ret ;
145
229
230
+ if (phydev -> autoneg == AUTONEG_ENABLE ) {
231
+ /* We have to get the negotiated speed first, otherwise we are
232
+ * not able to read the link.
233
+ */
234
+ ret = mv88q2xxx_read_aneg_speed (phydev );
235
+ if (ret < 0 )
236
+ return ret ;
237
+
238
+ ret = mv88q2xxx_read_link (phydev );
239
+ if (ret < 0 )
240
+ return ret ;
241
+
242
+ ret = genphy_c45_read_lpa (phydev );
243
+ if (ret < 0 )
244
+ return ret ;
245
+
246
+ ret = genphy_c45_baset1_read_status (phydev );
247
+ if (ret < 0 )
248
+ return ret ;
249
+
250
+ ret = mv88q2xxx_read_master_slave_state (phydev );
251
+ if (ret < 0 )
252
+ return ret ;
253
+
254
+ phy_resolve_aneg_linkmode (phydev );
255
+
256
+ return 0 ;
257
+ }
258
+
146
259
ret = mv88q2xxx_read_link (phydev );
147
260
if (ret < 0 )
148
261
return ret ;
@@ -171,7 +284,9 @@ static int mv88q2xxx_get_features(struct phy_device *phydev)
171
284
* sequence provided by Marvell. Disable it for now until a proper
172
285
* workaround is found or a new PHY revision is released.
173
286
*/
174
- linkmode_clear_bit (ETHTOOL_LINK_MODE_Autoneg_BIT , phydev -> supported );
287
+ if (phydev -> drv -> phy_id == MARVELL_PHY_ID_88Q2110 )
288
+ linkmode_clear_bit (ETHTOOL_LINK_MODE_Autoneg_BIT ,
289
+ phydev -> supported );
175
290
176
291
return 0 ;
177
292
}
@@ -241,6 +356,75 @@ static int mv88q2xxx_get_sqi_max(struct phy_device *phydev)
241
356
return 15 ;
242
357
}
243
358
359
+ static int mv88q222x_soft_reset (struct phy_device * phydev )
360
+ {
361
+ int ret ;
362
+
363
+ /* Enable RESET of DCL */
364
+ if (phydev -> autoneg == AUTONEG_ENABLE || phydev -> speed == SPEED_1000 ) {
365
+ ret = phy_write_mmd (phydev , MDIO_MMD_PCS , 0xfe1b , 0x48 );
366
+ if (ret < 0 )
367
+ return ret ;
368
+ }
369
+
370
+ ret = phy_write_mmd (phydev , MDIO_MMD_PCS , MDIO_PCS_1000BT1_CTRL ,
371
+ MDIO_PCS_1000BT1_CTRL_RESET );
372
+ if (ret < 0 )
373
+ return ret ;
374
+
375
+ ret = phy_write_mmd (phydev , MDIO_MMD_PCS , 0xffe4 , 0xc );
376
+ if (ret < 0 )
377
+ return ret ;
378
+
379
+ /* Disable RESET of DCL */
380
+ if (phydev -> autoneg == AUTONEG_ENABLE || phydev -> speed == SPEED_1000 )
381
+ return phy_write_mmd (phydev , MDIO_MMD_PCS , 0xfe1b , 0x58 );
382
+
383
+ return 0 ;
384
+ }
385
+
386
+ static int mv88q222x_config_aneg (struct phy_device * phydev )
387
+ {
388
+ int ret ;
389
+
390
+ ret = genphy_c45_config_aneg (phydev );
391
+ if (ret )
392
+ return ret ;
393
+
394
+ return mv88q222x_soft_reset (phydev );
395
+ }
396
+
397
+ static int mv88q222x_revb0_config_init (struct phy_device * phydev )
398
+ {
399
+ int ret , i ;
400
+
401
+ for (i = 0 ; i < ARRAY_SIZE (mv88q222x_revb0_init_seq0 ); i ++ ) {
402
+ ret = phy_write_mmd (phydev , mv88q222x_revb0_init_seq0 [i ].devad ,
403
+ mv88q222x_revb0_init_seq0 [i ].regnum ,
404
+ mv88q222x_revb0_init_seq0 [i ].val );
405
+ if (ret < 0 )
406
+ return ret ;
407
+ }
408
+
409
+ usleep_range (5000 , 10000 );
410
+
411
+ for (i = 0 ; i < ARRAY_SIZE (mv88q222x_revb0_init_seq1 ); i ++ ) {
412
+ ret = phy_write_mmd (phydev , mv88q222x_revb0_init_seq1 [i ].devad ,
413
+ mv88q222x_revb0_init_seq1 [i ].regnum ,
414
+ mv88q222x_revb0_init_seq1 [i ].val );
415
+ if (ret < 0 )
416
+ return ret ;
417
+ }
418
+
419
+ /* The 88Q2XXX PHYs do have the extended ability register available, but
420
+ * register MDIO_PMA_EXTABLE where they should signalize it does not
421
+ * work according to specification. Therefore, we force it here.
422
+ */
423
+ phydev -> pma_extable = MDIO_PMA_EXTABLE_BT1 ;
424
+
425
+ return 0 ;
426
+ }
427
+
244
428
static struct phy_driver mv88q2xxx_driver [] = {
245
429
{
246
430
.phy_id = MARVELL_PHY_ID_88Q2110 ,
@@ -255,12 +439,26 @@ static struct phy_driver mv88q2xxx_driver[] = {
255
439
.get_sqi = mv88q2xxx_get_sqi ,
256
440
.get_sqi_max = mv88q2xxx_get_sqi_max ,
257
441
},
442
+ {
443
+ PHY_ID_MATCH_EXACT (PHY_ID_88Q2220_REVB0 ),
444
+ .name = "mv88q2220" ,
445
+ .get_features = mv88q2xxx_get_features ,
446
+ .config_aneg = mv88q222x_config_aneg ,
447
+ .aneg_done = genphy_c45_aneg_done ,
448
+ .config_init = mv88q222x_revb0_config_init ,
449
+ .read_status = mv88q2xxx_read_status ,
450
+ .soft_reset = mv88q222x_soft_reset ,
451
+ .set_loopback = genphy_c45_loopback ,
452
+ .get_sqi = mv88q2xxx_get_sqi ,
453
+ .get_sqi_max = mv88q2xxx_get_sqi_max ,
454
+ },
258
455
};
259
456
260
457
module_phy_driver (mv88q2xxx_driver );
261
458
262
459
static struct mdio_device_id __maybe_unused mv88q2xxx_tbl [] = {
263
460
{ MARVELL_PHY_ID_88Q2110 , MARVELL_PHY_ID_MASK },
461
+ { PHY_ID_MATCH_EXACT (PHY_ID_88Q2220_REVB0 ), },
264
462
{ /*sentinel*/ }
265
463
};
266
464
MODULE_DEVICE_TABLE (mdio , mv88q2xxx_tbl );
0 commit comments