280ddaef4a
Add `mdio_read_c45()`/`mdio_write_c45()` APIs for Clause 45 access and remove the `protocol` MDIO binding property so that MDIO bus controller can support more than one protocol. A new MDIO header is introduced with generic opcodes, MMD and registers addresses, to be used by MDIO and PHY drivers. Existing MDIO drivers that support both Clause 22 and Clause 45 access are migrated to the new APIs. Signed-off-by: Manuel Argüelles <manuel.arguelles@nxp.com>
492 lines
12 KiB
C
492 lines
12 KiB
C
/*
|
|
* Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
|
* Copyright 2023 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(phy_adin2111, CONFIG_PHY_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT adi_adin2111_phy
|
|
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/net/phy.h>
|
|
#include <zephyr/net/mii.h>
|
|
#include <zephyr/net/mdio.h>
|
|
#include <zephyr/drivers/mdio.h>
|
|
|
|
/* PHYs out of reset check retry delay */
|
|
#define ADIN2111_PHY_AWAIT_DELAY_POLL_US 15U
|
|
/* Number of retries for PHYs out of reset check */
|
|
#define ADIN2111_PHY_AWAIT_RETRY_COUNT 200U
|
|
|
|
/* PHY's software powerdown check retry delay */
|
|
#define ADIN2111_PHY_SFT_PD_DELAY_POLL_US 15U
|
|
/* Number of retries for PHY's software powerdown check */
|
|
#define ADIN2111_PHY_SFT_PD_RETRY_COUNT 200U
|
|
|
|
/* PHYs autonegotiation complete timeout */
|
|
#define ADIN2111_AN_COMPLETE_AWAIT_TIMEOUT_MS 3000U
|
|
|
|
/* ADIN2111 PHY identifier */
|
|
#define ADIN2111_PHY_ID 0x0283BCA1U
|
|
#define ADIN1110_PHY_ID 0x0283BC91U
|
|
|
|
/* System Interrupt Mask Register */
|
|
#define ADIN2111_PHY_CRSM_IRQ_MASK 0x0020U
|
|
/* System Interrupt Status Register */
|
|
#define ADIN2111_PHY_CRSM_IRQ_STATUS 0x0010U
|
|
/**
|
|
* Mask of reserved interrupts that indicates a fatal error in the system.
|
|
*
|
|
* There is inconsistency between RM and ADI driver example:
|
|
* - RM mask 0x6FFF
|
|
* - ADI driver example mask 0x2BFF
|
|
*
|
|
* The value from the example doesn't include reserved bits 10 and 14.
|
|
* The tests show that PHY is still functioning when bit 10 is raised.
|
|
*
|
|
* Here the value from ADI driver example is used instead of RM.
|
|
*/
|
|
#define ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR 0x2BFFU
|
|
|
|
/* PHY Subsystem Interrupt Mask Register */
|
|
#define ADIN2111_PHY_SUBSYS_IRQ_MASK 0x0021U
|
|
/* PHY Subsystem Interrupt Status Register */
|
|
#define ADIN2111_PHY_SUBSYS_IRQ_STATUS 0x0011U
|
|
/* Link Status Change */
|
|
#define ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH BIT(1)
|
|
|
|
/* Software Power-down Control Register */
|
|
#define ADIN2111_PHY_CRSM_SFT_PD_CNTRL 0x8812U
|
|
/* System Status Register */
|
|
#define ADIN2111_PHY_CRSM_STAT 0x8818U
|
|
/* Software Power-down Status */
|
|
#define ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY BIT(1)
|
|
|
|
/* LED Control Register */
|
|
#define ADIN2111_PHY_LED_CNTRL 0x8C82U
|
|
/* LED 1 Enable */
|
|
#define ADIN2111_PHY_LED_CNTRL_LED1_EN BIT(15)
|
|
/* LED 0 Enable */
|
|
#define ADIN2111_PHY_LED_CNTRL_LED0_EN BIT(7)
|
|
|
|
struct phy_adin2111_config {
|
|
const struct device *mdio;
|
|
uint8_t phy_addr;
|
|
bool led0_en;
|
|
bool led1_en;
|
|
bool tx_24v;
|
|
};
|
|
|
|
struct phy_adin2111_data {
|
|
struct phy_link_state state;
|
|
struct k_sem sem;
|
|
};
|
|
|
|
static inline int phy_adin2111_c22_read(const struct device *dev, uint16_t reg,
|
|
uint16_t *val)
|
|
{
|
|
const struct phy_adin2111_config *const cfg = dev->config;
|
|
|
|
return mdio_read(cfg->mdio, cfg->phy_addr, reg, val);
|
|
}
|
|
|
|
static inline int phy_adin2111_c22_write(const struct device *dev, uint16_t reg,
|
|
uint16_t val)
|
|
{
|
|
const struct phy_adin2111_config *const cfg = dev->config;
|
|
|
|
return mdio_write(cfg->mdio, cfg->phy_addr, reg, val);
|
|
}
|
|
|
|
static inline int phy_adin2111_c45_write(const struct device *dev, uint16_t devad,
|
|
uint16_t reg, uint16_t val)
|
|
{
|
|
const struct phy_adin2111_config *cfg = dev->config;
|
|
|
|
return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
|
|
}
|
|
|
|
static inline int phy_adin2111_c45_read(const struct device *dev, uint16_t devad,
|
|
uint16_t reg, uint16_t *val)
|
|
{
|
|
const struct phy_adin2111_config *cfg = dev->config;
|
|
|
|
return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
|
|
}
|
|
|
|
static int phy_adin2111_reg_read(const struct device *dev, uint16_t reg_addr,
|
|
uint32_t *data)
|
|
{
|
|
const struct phy_adin2111_config *cfg = dev->config;
|
|
int ret;
|
|
|
|
mdio_bus_enable(cfg->mdio);
|
|
|
|
ret = phy_adin2111_c22_read(dev, reg_addr, (uint16_t *) data);
|
|
|
|
mdio_bus_disable(cfg->mdio);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phy_adin2111_reg_write(const struct device *dev, uint16_t reg_addr,
|
|
uint32_t data)
|
|
{
|
|
const struct phy_adin2111_config *cfg = dev->config;
|
|
int ret;
|
|
|
|
mdio_bus_enable(cfg->mdio);
|
|
|
|
ret = phy_adin2111_c22_write(dev, reg_addr, (uint16_t) data);
|
|
|
|
mdio_bus_disable(cfg->mdio);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phy_adin2111_await_phy(const struct device *dev)
|
|
{
|
|
int ret;
|
|
uint32_t count;
|
|
uint16_t val;
|
|
|
|
/**
|
|
* Port 2 PHY comes out of reset after Port 1 PHY,
|
|
* wait until both are out of reset.
|
|
* Reading Port 2 PHY registers returns 0s until
|
|
* it comes out from reset.
|
|
*/
|
|
for (count = 0U; count < ADIN2111_PHY_AWAIT_RETRY_COUNT; ++count) {
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_CRSM_IRQ_MASK, &val);
|
|
if (ret >= 0) {
|
|
if (val != 0U) {
|
|
break;
|
|
}
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
k_sleep(K_USEC(ADIN2111_PHY_AWAIT_DELAY_POLL_US));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phy_adin2111_an_state_read(const struct device *dev)
|
|
{
|
|
struct phy_adin2111_data *const data = dev->data;
|
|
uint16_t bmsr;
|
|
int ret;
|
|
|
|
/* read twice to get actual link status, latch low */
|
|
ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
data->state.is_up = !!(bmsr & MII_BMSR_LINK_STATUS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int phy_adin2111_handle_phy_irq(const struct device *dev,
|
|
struct phy_link_state *state)
|
|
{
|
|
struct phy_adin2111_data *const data = dev->data;
|
|
uint16_t subsys_status;
|
|
int ret;
|
|
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2,
|
|
ADIN2111_PHY_SUBSYS_IRQ_STATUS,
|
|
&subsys_status);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if ((subsys_status & ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH) == 0U) {
|
|
/* nothing to process */
|
|
return -EAGAIN;
|
|
}
|
|
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
|
|
ret = phy_adin2111_an_state_read(dev);
|
|
|
|
memcpy(state, &data->state, sizeof(struct phy_link_state));
|
|
|
|
k_sem_give(&data->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phy_adin2111_sft_pd(const struct device *dev, bool enter)
|
|
{
|
|
int ret;
|
|
uint32_t count;
|
|
const uint16_t expected = enter ? ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY : 0U;
|
|
uint16_t val;
|
|
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_CRSM_SFT_PD_CNTRL,
|
|
enter ? 1U : 0U);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
for (count = 0U; count < ADIN2111_PHY_SFT_PD_RETRY_COUNT; ++count) {
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_CRSM_STAT, &val);
|
|
if (ret >= 0) {
|
|
if ((val & ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY) == expected) {
|
|
break;
|
|
}
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
k_sleep(K_USEC(ADIN2111_PHY_SFT_PD_DELAY_POLL_US));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phy_adin2111_id(const struct device *dev, uint32_t *phy_id)
|
|
{
|
|
uint16_t val;
|
|
|
|
if (phy_adin2111_c22_read(dev, MII_PHYID1R, &val) < 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
*phy_id = (val & UINT16_MAX) << 16;
|
|
|
|
if (phy_adin2111_c22_read(dev, MII_PHYID2R, &val) < 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
*phy_id |= (val & UINT16_MAX);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_adin2111_get_link_state(const struct device *dev,
|
|
struct phy_link_state *state)
|
|
{
|
|
struct phy_adin2111_data *const data = dev->data;
|
|
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
|
|
memcpy(state, &data->state, sizeof(struct phy_link_state));
|
|
|
|
k_sem_give(&data->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_adin2111_cfg_link(const struct device *dev,
|
|
enum phy_link_speed adv_speeds)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
if (!!(adv_speeds & LINK_FULL_10BASE_T)) {
|
|
return 0;
|
|
}
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int phy_adin2111_init(const struct device *dev)
|
|
{
|
|
const struct phy_adin2111_config *const cfg = dev->config;
|
|
struct phy_adin2111_data *const data = dev->data;
|
|
uint32_t phy_id;
|
|
uint16_t val;
|
|
bool tx_24v_supported = false;
|
|
int ret;
|
|
|
|
data->state.is_up = false;
|
|
data->state.speed = LINK_FULL_10BASE_T;
|
|
|
|
ret = phy_adin2111_await_phy(dev);
|
|
if (ret < 0) {
|
|
LOG_ERR("PHY %u didn't come out of reset, %d",
|
|
cfg->phy_addr, ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = phy_adin2111_id(dev, &phy_id);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to read PHY %u ID, %d",
|
|
cfg->phy_addr, ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (phy_id != ADIN2111_PHY_ID && phy_id != ADIN1110_PHY_ID) {
|
|
LOG_ERR("PHY %u unexpected PHY ID %X", cfg->phy_addr, phy_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_INF("PHY %u ID %X", cfg->phy_addr, phy_id);
|
|
|
|
/* enter software powerdown */
|
|
ret = phy_adin2111_sft_pd(dev, true);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* disable interrupts */
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_CRSM_IRQ_MASK, 0U);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* enable link status change irq */
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2,
|
|
ADIN2111_PHY_SUBSYS_IRQ_MASK,
|
|
ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* clear PHY IRQ status before enabling ADIN IRQs */
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_CRSM_IRQ_STATUS, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (val & ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR) {
|
|
LOG_ERR("PHY %u CRSM reports fatal system error", cfg->phy_addr);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2,
|
|
ADIN2111_PHY_SUBSYS_IRQ_STATUS, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!cfg->led0_en || !cfg->led1_en) {
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_LED_CNTRL, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
if (!cfg->led0_en) {
|
|
val &= ~(ADIN2111_PHY_LED_CNTRL_LED0_EN);
|
|
}
|
|
if (!cfg->led1_en) {
|
|
val &= ~(ADIN2111_PHY_LED_CNTRL_LED1_EN);
|
|
}
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
|
ADIN2111_PHY_LED_CNTRL, val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* check 2.4V support */
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_PMAPMD, MDIO_PMA_B10L_STAT, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
tx_24v_supported = !!(val & MDIO_PMA_B10L_STAT_2V4_ABLE);
|
|
|
|
LOG_INF("PHY %u 2.4V mode %s", cfg->phy_addr,
|
|
tx_24v_supported ? "supported" : "not supported");
|
|
|
|
if (!cfg->tx_24v & tx_24v_supported) {
|
|
LOG_ERR("PHY %u 2.4V mode supported, but not enabled",
|
|
cfg->phy_addr);
|
|
}
|
|
|
|
/* config 2.4V auto-negotiation */
|
|
ret = phy_adin2111_c45_read(dev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (tx_24v_supported) {
|
|
val |= MDIO_AN_T1_ADV_H_10L_TX_HI;
|
|
} else {
|
|
val &= ~MDIO_AN_T1_ADV_H_10L_TX_HI;
|
|
}
|
|
|
|
if (cfg->tx_24v) {
|
|
if (!tx_24v_supported) {
|
|
LOG_ERR("PHY %u 2.4V mode enabled, but not supported",
|
|
cfg->phy_addr);
|
|
return -EINVAL;
|
|
}
|
|
|
|
val |= MDIO_AN_T1_ADV_H_10L_TX_HI_REQ;
|
|
} else {
|
|
val &= ~MDIO_AN_T1_ADV_H_10L_TX_HI_REQ;
|
|
}
|
|
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H, val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* enable auto-negotiation */
|
|
ret = phy_adin2111_c45_write(dev, MDIO_MMD_AN, MDIO_AN_T1_CTRL,
|
|
MDIO_AN_T1_CTRL_EN);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* done, PHY is in software powerdown (SFT PD)
|
|
* exit software powerdown, PHY 1 has to exit before PHY 2
|
|
* correct PHY order is expected to be in DTS to guarantee that
|
|
*/
|
|
return phy_adin2111_sft_pd(dev, false);
|
|
}
|
|
|
|
static int phy_adin2111_link_cb_set(const struct device *dev, phy_callback_t cb,
|
|
void *user_data)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(cb);
|
|
ARG_UNUSED(user_data);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static const struct ethphy_driver_api phy_adin2111_api = {
|
|
.get_link = phy_adin2111_get_link_state,
|
|
.cfg_link = phy_adin2111_cfg_link,
|
|
.link_cb_set = phy_adin2111_link_cb_set,
|
|
.read = phy_adin2111_reg_read,
|
|
.write = phy_adin2111_reg_write,
|
|
};
|
|
|
|
#define ADIN2111_PHY_INITIALIZE(n) \
|
|
static const struct phy_adin2111_config phy_adin2111_config_##n = { \
|
|
.mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \
|
|
.phy_addr = DT_INST_REG_ADDR(n), \
|
|
.led0_en = DT_INST_PROP(n, led0_en), \
|
|
.led1_en = DT_INST_PROP(n, led1_en), \
|
|
.tx_24v = !(DT_INST_PROP(n, disable_tx_mode_24v)), \
|
|
}; \
|
|
static struct phy_adin2111_data phy_adin2111_data_##n = { \
|
|
.sem = Z_SEM_INITIALIZER(phy_adin2111_data_##n.sem, 1, 1), \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(n, &phy_adin2111_init, NULL, \
|
|
&phy_adin2111_data_##n, &phy_adin2111_config_##n, \
|
|
POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \
|
|
&phy_adin2111_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(ADIN2111_PHY_INITIALIZE)
|