| /* |
| * Copyright (c) 2024 Microchip Technology Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_t1s_phy |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/net/phy.h> |
| #include <zephyr/net/mii.h> |
| #include <zephyr/net/mdio.h> |
| #include <zephyr/drivers/mdio.h> |
| |
| #define LOG_MODULE_NAME phy_mc_t1s |
| #define LOG_LEVEL CONFIG_PHY_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
| |
| /* Support: Microchip Phys: |
| * lan8650/1 Rev.B0/B1 Internal PHYs |
| * lan8670/1/2 Rev.C1/C2 PHYs |
| */ |
| /* Both Rev.B0 and B1 clause 22 PHYID's are same due to B1 chip limitation */ |
| #define PHY_ID_LAN865X_REVB 0x0007C1B3 |
| #define PHY_ID_LAN867X_REVC1 0x0007C164 |
| #define PHY_ID_LAN867X_REVC2 0x0007C165 |
| |
| /* Configuration param registers */ |
| #define LAN865X_REG_CFGPARAM_ADDR 0x00D8 |
| #define LAN865X_REG_CFGPARAM_DATA 0x00D9 |
| #define LAN865X_REG_CFGPARAM_CTRL 0x00DA |
| #define LAN865X_REG_STS2 0x0019 |
| #define LAN865X_CFGPARAM_READ_ENABLE BIT(1) |
| |
| /* Collision detection enable/disable registers */ |
| #define LAN86XX_DISABLE_COL_DET 0x0000 |
| #define LAN86XX_ENABLE_COL_DET 0x8000 |
| #define LAN86XX_COL_DET_MASK 0x8000 |
| #define LAN86XX_REG_COL_DET_CTRL0 0x0087 |
| |
| /* Structure holding configuration register address and value */ |
| typedef struct { |
| uint32_t address; |
| uint16_t value; |
| } lan865x_config; |
| |
| /* LAN865x Rev.B0/B1 configuration parameters from AN1760 |
| * As per the Configuration Application Note AN1760 published in the below link, |
| * https://www.microchip.com/en-us/application-notes/an1760 |
| * Revision F (DS60001760G - June 2024) |
| * Addresses 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF will be updated with cfgparam1, cfgparam2, |
| * cfgparam3, cfgparam4 and cfgparam5 respectively. |
| * |
| * LAN867x Rev.C1/C2 configuration settings described in AN1699 are equal to |
| * the first 11 configuration settings and all the sqi fixup settings from |
| * LAN865x Rev.B0/B1. So the same fixup registers and values from LAN865x |
| * Rev.B0/B1 are used for LAN867x Rev.C1/C2 to avoid duplication. |
| * Refer the below link for the AN1699, |
| * https://www.microchip.com/en-us/application-notes/an1699 |
| * Revision E (DS60001699F - June 2024) |
| */ |
| static lan865x_config lan865x_revb_config[] = { |
| {.address = 0x00D0, .value = 0x3F31}, {.address = 0x00E0, .value = 0xC000}, |
| {.address = 0x0084, .value = 0x0000}, {.address = 0x008A, .value = 0x0000}, |
| {.address = 0x00E9, .value = 0x9E50}, {.address = 0x00F5, .value = 0x1CF8}, |
| {.address = 0x00F4, .value = 0xC020}, {.address = 0x00F8, .value = 0xB900}, |
| {.address = 0x00F9, .value = 0x4E53}, {.address = 0x0081, .value = 0x0080}, |
| {.address = 0x0091, .value = 0x9660}, {.address = 0x0043, .value = 0x00FF}, |
| {.address = 0x0044, .value = 0xFFFF}, {.address = 0x0045, .value = 0x0000}, |
| {.address = 0x0053, .value = 0x00FF}, {.address = 0x0054, .value = 0xFFFF}, |
| {.address = 0x0055, .value = 0x0000}, {.address = 0x0040, .value = 0x0002}, |
| {.address = 0x0050, .value = 0x0002}, {.address = 0x00AD, .value = 0x0000}, |
| {.address = 0x00AE, .value = 0x0000}, {.address = 0x00AF, .value = 0x0000}, |
| {.address = 0x00B0, .value = 0x0103}, {.address = 0x00B1, .value = 0x0910}, |
| {.address = 0x00B2, .value = 0x1D26}, {.address = 0x00B3, .value = 0x002A}, |
| {.address = 0x00B4, .value = 0x0103}, {.address = 0x00B5, .value = 0x070D}, |
| {.address = 0x00B6, .value = 0x1720}, {.address = 0x00B7, .value = 0x0027}, |
| {.address = 0x00B8, .value = 0x0509}, {.address = 0x00B9, .value = 0x0E13}, |
| {.address = 0x00BA, .value = 0x1C25}, {.address = 0x00BB, .value = 0x002B}, |
| }; |
| |
| struct mc_t1s_plca_config { |
| bool enable; |
| uint8_t node_id; |
| uint8_t node_count; |
| uint8_t burst_count; |
| uint8_t burst_timer; |
| uint8_t to_timer; |
| }; |
| |
| struct mc_t1s_config { |
| uint8_t phy_addr; |
| const struct device *mdio; |
| struct mc_t1s_plca_config *plca; |
| }; |
| |
| struct mc_t1s_data { |
| uint32_t phy_id; |
| const struct device *dev; |
| struct phy_link_state state; |
| phy_callback_t cb; |
| void *cb_data; |
| struct k_work_delayable phy_monitor_work; |
| }; |
| |
| static int phy_mc_t1s_read(const struct device *dev, uint16_t reg, uint32_t *data) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| int ret; |
| |
| /* Make sure excessive bits 16-31 are reset */ |
| *data = 0U; |
| |
| mdio_bus_enable(cfg->mdio); |
| |
| ret = mdio_read(cfg->mdio, cfg->phy_addr, reg, (uint16_t *)data); |
| |
| mdio_bus_disable(cfg->mdio); |
| |
| return ret; |
| } |
| |
| static int phy_mc_t1s_write(const struct device *dev, uint16_t reg, uint32_t data) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| int ret; |
| |
| mdio_bus_enable(cfg->mdio); |
| |
| ret = mdio_write(cfg->mdio, cfg->phy_addr, reg, (uint16_t)data); |
| |
| mdio_bus_disable(cfg->mdio); |
| |
| return ret; |
| } |
| |
| static int mdio_setup_c45_indirect_access(const struct device *dev, uint16_t devad, uint16_t reg) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| int ret; |
| |
| ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_ACR, devad); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, reg); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_ACR, devad | BIT(14)); |
| } |
| |
| static int phy_mc_t1s_c45_read(const struct device *dev, uint8_t devad, uint16_t reg, uint16_t *val) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| struct mc_t1s_data *data = dev->data; |
| int ret; |
| |
| /* C45 direct read access is only supported by LAN865x internal PHY */ |
| if (data->phy_id == PHY_ID_LAN865X_REVB) { |
| return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val); |
| } |
| |
| mdio_bus_enable(cfg->mdio); |
| |
| /* Read C45 registers using C22 indirect access registers */ |
| ret = mdio_setup_c45_indirect_access(dev, devad, reg); |
| if (ret) { |
| mdio_bus_disable(cfg->mdio); |
| return ret; |
| } |
| |
| ret = mdio_read(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, val); |
| |
| mdio_bus_disable(cfg->mdio); |
| |
| return ret; |
| } |
| |
| static int phy_mc_t1s_c45_write(const struct device *dev, uint8_t devad, uint16_t reg, uint16_t val) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| struct mc_t1s_data *data = dev->data; |
| int ret; |
| |
| /* C45 direct write access is only supported by LAN865x internal PHY */ |
| if (data->phy_id == PHY_ID_LAN865X_REVB) { |
| return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val); |
| } |
| |
| mdio_bus_enable(cfg->mdio); |
| |
| /* Write C45 registers using C22 indirect access registers */ |
| ret = mdio_setup_c45_indirect_access(dev, devad, reg); |
| if (ret) { |
| mdio_bus_disable(cfg->mdio); |
| return ret; |
| } |
| |
| ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, val); |
| |
| mdio_bus_disable(cfg->mdio); |
| |
| return ret; |
| } |
| |
| static int phy_mc_t1s_get_link(const struct device *dev, struct phy_link_state *state) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| struct mc_t1s_data *data = dev->data; |
| struct phy_link_state old_state = data->state; |
| uint32_t value = 0; |
| int ret; |
| |
| ret = phy_mc_t1s_read(dev, MII_BMSR, &value); |
| if (ret) { |
| LOG_ERR("Failed MII_BMSR register read: %d\n", ret); |
| return ret; |
| } |
| |
| state->is_up = value & MII_BMSR_LINK_STATUS; |
| state->speed = LINK_HALF_10BASE; |
| |
| if (memcmp(&old_state, state, sizeof(struct phy_link_state)) != 0) { |
| if (state->is_up) { |
| LOG_INF("PHY (%d) Link speed 10 Mbps, half duplex\n", cfg->phy_addr); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int phy_mc_t1s_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) |
| { |
| struct mc_t1s_data *data = dev->data; |
| |
| data->cb = cb; |
| data->cb_data = user_data; |
| |
| if (data->cb) { |
| data->cb(dev, &data->state, data->cb_data); |
| } |
| |
| return 0; |
| } |
| |
| static void phy_monitor_work_handler(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct mc_t1s_data *const data = CONTAINER_OF(dwork, struct mc_t1s_data, phy_monitor_work); |
| const struct device *dev = data->dev; |
| |
| if (!data->cb) { |
| k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); |
| return; |
| } |
| |
| phy_mc_t1s_get_link(dev, &data->state); |
| |
| data->cb(dev, &data->state, data->cb_data); |
| |
| /* Submit delayed work */ |
| k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); |
| } |
| |
| /* Pulled from AN1760 describing 'indirect read' |
| * |
| * write_register(0x4, 0x00D8, addr) |
| * write_register(0x4, 0x00DA, 0x2) |
| * return (int8)(read_register(0x4, 0x00D9)) |
| * |
| * 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VENDOR_SPECIFIC2 |
| */ |
| static int lan865x_indirect_read(const struct device *dev, uint16_t addr, uint16_t *value) |
| { |
| int ret; |
| |
| ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_ADDR, addr); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_CTRL, |
| LAN865X_CFGPARAM_READ_ENABLE); |
| if (ret) { |
| return ret; |
| } |
| |
| return phy_mc_t1s_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_DATA, |
| value); |
| } |
| |
| static int lan865x_calculate_offset(const struct device *dev, uint16_t address, int8_t *offset) |
| { |
| uint16_t value; |
| int ret; |
| |
| ret = lan865x_indirect_read(dev, address, &value); |
| if (ret) { |
| return ret; |
| } |
| |
| /* 5-bit signed value, sign extend */ |
| value &= GENMASK(4, 0); |
| if (value & BIT(4)) { |
| *offset = (int8_t)((uint8_t)value - 0x20); |
| } else { |
| *offset = (int8_t)value; |
| } |
| |
| return 0; |
| } |
| |
| static void lan865x_update_cfgparam(uint32_t address, uint16_t cfgparam) |
| { |
| for (uint8_t i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) { |
| if (lan865x_revb_config[i].address == address) { |
| lan865x_revb_config[i].value = cfgparam; |
| } |
| } |
| } |
| |
| static int lan865x_calculate_update_cfgparams(const struct device *dev) |
| { |
| uint16_t cfgparam; |
| int8_t offset1; |
| int8_t offset2; |
| int ret; |
| |
| /* Calculate offset1 */ |
| ret = lan865x_calculate_offset(dev, 0x04, &offset1); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Calculate offset2 */ |
| ret = lan865x_calculate_offset(dev, 0x08, &offset2); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Calculate & update cfgparam1 for configuration */ |
| cfgparam = (uint16_t)(((9 + offset1) & 0x3F) << 10) | |
| (uint16_t)(((14 + offset1) & 0x3F) << 4) | 0x03; |
| lan865x_update_cfgparam(0x0084, cfgparam); |
| |
| /* Calculate & update cfgparam2 for configuration */ |
| cfgparam = (uint16_t)(((40 + offset2) & 0x3F) << 10); |
| lan865x_update_cfgparam(0x008A, cfgparam); |
| |
| /* Calculate & update cfgparam3 for configuration */ |
| cfgparam = (uint16_t)(((5 + offset1) & 0x3F) << 8) | (uint16_t)((9 + offset1) & 0x3F); |
| lan865x_update_cfgparam(0x00AD, cfgparam); |
| |
| /* Calculate & update cfgparam4 for configuration */ |
| cfgparam = (uint16_t)(((9 + offset1) & 0x3F) << 8) | (uint16_t)((14 + offset1) & 0x3F); |
| lan865x_update_cfgparam(0x00AE, cfgparam); |
| |
| /* Calculate & update cfgparam5 for configuration */ |
| cfgparam = (uint16_t)(((17 + offset1) & 0x3F) << 8) | (uint16_t)((22 + offset1) & 0x3F); |
| lan865x_update_cfgparam(0x00AF, cfgparam); |
| |
| return 0; |
| } |
| |
| static int phy_mc_lan865x_revb_config_init(const struct device *dev) |
| { |
| int ret; |
| |
| ret = lan865x_calculate_update_cfgparams(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Configure lan865x initial settings */ |
| for (int i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) { |
| ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, |
| lan865x_revb_config[i].address, |
| lan865x_revb_config[i].value); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* LAN867x Rev.C1/C2 configuration settings are equal to the first 11 configuration settings and all |
| * the sqi fixup settings from LAN865x Rev.B0/B1. So the same fixup registers and values from |
| * LAN865x Rev.B0/B1 are used for LAN867x Rev.C1/C2 to avoid duplication. |
| * Refer the below links for the comparison. |
| * https://www.microchip.com/en-us/application-notes/an1760 |
| * Revision F (DS60001760G - June 2024) |
| * https://www.microchip.com/en-us/application-notes/an1699 |
| * Revision E (DS60001699F - June 2024) |
| */ |
| static int phy_mc_lan867x_revc_config_init(const struct device *dev) |
| { |
| int ret; |
| |
| ret = lan865x_calculate_update_cfgparams(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| for (int i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) { |
| ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, |
| lan865x_revb_config[i].address, |
| lan865x_revb_config[i].value); |
| if (ret) { |
| return ret; |
| } |
| |
| /* LAN867x Rev.C1/C2 configuration settings are equal to the first 11 configuration |
| * settings and all the sqi fixup settings from LAN865x Rev.B0/B1. So the 8 |
| * inbetween configuration settings are skipped. |
| */ |
| if (i == 10) { |
| i += 8; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int lan86xx_config_collision_detection(const struct device *dev, bool plca_enable) |
| { |
| uint16_t val; |
| uint16_t new; |
| int ret; |
| |
| ret = phy_mc_t1s_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN86XX_REG_COL_DET_CTRL0, &val); |
| if (ret) { |
| return ret; |
| } |
| |
| if (plca_enable) { |
| new = (val & ~LAN86XX_COL_DET_MASK) | LAN86XX_DISABLE_COL_DET; |
| } else { |
| new = (val & ~LAN86XX_COL_DET_MASK) | LAN86XX_ENABLE_COL_DET; |
| } |
| |
| if (new == val) { |
| return 0; |
| } |
| |
| return phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN86XX_REG_COL_DET_CTRL0, new); |
| } |
| |
| static int phy_mc_t1s_id(const struct device *dev, uint32_t *phy_id) |
| { |
| uint32_t value; |
| int ret; |
| |
| ret = phy_mc_t1s_read(dev, MII_PHYID1R, &value); |
| if (ret) { |
| LOG_ERR("Failed MII_PHYID1R register read: %d\n", ret); |
| return ret; |
| } |
| |
| *phy_id = value << 16; |
| |
| ret = phy_mc_t1s_read(dev, MII_PHYID2R, &value); |
| if (ret) { |
| LOG_ERR("Failed MII_PHYID2R register read: %d\n", ret); |
| return ret; |
| } |
| |
| *phy_id |= value; |
| |
| return 0; |
| } |
| |
| static int phy_mc_t1s_set_plca_cfg(const struct device *dev, struct phy_plca_cfg *plca_cfg) |
| { |
| int ret; |
| |
| ret = genphy_set_plca_cfg(dev, plca_cfg); |
| if (ret) { |
| return ret; |
| } |
| |
| return lan86xx_config_collision_detection(dev, plca_cfg->enable); |
| } |
| |
| static int phy_mc_t1s_set_dt_plca(const struct device *dev) |
| { |
| const struct mc_t1s_config *cfg = dev->config; |
| struct phy_plca_cfg plca_cfg; |
| |
| if (!cfg->plca->enable) { |
| return 0; |
| } |
| |
| plca_cfg.enable = cfg->plca->enable; |
| plca_cfg.node_id = cfg->plca->node_id; |
| plca_cfg.node_count = cfg->plca->node_count; |
| plca_cfg.burst_count = cfg->plca->burst_count; |
| plca_cfg.burst_timer = cfg->plca->burst_timer; |
| plca_cfg.to_timer = cfg->plca->to_timer; |
| |
| return phy_mc_t1s_set_plca_cfg(dev, &plca_cfg); |
| } |
| |
| static int phy_mc_t1s_init(const struct device *dev) |
| { |
| struct mc_t1s_data *data = dev->data; |
| int ret; |
| |
| data->dev = dev; |
| |
| ret = phy_mc_t1s_id(dev, &data->phy_id); |
| if (ret) { |
| return ret; |
| } |
| |
| switch (data->phy_id) { |
| case PHY_ID_LAN867X_REVC1: |
| case PHY_ID_LAN867X_REVC2: |
| ret = phy_mc_lan867x_revc_config_init(dev); |
| if (ret) { |
| LOG_ERR("PHY initial configuration error: %d\n", ret); |
| return ret; |
| } |
| break; |
| case PHY_ID_LAN865X_REVB: |
| ret = phy_mc_lan865x_revb_config_init(dev); |
| if (ret) { |
| LOG_ERR("PHY initial configuration error: %d\n", ret); |
| return ret; |
| } |
| break; |
| default: |
| LOG_ERR("Unsupported PHY ID: %x\n", data->phy_id); |
| return -ENODEV; |
| } |
| |
| ret = phy_mc_t1s_set_dt_plca(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| k_work_init_delayable(&data->phy_monitor_work, phy_monitor_work_handler); |
| phy_monitor_work_handler(&data->phy_monitor_work.work); |
| |
| return 0; |
| } |
| |
| static DEVICE_API(ethphy, mc_t1s_phy_api) = { |
| .get_link = phy_mc_t1s_get_link, |
| .link_cb_set = phy_mc_t1s_link_cb_set, |
| .set_plca_cfg = phy_mc_t1s_set_plca_cfg, |
| .get_plca_cfg = genphy_get_plca_cfg, |
| .get_plca_sts = genphy_get_plca_sts, |
| .read = phy_mc_t1s_read, |
| .write = phy_mc_t1s_write, |
| .read_c45 = phy_mc_t1s_c45_read, |
| .write_c45 = phy_mc_t1s_c45_write, |
| }; |
| |
| #define MICROCHIP_T1S_PHY_INIT(n) \ |
| static struct mc_t1s_plca_config mc_t1s_plca_##n##_config = { \ |
| .enable = DT_INST_PROP(n, plca_enable), \ |
| .node_id = DT_INST_PROP(n, plca_node_id), \ |
| .node_count = DT_INST_PROP(n, plca_node_count), \ |
| .burst_count = DT_INST_PROP(n, plca_burst_count), \ |
| .burst_timer = DT_INST_PROP(n, plca_burst_timer), \ |
| .to_timer = DT_INST_PROP(n, plca_to_timer), \ |
| }; \ |
| \ |
| static const struct mc_t1s_config mc_t1s_##n##_config = { \ |
| .phy_addr = DT_INST_REG_ADDR(n), \ |
| .mdio = DEVICE_DT_GET(DT_INST_PARENT(n)), \ |
| .plca = &mc_t1s_plca_##n##_config, \ |
| }; \ |
| \ |
| static struct mc_t1s_data mc_t1s_##n##_data; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, &phy_mc_t1s_init, NULL, &mc_t1s_##n##_data, &mc_t1s_##n##_config, \ |
| POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, &mc_t1s_phy_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(MICROCHIP_T1S_PHY_INIT); |