267 lines
7.3 KiB
C
267 lines
7.3 KiB
C
/*
|
|
* Copyright 2023-2024 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_enet_mdio
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/net/mdio.h>
|
|
#include <zephyr/drivers/mdio.h>
|
|
#include <zephyr/drivers/ethernet/eth_nxp_enet.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/sys_clock.h>
|
|
|
|
struct nxp_enet_mdio_config {
|
|
const struct pinctrl_dev_config *pincfg;
|
|
const struct device *module_dev;
|
|
const struct device *clock_dev;
|
|
clock_control_subsys_t clock_subsys;
|
|
uint32_t mdc_freq;
|
|
bool disable_preamble;
|
|
};
|
|
|
|
struct nxp_enet_mdio_data {
|
|
ENET_Type *base;
|
|
struct k_mutex mdio_mutex;
|
|
struct k_sem mdio_sem;
|
|
bool interrupt_up;
|
|
};
|
|
|
|
/*
|
|
* This function is used for both read and write operations
|
|
* in order to wait for the completion of an MDIO transaction.
|
|
* It returns -ETIMEDOUT if timeout occurs as specified in DT,
|
|
* otherwise returns 0 if EIR MII bit is set indicting completed
|
|
* operation, otherwise -EIO.
|
|
*/
|
|
static int nxp_enet_mdio_wait_xfer(const struct device *dev)
|
|
{
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
|
|
/* This function will not make sense from IRQ context */
|
|
if (k_is_in_isr()) {
|
|
return -EWOULDBLOCK;
|
|
}
|
|
|
|
if (!data->interrupt_up) {
|
|
/* If the interrupt is not available to use yet, just busy wait */
|
|
k_busy_wait(CONFIG_MDIO_NXP_ENET_TIMEOUT);
|
|
k_sem_give(&data->mdio_sem);
|
|
}
|
|
|
|
/* Wait for the MDIO transaction to finish or time out */
|
|
k_sem_take(&data->mdio_sem, K_USEC(CONFIG_MDIO_NXP_ENET_TIMEOUT));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* MDIO Read API implementation */
|
|
static int nxp_enet_mdio_read(const struct device *dev,
|
|
uint8_t prtad, uint8_t regad, uint16_t *read_data)
|
|
{
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
int ret;
|
|
|
|
/* Only one MDIO bus operation attempt at a time */
|
|
(void)k_mutex_lock(&data->mdio_mutex, K_FOREVER);
|
|
|
|
/*
|
|
* Clear the bit (W1C) that indicates MDIO transfer is ready to
|
|
* prepare to wait for it to be set once this read is done
|
|
*/
|
|
data->base->EIR = ENET_EIR_MII_MASK;
|
|
|
|
/*
|
|
* Write MDIO frame to MII management register which will
|
|
* send the read command and data out to the MDIO bus as this frame:
|
|
* ST = start, 1 means start
|
|
* OP = operation, 2 means read
|
|
* PA = PHY/Port address
|
|
* RA = Register/Device Address
|
|
* TA = Turnaround, must be 2 to be valid
|
|
* data = data to be written to the PHY register
|
|
*/
|
|
data->base->MMFR = ENET_MMFR_ST(0x1U) |
|
|
ENET_MMFR_OP(MDIO_OP_C22_READ) |
|
|
ENET_MMFR_PA(prtad) |
|
|
ENET_MMFR_RA(regad) |
|
|
ENET_MMFR_TA(0x2U);
|
|
|
|
ret = nxp_enet_mdio_wait_xfer(dev);
|
|
if (ret) {
|
|
(void)k_mutex_unlock(&data->mdio_mutex);
|
|
return ret;
|
|
}
|
|
|
|
/* The data is received in the same register that we wrote the command to */
|
|
*read_data = (data->base->MMFR & ENET_MMFR_DATA_MASK) >> ENET_MMFR_DATA_SHIFT;
|
|
|
|
/* Clear the same bit as before because the event has been handled */
|
|
data->base->EIR = ENET_EIR_MII_MASK;
|
|
|
|
/* This MDIO interaction is finished */
|
|
(void)k_mutex_unlock(&data->mdio_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* MDIO Write API implementation */
|
|
static int nxp_enet_mdio_write(const struct device *dev,
|
|
uint8_t prtad, uint8_t regad, uint16_t write_data)
|
|
{
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
int ret;
|
|
|
|
/* Only one MDIO bus operation attempt at a time */
|
|
(void)k_mutex_lock(&data->mdio_mutex, K_FOREVER);
|
|
|
|
/*
|
|
* Clear the bit (W1C) that indicates MDIO transfer is ready to
|
|
* prepare to wait for it to be set once this write is done
|
|
*/
|
|
data->base->EIR = ENET_EIR_MII_MASK;
|
|
|
|
/*
|
|
* Write MDIO frame to MII management register which will
|
|
* send the write command and data out to the MDIO bus as this frame:
|
|
* ST = start, 1 means start
|
|
* OP = operation, 1 means write
|
|
* PA = PHY/Port address
|
|
* RA = Register/Device Address
|
|
* TA = Turnaround, must be 2 to be valid
|
|
* data = data to be written to the PHY register
|
|
*/
|
|
data->base->MMFR = ENET_MMFR_ST(0x1U) |
|
|
ENET_MMFR_OP(MDIO_OP_C22_WRITE) |
|
|
ENET_MMFR_PA(prtad) |
|
|
ENET_MMFR_RA(regad) |
|
|
ENET_MMFR_TA(0x2U) |
|
|
write_data;
|
|
|
|
ret = nxp_enet_mdio_wait_xfer(dev);
|
|
if (ret) {
|
|
(void)k_mutex_unlock(&data->mdio_mutex);
|
|
return ret;
|
|
}
|
|
|
|
/* Clear the same bit as before because the event has been handled */
|
|
data->base->EIR = ENET_EIR_MII_MASK;
|
|
|
|
/* This MDIO interaction is finished */
|
|
(void)k_mutex_unlock(&data->mdio_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct mdio_driver_api nxp_enet_mdio_api = {
|
|
.read = nxp_enet_mdio_read,
|
|
.write = nxp_enet_mdio_write,
|
|
};
|
|
|
|
static void nxp_enet_mdio_isr_cb(const struct device *dev)
|
|
{
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
|
|
data->base->EIR = ENET_EIR_MII_MASK;
|
|
|
|
k_sem_give(&data->mdio_sem);
|
|
}
|
|
|
|
static void nxp_enet_mdio_post_module_reset_init(const struct device *dev)
|
|
{
|
|
const struct nxp_enet_mdio_config *config = dev->config;
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
uint32_t enet_module_clock_rate;
|
|
|
|
/* Set up MSCR register */
|
|
(void) clock_control_get_rate(config->clock_dev, config->clock_subsys,
|
|
&enet_module_clock_rate);
|
|
uint32_t mii_speed = (enet_module_clock_rate + 2 * config->mdc_freq - 1) /
|
|
(2 * config->mdc_freq) - 1;
|
|
uint32_t holdtime = (10 + NSEC_PER_SEC / enet_module_clock_rate - 1) /
|
|
(NSEC_PER_SEC / enet_module_clock_rate) - 1;
|
|
uint32_t mscr = ENET_MSCR_MII_SPEED(mii_speed) | ENET_MSCR_HOLDTIME(holdtime) |
|
|
(config->disable_preamble ? ENET_MSCR_DIS_PRE_MASK : 0);
|
|
data->base->MSCR = mscr;
|
|
}
|
|
|
|
void nxp_enet_mdio_callback(const struct device *dev,
|
|
enum nxp_enet_callback_reason event, void *cb_data)
|
|
{
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
|
|
ARG_UNUSED(cb_data);
|
|
|
|
switch (event) {
|
|
case NXP_ENET_MODULE_RESET:
|
|
nxp_enet_mdio_post_module_reset_init(dev);
|
|
break;
|
|
case NXP_ENET_INTERRUPT:
|
|
nxp_enet_mdio_isr_cb(dev);
|
|
break;
|
|
case NXP_ENET_INTERRUPT_ENABLED:
|
|
/* IRQ was enabled in NVIC, now enable in enet */
|
|
data->interrupt_up = true;
|
|
data->base->EIMR |= ENET_EIMR_MII_MASK;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int nxp_enet_mdio_init(const struct device *dev)
|
|
{
|
|
const struct nxp_enet_mdio_config *config = dev->config;
|
|
struct nxp_enet_mdio_data *data = dev->data;
|
|
int ret = 0;
|
|
|
|
data->base = (ENET_Type *)DEVICE_MMIO_GET(config->module_dev);
|
|
|
|
ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = k_mutex_init(&data->mdio_mutex);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = k_sem_init(&data->mdio_sem, 0, 1);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* All operations done after module reset should be done during device init too */
|
|
nxp_enet_mdio_post_module_reset_init(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define NXP_ENET_MDIO_INIT(inst) \
|
|
PINCTRL_DT_INST_DEFINE(inst); \
|
|
\
|
|
static const struct nxp_enet_mdio_config nxp_enet_mdio_cfg_##inst = { \
|
|
.module_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
|
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
|
.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(inst))), \
|
|
.clock_subsys = (void *) DT_CLOCKS_CELL_BY_IDX( \
|
|
DT_INST_PARENT(inst), 0, name), \
|
|
.disable_preamble = DT_INST_PROP(inst, suppress_preamble), \
|
|
.mdc_freq = DT_INST_PROP(inst, clock_frequency), \
|
|
}; \
|
|
\
|
|
static struct nxp_enet_mdio_data nxp_enet_mdio_data_##inst; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(inst, &nxp_enet_mdio_init, NULL, \
|
|
&nxp_enet_mdio_data_##inst, &nxp_enet_mdio_cfg_##inst, \
|
|
POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \
|
|
&nxp_enet_mdio_api);
|
|
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(NXP_ENET_MDIO_INIT)
|