/* * Copyright (c) 2021 IP-Logix Inc. * Copyright 2022 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ethernet_phy #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(phy_mii, CONFIG_PHY_LOG_LEVEL); struct phy_mii_dev_config { uint8_t phy_addr; bool no_reset; bool fixed; int fixed_speed; const struct device * const mdio; }; struct phy_mii_dev_data { const struct device *dev; phy_callback_t cb; void *cb_data; struct k_work_delayable monitor_work; struct phy_link_state state; struct k_sem sem; bool gigabit_supported; }; /* Offset to align capabilities bits of 1000BASE-T Control and Status regs */ #define MII_1KSTSR_OFFSET 2 #define MII_INVALID_PHY_ID UINT32_MAX static int phy_mii_get_link_state(const struct device *dev, struct phy_link_state *state); static inline int phy_mii_reg_read(const struct device *dev, uint16_t reg_addr, uint16_t *value) { const struct phy_mii_dev_config *const cfg = dev->config; /* if there is no mdio (fixed-link) it is not supported to read */ if (cfg->mdio == NULL) { return -ENOTSUP; } return mdio_read(cfg->mdio, cfg->phy_addr, reg_addr, value); } static inline int phy_mii_reg_write(const struct device *dev, uint16_t reg_addr, uint16_t value) { const struct phy_mii_dev_config *const cfg = dev->config; /* if there is no mdio (fixed-link) it is not supported to write */ if (cfg->mdio == NULL) { return -ENOTSUP; } return mdio_write(cfg->mdio, cfg->phy_addr, reg_addr, value); } static bool is_gigabit_supported(const struct device *dev) { uint16_t bmsr_reg; uint16_t estat_reg; if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { return -EIO; } if (bmsr_reg & MII_BMSR_EXTEND_STATUS) { if (phy_mii_reg_read(dev, MII_ESTAT, &estat_reg) < 0) { return -EIO; } if (estat_reg & (MII_ESTAT_1000BASE_T_HALF | MII_ESTAT_1000BASE_T_FULL)) { return true; } } return false; } static int reset(const struct device *dev) { uint32_t timeout = 12U; uint16_t value; /* Issue a soft reset */ if (phy_mii_reg_write(dev, MII_BMCR, MII_BMCR_RESET) < 0) { return -EIO; } /* Wait up to 0.6s for the reset sequence to finish. According to * IEEE 802.3, Section 2, Subsection 22.2.4.1.1 a PHY reset may take * up to 0.5 s. */ do { if (timeout-- == 0U) { return -ETIMEDOUT; } k_sleep(K_MSEC(50)); if (phy_mii_reg_read(dev, MII_BMCR, &value) < 0) { return -EIO; } } while (value & MII_BMCR_RESET); return 0; } static int get_id(const struct device *dev, uint32_t *phy_id) { uint16_t value; if (phy_mii_reg_read(dev, MII_PHYID1R, &value) < 0) { return -EIO; } *phy_id = value << 16; if (phy_mii_reg_read(dev, MII_PHYID2R, &value) < 0) { return -EIO; } *phy_id |= value; return 0; } static int update_link_state(const struct device *dev) { const struct phy_mii_dev_config *const cfg = dev->config; struct phy_mii_dev_data *const data = dev->data; bool link_up; uint16_t anar_reg = 0; uint16_t bmcr_reg = 0; uint16_t bmsr_reg = 0; uint16_t anlpar_reg = 0; uint16_t c1kt_reg = 0; uint16_t s1kt_reg = 0; uint32_t timeout = CONFIG_PHY_AUTONEG_TIMEOUT_MS / 100; if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { return -EIO; } link_up = bmsr_reg & MII_BMSR_LINK_STATUS; /* If there is no change in link state don't proceed. */ if (link_up == data->state.is_up) { return -EAGAIN; } data->state.is_up = link_up; /* If link is down, there is nothing more to be done */ if (data->state.is_up == false) { LOG_INF("PHY (%d) is down", cfg->phy_addr); return 0; } /** * Perform auto-negotiation sequence. */ LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", cfg->phy_addr); /* Read PHY default advertising parameters */ if (phy_mii_reg_read(dev, MII_ANAR, &anar_reg) < 0) { return -EIO; } /* Configure and start auto-negotiation process */ if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { return -EIO; } bmcr_reg |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; bmcr_reg &= ~MII_BMCR_ISOLATE; /* Don't isolate the PHY */ if (phy_mii_reg_write(dev, MII_BMCR, bmcr_reg) < 0) { return -EIO; } /* Wait for the auto-negotiation process to complete */ do { if (timeout-- == 0U) { LOG_DBG("PHY (%d) auto-negotiate timedout", cfg->phy_addr); return -ETIMEDOUT; } k_sleep(K_MSEC(100)); /* On some PHY chips, the BMSR bits are latched, so the first read may * show incorrect status. A second read ensures correct values. */ if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { return -EIO; } /* Second read, clears the latched bits and gives the correct status */ if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { return -EIO; } } while (!(bmsr_reg & MII_BMSR_AUTONEG_COMPLETE)); LOG_DBG("PHY (%d) auto-negotiate sequence completed", cfg->phy_addr); /** Read peer device capability */ if (phy_mii_reg_read(dev, MII_ANLPAR, &anlpar_reg) < 0) { return -EIO; } if (data->gigabit_supported) { if (phy_mii_reg_read(dev, MII_1KTCR, &c1kt_reg) < 0) { return -EIO; } if (phy_mii_reg_read(dev, MII_1KSTSR, &s1kt_reg) < 0) { return -EIO; } s1kt_reg = (uint16_t)(s1kt_reg >> MII_1KSTSR_OFFSET); } if (data->gigabit_supported && ((c1kt_reg & s1kt_reg) & MII_ADVERTISE_1000_FULL)) { data->state.speed = LINK_FULL_1000BASE_T; } else if (data->gigabit_supported && ((c1kt_reg & s1kt_reg) & MII_ADVERTISE_1000_HALF)) { data->state.speed = LINK_HALF_1000BASE_T; } else if ((anar_reg & anlpar_reg) & MII_ADVERTISE_100_FULL) { data->state.speed = LINK_FULL_100BASE_T; } else if ((anar_reg & anlpar_reg) & MII_ADVERTISE_100_HALF) { data->state.speed = LINK_HALF_100BASE_T; } else if ((anar_reg & anlpar_reg) & MII_ADVERTISE_10_FULL) { data->state.speed = LINK_FULL_10BASE_T; } else { data->state.speed = LINK_HALF_10BASE_T; } LOG_INF("PHY (%d) Link speed %s Mb, %s duplex", cfg->phy_addr, PHY_LINK_IS_SPEED_1000M(data->state.speed) ? "1000" : (PHY_LINK_IS_SPEED_100M(data->state.speed) ? "100" : "10"), PHY_LINK_IS_FULL_DUPLEX(data->state.speed) ? "full" : "half"); return 0; } static void invoke_link_cb(const struct device *dev) { struct phy_mii_dev_data *const data = dev->data; struct phy_link_state state; if (data->cb == NULL) { return; } phy_mii_get_link_state(dev, &state); data->cb(data->dev, &state, data->cb_data); } static void monitor_work_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); struct phy_mii_dev_data *const data = CONTAINER_OF(dwork, struct phy_mii_dev_data, monitor_work); const struct device *dev = data->dev; int rc; k_sem_take(&data->sem, K_FOREVER); rc = update_link_state(dev); k_sem_give(&data->sem); /* If link state has changed and a callback is set, invoke callback */ if (rc == 0) { invoke_link_cb(dev); } /* Submit delayed work */ k_work_reschedule(&data->monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); } static int phy_mii_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) { return phy_mii_reg_read(dev, reg_addr, (uint16_t *)data); } static int phy_mii_write(const struct device *dev, uint16_t reg_addr, uint32_t data) { return phy_mii_reg_write(dev, reg_addr, (uint16_t)data); } static int phy_mii_cfg_link(const struct device *dev, enum phy_link_speed adv_speeds) { struct phy_mii_dev_data *const data = dev->data; uint16_t anar_reg; uint16_t bmcr_reg; uint16_t c1kt_reg; if (phy_mii_reg_read(dev, MII_ANAR, &anar_reg) < 0) { return -EIO; } if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { return -EIO; } if (data->gigabit_supported) { if (phy_mii_reg_read(dev, MII_1KTCR, &c1kt_reg) < 0) { return -EIO; } } if (adv_speeds & LINK_FULL_10BASE_T) { anar_reg |= MII_ADVERTISE_10_FULL; } else { anar_reg &= ~MII_ADVERTISE_10_FULL; } if (adv_speeds & LINK_HALF_10BASE_T) { anar_reg |= MII_ADVERTISE_10_HALF; } else { anar_reg &= ~MII_ADVERTISE_10_HALF; } if (adv_speeds & LINK_FULL_100BASE_T) { anar_reg |= MII_ADVERTISE_100_FULL; } else { anar_reg &= ~MII_ADVERTISE_100_FULL; } if (adv_speeds & LINK_HALF_100BASE_T) { anar_reg |= MII_ADVERTISE_100_HALF; } else { anar_reg &= ~MII_ADVERTISE_100_HALF; } if (data->gigabit_supported) { if (adv_speeds & LINK_FULL_1000BASE_T) { c1kt_reg |= MII_ADVERTISE_1000_FULL; } else { c1kt_reg &= ~MII_ADVERTISE_1000_FULL; } if (adv_speeds & LINK_HALF_1000BASE_T) { c1kt_reg |= MII_ADVERTISE_1000_HALF; } else { c1kt_reg &= ~MII_ADVERTISE_1000_HALF; } if (phy_mii_reg_write(dev, MII_1KTCR, c1kt_reg) < 0) { return -EIO; } } bmcr_reg |= MII_BMCR_AUTONEG_ENABLE; if (phy_mii_reg_write(dev, MII_ANAR, anar_reg) < 0) { return -EIO; } if (phy_mii_reg_write(dev, MII_BMCR, bmcr_reg) < 0) { return -EIO; } return 0; } static int phy_mii_get_link_state(const struct device *dev, struct phy_link_state *state) { struct phy_mii_dev_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_mii_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) { struct phy_mii_dev_data *const data = dev->data; data->cb = cb; data->cb_data = user_data; /** * Immediately invoke the callback to notify the caller of the * current link status. */ invoke_link_cb(dev); return 0; } static int phy_mii_initialize(const struct device *dev) { const struct phy_mii_dev_config *const cfg = dev->config; struct phy_mii_dev_data *const data = dev->data; uint32_t phy_id; k_sem_init(&data->sem, 1, 1); data->dev = dev; data->cb = NULL; /** * If this is a *fixed* link then we don't need to communicate * with a PHY. We set the link parameters as configured * and set link state to up. */ if (cfg->fixed) { const static int speed_to_phy_link_speed[] = { LINK_HALF_10BASE_T, LINK_FULL_10BASE_T, LINK_HALF_100BASE_T, LINK_FULL_100BASE_T, LINK_HALF_1000BASE_T, LINK_FULL_1000BASE_T, }; data->state.speed = speed_to_phy_link_speed[cfg->fixed_speed]; data->state.is_up = true; } else { data->state.is_up = false; mdio_bus_enable(cfg->mdio); if (cfg->no_reset == false) { reset(dev); } if (get_id(dev, &phy_id) == 0) { if (phy_id == MII_INVALID_PHY_ID) { LOG_ERR("No PHY found at address %d", cfg->phy_addr); return -EINVAL; } LOG_INF("PHY (%d) ID %X", cfg->phy_addr, phy_id); } data->gigabit_supported = is_gigabit_supported(dev); /* Advertise all speeds */ phy_mii_cfg_link(dev, LINK_HALF_10BASE_T | LINK_FULL_10BASE_T | LINK_HALF_100BASE_T | LINK_FULL_100BASE_T | LINK_HALF_1000BASE_T | LINK_FULL_1000BASE_T); k_work_init_delayable(&data->monitor_work, monitor_work_handler); monitor_work_handler(&data->monitor_work.work); } return 0; } #define IS_FIXED_LINK(n) DT_INST_NODE_HAS_PROP(n, fixed_link) static DEVICE_API(ethphy, phy_mii_driver_api) = { .get_link = phy_mii_get_link_state, .cfg_link = phy_mii_cfg_link, .link_cb_set = phy_mii_link_cb_set, .read = phy_mii_read, .write = phy_mii_write, }; #define PHY_MII_CONFIG(n) \ static const struct phy_mii_dev_config phy_mii_dev_config_##n = { \ .phy_addr = DT_INST_REG_ADDR(n), \ .no_reset = DT_INST_PROP(n, no_reset), \ .fixed = IS_FIXED_LINK(n), \ .fixed_speed = DT_INST_ENUM_IDX_OR(n, fixed_link, 0), \ .mdio = UTIL_AND(UTIL_NOT(IS_FIXED_LINK(n)), \ DEVICE_DT_GET(DT_INST_BUS(n))) \ }; #define PHY_MII_DEVICE(n) \ PHY_MII_CONFIG(n); \ static struct phy_mii_dev_data phy_mii_dev_data_##n; \ DEVICE_DT_INST_DEFINE(n, \ &phy_mii_initialize, \ NULL, \ &phy_mii_dev_data_##n, \ &phy_mii_dev_config_##n, POST_KERNEL, \ CONFIG_PHY_INIT_PRIORITY, \ &phy_mii_driver_api); DT_INST_FOREACH_STATUS_OKAY(PHY_MII_DEVICE)