/* * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH * Copyright 2023 NXP * * 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 #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)