/* * Copyright 2023 NXP * Copyright 2023 CogniPilot Foundation * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_tja1103 #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(phy_tja1103, CONFIG_PHY_LOG_LEVEL); /* PHYs out of reset check retry delay */ #define TJA1103_AWAIT_DELAY_POLL_US 15000U /* Number of retries for PHYs out of reset check */ #define TJA1103_AWAIT_RETRY_COUNT 200U /* TJA1103 PHY identifier */ #define TJA1103_ID 0x1BB013 /* MMD30 - Device status register */ #define TJA1103_DEVICE_CONTROL (0x0040U) #define TJA1103_DEVICE_CONTROL_GLOBAL_CFG_EN BIT(14) #define TJA1103_DEVICE_CONTROL_SUPER_CFG_EN BIT(13) /* Shared - PHY control register */ #define TJA1103_PHY_CONTROL (0x8100U) #define TJA1103_PHY_CONTROL_CFG_EN BIT(14) /* Shared - PHY status register */ #define TJA1103_PHY_STATUS (0x8102U) #define TJA1103_PHY_STATUS_LINK_STAT BIT(2) /* Shared - PHY functional IRQ masked status register */ #define TJA1103_PHY_FUNC_IRQ_MSTATUS (0x80A2) #define TJA1103_PHY_FUNC_IRQ_LINK_EVENT BIT(1) #define TJA1103_PHY_FUNC_IRQ_LINK_AVAIL BIT(2) /* Shared -PHY functional IRQ source & enable registers */ #define TJA1103_PHY_FUNC_IRQ_ACK (0x80A0) #define TJA1103_PHY_FUNC_IRQ_EN (0x80A1) #define TJA1103_PHY_FUNC_IRQ_LINK_EVENT_EN BIT(1) #define TJA1103_PHY_FUNC_IRQ_LINK_AVAIL_EN BIT(2) /* Always accessible reg for NMIs */ #define TJA1103_ALWAYS_ACCESSIBLE (0x801F) #define TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ BIT(4) struct phy_tja1103_config { const struct device *mdio; struct gpio_dt_spec gpio_interrupt; uint8_t phy_addr; uint8_t master_slave; }; struct phy_tja1103_data { const struct device *dev; struct phy_link_state state; struct k_sem sem; struct k_sem offload_sem; phy_callback_t cb; struct gpio_callback phy_tja1103_int_callback; void *cb_data; K_KERNEL_STACK_MEMBER(irq_thread_stack, CONFIG_PHY_TJA1103_IRQ_THREAD_STACK_SIZE); struct k_thread irq_thread; struct k_work_delayable monitor_work; }; static inline int phy_tja1103_c22_read(const struct device *dev, uint16_t reg, uint16_t *val) { const struct phy_tja1103_config *const cfg = dev->config; return mdio_read(cfg->mdio, cfg->phy_addr, reg, val); } static inline int phy_tja1103_c22_write(const struct device *dev, uint16_t reg, uint16_t val) { const struct phy_tja1103_config *const cfg = dev->config; return mdio_write(cfg->mdio, cfg->phy_addr, reg, val); } static inline int phy_tja1103_c45_write(const struct device *dev, uint16_t devad, uint16_t reg, uint16_t val) { const struct phy_tja1103_config *cfg = dev->config; return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val); } static inline int phy_tja1103_c45_read(const struct device *dev, uint16_t devad, uint16_t reg, uint16_t *val) { const struct phy_tja1103_config *cfg = dev->config; return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val); } static int phy_tja1103_reg_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) { const struct phy_tja1103_config *cfg = dev->config; int ret; mdio_bus_enable(cfg->mdio); ret = phy_tja1103_c22_read(dev, reg_addr, (uint16_t *)data); mdio_bus_disable(cfg->mdio); return ret; } static int phy_tja1103_reg_write(const struct device *dev, uint16_t reg_addr, uint32_t data) { const struct phy_tja1103_config *cfg = dev->config; int ret; mdio_bus_enable(cfg->mdio); ret = phy_tja1103_c22_write(dev, reg_addr, (uint16_t)data); mdio_bus_disable(cfg->mdio); return ret; } static int phy_tja1103_id(const struct device *dev, uint32_t *phy_id) { uint16_t val; if (phy_tja1103_c22_read(dev, MII_PHYID1R, &val) < 0) { return -EIO; } *phy_id = (val & UINT16_MAX) << 16; if (phy_tja1103_c22_read(dev, MII_PHYID2R, &val) < 0) { return -EIO; } *phy_id |= (val & UINT16_MAX); return 0; } static int update_link_state(const struct device *dev) { struct phy_tja1103_data *const data = dev->data; bool link_up; uint16_t val; if (phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_STATUS, &val) < 0) { return -EIO; } link_up = (val & TJA1103_PHY_STATUS_LINK_STAT) != 0; /* Let workqueue re-schedule and re-check if the * link status is unchanged this time */ if (data->state.is_up == link_up) { return -EAGAIN; } data->state.is_up = link_up; return 0; } static int phy_tja1103_get_link_state(const struct device *dev, struct phy_link_state *state) { struct phy_tja1103_data *const data = dev->data; const struct phy_tja1103_config *const cfg = dev->config; int rc = 0; k_sem_take(&data->sem, K_FOREVER); /* If Interrupt is configured then the workqueue will not * update the link state periodically so do it explicitly */ if (cfg->gpio_interrupt.port != NULL) { rc = update_link_state(dev); } memcpy(state, &data->state, sizeof(struct phy_link_state)); k_sem_give(&data->sem); return rc; } static void invoke_link_cb(const struct device *dev) { struct phy_tja1103_data *const data = dev->data; struct phy_link_state state; if (data->cb == NULL) { return; } /* Send callback only on link state change */ if (phy_tja1103_get_link_state(dev, &state) != 0) { return; } data->cb(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_tja1103_data *const data = CONTAINER_OF(dwork, struct phy_tja1103_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 void phy_tja1103_irq_offload_thread(void *p1, void *p2, void *p3) { ARG_UNUSED(p2); ARG_UNUSED(p3); const struct device *dev = p1; struct phy_tja1103_data *const data = dev->data; uint16_t irq; for (;;) { /* await trigger from ISR */ k_sem_take(&data->offload_sem, K_FOREVER); if (phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_FUNC_IRQ_MSTATUS, &irq) < 0) { return; } /* Handling Link related Functional IRQs */ if (irq & (TJA1103_PHY_FUNC_IRQ_LINK_EVENT | TJA1103_PHY_FUNC_IRQ_LINK_AVAIL)) { /* Send callback to MAC on link status changed */ invoke_link_cb(dev); /* Ack the assered link related interrupts */ phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_FUNC_IRQ_ACK, irq); } } } static void phy_tja1103_handle_irq(const struct device *port, struct gpio_callback *cb, uint32_t pins) { ARG_UNUSED(pins); ARG_UNUSED(port); struct phy_tja1103_data *const data = CONTAINER_OF(cb, struct phy_tja1103_data, phy_tja1103_int_callback); /* Trigger BH before leaving the ISR */ k_sem_give(&data->offload_sem); } static void phy_tja1103_cfg_irq_poll(const struct device *dev) { struct phy_tja1103_data *const data = dev->data; const struct phy_tja1103_config *const cfg = dev->config; int ret; if (cfg->gpio_interrupt.port != NULL) { if (!gpio_is_ready_dt(&cfg->gpio_interrupt)) { LOG_ERR("Interrupt GPIO device %s is not ready", cfg->gpio_interrupt.port->name); return; } ret = gpio_pin_configure_dt(&cfg->gpio_interrupt, GPIO_INPUT); if (ret < 0) { LOG_ERR("Failed to configure interrupt GPIO, %d", ret); return; } gpio_init_callback(&(data->phy_tja1103_int_callback), phy_tja1103_handle_irq, BIT(cfg->gpio_interrupt.pin)); /* Add callback structure to global syslist */ ret = gpio_add_callback(cfg->gpio_interrupt.port, &data->phy_tja1103_int_callback); if (ret < 0) { LOG_ERR("Failed to add INT callback, %d", ret); return; } ret = phy_tja1103_c45_write( dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_FUNC_IRQ_EN, (TJA1103_PHY_FUNC_IRQ_LINK_EVENT_EN | TJA1103_PHY_FUNC_IRQ_LINK_AVAIL_EN)); if (ret < 0) { return; } ret = gpio_pin_interrupt_configure_dt(&cfg->gpio_interrupt, GPIO_INT_EDGE_FALLING); if (ret < 0) { LOG_ERR("Failed to enable INT, %d", ret); return; } /* PHY initialized, IRQ configured, now initialize the BH handler */ k_thread_create(&data->irq_thread, data->irq_thread_stack, CONFIG_PHY_TJA1103_IRQ_THREAD_STACK_SIZE, phy_tja1103_irq_offload_thread, (void *)dev, NULL, NULL, CONFIG_PHY_TJA1103_IRQ_THREAD_PRIO, K_ESSENTIAL, K_NO_WAIT); k_thread_name_set(&data->irq_thread, "phy_tja1103_irq_offload"); } else { k_work_init_delayable(&data->monitor_work, monitor_work_handler); monitor_work_handler(&data->monitor_work.work); } } static int phy_tja1103_cfg_link(const struct device *dev, enum phy_link_speed adv_speeds) { ARG_UNUSED(dev); if (adv_speeds & LINK_FULL_100BASE_T) { return 0; } return -ENOTSUP; } static int phy_tja1103_init(const struct device *dev) { const struct phy_tja1103_config *const cfg = dev->config; struct phy_tja1103_data *const data = dev->data; uint32_t phy_id = 0; uint16_t val; int ret; data->dev = dev; data->cb = NULL; data->state.is_up = false; data->state.speed = LINK_FULL_100BASE_T; ret = WAIT_FOR(!phy_tja1103_id(dev, &phy_id) && phy_id == TJA1103_ID, TJA1103_AWAIT_RETRY_COUNT * TJA1103_AWAIT_DELAY_POLL_US, k_sleep(K_USEC(TJA1103_AWAIT_DELAY_POLL_US))); if (ret < 0) { LOG_ERR("Unable to obtain PHY ID for device 0x%x", cfg->phy_addr); return -ENODEV; } /* enable config registers */ ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_DEVICE_CONTROL, TJA1103_DEVICE_CONTROL_GLOBAL_CFG_EN | TJA1103_DEVICE_CONTROL_SUPER_CFG_EN); if (ret < 0) { return ret; } ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_CONTROL, TJA1103_PHY_CONTROL_CFG_EN); if (ret < 0) { return ret; } ret = phy_tja1103_c45_read(dev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, &val); if (ret < 0) { return ret; } /* Change master/slave mode if need */ if (cfg->master_slave == 1) { val |= MDIO_PMA_PMD_BT1_CTRL_CFG_MST; } else if (cfg->master_slave == 2) { val &= ~MDIO_PMA_PMD_BT1_CTRL_CFG_MST; } ret = phy_tja1103_c45_write(dev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, val); if (ret < 0) { return ret; } /* Check always accesible register for handling NMIs */ ret = phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_ALWAYS_ACCESSIBLE, &val); if (ret < 0) { return ret; } /* Ack Fusa Pass Interrupt if Startup Self Test Passed successfully */ if (val & TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ) { ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_ALWAYS_ACCESSIBLE, TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ); } /* Configure interrupt or poll mode for reporting link changes */ phy_tja1103_cfg_irq_poll(dev); return ret; } static int phy_tja1103_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) { struct phy_tja1103_data *const data = dev->data; data->cb = cb; data->cb_data = user_data; /* Invoke the callback to notify the caller of the current * link status. */ invoke_link_cb(dev); return 0; } static const struct ethphy_driver_api phy_tja1103_api = { .get_link = phy_tja1103_get_link_state, .cfg_link = phy_tja1103_cfg_link, .link_cb_set = phy_tja1103_link_cb_set, .read = phy_tja1103_reg_read, .write = phy_tja1103_reg_write, }; #define TJA1103_INITIALIZE(n) \ static const struct phy_tja1103_config phy_tja1103_config_##n = { \ .phy_addr = DT_INST_REG_ADDR(n), \ .mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \ .gpio_interrupt = GPIO_DT_SPEC_INST_GET_OR(n, int_gpios, {0}), \ .master_slave = DT_INST_ENUM_IDX(n, master_slave), \ }; \ static struct phy_tja1103_data phy_tja1103_data_##n = { \ .sem = Z_SEM_INITIALIZER(phy_tja1103_data_##n.sem, 1, 1), \ .offload_sem = Z_SEM_INITIALIZER(phy_tja1103_data_##n.offload_sem, 0, 1), \ }; \ DEVICE_DT_INST_DEFINE(n, &phy_tja1103_init, NULL, &phy_tja1103_data_##n, \ &phy_tja1103_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ &phy_tja1103_api); DT_INST_FOREACH_STATUS_OKAY(TJA1103_INITIALIZE)