Skip to content

Commit ff7fc31

Browse files
committed
drivers: mdio: add MDIO driver for Xilinx GEM
Separate the MDIO functionality from the Xilinx GEM MAC driver into a separate driver conforming with the standard MDIO API. Signed-off-by: Immo Birnbaum <mail@birnbaum.immo>
1 parent c51c0c5 commit ff7fc31

File tree

5 files changed

+341
-0
lines changed

5 files changed

+341
-0
lines changed

drivers/mdio/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ zephyr_library_sources_ifdef(CONFIG_MDIO_RENESAS_RA mdio_renesas_ra.c)
2020
zephyr_library_sources_ifdef(CONFIG_MDIO_LAN865X mdio_lan865x.c)
2121
zephyr_library_sources_ifdef(CONFIG_MDIO_SENSRY_SY1XX mdio_sy1xx.c)
2222
zephyr_library_sources_ifdef(CONFIG_MDIO_XILINX_AXI_ENET mdio_xilinx_axienet.c)
23+
zephyr_library_sources_ifdef(CONFIG_MDIO_XLNX_GEM mdio_xlnx_gem.c)

drivers/mdio/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ source "drivers/mdio/Kconfig.renesas_ra"
4141
source "drivers/mdio/Kconfig.lan865x"
4242
source "drivers/mdio/Kconfig.sy1xx"
4343
source "drivers/mdio/Kconfig.xilinx_axienet"
44+
source "drivers/mdio/Kconfig.xlnx_gem"
4445

4546
config MDIO_INIT_PRIORITY
4647
int "Init priority"

drivers/mdio/Kconfig.xlnx_gem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) 2025 Immo Birnbaum
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config MDIO_XLNX_GEM
5+
bool "Xilinx GEM MDIO controller driver"
6+
default y
7+
depends on DT_HAS_XLNX_GEM_MDIO_ENABLED
8+
help
9+
Enable Xilinx GEM MDIO support.
10+
11+
if MDIO_XLNX_GEM
12+
13+
config MDIO_XLNX_GEM_MAX_POLL_RETRIES
14+
int "Poll MDIO transaction completion max. retries"
15+
default 10
16+
help
17+
The number of times the completion of an MDIO
18+
transaction is polled before it is considered
19+
timed out.
20+
21+
config MDIO_XLNX_GEM_POLL_DELAY
22+
int "Poll MDIO transaction completion delay"
23+
default 50
24+
help
25+
Delay in microseconds between two MDIO transaction
26+
completion polling operations.
27+
28+
endif # MDIO_XLNX_GEM

drivers/mdio/mdio_xlnx_gem.c

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/*
2+
* Copyright (c) 2021 Weidmueller Interface GmbH & Co. KG
3+
* Copyright (c) 2025 Immo Birnbaum
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#include <zephyr/device.h>
9+
#include <zephyr/kernel.h>
10+
#include <zephyr/drivers/mdio.h>
11+
12+
#include <zephyr/logging/log.h>
13+
LOG_MODULE_REGISTER(xlnx_gem_mdio, CONFIG_MDIO_LOG_LEVEL);
14+
15+
#define DT_DRV_COMPAT xlnx_gem_mdio
16+
17+
/*
18+
* Subset of register offsets and control bits / masks required for MDIO:
19+
*
20+
* Register offsets within the respective GEM's address space:
21+
* NWCTRL = gem.net_ctrl Network Control register
22+
* NWCFG = gem.net_cfg Network Configuration register
23+
* NWSR = gem.net_status Network Status register
24+
* PHYMNTNC = gem.phy_maint PHY maintenance register
25+
*
26+
* gem.net_ctrl:
27+
* [04] Enable MDIO port
28+
* gem.net_cfg:
29+
* [20 .. 18] MDC clock division setting
30+
* gem.net_status:
31+
* [02] PHY management idle bit
32+
* [01] MDIO input status
33+
* gem.phy_maint:
34+
* [31 .. 30] constant values
35+
* [17 .. 16] constant values
36+
* [29] Read operation control bit
37+
* [28] Write operation control bit
38+
* [27 .. 23] PHY address
39+
* [22 .. 18] Register address
40+
* [15 .. 00] 16-bit data word
41+
*/
42+
#define ETH_XLNX_GEM_NWCTRL_OFFSET 0x00000000
43+
#define ETH_XLNX_GEM_NWCTRL_MDEN_BIT BIT(4)
44+
45+
#define ETH_XLNX_GEM_NWCFG_OFFSET 0x00000004
46+
#define ETH_XLNX_GEM_NWCFG_MDC_MASK 0x7
47+
#define ETH_XLNX_GEM_NWCFG_MDC_SHIFT 18
48+
49+
#define ETH_XLNX_GEM_NWSR_OFFSET 0x00000008
50+
#define ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT BIT(2)
51+
52+
#define ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET 0x00000034
53+
#define ETH_XLNX_GEM_PHY_MAINT_CONST_BITS 0x40020000
54+
#define ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT BIT(29)
55+
#define ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT BIT(28)
56+
#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK 0x0000001F
57+
#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT 23
58+
#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK 0x0000001F
59+
#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT 18
60+
#define ETH_XLNX_GEM_PHY_MAINT_DATA_MASK 0x0000FFFF
61+
62+
/**
63+
* @brief MDC clock divider configuration enumeration type.
64+
*
65+
* Enumeration type containing the supported clock divider values
66+
* used to generate the MDIO interface clock (MDC) from either the
67+
* cpu_1x clock (Zynq-7000) or the LPD LSBUS clock (ZynqMP).
68+
* This is a configuration item in the controller's net_cfg register.
69+
*/
70+
enum eth_xlnx_mdc_clock_divider {
71+
MDC_DIVIDER_8 = 0,
72+
MDC_DIVIDER_16,
73+
MDC_DIVIDER_32,
74+
MDC_DIVIDER_48,
75+
MDC_DIVIDER_64,
76+
MDC_DIVIDER_96,
77+
MDC_DIVIDER_128,
78+
MDC_DIVIDER_224
79+
};
80+
81+
/**
82+
* @brief Constant device configuration data structure.
83+
*
84+
* This struct contains all device configuration data for a GEM
85+
* MDIO interface instance which is constant.
86+
*/
87+
struct xlnx_gem_mdio_config {
88+
uint32_t gem_base_addr;
89+
};
90+
91+
/**
92+
* @brief GEM MDIO interface data read function
93+
*
94+
* @param dev Pointer to the GEM MDIO device
95+
* @param prtad MDIO address of the PHY to be accessed
96+
* @param regad Index of the PHY register to be read
97+
* @param data Read data output pointer
98+
* @return 0 in case of success, -ETIMEDOUT if the read operation
99+
* timed out (idle bit not set as expected)
100+
*/
101+
static int xlnx_gem_mdio_read(const struct device *dev, uint8_t prtad, uint8_t regad,
102+
uint16_t *data)
103+
{
104+
const struct xlnx_gem_mdio_config *const dev_conf = dev->config;
105+
106+
uint32_t reg_val;
107+
uint32_t poll_cnt = 0;
108+
109+
/*
110+
* MDIO read operation as described in Zynq-7000 TRM,
111+
* chapter 16.3.4, p. 517.
112+
*/
113+
114+
/*
115+
* Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the
116+
* current command.
117+
*/
118+
do {
119+
if (poll_cnt++ > 0) {
120+
k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY);
121+
}
122+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
123+
} while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 &&
124+
poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES);
125+
if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) {
126+
LOG_ERR("%s: read from PHY address %hhu, register address %hhu timed out",
127+
dev->name, prtad, regad);
128+
return -ETIMEDOUT;
129+
}
130+
131+
/* Assemble & write the read command to the gem.phy_maint register */
132+
133+
/* Set the bits constant for any operation */
134+
reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS;
135+
/* Indicate a read operation */
136+
reg_val |= ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT;
137+
/* PHY address */
138+
reg_val |= (((uint32_t)prtad & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK)
139+
<< ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT);
140+
/* Register address */
141+
reg_val |= (((uint32_t)regad & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK)
142+
<< ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT);
143+
144+
sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
145+
146+
/*
147+
* Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command
148+
* completed.
149+
*/
150+
poll_cnt = 0;
151+
do {
152+
if (poll_cnt++ > 0) {
153+
k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY);
154+
}
155+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
156+
} while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 &&
157+
poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES);
158+
if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) {
159+
LOG_ERR("%s: read from PHY address %hhu, register address %hhu timed out",
160+
dev->name, prtad, regad);
161+
return -ETIMEDOUT;
162+
}
163+
164+
/*
165+
* Read the data returned by the PHY -> lower 16 bits of the PHY main-
166+
* tenance register
167+
*/
168+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
169+
170+
*data = (uint16_t)reg_val;
171+
return 0;
172+
}
173+
174+
/**
175+
* @brief GEM MDIO interface data write function
176+
*
177+
* @param dev Pointer to the GEM MDIO device
178+
* @param prtad MDIO address of the PHY to be accessed
179+
* @param regad Index of the PHY register to write to
180+
* @param data Data word to be written to the target register
181+
* @return 0 in case of success, -ETIMEDOUT if the read operation
182+
* timed out (idle bit not set as expected)
183+
*/
184+
static int xlnx_gem_mdio_write(const struct device *dev, uint8_t prtad, uint8_t regad,
185+
uint16_t data)
186+
{
187+
const struct xlnx_gem_mdio_config *const dev_conf = dev->config;
188+
189+
uint32_t reg_val;
190+
uint32_t poll_cnt = 0;
191+
192+
/*
193+
* MDIO write operation as described in Zynq-7000 TRM,
194+
* chapter 16.3.4, p. 517.
195+
*/
196+
197+
/*
198+
* Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the
199+
* current command.
200+
*/
201+
do {
202+
if (poll_cnt++ > 0) {
203+
k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY);
204+
}
205+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
206+
} while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 &&
207+
poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES);
208+
if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) {
209+
LOG_ERR("%s: write to PHY address %hhu, register address %hhu timed out", dev->name,
210+
prtad, regad);
211+
return -ETIMEDOUT;
212+
}
213+
214+
/* Assemble & write the read command to the gem.phy_maint register */
215+
216+
/* Set the bits constant for any operation */
217+
reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS;
218+
/* Indicate a read operation */
219+
reg_val |= ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT;
220+
/* PHY address */
221+
reg_val |= (((uint32_t)prtad & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK)
222+
<< ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT);
223+
/* Register address */
224+
reg_val |= (((uint32_t)regad & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK)
225+
<< ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT);
226+
/* 16 bits of data for the destination register */
227+
reg_val |= ((uint32_t)data & ETH_XLNX_GEM_PHY_MAINT_DATA_MASK);
228+
229+
sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET);
230+
231+
/*
232+
* Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command
233+
* completed.
234+
*/
235+
poll_cnt = 0;
236+
do {
237+
if (poll_cnt++ > 0) {
238+
k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY);
239+
}
240+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET);
241+
} while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 &&
242+
poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES);
243+
if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) {
244+
LOG_ERR("%s: write to PHY address %hhu, register address %hhu timed out", dev->name,
245+
prtad, regad);
246+
return -ETIMEDOUT;
247+
}
248+
249+
return 0;
250+
}
251+
252+
/**
253+
* @brief GEM MDIO interface initialization function
254+
* Configures the MDC clock divider in the associated GEM instance's
255+
* net_config (NWCFG) register and sets the MDIO enable bit in the
256+
* net_control (NWCTRL) register.
257+
*
258+
* @param dev Pointer to the GEM MDIO device
259+
* @return always returns 0
260+
*/
261+
static int xlnx_gem_mdio_initialize(const struct device *dev)
262+
{
263+
const struct xlnx_gem_mdio_config *const dev_conf = dev->config;
264+
265+
uint32_t reg_val;
266+
uint32_t mdc_divider = (uint32_t)MDC_DIVIDER_224;
267+
268+
/* Set the MDC divider in gem.net_config */
269+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCFG_OFFSET);
270+
reg_val &= ~(ETH_XLNX_GEM_NWCFG_MDC_MASK << ETH_XLNX_GEM_NWCFG_MDC_SHIFT);
271+
reg_val |= ((mdc_divider & ETH_XLNX_GEM_NWCFG_MDC_MASK) << ETH_XLNX_GEM_NWCFG_MDC_SHIFT);
272+
sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCFG_OFFSET);
273+
274+
/* Enable the MDIO interface */
275+
reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET);
276+
reg_val |= ETH_XLNX_GEM_NWCTRL_MDEN_BIT;
277+
sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET);
278+
279+
LOG_DBG("%s: initialized", dev->name);
280+
return 0;
281+
}
282+
283+
static DEVICE_API(mdio, xlnx_gem_mdio_api) = {
284+
.read = xlnx_gem_mdio_read,
285+
.write = xlnx_gem_mdio_write,
286+
};
287+
288+
#define XLNX_GEM_MDIO_DEV_CONFIG(port) \
289+
static const struct xlnx_gem_mdio_config xlnx_gem##port##_mdio_cfg = { \
290+
.gem_base_addr = DT_REG_ADDR_BY_IDX(DT_INST(port, xlnx_gem), 0), \
291+
};
292+
293+
#define XLNX_GEM_MDIO_DEV_INIT(port) \
294+
DEVICE_DT_INST_DEFINE(port, &xlnx_gem_mdio_initialize, NULL, NULL, \
295+
&xlnx_gem##port##_mdio_cfg, POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \
296+
&xlnx_gem_mdio_api);
297+
298+
#define XLNX_GEM_MDIO_INITIALIZE(port) \
299+
XLNX_GEM_MDIO_DEV_CONFIG(port); \
300+
XLNX_GEM_MDIO_DEV_INIT(port);
301+
302+
DT_INST_FOREACH_STATUS_OKAY(XLNX_GEM_MDIO_INITIALIZE)

dts/bindings/mdio/xlnx,gem-mdio.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG
2+
# Copyright (c) 2025 Immo Birnbaum
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
description: Xilinx GEM MDIO node
6+
7+
compatible: "xlnx,gem-mdio"
8+
9+
include: mdio-controller.yaml

0 commit comments

Comments
 (0)