Linux Audio

Check our new training course

Yocto / OpenEmbedded training

Mar 24-27, 2025, special US time zones
Register
Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2/*
  3 * Copyright (C) 2021 - 2023 Intel Corporation
  4 */
  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	0x100000000ULL
 12#define IWL_PTP_WRAP_TIME	(3600 * HZ)
 13
 14/* The scaled_ppm parameter is ppm (parts per million) with a 16-bit fractional
 15 * part, which means that a value of 1 in one of those fields actually means
 16 * 2^-16 ppm, and 2^16=65536 is 1 ppm.
 17 */
 18#define SCALE_FACTOR	65536000000ULL
 19#define IWL_PTP_WRAP_THRESHOLD_USEC	(5000)
 20
 21#define IWL_PTP_GET_CROSS_TS_NUM	5
 22
 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 the
 64	 * 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 int
 87iwl_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 int
150iwl_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 updating
248	 * data->scale_update_gp2 or data->scaled_freq since
249	 * 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}