| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Expose virtio_rtc clocks as PTP clocks.
- *
- * Copyright (C) 2022-2023 OpenSynergy GmbH
- * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- *
- * Derived from ptp_kvm_common.c, virtual PTP 1588 clock for use with KVM
- * guests.
- *
- * Copyright (C) 2017 Red Hat Inc.
- */
- #include <linux/device.h>
- #include <linux/err.h>
- #include <linux/ptp_clock_kernel.h>
- #include <uapi/linux/virtio_rtc.h>
- #include "virtio_rtc_internal.h"
- /**
- * struct viortc_ptp_clock - PTP clock abstraction
- * @ptp_clock: PTP clock handle for unregistering
- * @viortc: virtio_rtc device data
- * @ptp_info: PTP clock description
- * @vio_clk_id: virtio_rtc clock id
- * @have_cross: device supports crosststamp with available HW counter
- */
- struct viortc_ptp_clock {
- struct ptp_clock *ptp_clock;
- struct viortc_dev *viortc;
- struct ptp_clock_info ptp_info;
- u16 vio_clk_id;
- bool have_cross;
- };
- /**
- * struct viortc_ptp_cross_ctx - context for get_device_system_crosststamp()
- * @device_time: device clock reading
- * @system_counterval: HW counter value at device_time
- *
- * Provides the already obtained crosststamp to get_device_system_crosststamp().
- */
- struct viortc_ptp_cross_ctx {
- ktime_t device_time;
- struct system_counterval_t system_counterval;
- };
- /* Weak function in case get_device_system_crosststamp() is not supported */
- int __weak viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id)
- {
- return -EOPNOTSUPP;
- }
- /**
- * viortc_ptp_get_time_fn() - callback for get_device_system_crosststamp()
- * @device_time: device clock reading
- * @system_counterval: HW counter value at device_time
- * @ctx: context with already obtained crosststamp
- *
- * Return: zero (success).
- */
- static int viortc_ptp_get_time_fn(ktime_t *device_time,
- struct system_counterval_t *system_counterval,
- void *ctx)
- {
- struct viortc_ptp_cross_ctx *vio_ctx = ctx;
- *device_time = vio_ctx->device_time;
- *system_counterval = vio_ctx->system_counterval;
- return 0;
- }
- /**
- * viortc_ptp_do_xtstamp() - get crosststamp from device
- * @vio_ptp: virtio_rtc PTP clock
- * @hw_counter: virtio_rtc HW counter type
- * @cs_id: clocksource id corresponding to hw_counter
- * @ctx: context for get_device_system_crosststamp()
- *
- * Reads HW-specific crosststamp from device.
- *
- * Context: Process context.
- * Return: Zero on success, negative error code otherwise.
- */
- static int viortc_ptp_do_xtstamp(struct viortc_ptp_clock *vio_ptp,
- u8 hw_counter, enum clocksource_ids cs_id,
- struct viortc_ptp_cross_ctx *ctx)
- {
- u64 max_ns, ns;
- int ret;
- ctx->system_counterval.cs_id = cs_id;
- ret = viortc_read_cross(vio_ptp->viortc, vio_ptp->vio_clk_id,
- hw_counter, &ns,
- &ctx->system_counterval.cycles);
- if (ret)
- return ret;
- max_ns = (u64)ktime_to_ns(KTIME_MAX);
- if (ns > max_ns)
- return -EINVAL;
- ctx->device_time = ns_to_ktime(ns);
- return 0;
- }
- /*
- * PTP clock operations
- */
- /**
- * viortc_ptp_getcrosststamp() - PTP clock getcrosststamp op
- * @ptp: PTP clock info
- * @xtstamp: crosststamp
- *
- * Context: Process context.
- * Return: Zero on success, negative error code otherwise.
- */
- static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp,
- struct system_device_crosststamp *xtstamp)
- {
- struct viortc_ptp_clock *vio_ptp =
- container_of(ptp, struct viortc_ptp_clock, ptp_info);
- struct system_time_snapshot history_begin;
- struct viortc_ptp_cross_ctx ctx;
- enum clocksource_ids cs_id;
- u8 hw_counter;
- int ret;
- if (!vio_ptp->have_cross)
- return -EOPNOTSUPP;
- ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
- if (ret)
- return ret;
- ktime_get_snapshot(&history_begin);
- if (history_begin.cs_id != cs_id)
- return -EOPNOTSUPP;
- /*
- * Getting the timestamp can take many milliseconds with a slow Virtio
- * device. This is too long for viortc_ptp_get_time_fn() passed to
- * get_device_system_crosststamp(), which has to usually return before
- * the timekeeper seqcount increases (every tick or so).
- *
- * So, get the actual cross-timestamp first.
- */
- ret = viortc_ptp_do_xtstamp(vio_ptp, hw_counter, cs_id, &ctx);
- if (ret)
- return ret;
- ret = get_device_system_crosststamp(viortc_ptp_get_time_fn, &ctx,
- &history_begin, xtstamp);
- if (ret)
- pr_debug("%s: get_device_system_crosststamp() returned %d\n",
- __func__, ret);
- return ret;
- }
- /* viortc_ptp_adjfine() - unsupported PTP clock adjfine op */
- static int viortc_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
- {
- return -EOPNOTSUPP;
- }
- /* viortc_ptp_adjtime() - unsupported PTP clock adjtime op */
- static int viortc_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
- {
- return -EOPNOTSUPP;
- }
- /* viortc_ptp_settime64() - unsupported PTP clock settime64 op */
- static int viortc_ptp_settime64(struct ptp_clock_info *ptp,
- const struct timespec64 *ts)
- {
- return -EOPNOTSUPP;
- }
- /*
- * viortc_ptp_gettimex64() - PTP clock gettimex64 op
- *
- * Context: Process context.
- */
- static int viortc_ptp_gettimex64(struct ptp_clock_info *ptp,
- struct timespec64 *ts,
- struct ptp_system_timestamp *sts)
- {
- struct viortc_ptp_clock *vio_ptp =
- container_of(ptp, struct viortc_ptp_clock, ptp_info);
- int ret;
- u64 ns;
- ptp_read_system_prets(sts);
- ret = viortc_read(vio_ptp->viortc, vio_ptp->vio_clk_id, &ns);
- ptp_read_system_postts(sts);
- if (ret)
- return ret;
- if (ns > (u64)S64_MAX)
- return -EINVAL;
- *ts = ns_to_timespec64((s64)ns);
- return 0;
- }
- /* viortc_ptp_enable() - unsupported PTP clock enable op */
- static int viortc_ptp_enable(struct ptp_clock_info *ptp,
- struct ptp_clock_request *rq, int on)
- {
- return -EOPNOTSUPP;
- }
- /*
- * viortc_ptp_info_template - ptp_clock_info template
- *
- * The .name member will be set for individual virtio_rtc PTP clocks.
- *
- * The .getcrosststamp member will be cleared for PTP clocks not supporting
- * crosststamp.
- */
- static const struct ptp_clock_info viortc_ptp_info_template = {
- .owner = THIS_MODULE,
- /* .name is set according to clock type */
- .adjfine = viortc_ptp_adjfine,
- .adjtime = viortc_ptp_adjtime,
- .gettimex64 = viortc_ptp_gettimex64,
- .settime64 = viortc_ptp_settime64,
- .enable = viortc_ptp_enable,
- .getcrosststamp = viortc_ptp_getcrosststamp,
- };
- /**
- * viortc_ptp_unregister() - PTP clock unregistering wrapper
- * @vio_ptp: virtio_rtc PTP clock
- * @parent_dev: parent device of PTP clock
- *
- * Return: Zero on success, negative error code otherwise.
- */
- int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp,
- struct device *parent_dev)
- {
- int ret = ptp_clock_unregister(vio_ptp->ptp_clock);
- if (!ret)
- devm_kfree(parent_dev, vio_ptp);
- return ret;
- }
- /**
- * viortc_ptp_get_cross_cap() - get xtstamp support info from device
- * @viortc: virtio_rtc device data
- * @vio_ptp: virtio_rtc PTP clock abstraction
- *
- * Context: Process context.
- * Return: Zero on success, negative error code otherwise.
- */
- static int viortc_ptp_get_cross_cap(struct viortc_dev *viortc,
- struct viortc_ptp_clock *vio_ptp)
- {
- enum clocksource_ids cs_id;
- bool xtstamp_supported;
- u8 hw_counter;
- int ret;
- ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
- if (ret) {
- vio_ptp->have_cross = false;
- return 0;
- }
- ret = viortc_cross_cap(viortc, vio_ptp->vio_clk_id, hw_counter,
- &xtstamp_supported);
- if (ret)
- return ret;
- vio_ptp->have_cross = xtstamp_supported;
- return 0;
- }
- /**
- * viortc_ptp_register() - prepare and register PTP clock
- * @viortc: virtio_rtc device data
- * @parent_dev: parent device for PTP clock
- * @vio_clk_id: id of virtio_rtc clock which backs PTP clock
- * @ptp_clock_name: PTP clock name
- *
- * Context: Process context.
- * Return: Pointer on success, ERR_PTR() otherwise; NULL if PTP clock support
- * not available.
- */
- struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc,
- struct device *parent_dev,
- u16 vio_clk_id,
- const char *ptp_clock_name)
- {
- struct viortc_ptp_clock *vio_ptp;
- struct ptp_clock *ptp_clock;
- ssize_t len;
- int ret;
- vio_ptp = devm_kzalloc(parent_dev, sizeof(*vio_ptp), GFP_KERNEL);
- if (!vio_ptp)
- return ERR_PTR(-ENOMEM);
- vio_ptp->viortc = viortc;
- vio_ptp->vio_clk_id = vio_clk_id;
- vio_ptp->ptp_info = viortc_ptp_info_template;
- len = strscpy(vio_ptp->ptp_info.name, ptp_clock_name,
- sizeof(vio_ptp->ptp_info.name));
- if (len < 0) {
- ret = len;
- goto err_free_dev;
- }
- ret = viortc_ptp_get_cross_cap(viortc, vio_ptp);
- if (ret)
- goto err_free_dev;
- if (!vio_ptp->have_cross)
- vio_ptp->ptp_info.getcrosststamp = NULL;
- ptp_clock = ptp_clock_register(&vio_ptp->ptp_info, parent_dev);
- if (IS_ERR(ptp_clock))
- goto err_on_register;
- vio_ptp->ptp_clock = ptp_clock;
- return vio_ptp;
- err_on_register:
- ret = PTR_ERR(ptp_clock);
- err_free_dev:
- devm_kfree(parent_dev, vio_ptp);
- return ERR_PTR(ret);
- }
|