Path: blob/main/sys/contrib/dev/iwlwifi/mvm/ptp.c
108653 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2021 - 2023, 2025 Intel Corporation3*/45#include "mvm.h"6#include "iwl-debug.h"7#include <linux/timekeeping.h>8#include <linux/math64.h>910#define IWL_PTP_GP2_WRAP 0x100000000ULL11#define IWL_PTP_WRAP_TIME (3600 * HZ)1213/* The scaled_ppm parameter is ppm (parts per million) with a 16-bit fractional14* part, which means that a value of 1 in one of those fields actually means15* 2^-16 ppm, and 2^16=65536 is 1 ppm.16*/17#define SCALE_FACTOR 65536000000ULL18#define IWL_PTP_WRAP_THRESHOLD_USEC (5000)1920#define IWL_PTP_GET_CROSS_TS_NUM 52122static void iwl_mvm_ptp_update_new_read(struct iwl_mvm *mvm, u32 gp2)23{24/* If the difference is above the threshold, assume it's a wraparound.25* Otherwise assume it's an old read and ignore it.26*/27if (gp2 < mvm->ptp_data.last_gp2 &&28mvm->ptp_data.last_gp2 - gp2 < IWL_PTP_WRAP_THRESHOLD_USEC) {29IWL_DEBUG_INFO(mvm,30"PTP: ignore old read (gp2=%u, last_gp2=%u)\n",31gp2, mvm->ptp_data.last_gp2);32return;33}3435if (gp2 < mvm->ptp_data.last_gp2) {36mvm->ptp_data.wrap_counter++;37IWL_DEBUG_INFO(mvm,38"PTP: wraparound detected (new counter=%u)\n",39mvm->ptp_data.wrap_counter);40}4142mvm->ptp_data.last_gp2 = gp2;43schedule_delayed_work(&mvm->ptp_data.dwork, IWL_PTP_WRAP_TIME);44}4546u64 iwl_mvm_ptp_get_adj_time(struct iwl_mvm *mvm, u64 base_time_ns)47{48struct ptp_data *data = &mvm->ptp_data;49u64 last_gp2_ns = mvm->ptp_data.scale_update_gp2 * NSEC_PER_USEC;50u64 res;51u64 diff;5253iwl_mvm_ptp_update_new_read(mvm,54div64_u64(base_time_ns, NSEC_PER_USEC));5556IWL_DEBUG_INFO(mvm, "base_time_ns=%llu, wrap_counter=%u\n",57(unsigned long long)base_time_ns, data->wrap_counter);5859base_time_ns = base_time_ns +60(data->wrap_counter * IWL_PTP_GP2_WRAP * NSEC_PER_USEC);6162/* It is possible that a GP2 timestamp was received from fw before the63* last scale update. Since we don't know how to scale - ignore it.64*/65if (base_time_ns < last_gp2_ns) {66IWL_DEBUG_INFO(mvm, "Time before scale update - ignore\n");67return 0;68}6970diff = base_time_ns - last_gp2_ns;71IWL_DEBUG_INFO(mvm, "diff ns=%llu\n", (unsigned long long)diff);7273diff = mul_u64_u64_div_u64(diff, data->scaled_freq,74SCALE_FACTOR);75IWL_DEBUG_INFO(mvm, "scaled diff ns=%llu\n", (unsigned long long)diff);7677res = data->scale_update_adj_time_ns + data->delta + diff;7879IWL_DEBUG_INFO(mvm, "base=%llu delta=%lld adj=%llu\n",80(unsigned long long)base_time_ns, (long long)data->delta,81(unsigned long long)res);82return res;83}8485static int86iwl_mvm_get_crosstimestamp_fw(struct iwl_mvm *mvm, u32 *gp2, u64 *sys_time)87{88struct iwl_synced_time_cmd synced_time_cmd = {89.operation = cpu_to_le32(IWL_SYNCED_TIME_OPERATION_READ_BOTH)90};91struct iwl_host_cmd cmd = {92.id = WIDE_ID(DATA_PATH_GROUP, WNM_PLATFORM_PTM_REQUEST_CMD),93.flags = CMD_WANT_SKB,94.data[0] = &synced_time_cmd,95.len[0] = sizeof(synced_time_cmd),96};97struct iwl_synced_time_rsp *resp;98struct iwl_rx_packet *pkt;99int ret;100u64 gp2_10ns;101102ret = iwl_mvm_send_cmd(mvm, &cmd);103if (ret)104return ret;105106pkt = cmd.resp_pkt;107108if (iwl_rx_packet_payload_len(pkt) != sizeof(*resp)) {109IWL_ERR(mvm, "PTP: Invalid command response\n");110iwl_free_resp(&cmd);111return -EIO;112}113114resp = (void *)pkt->data;115116gp2_10ns = (u64)le32_to_cpu(resp->gp2_timestamp_hi) << 32 |117le32_to_cpu(resp->gp2_timestamp_lo);118*gp2 = div_u64(gp2_10ns, 100);119120*sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 |121le32_to_cpu(resp->platform_timestamp_lo);122123return ret;124}125126static void iwl_mvm_phc_get_crosstimestamp_loop(struct iwl_mvm *mvm,127ktime_t *sys_time, u32 *gp2)128{129u64 diff = 0, new_diff;130u64 tmp_sys_time;131u32 tmp_gp2;132int i;133134for (i = 0; i < IWL_PTP_GET_CROSS_TS_NUM; i++) {135iwl_mvm_get_sync_time(mvm, CLOCK_REALTIME, &tmp_gp2, NULL,136&tmp_sys_time);137new_diff = tmp_sys_time - ((u64)tmp_gp2 * NSEC_PER_USEC);138if (!diff || new_diff < diff) {139*sys_time = tmp_sys_time;140*gp2 = tmp_gp2;141diff = new_diff;142IWL_DEBUG_INFO(mvm, "PTP: new times: gp2=%u sys=%lld\n",143*gp2, *sys_time);144}145}146}147148static int149iwl_mvm_phc_get_crosstimestamp(struct ptp_clock_info *ptp,150struct system_device_crosststamp *xtstamp)151{152struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,153ptp_data.ptp_clock_info);154int ret = 0;155/* Raw value read from GP2 register in usec */156u32 gp2;157/* GP2 value in ns*/158s64 gp2_ns;159/* System (wall) time */160ktime_t sys_time;161162memset(xtstamp, 0, sizeof(struct system_device_crosststamp));163164if (!mvm->ptp_data.ptp_clock) {165IWL_ERR(mvm, "No PHC clock registered\n");166return -ENODEV;167}168169mutex_lock(&mvm->mutex);170if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SYNCED_TIME)) {171ret = iwl_mvm_get_crosstimestamp_fw(mvm, &gp2, &sys_time);172173if (ret)174goto out;175} else {176iwl_mvm_phc_get_crosstimestamp_loop(mvm, &sys_time, &gp2);177}178179gp2_ns = iwl_mvm_ptp_get_adj_time(mvm, (u64)gp2 * NSEC_PER_USEC);180181IWL_INFO(mvm, "Got Sync Time: GP2:%u, last_GP2: %u, GP2_ns: %lld, sys_time: %lld\n",182gp2, mvm->ptp_data.last_gp2, gp2_ns, (s64)sys_time);183184/* System monotonic raw time is not used */185xtstamp->device = (ktime_t)gp2_ns;186xtstamp->sys_realtime = sys_time;187188out:189mutex_unlock(&mvm->mutex);190return ret;191}192193static void iwl_mvm_ptp_work(struct work_struct *wk)194{195struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm,196ptp_data.dwork.work);197u32 gp2;198199mutex_lock(&mvm->mutex);200gp2 = iwl_mvm_get_systime(mvm);201iwl_mvm_ptp_update_new_read(mvm, gp2);202mutex_unlock(&mvm->mutex);203}204205static int iwl_mvm_ptp_gettime(struct ptp_clock_info *ptp,206struct timespec64 *ts)207{208struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,209ptp_data.ptp_clock_info);210u64 gp2;211u64 ns;212213mutex_lock(&mvm->mutex);214gp2 = iwl_mvm_get_systime(mvm);215ns = iwl_mvm_ptp_get_adj_time(mvm, gp2 * NSEC_PER_USEC);216mutex_unlock(&mvm->mutex);217218*ts = ns_to_timespec64(ns);219return 0;220}221222static int iwl_mvm_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)223{224struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,225ptp_data.ptp_clock_info);226struct ptp_data *data = container_of(ptp, struct ptp_data,227ptp_clock_info);228229mutex_lock(&mvm->mutex);230data->delta += delta;231IWL_DEBUG_INFO(mvm, "delta=%lld, new delta=%lld\n", (long long)delta,232(long long)data->delta);233mutex_unlock(&mvm->mutex);234return 0;235}236237static int iwl_mvm_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)238{239struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,240ptp_data.ptp_clock_info);241struct ptp_data *data = &mvm->ptp_data;242u32 gp2;243244mutex_lock(&mvm->mutex);245246/* Must call _iwl_mvm_ptp_get_adj_time() before updating247* data->scale_update_gp2 or data->scaled_freq since248* scale_update_adj_time_ns should reflect the previous scaled_freq.249*/250gp2 = iwl_mvm_get_systime(mvm);251data->scale_update_adj_time_ns =252iwl_mvm_ptp_get_adj_time(mvm, gp2 * NSEC_PER_USEC);253data->scale_update_gp2 = gp2;254data->wrap_counter = 0;255data->delta = 0;256257data->scaled_freq = SCALE_FACTOR + scaled_ppm;258IWL_DEBUG_INFO(mvm, "adjfine: scaled_ppm=%ld new=%llu\n",259scaled_ppm, (unsigned long long)data->scaled_freq);260261mutex_unlock(&mvm->mutex);262return 0;263}264265/* iwl_mvm_ptp_init - initialize PTP for devices which support it.266* @mvm: internal mvm structure, see &struct iwl_mvm.267*268* Performs the required steps for enabling PTP support.269*/270void iwl_mvm_ptp_init(struct iwl_mvm *mvm)271{272/* Warn if the interface already has a ptp_clock defined */273if (WARN_ON(mvm->ptp_data.ptp_clock))274return;275276mvm->ptp_data.ptp_clock_info.owner = THIS_MODULE;277mvm->ptp_data.ptp_clock_info.max_adj = 0x7fffffff;278mvm->ptp_data.ptp_clock_info.getcrosststamp =279iwl_mvm_phc_get_crosstimestamp;280mvm->ptp_data.ptp_clock_info.adjfine = iwl_mvm_ptp_adjfine;281mvm->ptp_data.ptp_clock_info.adjtime = iwl_mvm_ptp_adjtime;282mvm->ptp_data.ptp_clock_info.gettime64 = iwl_mvm_ptp_gettime;283mvm->ptp_data.scaled_freq = SCALE_FACTOR;284285/* Give a short 'friendly name' to identify the PHC clock */286snprintf(mvm->ptp_data.ptp_clock_info.name,287sizeof(mvm->ptp_data.ptp_clock_info.name),288"%s", "iwlwifi-PTP");289290INIT_DELAYED_WORK(&mvm->ptp_data.dwork, iwl_mvm_ptp_work);291292mvm->ptp_data.ptp_clock =293ptp_clock_register(&mvm->ptp_data.ptp_clock_info, mvm->dev);294295if (IS_ERR(mvm->ptp_data.ptp_clock)) {296IWL_ERR(mvm, "Failed to register PHC clock (%ld)\n",297PTR_ERR(mvm->ptp_data.ptp_clock));298mvm->ptp_data.ptp_clock = NULL;299} else if (mvm->ptp_data.ptp_clock) {300IWL_DEBUG_INFO(mvm, "Registered PHC clock: %s, with index: %d\n",301mvm->ptp_data.ptp_clock_info.name,302ptp_clock_index(mvm->ptp_data.ptp_clock));303}304}305306/* iwl_mvm_ptp_remove - disable PTP device.307* @mvm: internal mvm structure, see &struct iwl_mvm.308*309* Disable PTP support.310*/311void iwl_mvm_ptp_remove(struct iwl_mvm *mvm)312{313if (mvm->ptp_data.ptp_clock) {314IWL_DEBUG_INFO(mvm, "Unregistering PHC clock: %s, with index: %d\n",315mvm->ptp_data.ptp_clock_info.name,316ptp_clock_index(mvm->ptp_data.ptp_clock));317318ptp_clock_unregister(mvm->ptp_data.ptp_clock);319mvm->ptp_data.ptp_clock = NULL;320memset(&mvm->ptp_data.ptp_clock_info, 0,321sizeof(mvm->ptp_data.ptp_clock_info));322mvm->ptp_data.last_gp2 = 0;323cancel_delayed_work_sync(&mvm->ptp_data.dwork);324}325}326327328