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