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