Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/dev/iwlwifi/mvm/ptp.c
48287 views
1
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
/*
3
* Copyright (C) 2021 - 2023, 2025 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
23
static 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
47
u64 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
86
static int
87
iwl_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
127
static 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
149
static int
150
iwl_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
189
out:
190
mutex_unlock(&mvm->mutex);
191
return ret;
192
}
193
194
static 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
206
static 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
223
static 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
238
static 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
*/
271
void 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_DEBUG_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
*/
312
void iwl_mvm_ptp_remove(struct iwl_mvm *mvm)
313
{
314
if (mvm->ptp_data.ptp_clock) {
315
IWL_DEBUG_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
328