327 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2/*3 * Copyright (C) 2021 - 2023 Intel Corporation4 */5 6#include "mvm.h"7#include "iwl-debug.h"8#include <linux/timekeeping.h>9#include <linux/math64.h>10 11#define IWL_PTP_GP2_WRAP 0x100000000ULL12#define IWL_PTP_WRAP_TIME (3600 * HZ)13 14/* The scaled_ppm parameter is ppm (parts per million) with a 16-bit fractional15 * part, which means that a value of 1 in one of those fields actually means16 * 2^-16 ppm, and 2^16=65536 is 1 ppm.17 */18#define SCALE_FACTOR 65536000000ULL19#define IWL_PTP_WRAP_THRESHOLD_USEC (5000)20 21#define IWL_PTP_GET_CROSS_TS_NUM 522 23static void iwl_mvm_ptp_update_new_read(struct iwl_mvm *mvm, u32 gp2)24{25 /* If the difference is above the threshold, assume it's a wraparound.26 * Otherwise assume it's an old read and ignore it.27 */28 if (gp2 < mvm->ptp_data.last_gp2 &&29 mvm->ptp_data.last_gp2 - gp2 < IWL_PTP_WRAP_THRESHOLD_USEC) {30 IWL_DEBUG_INFO(mvm,31 "PTP: ignore old read (gp2=%u, last_gp2=%u)\n",32 gp2, mvm->ptp_data.last_gp2);33 return;34 }35 36 if (gp2 < mvm->ptp_data.last_gp2) {37 mvm->ptp_data.wrap_counter++;38 IWL_DEBUG_INFO(mvm,39 "PTP: wraparound detected (new counter=%u)\n",40 mvm->ptp_data.wrap_counter);41 }42 43 mvm->ptp_data.last_gp2 = gp2;44 schedule_delayed_work(&mvm->ptp_data.dwork, IWL_PTP_WRAP_TIME);45}46 47u64 iwl_mvm_ptp_get_adj_time(struct iwl_mvm *mvm, u64 base_time_ns)48{49 struct ptp_data *data = &mvm->ptp_data;50 u64 last_gp2_ns = mvm->ptp_data.scale_update_gp2 * NSEC_PER_USEC;51 u64 res;52 u64 diff;53 54 iwl_mvm_ptp_update_new_read(mvm,55 div64_u64(base_time_ns, NSEC_PER_USEC));56 57 IWL_DEBUG_INFO(mvm, "base_time_ns=%llu, wrap_counter=%u\n",58 (unsigned long long)base_time_ns, data->wrap_counter);59 60 base_time_ns = base_time_ns +61 (data->wrap_counter * IWL_PTP_GP2_WRAP * NSEC_PER_USEC);62 63 /* It is possible that a GP2 timestamp was received from fw before the64 * last scale update. Since we don't know how to scale - ignore it.65 */66 if (base_time_ns < last_gp2_ns) {67 IWL_DEBUG_INFO(mvm, "Time before scale update - ignore\n");68 return 0;69 }70 71 diff = base_time_ns - last_gp2_ns;72 IWL_DEBUG_INFO(mvm, "diff ns=%llu\n", (unsigned long long)diff);73 74 diff = mul_u64_u64_div_u64(diff, data->scaled_freq,75 SCALE_FACTOR);76 IWL_DEBUG_INFO(mvm, "scaled diff ns=%llu\n", (unsigned long long)diff);77 78 res = data->scale_update_adj_time_ns + data->delta + diff;79 80 IWL_DEBUG_INFO(mvm, "base=%llu delta=%lld adj=%llu\n",81 (unsigned long long)base_time_ns, (long long)data->delta,82 (unsigned long long)res);83 return res;84}85 86static int87iwl_mvm_get_crosstimestamp_fw(struct iwl_mvm *mvm, u32 *gp2, u64 *sys_time)88{89 struct iwl_synced_time_cmd synced_time_cmd = {90 .operation = cpu_to_le32(IWL_SYNCED_TIME_OPERATION_READ_BOTH)91 };92 struct iwl_host_cmd cmd = {93 .id = WIDE_ID(DATA_PATH_GROUP, WNM_PLATFORM_PTM_REQUEST_CMD),94 .flags = CMD_WANT_SKB,95 .data[0] = &synced_time_cmd,96 .len[0] = sizeof(synced_time_cmd),97 };98 struct iwl_synced_time_rsp *resp;99 struct iwl_rx_packet *pkt;100 int ret;101 u64 gp2_10ns;102 103 ret = iwl_mvm_send_cmd(mvm, &cmd);104 if (ret)105 return ret;106 107 pkt = cmd.resp_pkt;108 109 if (iwl_rx_packet_payload_len(pkt) != sizeof(*resp)) {110 IWL_ERR(mvm, "PTP: Invalid command response\n");111 iwl_free_resp(&cmd);112 return -EIO;113 }114 115 resp = (void *)pkt->data;116 117 gp2_10ns = (u64)le32_to_cpu(resp->gp2_timestamp_hi) << 32 |118 le32_to_cpu(resp->gp2_timestamp_lo);119 *gp2 = div_u64(gp2_10ns, 100);120 121 *sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 |122 le32_to_cpu(resp->platform_timestamp_lo);123 124 return ret;125}126 127static void iwl_mvm_phc_get_crosstimestamp_loop(struct iwl_mvm *mvm,128 ktime_t *sys_time, u32 *gp2)129{130 u64 diff = 0, new_diff;131 u64 tmp_sys_time;132 u32 tmp_gp2;133 int i;134 135 for (i = 0; i < IWL_PTP_GET_CROSS_TS_NUM; i++) {136 iwl_mvm_get_sync_time(mvm, CLOCK_REALTIME, &tmp_gp2, NULL,137 &tmp_sys_time);138 new_diff = tmp_sys_time - ((u64)tmp_gp2 * NSEC_PER_USEC);139 if (!diff || new_diff < diff) {140 *sys_time = tmp_sys_time;141 *gp2 = tmp_gp2;142 diff = new_diff;143 IWL_DEBUG_INFO(mvm, "PTP: new times: gp2=%u sys=%lld\n",144 *gp2, *sys_time);145 }146 }147}148 149static int150iwl_mvm_phc_get_crosstimestamp(struct ptp_clock_info *ptp,151 struct system_device_crosststamp *xtstamp)152{153 struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,154 ptp_data.ptp_clock_info);155 int ret = 0;156 /* Raw value read from GP2 register in usec */157 u32 gp2;158 /* GP2 value in ns*/159 s64 gp2_ns;160 /* System (wall) time */161 ktime_t sys_time;162 163 memset(xtstamp, 0, sizeof(struct system_device_crosststamp));164 165 if (!mvm->ptp_data.ptp_clock) {166 IWL_ERR(mvm, "No PHC clock registered\n");167 return -ENODEV;168 }169 170 mutex_lock(&mvm->mutex);171 if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SYNCED_TIME)) {172 ret = iwl_mvm_get_crosstimestamp_fw(mvm, &gp2, &sys_time);173 174 if (ret)175 goto out;176 } else {177 iwl_mvm_phc_get_crosstimestamp_loop(mvm, &sys_time, &gp2);178 }179 180 gp2_ns = iwl_mvm_ptp_get_adj_time(mvm, (u64)gp2 * NSEC_PER_USEC);181 182 IWL_INFO(mvm, "Got Sync Time: GP2:%u, last_GP2: %u, GP2_ns: %lld, sys_time: %lld\n",183 gp2, mvm->ptp_data.last_gp2, gp2_ns, (s64)sys_time);184 185 /* System monotonic raw time is not used */186 xtstamp->device = (ktime_t)gp2_ns;187 xtstamp->sys_realtime = sys_time;188 189out:190 mutex_unlock(&mvm->mutex);191 return ret;192}193 194static void iwl_mvm_ptp_work(struct work_struct *wk)195{196 struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm,197 ptp_data.dwork.work);198 u32 gp2;199 200 mutex_lock(&mvm->mutex);201 gp2 = iwl_mvm_get_systime(mvm);202 iwl_mvm_ptp_update_new_read(mvm, gp2);203 mutex_unlock(&mvm->mutex);204}205 206static int iwl_mvm_ptp_gettime(struct ptp_clock_info *ptp,207 struct timespec64 *ts)208{209 struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,210 ptp_data.ptp_clock_info);211 u64 gp2;212 u64 ns;213 214 mutex_lock(&mvm->mutex);215 gp2 = iwl_mvm_get_systime(mvm);216 ns = iwl_mvm_ptp_get_adj_time(mvm, gp2 * NSEC_PER_USEC);217 mutex_unlock(&mvm->mutex);218 219 *ts = ns_to_timespec64(ns);220 return 0;221}222 223static int iwl_mvm_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)224{225 struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,226 ptp_data.ptp_clock_info);227 struct ptp_data *data = container_of(ptp, struct ptp_data,228 ptp_clock_info);229 230 mutex_lock(&mvm->mutex);231 data->delta += delta;232 IWL_DEBUG_INFO(mvm, "delta=%lld, new delta=%lld\n", (long long)delta,233 (long long)data->delta);234 mutex_unlock(&mvm->mutex);235 return 0;236}237 238static int iwl_mvm_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)239{240 struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,241 ptp_data.ptp_clock_info);242 struct ptp_data *data = &mvm->ptp_data;243 u32 gp2;244 245 mutex_lock(&mvm->mutex);246 247 /* Must call _iwl_mvm_ptp_get_adj_time() before updating248 * data->scale_update_gp2 or data->scaled_freq since249 * scale_update_adj_time_ns should reflect the previous scaled_freq.250 */251 gp2 = iwl_mvm_get_systime(mvm);252 data->scale_update_adj_time_ns =253 iwl_mvm_ptp_get_adj_time(mvm, gp2 * NSEC_PER_USEC);254 data->scale_update_gp2 = gp2;255 data->wrap_counter = 0;256 data->delta = 0;257 258 data->scaled_freq = SCALE_FACTOR + scaled_ppm;259 IWL_DEBUG_INFO(mvm, "adjfine: scaled_ppm=%ld new=%llu\n",260 scaled_ppm, (unsigned long long)data->scaled_freq);261 262 mutex_unlock(&mvm->mutex);263 return 0;264}265 266/* iwl_mvm_ptp_init - initialize PTP for devices which support it.267 * @mvm: internal mvm structure, see &struct iwl_mvm.268 *269 * Performs the required steps for enabling PTP support.270 */271void iwl_mvm_ptp_init(struct iwl_mvm *mvm)272{273 /* Warn if the interface already has a ptp_clock defined */274 if (WARN_ON(mvm->ptp_data.ptp_clock))275 return;276 277 mvm->ptp_data.ptp_clock_info.owner = THIS_MODULE;278 mvm->ptp_data.ptp_clock_info.max_adj = 0x7fffffff;279 mvm->ptp_data.ptp_clock_info.getcrosststamp =280 iwl_mvm_phc_get_crosstimestamp;281 mvm->ptp_data.ptp_clock_info.adjfine = iwl_mvm_ptp_adjfine;282 mvm->ptp_data.ptp_clock_info.adjtime = iwl_mvm_ptp_adjtime;283 mvm->ptp_data.ptp_clock_info.gettime64 = iwl_mvm_ptp_gettime;284 mvm->ptp_data.scaled_freq = SCALE_FACTOR;285 286 /* Give a short 'friendly name' to identify the PHC clock */287 snprintf(mvm->ptp_data.ptp_clock_info.name,288 sizeof(mvm->ptp_data.ptp_clock_info.name),289 "%s", "iwlwifi-PTP");290 291 INIT_DELAYED_WORK(&mvm->ptp_data.dwork, iwl_mvm_ptp_work);292 293 mvm->ptp_data.ptp_clock =294 ptp_clock_register(&mvm->ptp_data.ptp_clock_info, mvm->dev);295 296 if (IS_ERR(mvm->ptp_data.ptp_clock)) {297 IWL_ERR(mvm, "Failed to register PHC clock (%ld)\n",298 PTR_ERR(mvm->ptp_data.ptp_clock));299 mvm->ptp_data.ptp_clock = NULL;300 } else if (mvm->ptp_data.ptp_clock) {301 IWL_INFO(mvm, "Registered PHC clock: %s, with index: %d\n",302 mvm->ptp_data.ptp_clock_info.name,303 ptp_clock_index(mvm->ptp_data.ptp_clock));304 }305}306 307/* iwl_mvm_ptp_remove - disable PTP device.308 * @mvm: internal mvm structure, see &struct iwl_mvm.309 *310 * Disable PTP support.311 */312void iwl_mvm_ptp_remove(struct iwl_mvm *mvm)313{314 if (mvm->ptp_data.ptp_clock) {315 IWL_INFO(mvm, "Unregistering PHC clock: %s, with index: %d\n",316 mvm->ptp_data.ptp_clock_info.name,317 ptp_clock_index(mvm->ptp_data.ptp_clock));318 319 ptp_clock_unregister(mvm->ptp_data.ptp_clock);320 mvm->ptp_data.ptp_clock = NULL;321 memset(&mvm->ptp_data.ptp_clock_info, 0,322 sizeof(mvm->ptp_data.ptp_clock_info));323 mvm->ptp_data.last_gp2 = 0;324 cancel_delayed_work_sync(&mvm->ptp_data.dwork);325 }326}327