Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

357 lines
13 KiB

/*
* Xilinx AXI 1G / 2.5G Ethernet Subsystem
*
* Copyright(c) 2024, CISPA Helmholtz Center for Information Security
* SPDX - License - Identifier : Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(eth_xilinx_axienet_mdio, CONFIG_ETHERNET_LOG_LEVEL);
#include <sys/types.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/mdio.h>
#include <zephyr/sys/barrier.h>
#define XILINX_AXIENET_MDIO_SETUP_REG_OFFSET 0x00000500
#define XILINX_AXIENET_MDIO_SETUP_REG_MDIO_DISABLE_MASK BIT_MASK(0)
#define XILINX_AXIENET_MDIO_SETUP_REG_MDIO_ENABLE_MASK BIT(6)
#define XILINX_AXIENET_MDIO_SETUP_REG_MDIO_CLOCK_DIVIDER_MASK BIT_MASK(6)
#define XILINX_AXIENET_MDIO_CONTROL_REG_OFFSET 0x00000504
#define XILINX_AXIENET_MDIO_CONTROL_REG_MASK_READY BIT(7)
#define XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_PHYADDR 24
#define XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_REGADDR 16
#define XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_TXOP 14
#define XILINX_AXIENET_MDIO_CONTROL_REG_MASK_READ BIT(15)
#define XILINX_AXIENET_MDIO_CONTROL_REG_MASK_WRITE BIT(14)
#define XILINX_AXIENET_MDIO_CONTROL_REG_MASK_INITIATE BIT(11)
#define XILINX_AXIENET_MDIO_WRITE_DATA_REG_OFFSET 0x00000508
#define XILINX_AXIENET_MDIO_READ_DATA_REG_OFFSET 0x0000050C
#define XILINX_AXIENET_MDIO_READ_DATA_REG_DATA_MASK BIT_MASK(16)
/* same mask for all interrupt-related registers */
#define XILINX_AXIENET_MDIO_INTERRUPT_MASK BIT(0)
#define XILINX_AXIENET_MDIO_INTERRUPT_STATUS_REG_OFFSET 0x00000600
#define XILINX_AXIENET_MDIO_INTERRUPT_PENDING_REG_OFFSET 0x00000610
#define XILINX_AXIENET_MDIO_INTERRUPT_ENABLE_REG_OFFSET 0x00000620
#define XILINX_AXIENET_MDIO_INTERRUPT_DISABLE_ALL_MASK BIT_MASK(0)
#define XILINX_AXIENET_MDIO_INTERRUPT_CLEAR_REG_OFFSET 0x00000630
#define XILINX_AXIENET_MDIO_INTERRUPT_CLEAR_ALL_MASK BIT_MASK(8)
/* 2.5 MHz, i.e., max MDIO clock according to IEEE spec */
#define XILINX_AXIENET_MDIO_MDIO_TARGET_FREQUENCY_HZ 2500000
#define XILINX_AXIENET_MDIO_INTERRUPT_TIMEOUT_MS 100
struct mdio_xilinx_axienet_data {
struct k_sem irq_sema;
uint16_t clock_divider;
bool bus_enabled;
};
struct mdio_xilinx_axienet_config {
void *reg;
unsigned int clock_frequency_hz;
void (*config_func)(const struct mdio_xilinx_axienet_data *data);
bool have_irq;
};
static void xilinx_axienet_mdio_write_register(const struct mdio_xilinx_axienet_config *config,
uintptr_t reg_offset, uint32_t value)
{
volatile uint32_t *reg_addr = (uint32_t *)((uint8_t *)(config->reg) + reg_offset);
*reg_addr = value;
barrier_dmem_fence_full(); /* make sure that write commits */
}
static uint32_t xilinx_axienet_read_mdio_register(const struct mdio_xilinx_axienet_config *config,
uintptr_t reg_offset)
{
const volatile uint32_t *reg_addr = (uint32_t *)((uint8_t *)(config->reg) + reg_offset);
const uint32_t ret = *reg_addr;
barrier_dmem_fence_full(); /* make sure that read commits */
return ret;
}
static void mdio_xilinx_axienet_bus_disable(const struct device *dev)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *data = dev->data;
LOG_DBG("Disable MDIO Bus!");
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_INTERRUPT_ENABLE_REG_OFFSET,
XILINX_AXIENET_MDIO_INTERRUPT_DISABLE_ALL_MASK);
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_SETUP_REG_OFFSET,
XILINX_AXIENET_MDIO_SETUP_REG_MDIO_DISABLE_MASK);
data->bus_enabled = false;
}
static void enable_mdio_bus(const struct mdio_xilinx_axienet_config *config,
struct mdio_xilinx_axienet_data *data)
{
if ((xilinx_axienet_read_mdio_register(config, XILINX_AXIENET_MDIO_SETUP_REG_OFFSET) &
XILINX_AXIENET_MDIO_SETUP_REG_MDIO_ENABLE_MASK) == 0) {
int err;
int count = 0;
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_SETUP_REG_OFFSET,
XILINX_AXIENET_MDIO_SETUP_REG_MDIO_ENABLE_MASK |
data->clock_divider);
xilinx_axienet_mdio_write_register(config,
XILINX_AXIENET_MDIO_INTERRUPT_ENABLE_REG_OFFSET,
XILINX_AXIENET_MDIO_INTERRUPT_MASK);
if (config->have_irq) {
LOG_DBG("Waiting for bus enable IRQ");
err = k_sem_take(&data->irq_sema,
K_MSEC(XILINX_AXIENET_MDIO_INTERRUPT_TIMEOUT_MS));
if (err != 0) {
LOG_ERR("Could not enable MDIO bus: %d (%s)", err, strerror(-err));
}
}
while ((xilinx_axienet_read_mdio_register(config,
XILINX_AXIENET_MDIO_SETUP_REG_OFFSET) &
XILINX_AXIENET_MDIO_SETUP_REG_MDIO_ENABLE_MASK) == 0) {
LOG_DBG("Waiting for bus enable flag");
k_busy_wait(1);
if (count++ > 1000) {
LOG_ERR("MDIO bus enable timeout");
return;
}
}
} else {
data->bus_enabled = true;
}
}
static void mdio_xilinx_axienet_bus_enable(const struct device *dev)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *data = dev->data;
uint16_t clock_divider, clock_divider_full;
if (!config->clock_frequency_hz) {
LOG_ERR("No clock frequency specified for ethernet device!");
return;
}
/* this might result in a MDIO frequency that is a bit lower than the max frequency */
clock_divider_full = DIV_ROUND_UP(config->clock_frequency_hz,
XILINX_AXIENET_MDIO_MDIO_TARGET_FREQUENCY_HZ * 2);
clock_divider = clock_divider_full & XILINX_AXIENET_MDIO_SETUP_REG_MDIO_CLOCK_DIVIDER_MASK;
if (clock_divider != clock_divider_full) {
LOG_ERR("Clock divider overflow!");
/* maximum divider value - lowest MDIO frequency we can achieve */
clock_divider = XILINX_AXIENET_MDIO_SETUP_REG_MDIO_CLOCK_DIVIDER_MASK;
}
data->clock_divider = clock_divider;
LOG_DBG("Enable MDIO Bus assuming ethernet clock frequency %u divider %u!",
config->clock_frequency_hz, clock_divider);
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_SETUP_REG_OFFSET,
clock_divider);
enable_mdio_bus(config, data);
LOG_DBG("MDIO ready!");
}
static int mdio_xilinx_axienet_read(const struct device *dev, uint8_t prtad, uint8_t devad,
uint16_t *data)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *dev_data = dev->data;
int err;
int count = 0;
if (k_is_in_isr()) {
LOG_ERR("Called MDIO read in ISR!");
return -EWOULDBLOCK;
}
enable_mdio_bus(config, dev_data);
if (!dev_data->bus_enabled) {
LOG_ERR("Bus needs to be enabled!");
return -EIO;
}
LOG_DBG("Waiting for IRQ from MDIO!");
xilinx_axienet_mdio_write_register(
config, XILINX_AXIENET_MDIO_CONTROL_REG_OFFSET,
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_INITIATE |
(prtad << XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_PHYADDR) |
(devad << XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_REGADDR) |
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_READ);
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_INTERRUPT_ENABLE_REG_OFFSET,
XILINX_AXIENET_MDIO_INTERRUPT_MASK);
if (config->have_irq) {
err = k_sem_take(&dev_data->irq_sema,
K_MSEC(XILINX_AXIENET_MDIO_INTERRUPT_TIMEOUT_MS));
if (err != 0) {
LOG_DBG("Error %d (%s) from IRQ semaphore - polling!", err, strerror(-err));
}
}
while ((xilinx_axienet_read_mdio_register(config, XILINX_AXIENET_MDIO_CONTROL_REG_OFFSET) &
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_READY) == 0x0) {
LOG_DBG("Transfer is not yet ready!");
k_busy_wait(1);
if (count++ > 1000) {
LOG_ERR("MDIO read timeout");
return -ETIMEDOUT;
}
}
LOG_DBG("IRQ from MDIO received - read complete!");
*data = (uint16_t)(xilinx_axienet_read_mdio_register(
config, XILINX_AXIENET_MDIO_READ_DATA_REG_OFFSET) &
XILINX_AXIENET_MDIO_READ_DATA_REG_DATA_MASK);
LOG_DBG("Read %" PRIu16 " from MDIO!", *data);
return 0;
}
static int mdio_xilinx_axienet_write(const struct device *dev, uint8_t prtad, uint8_t devad,
uint16_t data)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *dev_data = dev->data;
int err;
int count = 0;
if (k_is_in_isr()) {
LOG_ERR("Called MDIO write in ISR!");
return -EWOULDBLOCK;
}
enable_mdio_bus(config, dev_data);
if (!dev_data->bus_enabled) {
LOG_ERR("Bus needs to be enabled!");
return -EIO;
}
LOG_DBG("Waiting for IRQ from MDIO!");
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_WRITE_DATA_REG_OFFSET, data);
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_INTERRUPT_ENABLE_REG_OFFSET,
XILINX_AXIENET_MDIO_INTERRUPT_MASK);
xilinx_axienet_mdio_write_register(
config, XILINX_AXIENET_MDIO_CONTROL_REG_OFFSET,
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_INITIATE |
(prtad << XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_PHYADDR) |
(devad << XILINX_AXIENET_MDIO_CONTROL_REG_SHIFT_REGADDR) |
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_WRITE);
if (config->have_irq) {
err = k_sem_take(&dev_data->irq_sema,
K_MSEC(XILINX_AXIENET_MDIO_INTERRUPT_TIMEOUT_MS));
if (err != 0) {
LOG_DBG("Error %d (%s) from IRQ semaphore - polling!", err, strerror(-err));
}
}
while ((xilinx_axienet_read_mdio_register(config, XILINX_AXIENET_MDIO_CONTROL_REG_OFFSET) &
XILINX_AXIENET_MDIO_CONTROL_REG_MASK_READY) == 0x0) {
LOG_DBG("IRQ from MDIO received but transfer is not yet ready!");
k_busy_wait(1);
if (count++ > 1000) {
LOG_ERR("MDIO write timeout");
return -ETIMEDOUT;
}
}
LOG_DBG("IRQ from MDIO received - write complete!");
return 0;
}
static inline void xilinx_axienet_mdio_isr(const struct device *dev)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *data = dev->data;
uint32_t interrupt_status;
interrupt_status = xilinx_axienet_read_mdio_register(
config, XILINX_AXIENET_MDIO_INTERRUPT_STATUS_REG_OFFSET);
if (interrupt_status & XILINX_AXIENET_MDIO_INTERRUPT_MASK) {
k_sem_give(&data->irq_sema);
LOG_DBG("MDIO interrupt received!");
} else {
LOG_DBG("Unknown interrupt received: %x!", interrupt_status);
}
xilinx_axienet_mdio_write_register(config, XILINX_AXIENET_MDIO_INTERRUPT_CLEAR_REG_OFFSET,
XILINX_AXIENET_MDIO_INTERRUPT_MASK);
}
static int xilinx_axienet_mdio_probe(const struct device *dev)
{
const struct mdio_xilinx_axienet_config *config = dev->config;
struct mdio_xilinx_axienet_data *data = dev->data;
int err;
if (config->have_irq) {
err = k_sem_init(&data->irq_sema, 0, K_SEM_MAX_LIMIT);
if (err != 0) {
LOG_ERR("Could not init semaphore: error %d (%s)", err, strerror(-err));
return err;
}
}
LOG_INF("Enabling IRQ!");
config->config_func(data);
return 0;
}
static DEVICE_API(mdio, mdio_xilinx_axienet_api) = {
.bus_disable = mdio_xilinx_axienet_bus_disable,
.bus_enable = mdio_xilinx_axienet_bus_enable,
.read = mdio_xilinx_axienet_read,
.write = mdio_xilinx_axienet_write,
};
#define SETUP_IRQS(inst) \
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), xilinx_axienet_mdio_isr, \
DEVICE_DT_INST_GET(inst), 0); \
\
irq_enable(DT_INST_IRQN(inst))
#define XILINX_AXIENET_MDIO_INIT(inst) \
\
static void xilinx_axienet_mdio_config_##inst(const struct mdio_xilinx_axienet_data *data) \
{ \
\
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, interrupts), (SETUP_IRQS(inst)), \
(LOG_INF("No IRQs defined!"))); \
} \
static const struct mdio_xilinx_axienet_config mdio_xilinx_axienet_config##inst = { \
.config_func = xilinx_axienet_mdio_config_##inst, \
.reg = (void *)(uintptr_t)DT_REG_ADDR(DT_INST_PARENT(inst)), \
.clock_frequency_hz = DT_INST_PROP(inst, clock_frequency), \
.have_irq = DT_INST_NODE_HAS_PROP(inst, interrupts)}; \
static struct mdio_xilinx_axienet_data mdio_xilinx_axienet_data##inst = {0}; \
DEVICE_DT_INST_DEFINE(inst, xilinx_axienet_mdio_probe, NULL, \
&mdio_xilinx_axienet_data##inst, &mdio_xilinx_axienet_config##inst, \
POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, &mdio_xilinx_axienet_api);
#define DT_DRV_COMPAT xlnx_axi_ethernet_1_00_a_mdio
DT_INST_FOREACH_STATUS_OKAY(XILINX_AXIENET_MDIO_INIT)