| /* |
| * Copyright 2023-2024 NXP |
| * |
| * Based on a commit to drivers/ethernet/eth_mcux.c which was: |
| * Copyright (c) 2018 Intel Coporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_enet_ptp_clock |
| |
| #include <zephyr/drivers/ptp_clock.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/ethernet/eth_nxp_enet.h> |
| |
| #include <fsl_enet.h> |
| |
| struct ptp_clock_nxp_enet_config { |
| const struct pinctrl_dev_config *pincfg; |
| const struct device *module_dev; |
| const struct device *port; |
| const struct device *clock_dev; |
| struct device *clock_subsys; |
| void (*irq_config_func)(void); |
| }; |
| |
| struct ptp_clock_nxp_enet_data { |
| ENET_Type *base; |
| double clock_ratio; |
| enet_handle_t enet_handle; |
| struct k_mutex ptp_mutex; |
| }; |
| |
| static int ptp_clock_nxp_enet_set(const struct device *dev, |
| struct net_ptp_time *tm) |
| { |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| enet_ptp_time_t enet_time; |
| |
| enet_time.second = tm->second; |
| enet_time.nanosecond = tm->nanosecond; |
| |
| ENET_Ptp1588SetTimer(data->base, &data->enet_handle, &enet_time); |
| |
| return 0; |
| } |
| |
| static int ptp_clock_nxp_enet_get(const struct device *dev, |
| struct net_ptp_time *tm) |
| { |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| enet_ptp_time_t enet_time; |
| |
| ENET_Ptp1588GetTimer(data->base, &data->enet_handle, &enet_time); |
| |
| tm->second = enet_time.second; |
| tm->nanosecond = enet_time.nanosecond; |
| |
| return 0; |
| } |
| |
| static int ptp_clock_nxp_enet_adjust(const struct device *dev, |
| int increment) |
| { |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| int ret = 0; |
| int key; |
| |
| if ((increment <= (int32_t)(-NSEC_PER_SEC)) || |
| (increment >= (int32_t)NSEC_PER_SEC)) { |
| ret = -EINVAL; |
| } else { |
| key = irq_lock(); |
| if (data->base->ATPER != NSEC_PER_SEC) { |
| ret = -EBUSY; |
| } else { |
| /* Seconds counter is handled by software. Change the |
| * period of one software second to adjust the clock. |
| */ |
| data->base->ATPER = NSEC_PER_SEC - increment; |
| ret = 0; |
| } |
| irq_unlock(key); |
| } |
| |
| return ret; |
| |
| } |
| |
| static int ptp_clock_nxp_enet_rate_adjust(const struct device *dev, |
| double ratio) |
| { |
| const struct ptp_clock_nxp_enet_config *config = dev->config; |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| int corr; |
| int32_t mul; |
| double val; |
| uint32_t enet_ref_pll_rate; |
| |
| (void) clock_control_get_rate(config->clock_dev, config->clock_subsys, |
| &enet_ref_pll_rate); |
| int hw_inc = NSEC_PER_SEC / enet_ref_pll_rate; |
| |
| /* No change needed. */ |
| if ((ratio > 1.0 && ratio - 1.0 < 0.00000001) || |
| (ratio < 1.0 && 1.0 - ratio < 0.00000001)) { |
| return 0; |
| } |
| |
| ratio *= data->clock_ratio; |
| |
| /* Limit possible ratio. */ |
| if ((ratio > 1.0 + 1.0/(2 * hw_inc)) || |
| (ratio < 1.0 - 1.0/(2 * hw_inc))) { |
| return -EINVAL; |
| } |
| |
| /* Save new ratio. */ |
| data->clock_ratio = ratio; |
| |
| if (ratio < 1.0) { |
| corr = hw_inc - 1; |
| val = 1.0 / (hw_inc * (1.0 - ratio)); |
| } else if (ratio > 1.0) { |
| corr = hw_inc + 1; |
| val = 1.0 / (hw_inc * (ratio - 1.0)); |
| } else { |
| val = 0; |
| corr = hw_inc; |
| } |
| |
| if (val >= INT32_MAX) { |
| /* Value is too high. |
| * It is not possible to adjust the rate of the clock. |
| */ |
| mul = 0; |
| } else { |
| mul = val; |
| } |
| |
| k_mutex_lock(&data->ptp_mutex, K_FOREVER); |
| |
| ENET_Ptp1588AdjustTimer(data->base, corr, mul); |
| |
| k_mutex_unlock(&data->ptp_mutex); |
| |
| return 0; |
| } |
| |
| void nxp_enet_ptp_clock_callback(const struct device *dev, |
| enum nxp_enet_callback_reason event, |
| void *cb_data) |
| { |
| const struct ptp_clock_nxp_enet_config *config = dev->config; |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| |
| if (event == NXP_ENET_MODULE_RESET) { |
| enet_ptp_config_t ptp_config; |
| uint32_t enet_ref_pll_rate; |
| uint8_t ptp_multicast[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 }; |
| uint8_t ptp_peer_multicast[6] = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E }; |
| |
| (void) clock_control_get_rate(config->clock_dev, config->clock_subsys, |
| &enet_ref_pll_rate); |
| |
| ENET_AddMulticastGroup(data->base, ptp_multicast); |
| ENET_AddMulticastGroup(data->base, ptp_peer_multicast); |
| |
| /* only for ERRATA_2579 */ |
| ptp_config.channel = kENET_PtpTimerChannel3; |
| ptp_config.ptp1588ClockSrc_Hz = enet_ref_pll_rate; |
| data->clock_ratio = 1.0; |
| |
| ENET_Ptp1588SetChannelMode(data->base, kENET_PtpTimerChannel3, |
| kENET_PtpChannelPulseHighonCompare, true); |
| ENET_Ptp1588Configure(data->base, &data->enet_handle, |
| &ptp_config); |
| } |
| |
| if (cb_data != NULL) { |
| /* Share the mutex with mac driver */ |
| *(uintptr_t *)cb_data = (uintptr_t)&data->ptp_mutex; |
| } |
| } |
| |
| static int ptp_clock_nxp_enet_init(const struct device *port) |
| { |
| const struct ptp_clock_nxp_enet_config *config = port->config; |
| struct ptp_clock_nxp_enet_data *data = port->data; |
| int ret; |
| |
| data->base = (ENET_Type *)DEVICE_MMIO_GET(config->module_dev); |
| |
| ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
| if (ret) { |
| return ret; |
| } |
| |
| k_mutex_init(&data->ptp_mutex); |
| |
| config->irq_config_func(); |
| |
| return 0; |
| } |
| |
| static void ptp_clock_nxp_enet_isr(const struct device *dev) |
| { |
| struct ptp_clock_nxp_enet_data *data = dev->data; |
| enet_ptp_timer_channel_t channel; |
| |
| unsigned int irq_lock_key = irq_lock(); |
| |
| /* clear channel */ |
| for (channel = kENET_PtpTimerChannel1; channel <= kENET_PtpTimerChannel4; channel++) { |
| if (ENET_Ptp1588GetChannelStatus(data->base, channel)) { |
| ENET_Ptp1588ClearChannelStatus(data->base, channel); |
| } |
| } |
| |
| ENET_TimeStampIRQHandler(data->base, &data->enet_handle); |
| |
| irq_unlock(irq_lock_key); |
| } |
| |
| static const struct ptp_clock_driver_api ptp_clock_nxp_enet_api = { |
| .set = ptp_clock_nxp_enet_set, |
| .get = ptp_clock_nxp_enet_get, |
| .adjust = ptp_clock_nxp_enet_adjust, |
| .rate_adjust = ptp_clock_nxp_enet_rate_adjust, |
| }; |
| |
| #define PTP_CLOCK_NXP_ENET_INIT(n) \ |
| static void nxp_enet_ptp_clock_##n##_irq_config_func(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 0, irq), \ |
| DT_INST_IRQ_BY_IDX(n, 0, priority), \ |
| ptp_clock_nxp_enet_isr, \ |
| DEVICE_DT_INST_GET(n), \ |
| 0); \ |
| irq_enable(DT_INST_IRQ_BY_IDX(n, 0, irq)); \ |
| } \ |
| \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| \ |
| static const struct ptp_clock_nxp_enet_config \ |
| ptp_clock_nxp_enet_##n##_config = { \ |
| .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .port = DEVICE_DT_INST_GET(n), \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
| .clock_subsys = (void *) \ |
| DT_INST_CLOCKS_CELL_BY_IDX(n, 0, name), \ |
| .irq_config_func = \ |
| nxp_enet_ptp_clock_##n##_irq_config_func, \ |
| }; \ |
| \ |
| static struct ptp_clock_nxp_enet_data ptp_clock_nxp_enet_##n##_data; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, &ptp_clock_nxp_enet_init, NULL, \ |
| &ptp_clock_nxp_enet_##n##_data, \ |
| &ptp_clock_nxp_enet_##n##_config, \ |
| POST_KERNEL, CONFIG_PTP_CLOCK_INIT_PRIORITY, \ |
| &ptp_clock_nxp_enet_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PTP_CLOCK_NXP_ENET_INIT) |