| 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* |
| 3 | * Expose virtio_rtc clocks as PTP clocks. |
| 4 | * |
| 5 | * Copyright (C) 2022-2023 OpenSynergy GmbH |
| 6 | * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. |
| 7 | * |
| 8 | * Derived from ptp_kvm_common.c, virtual PTP 1588 clock for use with KVM |
| 9 | * guests. |
| 10 | * |
| 11 | * Copyright (C) 2017 Red Hat Inc. |
| 12 | */ |
| 13 | |
| 14 | #include <linux/device.h> |
| 15 | #include <linux/err.h> |
| 16 | #include <linux/ptp_clock_kernel.h> |
| 17 | |
| 18 | #include <uapi/linux/virtio_rtc.h> |
| 19 | |
| 20 | #include "virtio_rtc_internal.h" |
| 21 | |
| 22 | /** |
| 23 | * struct viortc_ptp_clock - PTP clock abstraction |
| 24 | * @ptp_clock: PTP clock handle for unregistering |
| 25 | * @viortc: virtio_rtc device data |
| 26 | * @ptp_info: PTP clock description |
| 27 | * @vio_clk_id: virtio_rtc clock id |
| 28 | * @have_cross: device supports crosststamp with available HW counter |
| 29 | */ |
| 30 | struct viortc_ptp_clock { |
| 31 | struct ptp_clock *ptp_clock; |
| 32 | struct viortc_dev *viortc; |
| 33 | struct ptp_clock_info ptp_info; |
| 34 | u16 vio_clk_id; |
| 35 | bool have_cross; |
| 36 | }; |
| 37 | |
| 38 | /** |
| 39 | * struct viortc_ptp_cross_ctx - context for get_device_system_crosststamp() |
| 40 | * @device_time: device clock reading |
| 41 | * @system_counterval: HW counter value at device_time |
| 42 | * |
| 43 | * Provides the already obtained crosststamp to get_device_system_crosststamp(). |
| 44 | */ |
| 45 | struct viortc_ptp_cross_ctx { |
| 46 | ktime_t device_time; |
| 47 | struct system_counterval_t system_counterval; |
| 48 | }; |
| 49 | |
| 50 | /* Weak function in case get_device_system_crosststamp() is not supported */ |
| 51 | int __weak viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id) |
| 52 | { |
| 53 | return -EOPNOTSUPP; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * viortc_ptp_get_time_fn() - callback for get_device_system_crosststamp() |
| 58 | * @device_time: device clock reading |
| 59 | * @system_counterval: HW counter value at device_time |
| 60 | * @ctx: context with already obtained crosststamp |
| 61 | * |
| 62 | * Return: zero (success). |
| 63 | */ |
| 64 | static int viortc_ptp_get_time_fn(ktime_t *device_time, |
| 65 | struct system_counterval_t *system_counterval, |
| 66 | void *ctx) |
| 67 | { |
| 68 | struct viortc_ptp_cross_ctx *vio_ctx = ctx; |
| 69 | |
| 70 | *device_time = vio_ctx->device_time; |
| 71 | *system_counterval = vio_ctx->system_counterval; |
| 72 | |
| 73 | return 0; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * viortc_ptp_do_xtstamp() - get crosststamp from device |
| 78 | * @vio_ptp: virtio_rtc PTP clock |
| 79 | * @hw_counter: virtio_rtc HW counter type |
| 80 | * @cs_id: clocksource id corresponding to hw_counter |
| 81 | * @ctx: context for get_device_system_crosststamp() |
| 82 | * |
| 83 | * Reads HW-specific crosststamp from device. |
| 84 | * |
| 85 | * Context: Process context. |
| 86 | * Return: Zero on success, negative error code otherwise. |
| 87 | */ |
| 88 | static int viortc_ptp_do_xtstamp(struct viortc_ptp_clock *vio_ptp, |
| 89 | u8 hw_counter, enum clocksource_ids cs_id, |
| 90 | struct viortc_ptp_cross_ctx *ctx) |
| 91 | { |
| 92 | u64 max_ns, ns; |
| 93 | int ret; |
| 94 | |
| 95 | ctx->system_counterval.cs_id = cs_id; |
| 96 | |
| 97 | ret = viortc_read_cross(viortc: vio_ptp->viortc, vio_clk_id: vio_ptp->vio_clk_id, |
| 98 | hw_counter, reading: &ns, |
| 99 | cycles: &ctx->system_counterval.cycles); |
| 100 | if (ret) |
| 101 | return ret; |
| 102 | |
| 103 | max_ns = (u64)ktime_to_ns(KTIME_MAX); |
| 104 | if (ns > max_ns) |
| 105 | return -EINVAL; |
| 106 | |
| 107 | ctx->device_time = ns_to_ktime(ns); |
| 108 | |
| 109 | return 0; |
| 110 | } |
| 111 | |
| 112 | /* |
| 113 | * PTP clock operations |
| 114 | */ |
| 115 | |
| 116 | /** |
| 117 | * viortc_ptp_getcrosststamp() - PTP clock getcrosststamp op |
| 118 | * @ptp: PTP clock info |
| 119 | * @xtstamp: crosststamp |
| 120 | * |
| 121 | * Context: Process context. |
| 122 | * Return: Zero on success, negative error code otherwise. |
| 123 | */ |
| 124 | static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp, |
| 125 | struct system_device_crosststamp *xtstamp) |
| 126 | { |
| 127 | struct viortc_ptp_clock *vio_ptp = |
| 128 | container_of(ptp, struct viortc_ptp_clock, ptp_info); |
| 129 | struct system_time_snapshot history_begin; |
| 130 | struct viortc_ptp_cross_ctx ctx; |
| 131 | enum clocksource_ids cs_id; |
| 132 | u8 hw_counter; |
| 133 | int ret; |
| 134 | |
| 135 | if (!vio_ptp->have_cross) |
| 136 | return -EOPNOTSUPP; |
| 137 | |
| 138 | ret = viortc_hw_xtstamp_params(hw_counter: &hw_counter, cs_id: &cs_id); |
| 139 | if (ret) |
| 140 | return ret; |
| 141 | |
| 142 | ktime_get_snapshot(systime_snapshot: &history_begin); |
| 143 | if (history_begin.cs_id != cs_id) |
| 144 | return -EOPNOTSUPP; |
| 145 | |
| 146 | /* |
| 147 | * Getting the timestamp can take many milliseconds with a slow Virtio |
| 148 | * device. This is too long for viortc_ptp_get_time_fn() passed to |
| 149 | * get_device_system_crosststamp(), which has to usually return before |
| 150 | * the timekeeper seqcount increases (every tick or so). |
| 151 | * |
| 152 | * So, get the actual cross-timestamp first. |
| 153 | */ |
| 154 | ret = viortc_ptp_do_xtstamp(vio_ptp, hw_counter, cs_id, ctx: &ctx); |
| 155 | if (ret) |
| 156 | return ret; |
| 157 | |
| 158 | ret = get_device_system_crosststamp(get_time_fn: viortc_ptp_get_time_fn, ctx: &ctx, |
| 159 | history: &history_begin, xtstamp); |
| 160 | if (ret) |
| 161 | pr_debug("%s: get_device_system_crosststamp() returned %d\n" , |
| 162 | __func__, ret); |
| 163 | |
| 164 | return ret; |
| 165 | } |
| 166 | |
| 167 | /* viortc_ptp_adjfine() - unsupported PTP clock adjfine op */ |
| 168 | static int viortc_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) |
| 169 | { |
| 170 | return -EOPNOTSUPP; |
| 171 | } |
| 172 | |
| 173 | /* viortc_ptp_adjtime() - unsupported PTP clock adjtime op */ |
| 174 | static int viortc_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) |
| 175 | { |
| 176 | return -EOPNOTSUPP; |
| 177 | } |
| 178 | |
| 179 | /* viortc_ptp_settime64() - unsupported PTP clock settime64 op */ |
| 180 | static int viortc_ptp_settime64(struct ptp_clock_info *ptp, |
| 181 | const struct timespec64 *ts) |
| 182 | { |
| 183 | return -EOPNOTSUPP; |
| 184 | } |
| 185 | |
| 186 | /* |
| 187 | * viortc_ptp_gettimex64() - PTP clock gettimex64 op |
| 188 | * |
| 189 | * Context: Process context. |
| 190 | */ |
| 191 | static int viortc_ptp_gettimex64(struct ptp_clock_info *ptp, |
| 192 | struct timespec64 *ts, |
| 193 | struct ptp_system_timestamp *sts) |
| 194 | { |
| 195 | struct viortc_ptp_clock *vio_ptp = |
| 196 | container_of(ptp, struct viortc_ptp_clock, ptp_info); |
| 197 | int ret; |
| 198 | u64 ns; |
| 199 | |
| 200 | ptp_read_system_prets(sts); |
| 201 | ret = viortc_read(viortc: vio_ptp->viortc, vio_clk_id: vio_ptp->vio_clk_id, reading: &ns); |
| 202 | ptp_read_system_postts(sts); |
| 203 | |
| 204 | if (ret) |
| 205 | return ret; |
| 206 | |
| 207 | if (ns > (u64)S64_MAX) |
| 208 | return -EINVAL; |
| 209 | |
| 210 | *ts = ns_to_timespec64(nsec: (s64)ns); |
| 211 | |
| 212 | return 0; |
| 213 | } |
| 214 | |
| 215 | /* viortc_ptp_enable() - unsupported PTP clock enable op */ |
| 216 | static int viortc_ptp_enable(struct ptp_clock_info *ptp, |
| 217 | struct ptp_clock_request *rq, int on) |
| 218 | { |
| 219 | return -EOPNOTSUPP; |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | * viortc_ptp_info_template - ptp_clock_info template |
| 224 | * |
| 225 | * The .name member will be set for individual virtio_rtc PTP clocks. |
| 226 | * |
| 227 | * The .getcrosststamp member will be cleared for PTP clocks not supporting |
| 228 | * crosststamp. |
| 229 | */ |
| 230 | static const struct ptp_clock_info viortc_ptp_info_template = { |
| 231 | .owner = THIS_MODULE, |
| 232 | /* .name is set according to clock type */ |
| 233 | .adjfine = viortc_ptp_adjfine, |
| 234 | .adjtime = viortc_ptp_adjtime, |
| 235 | .gettimex64 = viortc_ptp_gettimex64, |
| 236 | .settime64 = viortc_ptp_settime64, |
| 237 | .enable = viortc_ptp_enable, |
| 238 | .getcrosststamp = viortc_ptp_getcrosststamp, |
| 239 | }; |
| 240 | |
| 241 | /** |
| 242 | * viortc_ptp_unregister() - PTP clock unregistering wrapper |
| 243 | * @vio_ptp: virtio_rtc PTP clock |
| 244 | * @parent_dev: parent device of PTP clock |
| 245 | * |
| 246 | * Return: Zero on success, negative error code otherwise. |
| 247 | */ |
| 248 | int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp, |
| 249 | struct device *parent_dev) |
| 250 | { |
| 251 | int ret = ptp_clock_unregister(ptp: vio_ptp->ptp_clock); |
| 252 | |
| 253 | if (!ret) |
| 254 | devm_kfree(dev: parent_dev, p: vio_ptp); |
| 255 | |
| 256 | return ret; |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * viortc_ptp_get_cross_cap() - get xtstamp support info from device |
| 261 | * @viortc: virtio_rtc device data |
| 262 | * @vio_ptp: virtio_rtc PTP clock abstraction |
| 263 | * |
| 264 | * Context: Process context. |
| 265 | * Return: Zero on success, negative error code otherwise. |
| 266 | */ |
| 267 | static int viortc_ptp_get_cross_cap(struct viortc_dev *viortc, |
| 268 | struct viortc_ptp_clock *vio_ptp) |
| 269 | { |
| 270 | enum clocksource_ids cs_id; |
| 271 | bool xtstamp_supported; |
| 272 | u8 hw_counter; |
| 273 | int ret; |
| 274 | |
| 275 | ret = viortc_hw_xtstamp_params(hw_counter: &hw_counter, cs_id: &cs_id); |
| 276 | if (ret) { |
| 277 | vio_ptp->have_cross = false; |
| 278 | return 0; |
| 279 | } |
| 280 | |
| 281 | ret = viortc_cross_cap(viortc, vio_clk_id: vio_ptp->vio_clk_id, hw_counter, |
| 282 | supported: &xtstamp_supported); |
| 283 | if (ret) |
| 284 | return ret; |
| 285 | |
| 286 | vio_ptp->have_cross = xtstamp_supported; |
| 287 | |
| 288 | return 0; |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * viortc_ptp_register() - prepare and register PTP clock |
| 293 | * @viortc: virtio_rtc device data |
| 294 | * @parent_dev: parent device for PTP clock |
| 295 | * @vio_clk_id: id of virtio_rtc clock which backs PTP clock |
| 296 | * @ptp_clock_name: PTP clock name |
| 297 | * |
| 298 | * Context: Process context. |
| 299 | * Return: Pointer on success, ERR_PTR() otherwise; NULL if PTP clock support |
| 300 | * not available. |
| 301 | */ |
| 302 | struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc, |
| 303 | struct device *parent_dev, |
| 304 | u16 vio_clk_id, |
| 305 | const char *ptp_clock_name) |
| 306 | { |
| 307 | struct viortc_ptp_clock *vio_ptp; |
| 308 | struct ptp_clock *ptp_clock; |
| 309 | ssize_t len; |
| 310 | int ret; |
| 311 | |
| 312 | vio_ptp = devm_kzalloc(dev: parent_dev, size: sizeof(*vio_ptp), GFP_KERNEL); |
| 313 | if (!vio_ptp) |
| 314 | return ERR_PTR(error: -ENOMEM); |
| 315 | |
| 316 | vio_ptp->viortc = viortc; |
| 317 | vio_ptp->vio_clk_id = vio_clk_id; |
| 318 | vio_ptp->ptp_info = viortc_ptp_info_template; |
| 319 | len = strscpy(vio_ptp->ptp_info.name, ptp_clock_name, |
| 320 | sizeof(vio_ptp->ptp_info.name)); |
| 321 | if (len < 0) { |
| 322 | ret = len; |
| 323 | goto err_free_dev; |
| 324 | } |
| 325 | |
| 326 | ret = viortc_ptp_get_cross_cap(viortc, vio_ptp); |
| 327 | if (ret) |
| 328 | goto err_free_dev; |
| 329 | |
| 330 | if (!vio_ptp->have_cross) |
| 331 | vio_ptp->ptp_info.getcrosststamp = NULL; |
| 332 | |
| 333 | ptp_clock = ptp_clock_register(info: &vio_ptp->ptp_info, parent: parent_dev); |
| 334 | if (IS_ERR(ptr: ptp_clock)) |
| 335 | goto err_on_register; |
| 336 | |
| 337 | vio_ptp->ptp_clock = ptp_clock; |
| 338 | |
| 339 | return vio_ptp; |
| 340 | |
| 341 | err_on_register: |
| 342 | ret = PTR_ERR(ptr: ptp_clock); |
| 343 | |
| 344 | err_free_dev: |
| 345 | devm_kfree(dev: parent_dev, p: vio_ptp); |
| 346 | return ERR_PTR(error: ret); |
| 347 | } |
| 348 | |