Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/dev/iwlwifi/mld/ptp.c
48286 views
1
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
/*
3
* Copyright (C) 2025 Intel Corporation
4
*/
5
6
#include "mld.h"
7
#include "iwl-debug.h"
8
#include "hcmd.h"
9
#include "ptp.h"
10
#include <linux/timekeeping.h>
11
12
/* The scaled_ppm parameter is ppm (parts per million) with a 16-bit fractional
13
* part, which means that a value of 1 in one of those fields actually means
14
* 2^-16 ppm, and 2^16=65536 is 1 ppm.
15
*/
16
#define PTP_SCALE_FACTOR 65536000000ULL
17
18
#define IWL_PTP_GP2_WRAP 0x100000000ULL
19
#define IWL_PTP_WRAP_TIME (3600 * HZ)
20
#define IWL_PTP_WRAP_THRESHOLD_USEC (5000)
21
22
static int iwl_mld_get_systime(struct iwl_mld *mld, u32 *gp2)
23
{
24
*gp2 = iwl_read_prph(mld->trans, mld->trans->mac_cfg->base->gp2_reg_addr);
25
26
if (*gp2 == 0x5a5a5a5a)
27
return -EINVAL;
28
29
return 0;
30
}
31
32
static void iwl_mld_ptp_update_new_read(struct iwl_mld *mld, u32 gp2)
33
{
34
IWL_DEBUG_PTP(mld, "PTP: last_gp2=%u, new gp2 read=%u\n",
35
mld->ptp_data.last_gp2, gp2);
36
37
/* If the difference is above the threshold, assume it's a wraparound.
38
* Otherwise assume it's an old read and ignore it.
39
*/
40
if (gp2 < mld->ptp_data.last_gp2) {
41
if (mld->ptp_data.last_gp2 - gp2 <
42
IWL_PTP_WRAP_THRESHOLD_USEC) {
43
IWL_DEBUG_PTP(mld,
44
"PTP: ignore old read (gp2=%u, last_gp2=%u)\n",
45
gp2, mld->ptp_data.last_gp2);
46
return;
47
}
48
49
mld->ptp_data.wrap_counter++;
50
IWL_DEBUG_PTP(mld,
51
"PTP: wraparound detected (new counter=%u)\n",
52
mld->ptp_data.wrap_counter);
53
}
54
55
mld->ptp_data.last_gp2 = gp2;
56
schedule_delayed_work(&mld->ptp_data.dwork, IWL_PTP_WRAP_TIME);
57
}
58
59
u64 iwl_mld_ptp_get_adj_time(struct iwl_mld *mld, u64 base_time_ns)
60
{
61
struct ptp_data *data = &mld->ptp_data;
62
u64 scale_time_gp2_ns = mld->ptp_data.scale_update_gp2 * NSEC_PER_USEC;
63
u64 res;
64
u64 diff;
65
s64 scaled_diff;
66
67
lockdep_assert_held(&data->lock);
68
69
iwl_mld_ptp_update_new_read(mld,
70
div64_u64(base_time_ns, NSEC_PER_USEC));
71
72
base_time_ns = base_time_ns +
73
(data->wrap_counter * IWL_PTP_GP2_WRAP * NSEC_PER_USEC);
74
75
/* It is possible that a GP2 timestamp was received from fw before the
76
* last scale update.
77
*/
78
if (base_time_ns < scale_time_gp2_ns) {
79
diff = scale_time_gp2_ns - base_time_ns;
80
scaled_diff = -mul_u64_u64_div_u64(diff,
81
data->scaled_freq,
82
PTP_SCALE_FACTOR);
83
} else {
84
diff = base_time_ns - scale_time_gp2_ns;
85
scaled_diff = mul_u64_u64_div_u64(diff,
86
data->scaled_freq,
87
PTP_SCALE_FACTOR);
88
}
89
90
IWL_DEBUG_PTP(mld, "base_time=%llu diff ns=%llu scaled_diff_ns=%lld\n",
91
(unsigned long long)base_time_ns,
92
(unsigned long long)diff, (long long)scaled_diff);
93
94
res = data->scale_update_adj_time_ns + data->delta + scaled_diff;
95
96
IWL_DEBUG_PTP(mld, "scale_update_ns=%llu delta=%lld adj=%llu\n",
97
(unsigned long long)data->scale_update_adj_time_ns,
98
(long long)data->delta, (unsigned long long)res);
99
return res;
100
}
101
102
static int iwl_mld_ptp_gettime(struct ptp_clock_info *ptp,
103
struct timespec64 *ts)
104
{
105
struct iwl_mld *mld = container_of(ptp, struct iwl_mld,
106
ptp_data.ptp_clock_info);
107
struct ptp_data *data = &mld->ptp_data;
108
u32 gp2;
109
u64 ns;
110
111
if (iwl_mld_get_systime(mld, &gp2)) {
112
IWL_DEBUG_PTP(mld, "PTP: gettime: failed to read systime\n");
113
return -EIO;
114
}
115
116
spin_lock_bh(&data->lock);
117
ns = iwl_mld_ptp_get_adj_time(mld, (u64)gp2 * NSEC_PER_USEC);
118
spin_unlock_bh(&data->lock);
119
120
*ts = ns_to_timespec64(ns);
121
return 0;
122
}
123
124
static int iwl_mld_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
125
{
126
struct iwl_mld *mld = container_of(ptp, struct iwl_mld,
127
ptp_data.ptp_clock_info);
128
struct ptp_data *data = &mld->ptp_data;
129
130
spin_lock_bh(&data->lock);
131
data->delta += delta;
132
IWL_DEBUG_PTP(mld, "delta=%lld, new delta=%lld\n", (long long)delta,
133
(long long)data->delta);
134
spin_unlock_bh(&data->lock);
135
return 0;
136
}
137
138
static int iwl_mld_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
139
{
140
struct iwl_mld *mld = container_of(ptp, struct iwl_mld,
141
ptp_data.ptp_clock_info);
142
struct ptp_data *data = &mld->ptp_data;
143
u32 gp2;
144
145
/* Must call iwl_mld_ptp_get_adj_time() before updating
146
* data->scale_update_gp2 or data->scaled_freq since
147
* scale_update_adj_time_ns should reflect the previous scaled_freq.
148
*/
149
if (iwl_mld_get_systime(mld, &gp2)) {
150
IWL_DEBUG_PTP(mld, "adjfine: failed to read systime\n");
151
return -EBUSY;
152
}
153
154
spin_lock_bh(&data->lock);
155
data->scale_update_adj_time_ns =
156
iwl_mld_ptp_get_adj_time(mld, gp2 * NSEC_PER_USEC);
157
data->scale_update_gp2 = gp2;
158
159
/* scale_update_adj_time_ns now relects the configured delta, the
160
* wrap_counter and the previous scaled frequency. Thus delta and
161
* wrap_counter should be reset, and the scale frequency is updated
162
* to the new frequency.
163
*/
164
data->delta = 0;
165
data->wrap_counter = 0;
166
data->scaled_freq = PTP_SCALE_FACTOR + scaled_ppm;
167
IWL_DEBUG_PTP(mld, "adjfine: scaled_ppm=%ld new=%llu\n",
168
scaled_ppm, (unsigned long long)data->scaled_freq);
169
spin_unlock_bh(&data->lock);
170
return 0;
171
}
172
173
static void iwl_mld_ptp_work(struct work_struct *wk)
174
{
175
struct iwl_mld *mld = container_of(wk, struct iwl_mld,
176
ptp_data.dwork.work);
177
struct ptp_data *data = &mld->ptp_data;
178
u32 gp2;
179
180
spin_lock_bh(&data->lock);
181
if (!iwl_mld_get_systime(mld, &gp2))
182
iwl_mld_ptp_update_new_read(mld, gp2);
183
else
184
IWL_DEBUG_PTP(mld, "PTP work: failed to read GP2\n");
185
spin_unlock_bh(&data->lock);
186
}
187
188
static int
189
iwl_mld_get_crosstimestamp_fw(struct iwl_mld *mld, u32 *gp2, u64 *sys_time)
190
{
191
struct iwl_synced_time_cmd synced_time_cmd = {
192
.operation = cpu_to_le32(IWL_SYNCED_TIME_OPERATION_READ_BOTH)
193
};
194
struct iwl_host_cmd cmd = {
195
.id = WIDE_ID(DATA_PATH_GROUP, WNM_PLATFORM_PTM_REQUEST_CMD),
196
.flags = CMD_WANT_SKB,
197
.data[0] = &synced_time_cmd,
198
.len[0] = sizeof(synced_time_cmd),
199
};
200
struct iwl_synced_time_rsp *resp;
201
struct iwl_rx_packet *pkt;
202
int ret;
203
u64 gp2_10ns;
204
205
wiphy_lock(mld->wiphy);
206
ret = iwl_mld_send_cmd(mld, &cmd);
207
wiphy_unlock(mld->wiphy);
208
if (ret)
209
return ret;
210
211
pkt = cmd.resp_pkt;
212
213
if (iwl_rx_packet_payload_len(pkt) != sizeof(*resp)) {
214
IWL_DEBUG_PTP(mld, "PTP: Invalid PTM command response\n");
215
iwl_free_resp(&cmd);
216
return -EIO;
217
}
218
219
resp = (void *)pkt->data;
220
221
gp2_10ns = (u64)le32_to_cpu(resp->gp2_timestamp_hi) << 32 |
222
le32_to_cpu(resp->gp2_timestamp_lo);
223
*gp2 = div_u64(gp2_10ns, 100);
224
225
*sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 |
226
le32_to_cpu(resp->platform_timestamp_lo);
227
228
iwl_free_resp(&cmd);
229
return ret;
230
}
231
232
static int
233
iwl_mld_phc_get_crosstimestamp(struct ptp_clock_info *ptp,
234
struct system_device_crosststamp *xtstamp)
235
{
236
struct iwl_mld *mld = container_of(ptp, struct iwl_mld,
237
ptp_data.ptp_clock_info);
238
struct ptp_data *data = &mld->ptp_data;
239
int ret = 0;
240
/* Raw value read from GP2 register in usec */
241
u32 gp2;
242
/* GP2 value in ns*/
243
s64 gp2_ns;
244
/* System (wall) time */
245
ktime_t sys_time;
246
247
memset(xtstamp, 0, sizeof(struct system_device_crosststamp));
248
249
ret = iwl_mld_get_crosstimestamp_fw(mld, &gp2, &sys_time);
250
if (ret) {
251
IWL_DEBUG_PTP(mld,
252
"PTP: fw get_crosstimestamp failed (ret=%d)\n",
253
ret);
254
return ret;
255
}
256
257
spin_lock_bh(&data->lock);
258
gp2_ns = iwl_mld_ptp_get_adj_time(mld, (u64)gp2 * NSEC_PER_USEC);
259
spin_unlock_bh(&data->lock);
260
261
IWL_DEBUG_PTP(mld,
262
"Got Sync Time: GP2:%u, last_GP2: %u, GP2_ns: %lld, sys_time: %lld\n",
263
gp2, mld->ptp_data.last_gp2, gp2_ns, (s64)sys_time);
264
265
/* System monotonic raw time is not used */
266
xtstamp->device = ns_to_ktime(gp2_ns);
267
xtstamp->sys_realtime = sys_time;
268
269
return ret;
270
}
271
272
void iwl_mld_ptp_init(struct iwl_mld *mld)
273
{
274
if (WARN_ON(mld->ptp_data.ptp_clock))
275
return;
276
277
spin_lock_init(&mld->ptp_data.lock);
278
INIT_DELAYED_WORK(&mld->ptp_data.dwork, iwl_mld_ptp_work);
279
280
mld->ptp_data.ptp_clock_info.owner = THIS_MODULE;
281
mld->ptp_data.ptp_clock_info.gettime64 = iwl_mld_ptp_gettime;
282
mld->ptp_data.ptp_clock_info.max_adj = 0x7fffffff;
283
mld->ptp_data.ptp_clock_info.adjtime = iwl_mld_ptp_adjtime;
284
mld->ptp_data.ptp_clock_info.adjfine = iwl_mld_ptp_adjfine;
285
mld->ptp_data.scaled_freq = PTP_SCALE_FACTOR;
286
mld->ptp_data.ptp_clock_info.getcrosststamp =
287
iwl_mld_phc_get_crosstimestamp;
288
289
/* Give a short 'friendly name' to identify the PHC clock */
290
snprintf(mld->ptp_data.ptp_clock_info.name,
291
sizeof(mld->ptp_data.ptp_clock_info.name),
292
"%s", "iwlwifi-PTP");
293
294
mld->ptp_data.ptp_clock =
295
ptp_clock_register(&mld->ptp_data.ptp_clock_info, mld->dev);
296
297
if (IS_ERR_OR_NULL(mld->ptp_data.ptp_clock)) {
298
IWL_ERR(mld, "Failed to register PHC clock (%ld)\n",
299
PTR_ERR(mld->ptp_data.ptp_clock));
300
mld->ptp_data.ptp_clock = NULL;
301
} else {
302
IWL_DEBUG_INFO(mld, "Registered PHC clock: %s, with index: %d\n",
303
mld->ptp_data.ptp_clock_info.name,
304
ptp_clock_index(mld->ptp_data.ptp_clock));
305
}
306
}
307
308
void iwl_mld_ptp_remove(struct iwl_mld *mld)
309
{
310
if (mld->ptp_data.ptp_clock) {
311
IWL_DEBUG_INFO(mld, "Unregistering PHC clock: %s, with index: %d\n",
312
mld->ptp_data.ptp_clock_info.name,
313
ptp_clock_index(mld->ptp_data.ptp_clock));
314
315
ptp_clock_unregister(mld->ptp_data.ptp_clock);
316
mld->ptp_data.ptp_clock = NULL;
317
mld->ptp_data.last_gp2 = 0;
318
mld->ptp_data.wrap_counter = 0;
319
cancel_delayed_work_sync(&mld->ptp_data.dwork);
320
}
321
}
322
323