1 /*
2  * Xilinx Processor System Gigabit Ethernet controller (GEM) driver
3  *
4  * PHY management interface implementation
5  * Models currently supported:
6  * - Marvell Alaska 88E1111 (QEMU simulated PHY)
7  * - Marvell Alaska 88E1510/88E1518/88E1512/88E1514 (Zedboard)
8  * - Texas Instruments TLK105
9  * - Texas Instruments DP83822
10  *
11  * Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG
12  * SPDX-License-Identifier: Apache-2.0
13  */
14 
15 #include <zephyr.h>
16 #include <device.h>
17 
18 #include "eth_xlnx_gem_priv.h"
19 
20 #define LOG_MODULE_NAME phy_xlnx_gem
21 #define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
22 #include <logging/log.h>
23 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
24 
25 /* Basic MDIO read / write functions for PHY access */
26 
27 /**
28  * @brief Read PHY data via the MDIO interface
29  * Reads data from a PHY attached to the respective GEM's MDIO interface
30  *
31  * @param base_addr Base address of the GEM's register space
32  * @param phy_addr  MDIO address of the PHY to be accessed
33  * @param reg_addr  Index of the PHY register to be read
34  * @return          16-bit data word received from the PHY
35  */
phy_xlnx_gem_mdio_read(uint32_t base_addr,uint8_t phy_addr,uint8_t reg_addr)36 static uint16_t phy_xlnx_gem_mdio_read(
37 	uint32_t base_addr, uint8_t phy_addr,
38 	uint8_t reg_addr)
39 {
40 	uint32_t reg_val;
41 	uint32_t poll_cnt = 0;
42 
43 	/*
44 	 * MDIO read operation as described in Zynq-7000 TRM,
45 	 * chapter 16.3.4, p. 517.
46 	 */
47 
48 	/*
49 	 * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the
50 	 * current command.
51 	 */
52 	do {
53 		if (poll_cnt++ > 0)
54 			k_busy_wait(100);
55 		reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
56 	} while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10);
57 	if (poll_cnt == 10) {
58 		LOG_ERR("GEM@0x%08X read from PHY address %hhu, "
59 			"register address %hhu timed out",
60 			base_addr, phy_addr, reg_addr);
61 		return 0;
62 	}
63 
64 	/* Assemble & write the read command to the gem.phy_maint register */
65 
66 	/* Set the bits constant for any operation */
67 	reg_val  = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS;
68 	/* Indicate a read operation */
69 	reg_val |= ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT;
70 	/* PHY address */
71 	reg_val |= (((uint32_t)phy_addr & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) <<
72 		   ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT);
73 	/* Register address */
74 	reg_val |= (((uint32_t)reg_addr & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) <<
75 		   ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT);
76 
77 	sys_write32(reg_val, base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
78 
79 	/*
80 	 * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command
81 	 * completed.
82 	 */
83 	poll_cnt = 0;
84 	do {
85 		if (poll_cnt++ > 0)
86 			k_busy_wait(100);
87 		reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
88 	} while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10);
89 	if (poll_cnt == 10) {
90 		LOG_ERR("GEM@0x%08X read from PHY address %hhu, "
91 			"register address %hhu timed out",
92 			base_addr, phy_addr, reg_addr);
93 		return 0;
94 	}
95 
96 	/*
97 	 * Read the data returned by the PHY -> lower 16 bits of the PHY main-
98 	 * tenance register
99 	 */
100 	reg_val = sys_read32(base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
101 	return (uint16_t)reg_val;
102 }
103 
104 /**
105  * @brief Writes PHY data via the MDIO interface
106  * Writes data to a PHY attached to the respective GEM's MDIO interface
107  *
108  * @param base_addr Base address of the GEM's register space
109  * @param phy_addr  MDIO address of the PHY to be accessed
110  * @param reg_addr  Index of the PHY register to be written to
111  * @param value     16-bit data word to be written to the target register
112  */
phy_xlnx_gem_mdio_write(uint32_t base_addr,uint8_t phy_addr,uint8_t reg_addr,uint16_t value)113 static void phy_xlnx_gem_mdio_write(
114 	uint32_t base_addr, uint8_t phy_addr,
115 	uint8_t reg_addr, uint16_t value)
116 {
117 	uint32_t reg_val;
118 	uint32_t poll_cnt = 0;
119 
120 	/*
121 	 * MDIO write operation as described in Zynq-7000 TRM,
122 	 * chapter 16.3.4, p. 517.
123 	 */
124 
125 	/*
126 	 * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the
127 	 * current command.
128 	 */
129 	do {
130 		if (poll_cnt++ > 0)
131 			k_busy_wait(100);
132 		reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
133 	} while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10);
134 	if (poll_cnt == 10) {
135 		LOG_ERR("GEM@0x%08X write to PHY address %hhu, "
136 			"register address %hhu timed out",
137 			base_addr, phy_addr, reg_addr);
138 		return;
139 	}
140 
141 	/* Assemble & write the read command to the gem.phy_maint register */
142 
143 	/* Set the bits constant for any operation */
144 	reg_val  = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS;
145 	/* Indicate a read operation */
146 	reg_val |= ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT;
147 	/* PHY address */
148 	reg_val |= (((uint32_t)phy_addr & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) <<
149 		   ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT);
150 	/* Register address */
151 	reg_val |= (((uint32_t)reg_addr & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) <<
152 		   ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT);
153 	/* 16 bits of data for the destination register */
154 	reg_val |= ((uint32_t)value & ETH_XLNX_GEM_PHY_MAINT_DATA_MASK);
155 
156 	sys_write32(reg_val, base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
157 
158 	/*
159 	 * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command
160 	 * completed.
161 	 */
162 	poll_cnt = 0;
163 	do {
164 		if (poll_cnt++ > 0)
165 			k_busy_wait(100);
166 		reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
167 	} while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10);
168 	if (poll_cnt == 10) {
169 		LOG_ERR("GEM@0x%08X write to PHY address %hhu, "
170 			"register address %hhu timed out",
171 			base_addr, phy_addr, reg_addr);
172 	}
173 }
174 
175 /*
176  * Vendor-specific PHY management functions for:
177  * Marvell Alaska 88E1111 (QEMU simulated PHY)
178  * Marvell Alaska 88E1510/88E1518/88E1512/88E1514 (Zedboard)
179  * Register IDs & procedures are based on the corresponding datasheets:
180  * https://www.marvell.com/content/dam/marvell/en/public-collateral/transceivers/marvell-phys-transceivers-alaska-88e1111-datasheet.pdf
181  * https://www.marvell.com/content/dam/marvell/en/public-collateral/transceivers/marvell-phys-transceivers-alaska-88e151x-datasheet.pdf
182  *
183  * NOTICE: Unless indicated otherwise, page/table source references refer to
184  * the 88E151x datasheet.
185  */
186 
187 /**
188  * @brief Marvell Alaska PHY reset function
189  * Reset function for the Marvell Alaska PHY series
190  *
191  * @param dev Pointer to the device data
192  */
phy_xlnx_gem_marvell_alaska_reset(const struct device * dev)193 static void phy_xlnx_gem_marvell_alaska_reset(const struct device *dev)
194 {
195 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
196 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
197 	uint16_t phy_data;
198 	uint32_t retries = 0;
199 
200 	/*
201 	 * Page 0, register address 0 = Copper control register,
202 	 * bit [15] = PHY reset. Register 0/0 access is R/M/W. Comp.
203 	 * datasheet chapter 2.6 and table 64 "Copper Control Register".
204 	 */
205 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
206 					  PHY_MRVL_COPPER_CONTROL_REGISTER);
207 	phy_data |= PHY_MRVL_COPPER_CONTROL_RESET_BIT;
208 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
209 				PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data);
210 
211 	/* Bit [15] reverts to 0 once the reset is complete. */
212 	while (((phy_data & PHY_MRVL_COPPER_CONTROL_RESET_BIT) != 0) && (retries++ < 10)) {
213 		phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
214 						  PHY_MRVL_COPPER_CONTROL_REGISTER);
215 	}
216 	if (retries == 10) {
217 		LOG_ERR("%s reset PHY address %hhu (Marvell Alaska) timed out",
218 			dev->name, dev_data->phy_addr);
219 	}
220 }
221 
222 /**
223  * @brief Marvell Alaska PHY configuration function
224  * Configuration function for the Marvell Alaska PHY series
225  *
226  * @param dev Pointer to the device data
227  */
phy_xlnx_gem_marvell_alaska_cfg(const struct device * dev)228 static void phy_xlnx_gem_marvell_alaska_cfg(const struct device *dev)
229 {
230 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
231 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
232 	uint16_t phy_data;
233 	uint16_t phy_data_gbit;
234 	uint32_t retries = 0;
235 
236 	/*
237 	 * Page 0, register address 0 = Copper control register,
238 	 * bit [12] = auto-negotiation enable bit is to be cleared
239 	 * for now, afterwards, trigger a PHY reset.
240 	 * Register 0/0 access is R/M/W. Comp. datasheet chapter 2.6
241 	 * and table 64 "Copper Control Register".
242 	 */
243 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
244 					  PHY_MRVL_COPPER_CONTROL_REGISTER);
245 	phy_data &= ~PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT;
246 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
247 				PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data);
248 	phy_xlnx_gem_marvell_alaska_reset(dev);
249 
250 	if ((dev_data->phy_id & PHY_MRVL_PHY_ID_MODEL_MASK) ==
251 			PHY_MRVL_PHY_ID_MODEL_88E151X) {
252 		/*
253 		 * 88E151x only: onfigure the system interface and media type
254 		 * (i.e. "RGMII to Copper", 0x0). On the 88E1111, this setting
255 		 * is configured using I/O pins on the device.
256 		 * TODO: Make this value configurable via KConfig or DT?
257 		 * Page 18, register address 20 = General Control Register 1,
258 		 * bits [2..0] = mode configuration
259 		 * Comp. datasheet table 129 "General Control Register 1"
260 		 * NOTICE: a change of this value requires a subsequent software
261 		 * reset command via the same register's bit [15].
262 		 */
263 		phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
264 					PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER,
265 					PHY_MRVL_GENERAL_CONTROL_1_PAGE);
266 
267 		phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
268 						  PHY_MRVL_GENERAL_CONTROL_1_REGISTER);
269 		phy_data &= ~(PHY_MRVL_MODE_CONFIG_MASK << PHY_MRVL_MODE_CONFIG_SHIFT);
270 		phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
271 					PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data);
272 
273 		/*
274 		 * [15] Mode Software Reset bit, affecting pages 6 and 18
275 		 * Reset is performed immediately, bit [15] is self-clearing.
276 		 * This reset bit is not to be confused with the actual PHY
277 		 * reset in register 0/0!
278 		 */
279 		phy_data |= PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT;
280 		phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
281 					PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data);
282 
283 		/* Bit [15] reverts to 0 once the reset is complete. */
284 		while (((phy_data & PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT) != 0) &&
285 				(retries++ < 10)) {
286 			phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr,
287 				dev_data->phy_addr,
288 				PHY_MRVL_GENERAL_CONTROL_1_REGISTER);
289 		}
290 		if (retries == 10) {
291 			LOG_ERR("%s configure PHY address %hhu (Marvell Alaska) timed out",
292 				dev->name, dev_data->phy_addr);
293 			return;
294 		}
295 
296 		/* Revert to register page 0 */
297 		phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
298 			PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER,
299 			PHY_MRVL_BASE_REGISTERS_PAGE);
300 	}
301 
302 	/*
303 	 * Configure MDIX
304 	 * TODO: Make this value configurable via KConfig or DT?
305 	 * 88E151x: Page 0, register address 16 = Copper specific control register 1,
306 	 * 88E1111: Page any, register address 16 = PHY specific control register,
307 	 * bits [6..5] = MDIO crossover mode. Comp. datasheet table 76.
308 	 * NOTICE: a change of this value requires a subsequent software
309 	 * reset command via Copper Control Register's bit [15].
310 	 */
311 
312 	/* [6..5] 11 = Enable auto cross over detection */
313 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
314 					  PHY_MRVL_COPPER_CONTROL_1_REGISTER);
315 	phy_data &= ~(PHY_MRVL_MDIX_CONFIG_MASK << PHY_MRVL_MDIX_CONFIG_SHIFT);
316 	phy_data |= (PHY_MRVL_MDIX_AUTO_CROSSOVER_ENABLE << PHY_MRVL_MDIX_CONFIG_SHIFT);
317 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
318 				PHY_MRVL_COPPER_CONTROL_1_REGISTER, phy_data);
319 
320 	/*
321 	 * Configure the Copper Specific Interrupt Enable Register
322 	 * (88E151x) / Interrupt Enable Register (88E1111).
323 	 * The interrupt status register provides a convenient way to
324 	 * detect relevant state changes, also, PHY management could
325 	 * eventually be changed from polling to interrupt-driven.
326 	 * There's just one big catch: at least on the Zedboard, the
327 	 * PHY interrupt line isn't wired up, therefore, the GEM can
328 	 * never trigger a PHY interrupt. Still, the PHY interrupts
329 	 * are configured & enabled in order to obtain all relevant
330 	 * status data from a single source.
331 	 *
332 	 * -> all bits contained herein will be retained during the
333 	 * upcoming software reset operation.
334 	 * Page 0, register address 18 = (Copper Specific) Interrupt
335 	 * Enable Register,
336 	 * bit [14] = Speed changed interrupt enable,
337 	 * bit [13] = Duplex changed interrupt enable,
338 	 * bit [11] = Auto-negotiation completed interrupt enable,
339 	 * bit [10] = Link status changed interrupt enable.
340 	 * Comp. datasheet table 78
341 	 */
342 	phy_data = PHY_MRVL_COPPER_SPEED_CHANGED_INT_BIT |
343 		PHY_MRVL_COPPER_DUPLEX_CHANGED_INT_BIT |
344 		PHY_MRVL_COPPER_AUTONEG_COMPLETED_INT_BIT |
345 		PHY_MRVL_COPPER_LINK_STATUS_CHANGED_INT_BIT;
346 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
347 				PHY_MRVL_COPPER_INT_ENABLE_REGISTER, phy_data);
348 
349 	/* Trigger a PHY Reset, affecting pages 0, 2, 3, 5, 7. */
350 	phy_xlnx_gem_marvell_alaska_reset(dev);
351 
352 	/*
353 	 * Clear the interrupt status register before advertising the
354 	 * supported link speed(s).
355 	 */
356 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
357 					  PHY_MRVL_COPPER_INT_STATUS_REGISTER);
358 
359 	/*
360 	 * Set which link speeds and duplex modes shall be advertised during
361 	 * auto-negotiation, then re-enable auto-negotiation. PHY link speed
362 	 * advertisement configuration as described in Zynq-7000 TRM, chapter
363 	 * 16.3.4, p. 517.
364 	 */
365 
366 	/*
367 	 * Advertise the link speed from the device configuration & perform
368 	 * auto-negotiation. This process involves:
369 	 *
370 	 * Page 0, register address 4 =
371 	 *     Copper Auto-Negotiation Advertisement Register,
372 	 * Page 0, register address 0 =
373 	 *     Copper Control Register, bit [15] = Reset -> apply all changes
374 	 *     made regarding advertisement,
375 	 * Page 0, register address 9 =
376 	 *     1000BASE-T Control Register (if link speed = 1GBit/s),
377 	 * Page 0, register address 1 =
378 	 *     Copper Status Register, bit [5] = Copper Auto-Negotiation
379 	 *     Complete.
380 	 *
381 	 * Comp. datasheet tables 68 & 73.
382 	 */
383 
384 	/*
385 	 * 88E151x only:
386 	 * Register 4, bits [4..0] = Selector field, 00001 = 802.3. Those bits
387 	 * are reserved in other Marvell PHYs.
388 	 */
389 	if ((dev_data->phy_id & PHY_MRVL_PHY_ID_MODEL_MASK) ==
390 			PHY_MRVL_PHY_ID_MODEL_88E151X) {
391 		phy_data = PHY_MRVL_ADV_SELECTOR_802_3;
392 	} else {
393 		phy_data = 0x0000;
394 	}
395 
396 	/*
397 	 * Clear the 1 GBit/s FDX/HDX advertisement bits from reg. 9's current
398 	 * contents in case we're going to advertise anything below 1 GBit/s
399 	 * as maximum / nominal link speed.
400 	 */
401 	phy_data_gbit = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
402 					       PHY_MRVL_1000BASET_CONTROL_REGISTER);
403 	phy_data_gbit &= ~PHY_MRVL_ADV_1000BASET_FDX_BIT;
404 	phy_data_gbit &= ~PHY_MRVL_ADV_1000BASET_HDX_BIT;
405 
406 	if (dev_conf->enable_fdx) {
407 		if (dev_conf->max_link_speed == LINK_1GBIT) {
408 			/* Advertise 1 GBit/s, full duplex */
409 			phy_data_gbit |= PHY_MRVL_ADV_1000BASET_FDX_BIT;
410 			if (dev_conf->phy_advertise_lower) {
411 				/* + 100 MBit/s, full duplex */
412 				phy_data |= PHY_MRVL_ADV_100BASET_FDX_BIT;
413 				/* + 10 MBit/s, full duplex */
414 				phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT;
415 			}
416 		} else if (dev_conf->max_link_speed == LINK_100MBIT) {
417 			/* Advertise 100 MBit/s, full duplex */
418 			phy_data |= PHY_MRVL_ADV_100BASET_FDX_BIT;
419 			if (dev_conf->phy_advertise_lower) {
420 				/* + 10 MBit/s, full duplex */
421 				phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT;
422 			}
423 		} else if (dev_conf->max_link_speed == LINK_10MBIT) {
424 			/* Advertise 10 MBit/s, full duplex */
425 			phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT;
426 		}
427 	} else {
428 		if (dev_conf->max_link_speed == LINK_1GBIT) {
429 			/* Advertise 1 GBit/s, half duplex */
430 			phy_data_gbit = PHY_MRVL_ADV_1000BASET_HDX_BIT;
431 			if (dev_conf->phy_advertise_lower) {
432 				/* + 100 MBit/s, half duplex */
433 				phy_data |= PHY_MRVL_ADV_100BASET_HDX_BIT;
434 				/* + 10 MBit/s, half duplex */
435 				phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT;
436 			}
437 		} else if (dev_conf->max_link_speed == LINK_100MBIT) {
438 			/* Advertise 100 MBit/s, half duplex */
439 			phy_data |= PHY_MRVL_ADV_100BASET_HDX_BIT;
440 			if (dev_conf->phy_advertise_lower) {
441 				/* + 10 MBit/s, half duplex */
442 				phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT;
443 			}
444 		} else if (dev_conf->max_link_speed == LINK_10MBIT) {
445 			/* Advertise 10 MBit/s, half duplex */
446 			phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT;
447 		}
448 	}
449 
450 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
451 				PHY_MRVL_1000BASET_CONTROL_REGISTER, phy_data_gbit);
452 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
453 				PHY_MRVL_COPPER_AUTONEG_ADV_REGISTER, phy_data);
454 
455 	/*
456 	 * Trigger a PHY reset, affecting pages 0, 2, 3, 5, 7.
457 	 * Afterwards, set the auto-negotiation enable bit [12] in the
458 	 * Copper Control Register.
459 	 */
460 	phy_xlnx_gem_marvell_alaska_reset(dev);
461 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
462 					  PHY_MRVL_COPPER_CONTROL_REGISTER);
463 	phy_data |= PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT;
464 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
465 				PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data);
466 
467 	/*
468 	 * Set the link speed to 'link down' for now, once auto-negotiation
469 	 * is complete, the result will be handled by the system work queue.
470 	 */
471 	dev_data->eff_link_speed = LINK_DOWN;
472 }
473 
474 /**
475  * @brief Marvell Alaska PHY status change polling function
476  * Status change polling function for the Marvell Alaska PHY series
477  *
478  * @param dev Pointer to the device data
479  * @return A set of bits indicating whether one or more of the following
480  *         events has occurred: auto-negotiation completed, link state
481  *         changed, link speed changed.
482  */
phy_xlnx_gem_marvell_alaska_poll_sc(const struct device * dev)483 static uint16_t phy_xlnx_gem_marvell_alaska_poll_sc(const struct device *dev)
484 {
485 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
486 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
487 	uint16_t phy_data;
488 	uint16_t phy_status = 0;
489 
490 	/*
491 	 * PHY status change detection is implemented by reading the
492 	 * interrupt status register.
493 	 * Page 0, register address 19 = Copper Interrupt Status Register
494 	 * bit [14] = Speed changed interrupt,
495 	 * bit [13] = Duplex changed interrupt,
496 	 * bit [11] = Auto-negotiation completed interrupt,
497 	 * bit [10] = Link status changed interrupt.
498 	 * Comp. datasheet table 79
499 	 */
500 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
501 					  PHY_MRVL_COPPER_INT_STATUS_REGISTER);
502 
503 	if ((phy_data & PHY_MRVL_COPPER_AUTONEG_COMPLETED_INT_BIT) != 0) {
504 		phy_status |= PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE;
505 	}
506 	if (((phy_data & PHY_MRVL_COPPER_DUPLEX_CHANGED_INT_BIT) != 0) ||
507 		((phy_data & PHY_MRVL_COPPER_LINK_STATUS_CHANGED_INT_BIT) != 0)) {
508 		phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED;
509 	}
510 	if ((phy_data & PHY_MRVL_COPPER_SPEED_CHANGED_INT_BIT) != 0) {
511 		phy_status |= PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED;
512 	}
513 
514 	/*
515 	 * Clear the status register, preserve reserved bit [3] as indicated
516 	 * by the datasheet
517 	 */
518 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
519 				PHY_MRVL_COPPER_INT_STATUS_REGISTER, (phy_data & 0x8));
520 
521 	return phy_status;
522 }
523 
524 /**
525  * @brief Marvell Alaska PHY link status polling function
526  * Link status polling function for the Marvell Alaska PHY series
527  *
528  * @param dev Pointer to the device data
529  * @return 1 if the PHY indicates link up, 0 if the link is down
530  */
phy_xlnx_gem_marvell_alaska_poll_lsts(const struct device * dev)531 static uint8_t phy_xlnx_gem_marvell_alaska_poll_lsts(const struct device *dev)
532 {
533 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
534 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
535 	uint16_t phy_data;
536 
537 	/*
538 	 * Current link status is obtained from:
539 	 * Page 0, register address 1 = Copper Status Register
540 	 * bit [2] = Copper Link Status
541 	 */
542 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
543 					  PHY_MRVL_COPPER_STATUS_REGISTER);
544 
545 	return ((phy_data >> PHY_MRVL_COPPER_LINK_STATUS_BIT_SHIFT) & 0x0001);
546 }
547 
548 /**
549  * @brief Marvell Alaska PHY link speed polling function
550  * Link speed polling function for the Marvell Alaska PHY series
551  *
552  * @param dev Pointer to the device data
553  * @return    Enum containing the current link speed reported by the PHY
554  */
phy_xlnx_gem_marvell_alaska_poll_lspd(const struct device * dev)555 static enum eth_xlnx_link_speed phy_xlnx_gem_marvell_alaska_poll_lspd(
556 	const struct device *dev)
557 {
558 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
559 	struct eth_xlnx_gem_dev_data *dev_data  = DEV_DATA(dev);
560 	enum eth_xlnx_link_speed link_speed;
561 	uint16_t phy_data;
562 
563 	/*
564 	 * Current link speed is obtained from:
565 	 * Page 0, register address 17 = Copper Specific Status Register 1
566 	 * bits [15 .. 14] = Speed.
567 	 */
568 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
569 					  PHY_MRVL_COPPER_STATUS_1_REGISTER);
570 	phy_data >>= PHY_MRVL_LINK_SPEED_SHIFT;
571 	phy_data  &= PHY_MRVL_LINK_SPEED_MASK;
572 
573 	/*
574 	 * Link speed bit masks: comp. datasheet, table 77 @ description
575 	 * of the 'Speed' bits.
576 	 */
577 	switch (phy_data) {
578 	case PHY_MRVL_LINK_SPEED_10MBIT:
579 		link_speed = LINK_10MBIT;
580 		break;
581 	case PHY_MRVL_LINK_SPEED_100MBIT:
582 		link_speed = LINK_100MBIT;
583 		break;
584 	case PHY_MRVL_LINK_SPEED_1GBIT:
585 		link_speed = LINK_1GBIT;
586 		break;
587 	default:
588 		link_speed = LINK_DOWN;
589 		break;
590 	};
591 
592 	return link_speed;
593 }
594 
595 /*
596  * Vendor-specific PHY management functions for:
597  * Texas Instruments TLK105
598  * Texas Instruments DP83822
599  * with the DP83822 being the successor to the deprecated TLK105.
600  * Register IDs & procedures are based on the corresponding datasheets:
601  * https://www.ti.com/lit/gpn/tlk105
602  * https://www.ti.com/lit/gpn/dp83822i
603  */
604 
605 /**
606  * @brief TI TLK105 & DP83822 PHY reset function
607  * Reset function for the TI TLK105 & DP83822 PHYs
608  *
609  * @param dev Pointer to the device data
610  */
phy_xlnx_gem_ti_dp83822_reset(const struct device * dev)611 static void phy_xlnx_gem_ti_dp83822_reset(const struct device *dev)
612 {
613 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
614 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
615 	uint16_t phy_data;
616 	uint32_t retries = 0;
617 
618 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
619 					  PHY_TI_BASIC_MODE_CONTROL_REGISTER);
620 	phy_data |= PHY_TI_BASIC_MODE_CONTROL_RESET_BIT;
621 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
622 				PHY_TI_BASIC_MODE_CONTROL_REGISTER, phy_data);
623 
624 	while (((phy_data & PHY_TI_BASIC_MODE_CONTROL_RESET_BIT) != 0) && (retries++ < 10)) {
625 		phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
626 						  PHY_TI_BASIC_MODE_CONTROL_REGISTER);
627 	}
628 	if (retries == 10) {
629 		LOG_ERR("%s reset PHY address %hhu (TI TLK105/DP83822) timed out",
630 			dev->name, dev_data->phy_addr);
631 	}
632 }
633 
634 /**
635  * @brief TI TLK105 & DP83822 PHY configuration function
636  * Configuration function for the TI TLK105 & DP83822 PHYs
637  *
638  * @param dev Pointer to the device data
639  */
phy_xlnx_gem_ti_dp83822_cfg(const struct device * dev)640 static void phy_xlnx_gem_ti_dp83822_cfg(const struct device *dev)
641 {
642 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
643 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
644 	uint16_t phy_data = PHY_TI_ADV_SELECTOR_802_3;
645 
646 	/* Configure link advertisement */
647 	if (dev_conf->enable_fdx) {
648 		if (dev_conf->max_link_speed == LINK_100MBIT) {
649 			/* Advertise 100BASE-TX, full duplex */
650 			phy_data |= PHY_TI_ADV_100BASET_FDX_BIT;
651 			if (dev_conf->phy_advertise_lower) {
652 				/* + 10BASE-TX, full duplex */
653 				phy_data |= PHY_TI_ADV_10BASET_FDX_BIT;
654 			}
655 		} else if (dev_conf->max_link_speed == LINK_10MBIT) {
656 			/* Advertise 10BASE-TX, full duplex */
657 			phy_data |= PHY_TI_ADV_10BASET_FDX_BIT;
658 		}
659 	} else {
660 		if (dev_conf->max_link_speed == LINK_100MBIT) {
661 			/* Advertise 100BASE-TX, half duplex */
662 			phy_data |= PHY_TI_ADV_100BASET_HDX_BIT;
663 			if (dev_conf->phy_advertise_lower) {
664 				/* + 10BASE-TX, half duplex */
665 				phy_data |= PHY_TI_ADV_10BASET_HDX_BIT;
666 			}
667 		} else if (dev_conf->max_link_speed == LINK_10MBIT) {
668 			/* Advertise 10BASE-TX, half duplex */
669 			phy_data |= PHY_TI_ADV_10BASET_HDX_BIT;
670 		}
671 	}
672 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
673 				PHY_TI_AUTONEG_ADV_REGISTER, phy_data);
674 
675 	/* Enable auto-negotiation */
676 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
677 					  PHY_TI_BASIC_MODE_CONTROL_REGISTER);
678 	phy_data |= PHY_TI_BASIC_MODE_CONTROL_AUTONEG_ENABLE_BIT;
679 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
680 				PHY_TI_BASIC_MODE_CONTROL_REGISTER, phy_data);
681 
682 	/* Robust Auto MDIX */
683 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
684 					  PHY_TI_CONTROL_REGISTER_1);
685 	phy_data |= PHY_TI_CR1_ROBUST_AUTO_MDIX_BIT;
686 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
687 				PHY_TI_CONTROL_REGISTER_1, phy_data);
688 
689 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
690 					  PHY_TI_PHY_CONTROL_REGISTER);
691 	/* Auto MDIX enable */
692 	phy_data |= PHY_TI_PHY_CONTROL_AUTO_MDIX_ENABLE_BIT;
693 	/* Link LED shall only indicate link up or down, no RX/TX activity */
694 	phy_data |= PHY_TI_PHY_CONTROL_LED_CONFIG_LINK_ONLY_BIT;
695 	/* Force MDIX disable */
696 	phy_data &= ~PHY_TI_PHY_CONTROL_FORCE_MDIX_BIT;
697 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
698 				PHY_TI_PHY_CONTROL_REGISTER, phy_data);
699 
700 	/* Set blink rate to 5 Hz */
701 	phy_data = (PHY_TI_LED_CONTROL_BLINK_RATE_5HZ <<
702 		    PHY_TI_LED_CONTROL_BLINK_RATE_SHIFT);
703 	phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,
704 				PHY_TI_LED_CONTROL_REGISTER, phy_data);
705 
706 	/*
707 	 * Set the link speed to 'link down' for now, once auto-negotiation
708 	 * is complete, the result will be handled by the system work queue.
709 	 */
710 	dev_data->eff_link_speed = LINK_DOWN;
711 }
712 
713 /**
714  * @brief TI TLK105 & DP83822 PHY status change polling function
715  * Status change polling function for the TI TLK105 & DP83822 PHYs
716  *
717  * @param dev Pointer to the device data
718  * @return A set of bits indicating whether one or more of the following
719  *         events has occurred: auto-negotiation completed, link state
720  *         changed, link speed changed.
721  */
phy_xlnx_gem_ti_dp83822_poll_sc(const struct device * dev)722 static uint16_t phy_xlnx_gem_ti_dp83822_poll_sc(const struct device *dev)
723 {
724 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
725 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
726 	uint16_t phy_data;
727 	uint16_t phy_status = 0;
728 
729 	/*
730 	 * The relevant status bits are obtained from the MII Interrupt
731 	 * Status Register 1. The upper byte of the register's data word
732 	 * contains the status bits which are set regardless of whether
733 	 * the corresponding interrupt enable bits are set in the lower
734 	 * byte or not (comp. TLK105 documentation, chapter 8.1.16).
735 	 */
736 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
737 					  PHY_TI_MII_INTERRUPT_STATUS_REGISTER_1);
738 
739 	if ((phy_data & PHY_TI_AUTONEG_COMPLETED_INT_BIT) != 0) {
740 		phy_status |= PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE;
741 	}
742 	if ((phy_data & PHY_TI_DUPLEX_CHANGED_INT_BIT) != 0) {
743 		phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED;
744 	}
745 	if ((phy_data & PHY_TI_LINK_STATUS_CHANGED_INT_BIT) != 0) {
746 		phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED;
747 	}
748 	if ((phy_data & PHY_TI_SPEED_CHANGED_INT_BIT) != 0) {
749 		phy_status |= PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED;
750 	}
751 
752 	return phy_status;
753 }
754 
755 /**
756  * @brief TI TLK105 & DP83822 PHY link status polling function
757  * Link status polling function for the TI TLK105 & DP83822 PHYs
758  *
759  * @param dev Pointer to the device data
760  * @return 1 if the PHY indicates link up, 0 if the link is down
761  */
phy_xlnx_gem_ti_dp83822_poll_lsts(const struct device * dev)762 static uint8_t phy_xlnx_gem_ti_dp83822_poll_lsts(const struct device *dev)
763 {
764 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
765 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
766 	uint16_t phy_data;
767 
768 	/*
769 	 * Double read of the BMSR is intentional - the relevant bit is latched
770 	 * low so that after a link down -> link up transition, the first read
771 	 * of the BMSR will still return the latched link down status rather
772 	 * than the current status.
773 	 */
774 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
775 					  PHY_TI_BASIC_MODE_STATUS_REGISTER);
776 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
777 					  PHY_TI_BASIC_MODE_STATUS_REGISTER);
778 
779 	return ((phy_data & PHY_TI_BASIC_MODE_STATUS_LINK_STATUS_BIT) != 0);
780 }
781 
782 /**
783  * @brief TI TLK105 & DP83822 PHY link speed polling function
784  * Link speed polling function for the TI TLK105 & DP83822 PHYs
785  *
786  * @param dev Pointer to the device data
787  * @return    Enum containing the current link speed reported by the PHY
788  */
phy_xlnx_gem_ti_dp83822_poll_lspd(const struct device * dev)789 static enum eth_xlnx_link_speed phy_xlnx_gem_ti_dp83822_poll_lspd(
790 	const struct device *dev)
791 {
792 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
793 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
794 	enum eth_xlnx_link_speed link_speed;
795 	uint16_t phy_data;
796 
797 	phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr,
798 					  PHY_TI_PHY_STATUS_REGISTER);
799 
800 	/* PHYSCR[0] is the link established indication bit */
801 	if ((phy_data & PHY_TI_PHY_STATUS_LINK_BIT) != 0) {
802 		/* PHYSCR[1] is the speed status bit: 0 = 100 Mbps, 1 = 10 Mbps. */
803 		if ((phy_data & PHY_TI_PHY_STATUS_SPEED_BIT) != 0) {
804 			link_speed = LINK_10MBIT;
805 		} else {
806 			link_speed = LINK_100MBIT;
807 		}
808 	} else {
809 		link_speed = LINK_DOWN;
810 	}
811 
812 	return link_speed;
813 }
814 
815 /**
816  * @brief Marvell Alaska PHY function pointer table
817  * Function pointer table for the Marvell Alaska PHY series
818  * specific management functions
819  */
820 static struct phy_xlnx_gem_api phy_xlnx_gem_marvell_alaska_api = {
821 	.phy_reset_func              = phy_xlnx_gem_marvell_alaska_reset,
822 	.phy_configure_func          = phy_xlnx_gem_marvell_alaska_cfg,
823 	.phy_poll_status_change_func = phy_xlnx_gem_marvell_alaska_poll_sc,
824 	.phy_poll_link_status_func   = phy_xlnx_gem_marvell_alaska_poll_lsts,
825 	.phy_poll_link_speed_func    = phy_xlnx_gem_marvell_alaska_poll_lspd
826 };
827 
828 /**
829  * @brief Texas Instruments TLK105 & DP83822 PHY function pointer table
830  * Function pointer table for the Texas Instruments TLK105 / DP83822 PHY
831  * series specific management functions
832  */
833 static struct phy_xlnx_gem_api phy_xlnx_gem_ti_dp83822_api = {
834 	.phy_reset_func              = phy_xlnx_gem_ti_dp83822_reset,
835 	.phy_configure_func          = phy_xlnx_gem_ti_dp83822_cfg,
836 	.phy_poll_status_change_func = phy_xlnx_gem_ti_dp83822_poll_sc,
837 	.phy_poll_link_status_func   = phy_xlnx_gem_ti_dp83822_poll_lsts,
838 	.phy_poll_link_speed_func    = phy_xlnx_gem_ti_dp83822_poll_lspd
839 };
840 
841 /*
842  * All vendor-specific API structs & code are located above
843  * -> assemble the top-level list of supported devices the
844  * upcoming function phy_xlnx_gem_detect will work with.
845  */
846 
847 /**
848  * @brief Top-level table of supported PHYs
849  * Top-level table of PHYs supported by the GEM driver. Contains 1..n
850  * supported PHY specifications, consisting of the PHY ID plus a mask
851  * for masking out variable parts of the PHY ID such as hardware revisions,
852  * as well as a textual description of the PHY model and a pointer to
853  * the corresponding PHY management function pointer table.
854  */
855 static struct phy_xlnx_gem_supported_dev phy_xlnx_gem_supported_devs[] = {
856 	{
857 		.phy_id      = PHY_MRVL_PHY_ID_MODEL_88E1111,
858 		.phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK,
859 		.api         = &phy_xlnx_gem_marvell_alaska_api,
860 		.identifier  = "Marvell Alaska 88E1111"
861 	},
862 	{
863 		.phy_id      = PHY_MRVL_PHY_ID_MODEL_88E151X,
864 		.phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK,
865 		.api         = &phy_xlnx_gem_marvell_alaska_api,
866 		.identifier  = "Marvell Alaska 88E151x"
867 	},
868 	{
869 		.phy_id      = PHY_TI_PHY_ID_MODEL_DP83822,
870 		.phy_id_mask = PHY_TI_PHY_ID_MODEL_MASK,
871 		.api         = &phy_xlnx_gem_ti_dp83822_api,
872 		.identifier  = "Texas Instruments DP83822"
873 	},
874 	{
875 		.phy_id      = PHY_TI_PHY_ID_MODEL_TLK105,
876 		.phy_id_mask = PHY_TI_PHY_ID_MODEL_MASK,
877 		.api         = &phy_xlnx_gem_ti_dp83822_api,
878 		.identifier  = "Texas Instruments TLK105"
879 	}
880 };
881 
882 /**
883  * @brief Top-level PHY detection function
884  * Top-level PHY detection function called by the GEM driver if PHY management
885  * is enabled for the current GEM device instance. This function is generic
886  * and does not require any knowledge regarding PHY vendors, models etc.
887  *
888  * @param dev Pointer to the device data
889  * @retval    -ENOTSUP if PHY management is disabled for the current GEM
890  *            device instance
891  * @retval    -EIO if no (supported) PHY was detected
892  * @retval    0 if a supported PHY has been detected
893  */
phy_xlnx_gem_detect(const struct device * dev)894 int phy_xlnx_gem_detect(const struct device *dev)
895 {
896 	const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev);
897 	struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
898 
899 	uint8_t phy_curr_addr;
900 	uint8_t phy_first_addr = dev_conf->phy_mdio_addr_fix;
901 	uint8_t phy_last_addr = (dev_conf->phy_mdio_addr_fix != 0) ?
902 		dev_conf->phy_mdio_addr_fix : 31;
903 	uint32_t phy_id;
904 	uint16_t phy_data;
905 	uint32_t list_iter;
906 
907 	/*
908 	 * Clear the PHY address & ID in the device data struct -> may be
909 	 * pre-initialized with a non-zero address meaning auto detection
910 	 * is disabled. If eventually a supported PHY is found, a non-
911 	 * zero address will be written back to the data struct.
912 	 */
913 	dev_data->phy_addr = 0;
914 	dev_data->phy_id = 0;
915 	dev_data->phy_access_api = NULL;
916 
917 	if (!dev_conf->init_phy) {
918 		return -ENOTSUP;
919 	}
920 
921 	/*
922 	 * PHY detection as described in Zynq-7000 TRM, chapter 16.3.4,
923 	 * p. 517
924 	 */
925 	for (phy_curr_addr = phy_first_addr;
926 		phy_curr_addr <= phy_last_addr;
927 		phy_curr_addr++) {
928 		/* Read the upper & lower PHY ID 16-bit words */
929 		phy_data = phy_xlnx_gem_mdio_read(
930 			dev_conf->base_addr, phy_curr_addr,
931 			PHY_IDENTIFIER_1_REGISTER);
932 		phy_id = (((uint32_t)phy_data << 16) & 0xFFFF0000);
933 		phy_data = phy_xlnx_gem_mdio_read(
934 			dev_conf->base_addr, phy_curr_addr,
935 			PHY_IDENTIFIER_2_REGISTER);
936 		phy_id |= ((uint32_t)phy_data & 0x0000FFFF);
937 
938 		if (phy_id != 0x00000000 && phy_id != 0xFFFFFFFF) {
939 			LOG_DBG("%s detected PHY at address %hhu: "
940 				"ID 0x%08X",
941 				dev->name,
942 				phy_curr_addr, phy_id);
943 
944 			/*
945 			 * Iterate the list of all supported PHYs -> if the
946 			 * current PHY is supported, store all related data
947 			 * in the device's run-time data struct.
948 			 */
949 			for (list_iter = 0; list_iter < ARRAY_SIZE(phy_xlnx_gem_supported_devs);
950 					list_iter++) {
951 				if (phy_xlnx_gem_supported_devs[list_iter].phy_id ==
952 					(phy_xlnx_gem_supported_devs[list_iter].phy_id_mask
953 					& phy_id)) {
954 					LOG_DBG("%s identified supported PHY: %s",
955 						dev->name,
956 						phy_xlnx_gem_supported_devs[list_iter].identifier);
957 
958 					/*
959 					 * Store the numeric values of the PHY ID and address
960 					 * as well as the corresponding set of function pointers
961 					 * in the device's run-time data struct.
962 					 */
963 					dev_data->phy_addr = phy_curr_addr;
964 					dev_data->phy_id = phy_id;
965 					dev_data->phy_access_api =
966 						phy_xlnx_gem_supported_devs[list_iter].api;
967 
968 					return 0;
969 				}
970 			}
971 		}
972 	}
973 
974 	LOG_ERR("%s PHY detection failed", dev->name);
975 	return -EIO;
976 }
977 
978 /* EOF */
979