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
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)
|
|
|