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