1 /*
2 * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
3 * Copyright 2023 NXP
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/logging/log.h>
9 LOG_MODULE_REGISTER(phy_adin2111, CONFIG_PHY_LOG_LEVEL);
10
11 #define DT_DRV_COMPAT adi_adin2111_phy
12
13 #include <errno.h>
14 #include <stdint.h>
15 #include <stdbool.h>
16 #include <zephyr/kernel.h>
17 #include <zephyr/device.h>
18 #include <zephyr/sys/util.h>
19 #include <zephyr/net/phy.h>
20 #include <zephyr/net/mii.h>
21 #include <zephyr/net/mdio.h>
22 #include <zephyr/drivers/mdio.h>
23
24 /* PHYs out of reset check retry delay */
25 #define ADIN2111_PHY_AWAIT_DELAY_POLL_US 15U
26 /* Number of retries for PHYs out of reset check */
27 #define ADIN2111_PHY_AWAIT_RETRY_COUNT 200U
28
29 /* PHY's software powerdown check retry delay */
30 #define ADIN2111_PHY_SFT_PD_DELAY_POLL_US 15U
31 /* Number of retries for PHY's software powerdown check */
32 #define ADIN2111_PHY_SFT_PD_RETRY_COUNT 200U
33
34 /* PHYs autonegotiation complete timeout */
35 #define ADIN2111_AN_COMPLETE_AWAIT_TIMEOUT_MS 3000U
36
37 /* ADIN2111 PHY identifier */
38 #define ADIN2111_PHY_ID 0x0283BCA1U
39 #define ADIN1110_PHY_ID 0x0283BC91U
40
41 /* System Interrupt Mask Register */
42 #define ADIN2111_PHY_CRSM_IRQ_MASK 0x0020U
43 /* System Interrupt Status Register */
44 #define ADIN2111_PHY_CRSM_IRQ_STATUS 0x0010U
45 /**
46 * Mask of reserved interrupts that indicates a fatal error in the system.
47 *
48 * There is inconsistency between RM and ADI driver example:
49 * - RM mask 0x6FFF
50 * - ADI driver example mask 0x2BFF
51 *
52 * The value from the example doesn't include reserved bits 10 and 14.
53 * The tests show that PHY is still functioning when bit 10 is raised.
54 *
55 * Here the value from ADI driver example is used instead of RM.
56 */
57 #define ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR 0x2BFFU
58
59 /* PHY Subsystem Interrupt Mask Register */
60 #define ADIN2111_PHY_SUBSYS_IRQ_MASK 0x0021U
61 /* PHY Subsystem Interrupt Status Register */
62 #define ADIN2111_PHY_SUBSYS_IRQ_STATUS 0x0011U
63 /* Link Status Change */
64 #define ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH BIT(1)
65
66 /* Software Power-down Control Register */
67 #define ADIN2111_PHY_CRSM_SFT_PD_CNTRL 0x8812U
68 /* System Status Register */
69 #define ADIN2111_PHY_CRSM_STAT 0x8818U
70 /* Software Power-down Status */
71 #define ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY BIT(1)
72
73 /* LED Control Register */
74 #define ADIN2111_PHY_LED_CNTRL 0x8C82U
75 /* LED 1 Enable */
76 #define ADIN2111_PHY_LED_CNTRL_LED1_EN BIT(15)
77 /* LED 0 Enable */
78 #define ADIN2111_PHY_LED_CNTRL_LED0_EN BIT(7)
79
80 struct phy_adin2111_config {
81 const struct device *mdio;
82 uint8_t phy_addr;
83 bool led0_en;
84 bool led1_en;
85 bool tx_24v;
86 };
87
88 struct phy_adin2111_data {
89 struct phy_link_state state;
90 struct k_sem sem;
91 };
92
phy_adin2111_c22_read(const struct device * dev,uint16_t reg,uint16_t * val)93 static inline int phy_adin2111_c22_read(const struct device *dev, uint16_t reg,
94 uint16_t *val)
95 {
96 const struct phy_adin2111_config *const cfg = dev->config;
97
98 return mdio_read(cfg->mdio, cfg->phy_addr, reg, val);
99 }
100
phy_adin2111_c22_write(const struct device * dev,uint16_t reg,uint16_t val)101 static inline int phy_adin2111_c22_write(const struct device *dev, uint16_t reg,
102 uint16_t val)
103 {
104 const struct phy_adin2111_config *const cfg = dev->config;
105
106 return mdio_write(cfg->mdio, cfg->phy_addr, reg, val);
107 }
108
phy_adin2111_c45_write(const struct device * dev,uint16_t devad,uint16_t reg,uint16_t val)109 static inline int phy_adin2111_c45_write(const struct device *dev, uint16_t devad,
110 uint16_t reg, uint16_t val)
111 {
112 const struct phy_adin2111_config *cfg = dev->config;
113
114 return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
115 }
116
phy_adin2111_c45_read(const struct device * dev,uint16_t devad,uint16_t reg,uint16_t * val)117 static inline int phy_adin2111_c45_read(const struct device *dev, uint16_t devad,
118 uint16_t reg, uint16_t *val)
119 {
120 const struct phy_adin2111_config *cfg = dev->config;
121
122 return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
123 }
124
phy_adin2111_reg_read(const struct device * dev,uint16_t reg_addr,uint32_t * data)125 static int phy_adin2111_reg_read(const struct device *dev, uint16_t reg_addr,
126 uint32_t *data)
127 {
128 const struct phy_adin2111_config *cfg = dev->config;
129 int ret;
130
131 mdio_bus_enable(cfg->mdio);
132
133 ret = phy_adin2111_c22_read(dev, reg_addr, (uint16_t *) data);
134
135 mdio_bus_disable(cfg->mdio);
136
137 return ret;
138 }
139
phy_adin2111_reg_write(const struct device * dev,uint16_t reg_addr,uint32_t data)140 static int phy_adin2111_reg_write(const struct device *dev, uint16_t reg_addr,
141 uint32_t data)
142 {
143 const struct phy_adin2111_config *cfg = dev->config;
144 int ret;
145
146 mdio_bus_enable(cfg->mdio);
147
148 ret = phy_adin2111_c22_write(dev, reg_addr, (uint16_t) data);
149
150 mdio_bus_disable(cfg->mdio);
151
152 return ret;
153 }
154
phy_adin2111_await_phy(const struct device * dev)155 static int phy_adin2111_await_phy(const struct device *dev)
156 {
157 int ret;
158 uint32_t count;
159 uint16_t val;
160
161 /**
162 * Port 2 PHY comes out of reset after Port 1 PHY,
163 * wait until both are out of reset.
164 * Reading Port 2 PHY registers returns 0s until
165 * it comes out from reset.
166 */
167 for (count = 0U; count < ADIN2111_PHY_AWAIT_RETRY_COUNT; ++count) {
168 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
169 ADIN2111_PHY_CRSM_IRQ_MASK, &val);
170 if (ret >= 0) {
171 if (val != 0U) {
172 break;
173 }
174 ret = -ETIMEDOUT;
175 }
176 k_sleep(K_USEC(ADIN2111_PHY_AWAIT_DELAY_POLL_US));
177 }
178
179 return ret;
180 }
181
phy_adin2111_an_state_read(const struct device * dev)182 static int phy_adin2111_an_state_read(const struct device *dev)
183 {
184 struct phy_adin2111_data *const data = dev->data;
185 uint16_t bmsr;
186 int ret;
187
188 /* read twice to get actual link status, latch low */
189 ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr);
190 if (ret < 0) {
191 return ret;
192 }
193 ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr);
194 if (ret < 0) {
195 return ret;
196 }
197
198 data->state.is_up = !!(bmsr & MII_BMSR_LINK_STATUS);
199
200 return 0;
201 }
202
phy_adin2111_handle_phy_irq(const struct device * dev,struct phy_link_state * state)203 int phy_adin2111_handle_phy_irq(const struct device *dev,
204 struct phy_link_state *state)
205 {
206 struct phy_adin2111_data *const data = dev->data;
207 uint16_t subsys_status;
208 int ret;
209
210 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2,
211 ADIN2111_PHY_SUBSYS_IRQ_STATUS,
212 &subsys_status);
213 if (ret < 0) {
214 return ret;
215 }
216
217 if ((subsys_status & ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH) == 0U) {
218 /* nothing to process */
219 return -EAGAIN;
220 }
221
222 k_sem_take(&data->sem, K_FOREVER);
223
224 ret = phy_adin2111_an_state_read(dev);
225
226 memcpy(state, &data->state, sizeof(struct phy_link_state));
227
228 k_sem_give(&data->sem);
229
230 return ret;
231 }
232
phy_adin2111_sft_pd(const struct device * dev,bool enter)233 static int phy_adin2111_sft_pd(const struct device *dev, bool enter)
234 {
235 int ret;
236 uint32_t count;
237 const uint16_t expected = enter ? ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY : 0U;
238 uint16_t val;
239
240 ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
241 ADIN2111_PHY_CRSM_SFT_PD_CNTRL,
242 enter ? 1U : 0U);
243 if (ret < 0) {
244 return ret;
245 }
246
247 for (count = 0U; count < ADIN2111_PHY_SFT_PD_RETRY_COUNT; ++count) {
248 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
249 ADIN2111_PHY_CRSM_STAT, &val);
250 if (ret >= 0) {
251 if ((val & ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY) == expected) {
252 break;
253 }
254 ret = -ETIMEDOUT;
255 }
256 k_sleep(K_USEC(ADIN2111_PHY_SFT_PD_DELAY_POLL_US));
257 }
258
259 return ret;
260 }
261
phy_adin2111_id(const struct device * dev,uint32_t * phy_id)262 static int phy_adin2111_id(const struct device *dev, uint32_t *phy_id)
263 {
264 uint16_t val;
265
266 if (phy_adin2111_c22_read(dev, MII_PHYID1R, &val) < 0) {
267 return -EIO;
268 }
269
270 *phy_id = (val & UINT16_MAX) << 16;
271
272 if (phy_adin2111_c22_read(dev, MII_PHYID2R, &val) < 0) {
273 return -EIO;
274 }
275
276 *phy_id |= (val & UINT16_MAX);
277
278 return 0;
279 }
280
phy_adin2111_get_link_state(const struct device * dev,struct phy_link_state * state)281 static int phy_adin2111_get_link_state(const struct device *dev,
282 struct phy_link_state *state)
283 {
284 struct phy_adin2111_data *const data = dev->data;
285
286 k_sem_take(&data->sem, K_FOREVER);
287
288 memcpy(state, &data->state, sizeof(struct phy_link_state));
289
290 k_sem_give(&data->sem);
291
292 return 0;
293 }
294
phy_adin2111_cfg_link(const struct device * dev,enum phy_link_speed adv_speeds)295 static int phy_adin2111_cfg_link(const struct device *dev,
296 enum phy_link_speed adv_speeds)
297 {
298 ARG_UNUSED(dev);
299
300 if (!!(adv_speeds & LINK_FULL_10BASE_T)) {
301 return 0;
302 }
303
304 return -ENOTSUP;
305 }
306
phy_adin2111_init(const struct device * dev)307 static int phy_adin2111_init(const struct device *dev)
308 {
309 const struct phy_adin2111_config *const cfg = dev->config;
310 struct phy_adin2111_data *const data = dev->data;
311 uint32_t phy_id;
312 uint16_t val;
313 bool tx_24v_supported = false;
314 int ret;
315
316 data->state.is_up = false;
317 data->state.speed = LINK_FULL_10BASE_T;
318
319 ret = phy_adin2111_await_phy(dev);
320 if (ret < 0) {
321 LOG_ERR("PHY %u didn't come out of reset, %d",
322 cfg->phy_addr, ret);
323 return -ENODEV;
324 }
325
326 ret = phy_adin2111_id(dev, &phy_id);
327 if (ret < 0) {
328 LOG_ERR("Failed to read PHY %u ID, %d",
329 cfg->phy_addr, ret);
330 return -ENODEV;
331 }
332
333 if (phy_id != ADIN2111_PHY_ID && phy_id != ADIN1110_PHY_ID) {
334 LOG_ERR("PHY %u unexpected PHY ID %X", cfg->phy_addr, phy_id);
335 return -EINVAL;
336 }
337
338 LOG_INF("PHY %u ID %X", cfg->phy_addr, phy_id);
339
340 /* enter software powerdown */
341 ret = phy_adin2111_sft_pd(dev, true);
342 if (ret < 0) {
343 return ret;
344 }
345
346 /* disable interrupts */
347 ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
348 ADIN2111_PHY_CRSM_IRQ_MASK, 0U);
349 if (ret < 0) {
350 return ret;
351 }
352
353 /* enable link status change irq */
354 ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2,
355 ADIN2111_PHY_SUBSYS_IRQ_MASK,
356 ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH);
357 if (ret < 0) {
358 return ret;
359 }
360
361 /* clear PHY IRQ status before enabling ADIN IRQs */
362 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
363 ADIN2111_PHY_CRSM_IRQ_STATUS, &val);
364 if (ret < 0) {
365 return ret;
366 }
367
368 if (val & ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR) {
369 LOG_ERR("PHY %u CRSM reports fatal system error", cfg->phy_addr);
370 return -ENODEV;
371 }
372
373 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2,
374 ADIN2111_PHY_SUBSYS_IRQ_STATUS, &val);
375 if (ret < 0) {
376 return ret;
377 }
378
379 if (!cfg->led0_en || !cfg->led1_en) {
380 ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
381 ADIN2111_PHY_LED_CNTRL, &val);
382 if (ret < 0) {
383 return ret;
384 }
385 if (!cfg->led0_en) {
386 val &= ~(ADIN2111_PHY_LED_CNTRL_LED0_EN);
387 }
388 if (!cfg->led1_en) {
389 val &= ~(ADIN2111_PHY_LED_CNTRL_LED1_EN);
390 }
391 ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
392 ADIN2111_PHY_LED_CNTRL, val);
393 if (ret < 0) {
394 return ret;
395 }
396 }
397
398 /* check 2.4V support */
399 ret = phy_adin2111_c45_read(dev, MDIO_MMD_PMAPMD, MDIO_PMA_B10L_STAT, &val);
400 if (ret < 0) {
401 return ret;
402 }
403
404 tx_24v_supported = !!(val & MDIO_PMA_B10L_STAT_2V4_ABLE);
405
406 LOG_INF("PHY %u 2.4V mode %s", cfg->phy_addr,
407 tx_24v_supported ? "supported" : "not supported");
408
409 if (!cfg->tx_24v & tx_24v_supported) {
410 LOG_ERR("PHY %u 2.4V mode supported, but not enabled",
411 cfg->phy_addr);
412 }
413
414 /* config 2.4V auto-negotiation */
415 ret = phy_adin2111_c45_read(dev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H, &val);
416 if (ret < 0) {
417 return ret;
418 }
419
420 if (tx_24v_supported) {
421 val |= MDIO_AN_T1_ADV_H_10L_TX_HI;
422 } else {
423 val &= ~MDIO_AN_T1_ADV_H_10L_TX_HI;
424 }
425
426 if (cfg->tx_24v) {
427 if (!tx_24v_supported) {
428 LOG_ERR("PHY %u 2.4V mode enabled, but not supported",
429 cfg->phy_addr);
430 return -EINVAL;
431 }
432
433 val |= MDIO_AN_T1_ADV_H_10L_TX_HI_REQ;
434 } else {
435 val &= ~MDIO_AN_T1_ADV_H_10L_TX_HI_REQ;
436 }
437
438 ret = phy_adin2111_c45_write(dev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H, val);
439 if (ret < 0) {
440 return ret;
441 }
442
443 /* enable auto-negotiation */
444 ret = phy_adin2111_c45_write(dev, MDIO_MMD_AN, MDIO_AN_T1_CTRL,
445 MDIO_AN_T1_CTRL_EN);
446 if (ret < 0) {
447 return ret;
448 }
449
450 /**
451 * done, PHY is in software powerdown (SFT PD)
452 * exit software powerdown, PHY 1 has to exit before PHY 2
453 * correct PHY order is expected to be in DTS to guarantee that
454 */
455 return phy_adin2111_sft_pd(dev, false);
456 }
457
phy_adin2111_link_cb_set(const struct device * dev,phy_callback_t cb,void * user_data)458 static int phy_adin2111_link_cb_set(const struct device *dev, phy_callback_t cb,
459 void *user_data)
460 {
461 ARG_UNUSED(dev);
462 ARG_UNUSED(cb);
463 ARG_UNUSED(user_data);
464 return -ENOTSUP;
465 }
466
467 static const struct ethphy_driver_api phy_adin2111_api = {
468 .get_link = phy_adin2111_get_link_state,
469 .cfg_link = phy_adin2111_cfg_link,
470 .link_cb_set = phy_adin2111_link_cb_set,
471 .read = phy_adin2111_reg_read,
472 .write = phy_adin2111_reg_write,
473 };
474
475 #define ADIN2111_PHY_INITIALIZE(n) \
476 static const struct phy_adin2111_config phy_adin2111_config_##n = { \
477 .mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \
478 .phy_addr = DT_INST_REG_ADDR(n), \
479 .led0_en = DT_INST_PROP(n, led0_en), \
480 .led1_en = DT_INST_PROP(n, led1_en), \
481 .tx_24v = !(DT_INST_PROP(n, disable_tx_mode_24v)), \
482 }; \
483 static struct phy_adin2111_data phy_adin2111_data_##n = { \
484 .sem = Z_SEM_INITIALIZER(phy_adin2111_data_##n.sem, 1, 1), \
485 }; \
486 DEVICE_DT_INST_DEFINE(n, &phy_adin2111_init, NULL, \
487 &phy_adin2111_data_##n, &phy_adin2111_config_##n, \
488 POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \
489 &phy_adin2111_api);
490
491 DT_INST_FOREACH_STATUS_OKAY(ADIN2111_PHY_INITIALIZE)
492