From bee76c19e1a328038b75017186f484c6267e12a9 Mon Sep 17 00:00:00 2001 From: Georgij Cernysiov Date: Fri, 12 May 2023 15:03:06 +0200 Subject: [PATCH] drivers: phy: add adin2111 Adds PHY driver. Works via MDIO API and exposed ADIN2111 MDIO Clause 45 functions. Link status detection is triggered by ADIN2111 driver within offloaded IRQ handler. Supports: - LED0, LED1 enable/disable - Fatal HW error detection - AN 2.4V tx mode enable/disable The initialization order is important. PHY 2 must be initialized after PHY1. Therefore, it shall be defined after the 1st one in the devicetree. Signed-off-by: Georgij Cernysiov --- CODEOWNERS | 1 + drivers/ethernet/phy/CMakeLists.txt | 3 +- drivers/ethernet/phy/Kconfig | 11 +- drivers/ethernet/phy/phy_adin2111.c | 510 ++++++++++++++++++++ drivers/ethernet/phy/phy_adin2111_priv.h | 30 ++ dts/bindings/ethernet/adi,adin2111-phy.yaml | 25 + 6 files changed, 578 insertions(+), 2 deletions(-) create mode 100644 drivers/ethernet/phy/phy_adin2111.c create mode 100644 drivers/ethernet/phy/phy_adin2111_priv.h create mode 100644 dts/bindings/ethernet/adi,adin2111-phy.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 24732085cbd6edd..4828c64091d9bdb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -286,6 +286,7 @@ /drivers/ethernet/*smsc91x* @sgrrzhf /drivers/ethernet/*adin2111* @GeorgeCGV /drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf +/drivers/ethernet/phy/*adin2111* @GeorgeCGV /drivers/mdio/ @rlubos @tbursztyka @arvinf /drivers/mdio/*adin2111* @GeorgeCGV /drivers/flash/ @nashif @de-nordic diff --git a/drivers/ethernet/phy/CMakeLists.txt b/drivers/ethernet/phy/CMakeLists.txt index 2592f9d4778e381..ce4cbe3e715f141 100644 --- a/drivers/ethernet/phy/CMakeLists.txt +++ b/drivers/ethernet/phy/CMakeLists.txt @@ -1,3 +1,4 @@ # SPDX-License-Identifier: Apache-2.0 -zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) +zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) +zephyr_library_sources_ifdef(CONFIG_PHY_ADIN2111 phy_adin2111.c) diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index 070bd8960cae3e0..bd5d9fb48ed680b 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -23,12 +23,21 @@ config PHY_INIT_PRIORITY config PHY_GENERIC_MII bool "Generic MII PHY Driver" - default y + default y if !ETH_ADIN2111 depends on MDIO help This is a generic MII PHY interface that communicates with the PHY using the MDIO bus. +config PHY_ADIN2111 + bool "ADIN2111 PHY driver" + default y + depends on DT_HAS_ADI_ADIN2111_PHY_ENABLED + depends on ETH_ADIN2111 + depends on MDIO_ADIN2111 + help + Enable ADIN2111 PHY driver. + config PHY_AUTONEG_TIMEOUT_MS int "Auto-negotiation timeout value in milliseconds" default 4000 diff --git a/drivers/ethernet/phy/phy_adin2111.c b/drivers/ethernet/phy/phy_adin2111.c new file mode 100644 index 000000000000000..d41bbd57b0a5453 --- /dev/null +++ b/drivers/ethernet/phy/phy_adin2111.c @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(phy_adin2111, CONFIG_PHY_LOG_LEVEL); + +#define DT_DRV_COMPAT adi_adin2111_phy + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + +/* 10BASE-T1L PMA Status Register */ +#define ADIN2111_PHY_PMA_STATUS 0x000108F7U +/* Indicates PHY support of 10BASE-T1L high voltage (2.4V) tx level op mode */ +#define ADIN2111_PHY_PMA_STATUS_B10L_TX_LVL_HI_ABLE BIT(12) + +/* BASE-T1 Autonegotiation Control Register */ +#define ADIN2111_PHY_AN_CONTROL 0x00070200U +/* Autonegotiation Enable */ +#define ADIN2111_PHY_AN_CONTROL_AN_EN BIT(12) +/* Autonegotiation Restart */ +#define ADIN2111_PHY_AN_CONTROL_AN_RESTART BIT(9) + +/* BASE-T1 Autonegotiation Status Register */ +#define ADIN2111_PHY_AN_STATUS 0x00070201U +/* Autonegotiation Complete */ +#define ADIN2111_PHY_AN_STATUS_AN_COMPLETE BIT(5) +/* Link Status */ +#define ADIN2111_PHY_AN_STATUS_AN_LINK_STATUS BIT(2) + +/* 10BASE-T1 Autonegotiation Advertisement Register */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H 0x00070204U +/* Advertise PHY capability of 2.4V tx level op mode */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL BIT(13) +/* Advertise PHY request of 2.4V tx level op mode */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ BIT(12) + +/* System Interrupt Mask Register */ +#define ADIN2111_PHY_CRSM_IRQ_MASK 0x001E0020U +/* System Interrupt Status Register */ +#define ADIN2111_PHY_CRSM_IRQ_STATUS 0x001E0010U +/** + * 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 0x001F0021U +/* PHY Subsystem Interrupt Status Register */ +#define ADIN2111_PHY_SUBSYS_IRQ_STATUS 0x001F0011U +/* 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 0x001E8812U +/* System Status Register */ +#define ADIN2111_PHY_CRSM_STAT 0x001E8818U +/* Software Power-down Status */ +#define ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY BIT(1) + +/* LED Control Register */ +#define ADIN2111_PHY_LED_CNTRL 0x001E8C82U +/* 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, uint32_t reg, + uint16_t val) +{ + const struct phy_adin2111_config *cfg = dev->config; + + return adin2111_mdio_c45_write(cfg->mdio, cfg->phy_addr, ((reg >> 16U) & 0x1FU), + (reg & UINT16_MAX), val); +} + +static inline int phy_adin2111_c45_read(const struct device *dev, uint32_t reg, + uint16_t *val) +{ + const struct phy_adin2111_config *cfg = dev->config; + + return adin2111_mdio_c45_read(cfg->mdio, cfg->phy_addr, ((reg >> 16U) & 0x1FU), + (reg & 0xFFFFU), 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, 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, 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, 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, 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) { + 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, ADIN2111_PHY_CRSM_IRQ_MASK, 0U); + if (ret < 0) { + return ret; + } + + /* enable link status change irq */ + ret = phy_adin2111_c45_write(dev, 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, 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, ADIN2111_PHY_SUBSYS_IRQ_STATUS, &val); + if (ret < 0) { + return ret; + } + + if (!cfg->led0_en || !cfg->led1_en) { + ret = phy_adin2111_c45_read(dev, 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, ADIN2111_PHY_LED_CNTRL, val); + if (ret < 0) { + return ret; + } + } + + /* check 2.4V support */ + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_PMA_STATUS, &val); + if (ret < 0) { + return ret; + } + + tx_24v_supported = !!(val & ADIN2111_PHY_PMA_STATUS_B10L_TX_LVL_HI_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, ADIN2111_PHY_AN_ADV_ABILITY_H, + &val); + if (ret < 0) { + return ret; + } + + if (tx_24v_supported) { + val |= ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL; + } else { + val &= ~ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL; + } + + 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 |= ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ; + } else { + val &= ~ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ; + } + + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_AN_ADV_ABILITY_H, + val); + if (ret < 0) { + return ret; + } + + /* enable auto-negotiation */ + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_AN_CONTROL, + ADIN2111_PHY_AN_CONTROL_AN_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) diff --git a/drivers/ethernet/phy/phy_adin2111_priv.h b/drivers/ethernet/phy/phy_adin2111_priv.h new file mode 100644 index 000000000000000..dc0c2d0574d8b2d --- /dev/null +++ b/drivers/ethernet/phy/phy_adin2111_priv.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef PHY_ADIN2111_PRIV_H__ +#define PHY_ADIN2111_PRIV_H__ + +#include +#include + +/** + * @brief Handles PHY interrupt. + * + * @note Used internally by the ADIN offloaded ISR handler. + * The caller is responsible for device lock. + * Shall not be called from ISR. + * + * @param[in] dev PHY device. + * @param[out] state Output of the link state. + * + * @retval 0 Successful and link state changed. + * @retval -EAGAIN Successful but link state didn't change. + * @retval <0 MDIO error. + */ +int phy_adin2111_handle_phy_irq(const struct device *dev, + struct phy_link_state *state); + +#endif /* PHY_ADIN2111_PRIV_H__ */ diff --git a/dts/bindings/ethernet/adi,adin2111-phy.yaml b/dts/bindings/ethernet/adi,adin2111-phy.yaml new file mode 100644 index 000000000000000..ca7bb7efc61b38c --- /dev/null +++ b/dts/bindings/ethernet/adi,adin2111-phy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: ADIN2111 PHY + +compatible: "adi,adin2111-phy" + +include: phy.yaml + +on-bus: mdio + +properties: + reg: + required: true + description: 5-bit physical/port address (PRTAD). + led0-en: + type: boolean + description: Enable LED 0. + led1-en: + type: boolean + description: Enable LED 1. + disable-tx-mode-24v: + type: boolean + description: | + Disables requirement of 2.4V TX operating mode in the AN advertisement.