Skip to content

Commit f85e5cc

Browse files
drivers: mdio: Add Xilinx Axi Ethernet Lite driver
This commit adds support for the Xilinx AXI Ethernet Lite device, also known as the emaclite. The emaclite is a light-weight 10/100 MII Ethernet device. It can optionally be configured to include an MDIO. The MMIO interface is controlled via MMIO registers and requires the software to busy-wait until completion. Signed-off-by: Eric Ackermann <[email protected]>
1 parent e0fcfa1 commit f85e5cc

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed

drivers/mdio/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ 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_XILINX_AXI_ETHERNET_LITE mdio_xilinx_axi_ethernet_lite.c)
2324
zephyr_library_sources_ifdef(CONFIG_MDIO_INTEL_IGC mdio_intel_igc.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.xilinx_axi_ethernet_lite"
4445
source "drivers/mdio/Kconfig.intel_igc"
4546

4647
config MDIO_INIT_PRIORITY
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright 2025 CISPA Helmholtz Center for Information Security
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config MDIO_XILINX_AXI_ETHERNET_LITE
5+
bool "Xilinx AXI Ethernet Lite MDIO driver"
6+
default y
7+
depends on DT_HAS_XLNX_XPS_ETHERNETLITE_3_00_A_MDIO_ENABLED
8+
help
9+
Enable Xilinx AXI Ethernet Lite MDIO bus driver.
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Xilinx AXI Ethernet Lite MDIO
3+
*
4+
* Copyright(c) 2025, CISPA Helmholtz Center for Information Security
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
#include <zephyr/logging/log.h>
8+
LOG_MODULE_REGISTER(mdio_axi_eth_lite, CONFIG_ETHERNET_LOG_LEVEL);
9+
10+
#include <zephyr/kernel.h>
11+
#include <zephyr/drivers/mdio.h>
12+
#include <zephyr/device.h>
13+
14+
#include <stdint.h>
15+
16+
#define AXI_ETH_LITE_MAX_PHY_DEVICES 32
17+
18+
#define AXI_ETH_LITE_MDIO_ADDRESS_REG_OFFSET 0x07e4
19+
#define AXI_ETH_LITE_MDIO_WRITE_DATA_REG_OFFSET 0x07e8
20+
#define AXI_ETH_LITE_MDIO_READ_DATA_REG_OFFSET 0x07ec
21+
#define AXI_ETH_LITE_MDIO_CONTROL_REG_OFFSET 0x07f0
22+
23+
#define AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_ENABLE_MASK BIT(3)
24+
#define AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_BUSY_MASK BIT(0)
25+
#define AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_DISABLE_MASK 0
26+
27+
#define AXI_ETH_LITE_MDIO_ADDRESS_REG_OP_READ BIT(10)
28+
#define AXI_ETH_LITE_MDIO_ADDRESS_REG_OP_WRITE 0
29+
#define AXI_ETH_LITE_MDIO_ADDRESS_REG_SHIFT_REGADDR 0
30+
#define AXI_ETH_LITE_MDIO_ADDRESS_REG_SHIFT_PHYADDR 5
31+
32+
struct axi_eth_lite_data {
33+
struct k_mutex mutex;
34+
};
35+
36+
struct axi_eth_lite_config {
37+
void *reg;
38+
};
39+
40+
static inline uint32_t axi_eth_lite_read_reg(const struct axi_eth_lite_config *config,
41+
mem_addr_t reg)
42+
{
43+
return sys_read32((mem_addr_t)config->reg + reg);
44+
}
45+
46+
static inline void axi_eth_lite_write_reg(const struct axi_eth_lite_config *config, mem_addr_t reg,
47+
uint32_t value)
48+
{
49+
sys_write32(value, (mem_addr_t)config->reg + reg);
50+
}
51+
52+
static inline int axi_eth_lite_check_busy(const struct axi_eth_lite_config *config)
53+
{
54+
uint32_t mdio_control_reg_val =
55+
axi_eth_lite_read_reg(config, AXI_ETH_LITE_MDIO_CONTROL_REG_OFFSET);
56+
57+
return mdio_control_reg_val & AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_BUSY_MASK ? -EBUSY : 0;
58+
}
59+
60+
static inline void axi_eth_lite_set_addr(const struct axi_eth_lite_config *config, uint8_t prtad,
61+
uint8_t regad, bool is_read)
62+
{
63+
uint32_t mdio_addr_val = is_read ? AXI_ETH_LITE_MDIO_ADDRESS_REG_OP_READ
64+
: AXI_ETH_LITE_MDIO_ADDRESS_REG_OP_WRITE;
65+
66+
/* range check done below in read/write functions */
67+
mdio_addr_val |= (uint32_t)regad << AXI_ETH_LITE_MDIO_ADDRESS_REG_SHIFT_REGADDR;
68+
mdio_addr_val |= (uint32_t)prtad << AXI_ETH_LITE_MDIO_ADDRESS_REG_SHIFT_PHYADDR;
69+
70+
axi_eth_lite_write_reg(config, AXI_ETH_LITE_MDIO_ADDRESS_REG_OFFSET, mdio_addr_val);
71+
}
72+
73+
static inline void axi_eth_lite_bus_enable(const struct device *dev)
74+
{
75+
const struct axi_eth_lite_config *config = dev->config;
76+
77+
axi_eth_lite_write_reg(config, AXI_ETH_LITE_MDIO_CONTROL_REG_OFFSET,
78+
AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_ENABLE_MASK);
79+
}
80+
81+
/* arbitrary but sufficient in testing */
82+
#define MDIO_MAX_WAIT_US 1000
83+
84+
static inline int axi_eth_lite_complete_transaction(const struct axi_eth_lite_config *config)
85+
{
86+
int waited_cycles = 0;
87+
88+
/* start transaction - everything set up */
89+
axi_eth_lite_write_reg(config, AXI_ETH_LITE_MDIO_CONTROL_REG_OFFSET,
90+
AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_ENABLE_MASK |
91+
AXI_ETH_LITE_MDIO_CONTROL_REG_MDIO_BUSY_MASK);
92+
93+
while (axi_eth_lite_check_busy(config) && waited_cycles < MDIO_MAX_WAIT_US) {
94+
/* no need to block the CPU */
95+
k_msleep(1);
96+
waited_cycles++;
97+
}
98+
99+
if (waited_cycles == MDIO_MAX_WAIT_US) {
100+
LOG_ERR("Timed out waiting for MDIO transaction to complete!");
101+
return -EIO;
102+
}
103+
/* busy went low - transaction complete */
104+
return 0;
105+
}
106+
107+
static int axi_eth_lite_read(const struct device *dev, uint8_t prtad, uint8_t regad,
108+
uint16_t *value)
109+
{
110+
const struct axi_eth_lite_config *config = dev->config;
111+
struct axi_eth_lite_data *data = dev->data;
112+
113+
if (prtad >= AXI_ETH_LITE_MAX_PHY_DEVICES) {
114+
LOG_ERR("Requested read port address %" PRIu8 " not supported - max %d", prtad,
115+
AXI_ETH_LITE_MAX_PHY_DEVICES);
116+
return -ENOSYS;
117+
}
118+
119+
(void)k_mutex_lock(&data->mutex, K_FOREVER);
120+
121+
if (axi_eth_lite_check_busy(config)) {
122+
LOG_ERR("MDIO bus busy!");
123+
(void)k_mutex_unlock(&data->mutex);
124+
return -ENOSYS;
125+
}
126+
127+
axi_eth_lite_set_addr(config, prtad, regad, true);
128+
129+
if (axi_eth_lite_complete_transaction(config)) {
130+
(void)k_mutex_unlock(&data->mutex);
131+
return -EIO;
132+
}
133+
134+
*value = (uint16_t)axi_eth_lite_read_reg(config, AXI_ETH_LITE_MDIO_READ_DATA_REG_OFFSET);
135+
136+
(void)k_mutex_unlock(&data->mutex);
137+
138+
return 0;
139+
}
140+
141+
static int axi_eth_lite_write(const struct device *dev, uint8_t prtad, uint8_t regad,
142+
uint16_t value)
143+
{
144+
const struct axi_eth_lite_config *config = dev->config;
145+
struct axi_eth_lite_data *data = dev->data;
146+
147+
if (prtad >= AXI_ETH_LITE_MAX_PHY_DEVICES) {
148+
LOG_ERR("Requested write port address %" PRIu8 " not supported - max %d", prtad,
149+
AXI_ETH_LITE_MAX_PHY_DEVICES);
150+
return -ENOSYS;
151+
}
152+
153+
(void)k_mutex_lock(&data->mutex, K_FOREVER);
154+
155+
if (axi_eth_lite_check_busy(config)) {
156+
LOG_ERR("MDIO bus busy!");
157+
(void)k_mutex_unlock(&data->mutex);
158+
return -ENOSYS;
159+
}
160+
axi_eth_lite_set_addr(config, prtad, regad, false);
161+
axi_eth_lite_write_reg(config, AXI_ETH_LITE_MDIO_WRITE_DATA_REG_OFFSET, value);
162+
163+
if (axi_eth_lite_complete_transaction(config)) {
164+
(void)k_mutex_unlock(&data->mutex);
165+
return -EIO;
166+
}
167+
168+
(void)k_mutex_unlock(&data->mutex);
169+
170+
return 0;
171+
}
172+
173+
static int axi_eth_lite_init(const struct device *dev)
174+
{
175+
struct axi_eth_lite_data *data = dev->data;
176+
177+
(void)k_mutex_init(&data->mutex);
178+
179+
axi_eth_lite_bus_enable(dev);
180+
181+
return 0;
182+
}
183+
184+
static DEVICE_API(mdio, axi_eth_lite_api) = {.read = axi_eth_lite_read,
185+
.write = axi_eth_lite_write};
186+
187+
#define XILINX_AXI_ETHERNET_LITE_MDIO_INIT(inst) \
188+
\
189+
static const struct axi_eth_lite_config axi_eth_lite_config##inst = { \
190+
.reg = (void *)(uintptr_t)DT_REG_ADDR(DT_INST_PARENT(inst)), \
191+
}; \
192+
static struct axi_eth_lite_data axi_eth_lite_data##inst = {0}; \
193+
DEVICE_DT_INST_DEFINE(inst, axi_eth_lite_init, NULL, &axi_eth_lite_data##inst, \
194+
&axi_eth_lite_config##inst, POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \
195+
&axi_eth_lite_api);
196+
197+
#define DT_DRV_COMPAT xlnx_xps_ethernetlite_3_00_a_mdio
198+
DT_INST_FOREACH_STATUS_OKAY(XILINX_AXI_ETHERNET_LITE_MDIO_INIT)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2025 CISPA Helmholtz Center for Information Security
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Common fields for MDIO controllers
5+
6+
include: mdio-controller.yaml
7+
8+
compatible: xlnx,xps-ethernetlite-3.00.a-mdio
9+
10+
description: MDIO interface of Xilinx AXI Ethernet Lite.

0 commit comments

Comments
 (0)