xref: /openbmc/linux/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c (revision 21fb8da6ebe40ce83468d6198143b4b583b967f9)
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 
10 #define IWL_PTP_GP2_WRAP	0x100000000ULL
11 #define IWL_PTP_WRAP_TIME	(3600 * HZ)
12 
13 static void iwl_mvm_ptp_update_new_read(struct iwl_mvm *mvm, u32 gp2)
14 {
15 	if (gp2 < mvm->ptp_data.last_gp2) {
16 		mvm->ptp_data.wrap_counter++;
17 		IWL_DEBUG_INFO(mvm,
18 			       "PTP: wraparound detected (new counter=%u)\n",
19 			       mvm->ptp_data.wrap_counter);
20 	}
21 
22 	mvm->ptp_data.last_gp2 = gp2;
23 	schedule_delayed_work(&mvm->ptp_data.dwork, IWL_PTP_WRAP_TIME);
24 }
25 
26 static int
27 iwl_mvm_get_crosstimestamp_fw(struct iwl_mvm *mvm, u32 *gp2, u64 *sys_time)
28 {
29 	struct iwl_synced_time_cmd synced_time_cmd = {
30 		.operation = cpu_to_le32(IWL_SYNCED_TIME_OPERATION_READ_BOTH)
31 	};
32 	struct iwl_host_cmd cmd = {
33 		.id = WIDE_ID(DATA_PATH_GROUP, WNM_PLATFORM_PTM_REQUEST_CMD),
34 		.flags = CMD_WANT_SKB,
35 		.data[0] = &synced_time_cmd,
36 		.len[0] = sizeof(synced_time_cmd),
37 	};
38 	struct iwl_synced_time_rsp *resp;
39 	struct iwl_rx_packet *pkt;
40 	int ret;
41 	u64 gp2_10ns;
42 
43 	ret = iwl_mvm_send_cmd(mvm, &cmd);
44 	if (ret)
45 		return ret;
46 
47 	pkt = cmd.resp_pkt;
48 
49 	if (iwl_rx_packet_payload_len(pkt) != sizeof(*resp)) {
50 		IWL_ERR(mvm, "PTP: Invalid command response\n");
51 		iwl_free_resp(&cmd);
52 		return -EIO;
53 	}
54 
55 	resp = (void *)pkt->data;
56 
57 	gp2_10ns = (u64)le32_to_cpu(resp->gp2_timestamp_hi) << 32 |
58 		le32_to_cpu(resp->gp2_timestamp_lo);
59 	*gp2 = gp2_10ns / 100;
60 
61 	*sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 |
62 		le32_to_cpu(resp->platform_timestamp_lo);
63 
64 	return ret;
65 }
66 
67 static int
68 iwl_mvm_phc_get_crosstimestamp(struct ptp_clock_info *ptp,
69 			       struct system_device_crosststamp *xtstamp)
70 {
71 	struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm,
72 					   ptp_data.ptp_clock_info);
73 	int ret = 0;
74 	/* Raw value read from GP2 register in usec */
75 	u32 gp2;
76 	/* GP2 value in ns*/
77 	s64 gp2_ns;
78 	/* System (wall) time */
79 	ktime_t sys_time;
80 
81 	memset(xtstamp, 0, sizeof(struct system_device_crosststamp));
82 
83 	if (!mvm->ptp_data.ptp_clock) {
84 		IWL_ERR(mvm, "No PHC clock registered\n");
85 		return -ENODEV;
86 	}
87 
88 	mutex_lock(&mvm->mutex);
89 	if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SYNCED_TIME)) {
90 		ret = iwl_mvm_get_crosstimestamp_fw(mvm, &gp2, &sys_time);
91 
92 		if (ret)
93 			goto out;
94 	} else {
95 		iwl_mvm_get_sync_time(mvm, CLOCK_REALTIME, &gp2, NULL,
96 				      &sys_time);
97 	}
98 
99 	iwl_mvm_ptp_update_new_read(mvm, gp2);
100 
101 	gp2_ns = (gp2 + (mvm->ptp_data.wrap_counter * IWL_PTP_GP2_WRAP)) *
102 		NSEC_PER_USEC;
103 
104 	IWL_INFO(mvm, "Got Sync Time: GP2:%u, last_GP2: %u, GP2_ns: %lld, sys_time: %lld\n",
105 		 gp2, mvm->ptp_data.last_gp2, gp2_ns, (s64)sys_time);
106 
107 	/* System monotonic raw time is not used */
108 	xtstamp->device = (ktime_t)gp2_ns;
109 	xtstamp->sys_realtime = sys_time;
110 
111 out:
112 	mutex_unlock(&mvm->mutex);
113 	return ret;
114 }
115 
116 static void iwl_mvm_ptp_work(struct work_struct *wk)
117 {
118 	struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm,
119 					   ptp_data.dwork.work);
120 	u32 gp2;
121 
122 	mutex_lock(&mvm->mutex);
123 	gp2 = iwl_mvm_get_systime(mvm);
124 	iwl_mvm_ptp_update_new_read(mvm, gp2);
125 	mutex_unlock(&mvm->mutex);
126 }
127 
128 /* iwl_mvm_ptp_init - initialize PTP for devices which support it.
129  * @mvm: internal mvm structure, see &struct iwl_mvm.
130  *
131  * Performs the required steps for enabling PTP support.
132  */
133 void iwl_mvm_ptp_init(struct iwl_mvm *mvm)
134 {
135 	/* Warn if the interface already has a ptp_clock defined */
136 	if (WARN_ON(mvm->ptp_data.ptp_clock))
137 		return;
138 
139 	mvm->ptp_data.ptp_clock_info.owner = THIS_MODULE;
140 	mvm->ptp_data.ptp_clock_info.max_adj = 0x7fffffff;
141 	mvm->ptp_data.ptp_clock_info.getcrosststamp =
142 					iwl_mvm_phc_get_crosstimestamp;
143 
144 	/* Give a short 'friendly name' to identify the PHC clock */
145 	snprintf(mvm->ptp_data.ptp_clock_info.name,
146 		 sizeof(mvm->ptp_data.ptp_clock_info.name),
147 		 "%s", "iwlwifi-PTP");
148 
149 	INIT_DELAYED_WORK(&mvm->ptp_data.dwork, iwl_mvm_ptp_work);
150 
151 	mvm->ptp_data.ptp_clock =
152 		ptp_clock_register(&mvm->ptp_data.ptp_clock_info, mvm->dev);
153 
154 	if (IS_ERR(mvm->ptp_data.ptp_clock)) {
155 		IWL_ERR(mvm, "Failed to register PHC clock (%ld)\n",
156 			PTR_ERR(mvm->ptp_data.ptp_clock));
157 		mvm->ptp_data.ptp_clock = NULL;
158 	} else if (mvm->ptp_data.ptp_clock) {
159 		IWL_INFO(mvm, "Registered PHC clock: %s, with index: %d\n",
160 			 mvm->ptp_data.ptp_clock_info.name,
161 			 ptp_clock_index(mvm->ptp_data.ptp_clock));
162 	}
163 }
164 
165 /* iwl_mvm_ptp_remove - disable PTP device.
166  * @mvm: internal mvm structure, see &struct iwl_mvm.
167  *
168  * Disable PTP support.
169  */
170 void iwl_mvm_ptp_remove(struct iwl_mvm *mvm)
171 {
172 	if (mvm->ptp_data.ptp_clock) {
173 		IWL_INFO(mvm, "Unregistering PHC clock: %s, with index: %d\n",
174 			 mvm->ptp_data.ptp_clock_info.name,
175 			 ptp_clock_index(mvm->ptp_data.ptp_clock));
176 
177 		ptp_clock_unregister(mvm->ptp_data.ptp_clock);
178 		mvm->ptp_data.ptp_clock = NULL;
179 		memset(&mvm->ptp_data.ptp_clock_info, 0,
180 		       sizeof(mvm->ptp_data.ptp_clock_info));
181 		mvm->ptp_data.last_gp2 = 0;
182 		cancel_delayed_work_sync(&mvm->ptp_data.dwork);
183 	}
184 }
185