Path: blob/main/sys/contrib/dev/iwlwifi/mld/ptp.c
108392 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2025 Intel Corporation3*/45#include "mld.h"6#include "iwl-debug.h"7#include "hcmd.h"8#include "ptp.h"9#include <linux/timekeeping.h>1011/* The scaled_ppm parameter is ppm (parts per million) with a 16-bit fractional12* part, which means that a value of 1 in one of those fields actually means13* 2^-16 ppm, and 2^16=65536 is 1 ppm.14*/15#define PTP_SCALE_FACTOR 65536000000ULL1617#define IWL_PTP_GP2_WRAP 0x100000000ULL18#define IWL_PTP_WRAP_TIME (3600 * HZ)19#define IWL_PTP_WRAP_THRESHOLD_USEC (5000)2021static int iwl_mld_get_systime(struct iwl_mld *mld, u32 *gp2)22{23*gp2 = iwl_read_prph(mld->trans, mld->trans->mac_cfg->base->gp2_reg_addr);2425if (*gp2 == 0x5a5a5a5a)26return -EINVAL;2728return 0;29}3031static void iwl_mld_ptp_update_new_read(struct iwl_mld *mld, u32 gp2)32{33IWL_DEBUG_PTP(mld, "PTP: last_gp2=%u, new gp2 read=%u\n",34mld->ptp_data.last_gp2, gp2);3536/* If the difference is above the threshold, assume it's a wraparound.37* Otherwise assume it's an old read and ignore it.38*/39if (gp2 < mld->ptp_data.last_gp2) {40if (mld->ptp_data.last_gp2 - gp2 <41IWL_PTP_WRAP_THRESHOLD_USEC) {42IWL_DEBUG_PTP(mld,43"PTP: ignore old read (gp2=%u, last_gp2=%u)\n",44gp2, mld->ptp_data.last_gp2);45return;46}4748mld->ptp_data.wrap_counter++;49IWL_DEBUG_PTP(mld,50"PTP: wraparound detected (new counter=%u)\n",51mld->ptp_data.wrap_counter);52}5354mld->ptp_data.last_gp2 = gp2;55schedule_delayed_work(&mld->ptp_data.dwork, IWL_PTP_WRAP_TIME);56}5758u64 iwl_mld_ptp_get_adj_time(struct iwl_mld *mld, u64 base_time_ns)59{60struct ptp_data *data = &mld->ptp_data;61u64 scale_time_gp2_ns = mld->ptp_data.scale_update_gp2 * NSEC_PER_USEC;62u64 res;63u64 diff;64s64 scaled_diff;6566lockdep_assert_held(&data->lock);6768iwl_mld_ptp_update_new_read(mld,69div64_u64(base_time_ns, NSEC_PER_USEC));7071base_time_ns = base_time_ns +72(data->wrap_counter * IWL_PTP_GP2_WRAP * NSEC_PER_USEC);7374/* It is possible that a GP2 timestamp was received from fw before the75* last scale update.76*/77if (base_time_ns < scale_time_gp2_ns) {78diff = scale_time_gp2_ns - base_time_ns;79scaled_diff = -mul_u64_u64_div_u64(diff,80data->scaled_freq,81PTP_SCALE_FACTOR);82} else {83diff = base_time_ns - scale_time_gp2_ns;84scaled_diff = mul_u64_u64_div_u64(diff,85data->scaled_freq,86PTP_SCALE_FACTOR);87}8889IWL_DEBUG_PTP(mld, "base_time=%llu diff ns=%llu scaled_diff_ns=%lld\n",90(unsigned long long)base_time_ns,91(unsigned long long)diff, (long long)scaled_diff);9293res = data->scale_update_adj_time_ns + data->delta + scaled_diff;9495IWL_DEBUG_PTP(mld, "scale_update_ns=%llu delta=%lld adj=%llu\n",96(unsigned long long)data->scale_update_adj_time_ns,97(long long)data->delta, (unsigned long long)res);98return res;99}100101static int iwl_mld_ptp_gettime(struct ptp_clock_info *ptp,102struct timespec64 *ts)103{104struct iwl_mld *mld = container_of(ptp, struct iwl_mld,105ptp_data.ptp_clock_info);106struct ptp_data *data = &mld->ptp_data;107u32 gp2;108u64 ns;109110if (iwl_mld_get_systime(mld, &gp2)) {111IWL_DEBUG_PTP(mld, "PTP: gettime: failed to read systime\n");112return -EIO;113}114115spin_lock_bh(&data->lock);116ns = iwl_mld_ptp_get_adj_time(mld, (u64)gp2 * NSEC_PER_USEC);117spin_unlock_bh(&data->lock);118119*ts = ns_to_timespec64(ns);120return 0;121}122123static int iwl_mld_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)124{125struct iwl_mld *mld = container_of(ptp, struct iwl_mld,126ptp_data.ptp_clock_info);127struct ptp_data *data = &mld->ptp_data;128129spin_lock_bh(&data->lock);130data->delta += delta;131IWL_DEBUG_PTP(mld, "delta=%lld, new delta=%lld\n", (long long)delta,132(long long)data->delta);133spin_unlock_bh(&data->lock);134return 0;135}136137static int iwl_mld_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)138{139struct iwl_mld *mld = container_of(ptp, struct iwl_mld,140ptp_data.ptp_clock_info);141struct ptp_data *data = &mld->ptp_data;142u32 gp2;143144/* Must call iwl_mld_ptp_get_adj_time() before updating145* data->scale_update_gp2 or data->scaled_freq since146* scale_update_adj_time_ns should reflect the previous scaled_freq.147*/148if (iwl_mld_get_systime(mld, &gp2)) {149IWL_DEBUG_PTP(mld, "adjfine: failed to read systime\n");150return -EBUSY;151}152153spin_lock_bh(&data->lock);154data->scale_update_adj_time_ns =155iwl_mld_ptp_get_adj_time(mld, gp2 * NSEC_PER_USEC);156data->scale_update_gp2 = gp2;157158/* scale_update_adj_time_ns now relects the configured delta, the159* wrap_counter and the previous scaled frequency. Thus delta and160* wrap_counter should be reset, and the scale frequency is updated161* to the new frequency.162*/163data->delta = 0;164data->wrap_counter = 0;165data->scaled_freq = PTP_SCALE_FACTOR + scaled_ppm;166IWL_DEBUG_PTP(mld, "adjfine: scaled_ppm=%ld new=%llu\n",167scaled_ppm, (unsigned long long)data->scaled_freq);168spin_unlock_bh(&data->lock);169return 0;170}171172static void iwl_mld_ptp_work(struct work_struct *wk)173{174struct iwl_mld *mld = container_of(wk, struct iwl_mld,175ptp_data.dwork.work);176struct ptp_data *data = &mld->ptp_data;177u32 gp2;178179spin_lock_bh(&data->lock);180if (!iwl_mld_get_systime(mld, &gp2))181iwl_mld_ptp_update_new_read(mld, gp2);182else183IWL_DEBUG_PTP(mld, "PTP work: failed to read GP2\n");184spin_unlock_bh(&data->lock);185}186187static int188iwl_mld_get_crosstimestamp_fw(struct iwl_mld *mld, u32 *gp2, u64 *sys_time)189{190struct iwl_synced_time_cmd synced_time_cmd = {191.operation = cpu_to_le32(IWL_SYNCED_TIME_OPERATION_READ_BOTH)192};193struct iwl_host_cmd cmd = {194.id = WIDE_ID(DATA_PATH_GROUP, WNM_PLATFORM_PTM_REQUEST_CMD),195.flags = CMD_WANT_SKB,196.data[0] = &synced_time_cmd,197.len[0] = sizeof(synced_time_cmd),198};199struct iwl_synced_time_rsp *resp;200struct iwl_rx_packet *pkt;201int ret;202u64 gp2_10ns;203204wiphy_lock(mld->wiphy);205ret = iwl_mld_send_cmd(mld, &cmd);206wiphy_unlock(mld->wiphy);207if (ret)208return ret;209210pkt = cmd.resp_pkt;211212if (iwl_rx_packet_payload_len(pkt) != sizeof(*resp)) {213IWL_DEBUG_PTP(mld, "PTP: Invalid PTM command response\n");214iwl_free_resp(&cmd);215return -EIO;216}217218resp = (void *)pkt->data;219220gp2_10ns = (u64)le32_to_cpu(resp->gp2_timestamp_hi) << 32 |221le32_to_cpu(resp->gp2_timestamp_lo);222*gp2 = div_u64(gp2_10ns, 100);223224*sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 |225le32_to_cpu(resp->platform_timestamp_lo);226227iwl_free_resp(&cmd);228return ret;229}230231static int232iwl_mld_phc_get_crosstimestamp(struct ptp_clock_info *ptp,233struct system_device_crosststamp *xtstamp)234{235struct iwl_mld *mld = container_of(ptp, struct iwl_mld,236ptp_data.ptp_clock_info);237struct ptp_data *data = &mld->ptp_data;238int ret = 0;239/* Raw value read from GP2 register in usec */240u32 gp2;241/* GP2 value in ns*/242s64 gp2_ns;243/* System (wall) time */244ktime_t sys_time;245246memset(xtstamp, 0, sizeof(struct system_device_crosststamp));247248ret = iwl_mld_get_crosstimestamp_fw(mld, &gp2, &sys_time);249if (ret) {250IWL_DEBUG_PTP(mld,251"PTP: fw get_crosstimestamp failed (ret=%d)\n",252ret);253return ret;254}255256spin_lock_bh(&data->lock);257gp2_ns = iwl_mld_ptp_get_adj_time(mld, (u64)gp2 * NSEC_PER_USEC);258spin_unlock_bh(&data->lock);259260IWL_DEBUG_PTP(mld,261"Got Sync Time: GP2:%u, last_GP2: %u, GP2_ns: %lld, sys_time: %lld\n",262gp2, mld->ptp_data.last_gp2, gp2_ns, (s64)sys_time);263264/* System monotonic raw time is not used */265xtstamp->device = ns_to_ktime(gp2_ns);266xtstamp->sys_realtime = sys_time;267268return ret;269}270271void iwl_mld_ptp_init(struct iwl_mld *mld)272{273if (WARN_ON(mld->ptp_data.ptp_clock))274return;275276spin_lock_init(&mld->ptp_data.lock);277INIT_DELAYED_WORK(&mld->ptp_data.dwork, iwl_mld_ptp_work);278279mld->ptp_data.ptp_clock_info.owner = THIS_MODULE;280mld->ptp_data.ptp_clock_info.gettime64 = iwl_mld_ptp_gettime;281mld->ptp_data.ptp_clock_info.max_adj = 0x7fffffff;282mld->ptp_data.ptp_clock_info.adjtime = iwl_mld_ptp_adjtime;283mld->ptp_data.ptp_clock_info.adjfine = iwl_mld_ptp_adjfine;284mld->ptp_data.scaled_freq = PTP_SCALE_FACTOR;285mld->ptp_data.ptp_clock_info.getcrosststamp =286iwl_mld_phc_get_crosstimestamp;287288/* Give a short 'friendly name' to identify the PHC clock */289snprintf(mld->ptp_data.ptp_clock_info.name,290sizeof(mld->ptp_data.ptp_clock_info.name),291"%s", "iwlwifi-PTP");292293mld->ptp_data.ptp_clock =294ptp_clock_register(&mld->ptp_data.ptp_clock_info, mld->dev);295296if (IS_ERR_OR_NULL(mld->ptp_data.ptp_clock)) {297IWL_ERR(mld, "Failed to register PHC clock (%ld)\n",298PTR_ERR(mld->ptp_data.ptp_clock));299mld->ptp_data.ptp_clock = NULL;300} else {301IWL_DEBUG_INFO(mld, "Registered PHC clock: %s, with index: %d\n",302mld->ptp_data.ptp_clock_info.name,303ptp_clock_index(mld->ptp_data.ptp_clock));304}305}306307void iwl_mld_ptp_remove(struct iwl_mld *mld)308{309if (mld->ptp_data.ptp_clock) {310IWL_DEBUG_INFO(mld, "Unregistering PHC clock: %s, with index: %d\n",311mld->ptp_data.ptp_clock_info.name,312ptp_clock_index(mld->ptp_data.ptp_clock));313314ptp_clock_unregister(mld->ptp_data.ptp_clock);315mld->ptp_data.ptp_clock = NULL;316mld->ptp_data.last_gp2 = 0;317mld->ptp_data.wrap_counter = 0;318cancel_delayed_work_sync(&mld->ptp_data.dwork);319}320}321322323