aboutsummaryrefslogtreecommitdiffstats
path: root/greybus_timesync.patch
diff options
Diffstat (limited to 'greybus_timesync.patch')
-rw-r--r--greybus_timesync.patch1494
1 files changed, 1494 insertions, 0 deletions
diff --git a/greybus_timesync.patch b/greybus_timesync.patch
new file mode 100644
index 00000000000000..fcdacf64a88cf4
--- /dev/null
+++ b/greybus_timesync.patch
@@ -0,0 +1,1494 @@
+---
+ drivers/greybus/timesync.c | 1357 ++++++++++++++++++++++++++++++++++++
+ drivers/greybus/timesync.h | 45 +
+ drivers/greybus/timesync_platform.c | 77 ++
+ 3 files changed, 1479 insertions(+)
+
+--- /dev/null
++++ b/drivers/greybus/timesync.c
+@@ -0,0 +1,1357 @@
++/*
++ * TimeSync API driver.
++ *
++ * Copyright 2016 Google Inc.
++ * Copyright 2016 Linaro Ltd.
++ *
++ * Released under the GPLv2 only.
++ */
++#include <linux/debugfs.h>
++#include <linux/hrtimer.h>
++#include "greybus.h"
++#include "timesync.h"
++#include "greybus_trace.h"
++
++/*
++ * Minimum inter-strobe value of one millisecond is chosen because it
++ * just-about fits the common definition of a jiffy.
++ *
++ * Maximum value OTOH is constrained by the number of bits the SVC can fit
++ * into a 16 bit up-counter. The SVC configures the timer in microseconds
++ * so the maximum allowable value is 65535 microseconds. We clip that value
++ * to 10000 microseconds for the sake of using nice round base 10 numbers
++ * and since right-now there's no imaginable use-case requiring anything
++ * other than a one millisecond inter-strobe time, let alone something
++ * higher than ten milliseconds.
++ */
++#define GB_TIMESYNC_STROBE_DELAY_US 1000
++#define GB_TIMESYNC_DEFAULT_OFFSET_US 1000
++
++/* Work queue timers long, short and SVC strobe timeout */
++#define GB_TIMESYNC_DELAYED_WORK_LONG msecs_to_jiffies(10)
++#define GB_TIMESYNC_DELAYED_WORK_SHORT msecs_to_jiffies(1)
++#define GB_TIMESYNC_MAX_WAIT_SVC msecs_to_jiffies(5000)
++#define GB_TIMESYNC_KTIME_UPDATE msecs_to_jiffies(1000)
++#define GB_TIMESYNC_MAX_KTIME_CONVERSION 15
++
++/* Maximum number of times we'll retry a failed synchronous sync */
++#define GB_TIMESYNC_MAX_RETRIES 5
++
++/* Reported nanoseconds/femtoseconds per clock */
++static u64 gb_timesync_ns_per_clock;
++static u64 gb_timesync_fs_per_clock;
++
++/* Maximum difference we will accept converting FrameTime to ktime */
++static u32 gb_timesync_max_ktime_diff;
++
++/* Reported clock rate */
++static unsigned long gb_timesync_clock_rate;
++
++/* Workqueue */
++static void gb_timesync_worker(struct work_struct *work);
++
++/* List of SVCs with one FrameTime per SVC */
++static LIST_HEAD(gb_timesync_svc_list);
++
++/* Synchronize parallel contexts accessing a valid timesync_svc pointer */
++static DEFINE_MUTEX(gb_timesync_svc_list_mutex);
++
++/* Structure to convert from FrameTime to timespec/ktime */
++struct gb_timesync_frame_time_data {
++ u64 frame_time;
++ struct timespec ts;
++};
++
++struct gb_timesync_svc {
++ struct list_head list;
++ struct list_head interface_list;
++ struct gb_svc *svc;
++ struct gb_timesync_host_device *timesync_hd;
++
++ spinlock_t spinlock; /* Per SVC spinlock to sync with ISR */
++ struct mutex mutex; /* Per SVC mutex for regular synchronization */
++
++ struct dentry *frame_time_dentry;
++ struct dentry *frame_ktime_dentry;
++ struct workqueue_struct *work_queue;
++ wait_queue_head_t wait_queue;
++ struct delayed_work delayed_work;
++ struct timer_list ktime_timer;
++
++ /* The current local FrameTime */
++ u64 frame_time_offset;
++ struct gb_timesync_frame_time_data strobe_data[GB_TIMESYNC_MAX_STROBES];
++ struct gb_timesync_frame_time_data ktime_data;
++
++ /* The SVC FrameTime and relative AP FrameTime @ last TIMESYNC_PING */
++ u64 svc_ping_frame_time;
++ u64 ap_ping_frame_time;
++
++ /* Transitory settings */
++ u32 strobe_mask;
++ bool offset_down;
++ bool print_ping;
++ bool capture_ping;
++ int strobe;
++
++ /* Current state */
++ int state;
++};
++
++struct gb_timesync_host_device {
++ struct list_head list;
++ struct gb_host_device *hd;
++ u64 ping_frame_time;
++};
++
++struct gb_timesync_interface {
++ struct list_head list;
++ struct gb_interface *interface;
++ u64 ping_frame_time;
++};
++
++enum gb_timesync_state {
++ GB_TIMESYNC_STATE_INVALID = 0,
++ GB_TIMESYNC_STATE_INACTIVE = 1,
++ GB_TIMESYNC_STATE_INIT = 2,
++ GB_TIMESYNC_STATE_WAIT_SVC = 3,
++ GB_TIMESYNC_STATE_AUTHORITATIVE = 4,
++ GB_TIMESYNC_STATE_PING = 5,
++ GB_TIMESYNC_STATE_ACTIVE = 6,
++};
++
++static void gb_timesync_ktime_timer_fn(unsigned long data);
++
++static u64 gb_timesync_adjust_count(struct gb_timesync_svc *timesync_svc,
++ u64 counts)
++{
++ if (timesync_svc->offset_down)
++ return counts - timesync_svc->frame_time_offset;
++ else
++ return counts + timesync_svc->frame_time_offset;
++}
++
++/*
++ * This function provides the authoritative FrameTime to a calling function. It
++ * is designed to be lockless and should remain that way the caller is assumed
++ * to be state-aware.
++ */
++static u64 __gb_timesync_get_frame_time(struct gb_timesync_svc *timesync_svc)
++{
++ u64 clocks = gb_timesync_platform_get_counter();
++
++ return gb_timesync_adjust_count(timesync_svc, clocks);
++}
++
++static void gb_timesync_schedule_svc_timeout(struct gb_timesync_svc
++ *timesync_svc)
++{
++ queue_delayed_work(timesync_svc->work_queue,
++ &timesync_svc->delayed_work,
++ GB_TIMESYNC_MAX_WAIT_SVC);
++}
++
++static void gb_timesync_set_state(struct gb_timesync_svc *timesync_svc,
++ int state)
++{
++ switch (state) {
++ case GB_TIMESYNC_STATE_INVALID:
++ timesync_svc->state = state;
++ wake_up(&timesync_svc->wait_queue);
++ break;
++ case GB_TIMESYNC_STATE_INACTIVE:
++ timesync_svc->state = state;
++ wake_up(&timesync_svc->wait_queue);
++ break;
++ case GB_TIMESYNC_STATE_INIT:
++ if (timesync_svc->state != GB_TIMESYNC_STATE_INVALID) {
++ timesync_svc->strobe = 0;
++ timesync_svc->frame_time_offset = 0;
++ timesync_svc->state = state;
++ cancel_delayed_work(&timesync_svc->delayed_work);
++ queue_delayed_work(timesync_svc->work_queue,
++ &timesync_svc->delayed_work,
++ GB_TIMESYNC_DELAYED_WORK_LONG);
++ }
++ break;
++ case GB_TIMESYNC_STATE_WAIT_SVC:
++ if (timesync_svc->state == GB_TIMESYNC_STATE_INIT)
++ timesync_svc->state = state;
++ break;
++ case GB_TIMESYNC_STATE_AUTHORITATIVE:
++ if (timesync_svc->state == GB_TIMESYNC_STATE_WAIT_SVC) {
++ timesync_svc->state = state;
++ cancel_delayed_work(&timesync_svc->delayed_work);
++ queue_delayed_work(timesync_svc->work_queue,
++ &timesync_svc->delayed_work, 0);
++ }
++ break;
++ case GB_TIMESYNC_STATE_PING:
++ if (timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE) {
++ timesync_svc->state = state;
++ queue_delayed_work(timesync_svc->work_queue,
++ &timesync_svc->delayed_work,
++ GB_TIMESYNC_DELAYED_WORK_SHORT);
++ }
++ break;
++ case GB_TIMESYNC_STATE_ACTIVE:
++ if (timesync_svc->state == GB_TIMESYNC_STATE_AUTHORITATIVE ||
++ timesync_svc->state == GB_TIMESYNC_STATE_PING) {
++ timesync_svc->state = state;
++ wake_up(&timesync_svc->wait_queue);
++ }
++ break;
++ }
++
++ if (WARN_ON(timesync_svc->state != state)) {
++ pr_err("Invalid state transition %d=>%d\n",
++ timesync_svc->state, state);
++ }
++}
++
++static void gb_timesync_set_state_atomic(struct gb_timesync_svc *timesync_svc,
++ int state)
++{
++ unsigned long flags;
++
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++ gb_timesync_set_state(timesync_svc, state);
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++}
++
++static u64 gb_timesync_diff(u64 x, u64 y)
++{
++ if (x > y)
++ return x - y;
++ else
++ return y - x;
++}
++
++static void gb_timesync_adjust_to_svc(struct gb_timesync_svc *svc,
++ u64 svc_frame_time, u64 ap_frame_time)
++{
++ if (svc_frame_time > ap_frame_time) {
++ svc->frame_time_offset = svc_frame_time - ap_frame_time;
++ svc->offset_down = false;
++ } else {
++ svc->frame_time_offset = ap_frame_time - svc_frame_time;
++ svc->offset_down = true;
++ }
++}
++
++/*
++ * Associate a FrameTime with a ktime timestamp represented as struct timespec
++ * Requires the calling context to hold timesync_svc->mutex
++ */
++static void gb_timesync_store_ktime(struct gb_timesync_svc *timesync_svc,
++ struct timespec ts, u64 frame_time)
++{
++ timesync_svc->ktime_data.ts = ts;
++ timesync_svc->ktime_data.frame_time = frame_time;
++}
++
++/*
++ * Find the two pulses that best-match our expected inter-strobe gap and
++ * then calculate the difference between the SVC time at the second pulse
++ * to the local time at the second pulse.
++ */
++static void gb_timesync_collate_frame_time(struct gb_timesync_svc *timesync_svc,
++ u64 *frame_time)
++{
++ int i = 0;
++ u64 delta, ap_frame_time;
++ u64 strobe_delay_ns = GB_TIMESYNC_STROBE_DELAY_US * NSEC_PER_USEC;
++ u64 least = 0;
++
++ for (i = 1; i < GB_TIMESYNC_MAX_STROBES; i++) {
++ delta = timesync_svc->strobe_data[i].frame_time -
++ timesync_svc->strobe_data[i - 1].frame_time;
++ delta *= gb_timesync_ns_per_clock;
++ delta = gb_timesync_diff(delta, strobe_delay_ns);
++
++ if (!least || delta < least) {
++ least = delta;
++ gb_timesync_adjust_to_svc(timesync_svc, frame_time[i],
++ timesync_svc->strobe_data[i].frame_time);
++
++ ap_frame_time = timesync_svc->strobe_data[i].frame_time;
++ ap_frame_time = gb_timesync_adjust_count(timesync_svc,
++ ap_frame_time);
++ gb_timesync_store_ktime(timesync_svc,
++ timesync_svc->strobe_data[i].ts,
++ ap_frame_time);
++
++ pr_debug("adjust %s local %llu svc %llu delta %llu\n",
++ timesync_svc->offset_down ? "down" : "up",
++ timesync_svc->strobe_data[i].frame_time,
++ frame_time[i], delta);
++ }
++ }
++}
++
++static void gb_timesync_teardown(struct gb_timesync_svc *timesync_svc)
++{
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_interface *interface;
++ struct gb_host_device *hd;
++ int ret;
++
++ list_for_each_entry(timesync_interface,
++ &timesync_svc->interface_list, list) {
++ interface = timesync_interface->interface;
++ ret = gb_interface_timesync_disable(interface);
++ if (ret) {
++ dev_err(&interface->dev,
++ "interface timesync_disable %d\n", ret);
++ }
++ }
++
++ hd = timesync_svc->timesync_hd->hd;
++ ret = hd->driver->timesync_disable(hd);
++ if (ret < 0) {
++ dev_err(&hd->dev, "host timesync_disable %d\n",
++ ret);
++ }
++
++ gb_svc_timesync_wake_pins_release(svc);
++ gb_svc_timesync_disable(svc);
++ gb_timesync_platform_unlock_bus();
++
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
++}
++
++static void gb_timesync_platform_lock_bus_fail(struct gb_timesync_svc
++ *timesync_svc, int ret)
++{
++ if (ret == -EAGAIN) {
++ gb_timesync_set_state(timesync_svc, timesync_svc->state);
++ } else {
++ pr_err("Failed to lock timesync bus %d\n", ret);
++ gb_timesync_set_state(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
++ }
++}
++
++static void gb_timesync_enable(struct gb_timesync_svc *timesync_svc)
++{
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_host_device *hd;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_interface *interface;
++ u64 init_frame_time;
++ unsigned long clock_rate = gb_timesync_clock_rate;
++ int ret;
++
++ /*
++ * Get access to the wake pins in the AP and SVC
++ * Release these pins either in gb_timesync_teardown() or in
++ * gb_timesync_authoritative()
++ */
++ ret = gb_timesync_platform_lock_bus(timesync_svc);
++ if (ret < 0) {
++ gb_timesync_platform_lock_bus_fail(timesync_svc, ret);
++ return;
++ }
++ ret = gb_svc_timesync_wake_pins_acquire(svc, timesync_svc->strobe_mask);
++ if (ret) {
++ dev_err(&svc->dev,
++ "gb_svc_timesync_wake_pins_acquire %d\n", ret);
++ gb_timesync_teardown(timesync_svc);
++ return;
++ }
++
++ /* Choose an initial time in the future */
++ init_frame_time = __gb_timesync_get_frame_time(timesync_svc) + 100000UL;
++
++ /* Send enable command to all relevant participants */
++ list_for_each_entry(timesync_interface, &timesync_svc->interface_list,
++ list) {
++ interface = timesync_interface->interface;
++ ret = gb_interface_timesync_enable(interface,
++ GB_TIMESYNC_MAX_STROBES,
++ init_frame_time,
++ GB_TIMESYNC_STROBE_DELAY_US,
++ clock_rate);
++ if (ret) {
++ dev_err(&interface->dev,
++ "interface timesync_enable %d\n", ret);
++ }
++ }
++
++ hd = timesync_svc->timesync_hd->hd;
++ ret = hd->driver->timesync_enable(hd, GB_TIMESYNC_MAX_STROBES,
++ init_frame_time,
++ GB_TIMESYNC_STROBE_DELAY_US,
++ clock_rate);
++ if (ret < 0) {
++ dev_err(&hd->dev, "host timesync_enable %d\n",
++ ret);
++ }
++
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_WAIT_SVC);
++ ret = gb_svc_timesync_enable(svc, GB_TIMESYNC_MAX_STROBES,
++ init_frame_time,
++ GB_TIMESYNC_STROBE_DELAY_US,
++ clock_rate);
++ if (ret) {
++ dev_err(&svc->dev,
++ "gb_svc_timesync_enable %d\n", ret);
++ gb_timesync_teardown(timesync_svc);
++ return;
++ }
++
++ /* Schedule a timeout waiting for SVC to complete strobing */
++ gb_timesync_schedule_svc_timeout(timesync_svc);
++}
++
++static void gb_timesync_authoritative(struct gb_timesync_svc *timesync_svc)
++{
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_host_device *hd;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_interface *interface;
++ u64 svc_frame_time[GB_TIMESYNC_MAX_STROBES];
++ int ret;
++
++ /* Get authoritative time from SVC and adjust local clock */
++ ret = gb_svc_timesync_authoritative(svc, svc_frame_time);
++ if (ret) {
++ dev_err(&svc->dev,
++ "gb_svc_timesync_authoritative %d\n", ret);
++ gb_timesync_teardown(timesync_svc);
++ return;
++ }
++ gb_timesync_collate_frame_time(timesync_svc, svc_frame_time);
++
++ /* Transmit authoritative time to downstream slaves */
++ hd = timesync_svc->timesync_hd->hd;
++ ret = hd->driver->timesync_authoritative(hd, svc_frame_time);
++ if (ret < 0)
++ dev_err(&hd->dev, "host timesync_authoritative %d\n", ret);
++
++ list_for_each_entry(timesync_interface,
++ &timesync_svc->interface_list, list) {
++ interface = timesync_interface->interface;
++ ret = gb_interface_timesync_authoritative(
++ interface,
++ svc_frame_time);
++ if (ret) {
++ dev_err(&interface->dev,
++ "interface timesync_authoritative %d\n", ret);
++ }
++ }
++
++ /* Release wake pins */
++ gb_svc_timesync_wake_pins_release(svc);
++ gb_timesync_platform_unlock_bus();
++
++ /* Transition to state ACTIVE */
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_ACTIVE);
++
++ /* Schedule a ping to verify the synchronized system time */
++ timesync_svc->print_ping = true;
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_PING);
++}
++
++static int __gb_timesync_get_status(struct gb_timesync_svc *timesync_svc)
++{
++ int ret = -EINVAL;
++
++ switch (timesync_svc->state) {
++ case GB_TIMESYNC_STATE_INVALID:
++ case GB_TIMESYNC_STATE_INACTIVE:
++ ret = -ENODEV;
++ break;
++ case GB_TIMESYNC_STATE_INIT:
++ case GB_TIMESYNC_STATE_WAIT_SVC:
++ case GB_TIMESYNC_STATE_AUTHORITATIVE:
++ ret = -EAGAIN;
++ break;
++ case GB_TIMESYNC_STATE_PING:
++ case GB_TIMESYNC_STATE_ACTIVE:
++ ret = 0;
++ break;
++ }
++ return ret;
++}
++
++/*
++ * This routine takes a FrameTime and derives the difference with-respect
++ * to a reference FrameTime/ktime pair. It then returns the calculated
++ * ktime based on the difference between the supplied FrameTime and
++ * the reference FrameTime.
++ *
++ * The time difference is calculated to six decimal places. Taking 19.2MHz
++ * as an example this means we have 52.083333~ nanoseconds per clock or
++ * 52083333~ femtoseconds per clock.
++ *
++ * Naively taking the count difference and converting to
++ * seconds/nanoseconds would quickly see the 0.0833 component produce
++ * noticeable errors. For example a time difference of one second would
++ * loose 19200000 * 0.08333x nanoseconds or 1.59 seconds.
++ *
++ * In contrast calculating in femtoseconds the same example of 19200000 *
++ * 0.000000083333x nanoseconds per count of error is just 1.59 nanoseconds!
++ *
++ * Continuing the example of 19.2 MHz we cap the maximum error difference
++ * at a worst-case 0.3 microseconds over a potential calculation window of
++ * abount 15 seconds, meaning you can convert a FrameTime that is <= 15
++ * seconds older/younger than the reference time with a maximum error of
++ * 0.2385 useconds. Note 19.2MHz is an example frequency not a requirement.
++ */
++static int gb_timesync_to_timespec(struct gb_timesync_svc *timesync_svc,
++ u64 frame_time, struct timespec *ts)
++{
++ unsigned long flags;
++ u64 delta_fs, counts, sec, nsec;
++ bool add;
++ int ret = 0;
++
++ memset(ts, 0x00, sizeof(*ts));
++ mutex_lock(&timesync_svc->mutex);
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++
++ ret = __gb_timesync_get_status(timesync_svc);
++ if (ret)
++ goto done;
++
++ /* Support calculating ktime upwards or downwards from the reference */
++ if (frame_time < timesync_svc->ktime_data.frame_time) {
++ add = false;
++ counts = timesync_svc->ktime_data.frame_time - frame_time;
++ } else {
++ add = true;
++ counts = frame_time - timesync_svc->ktime_data.frame_time;
++ }
++
++ /* Enforce the .23 of a usecond boundary @ 19.2MHz */
++ if (counts > gb_timesync_max_ktime_diff) {
++ ret = -EINVAL;
++ goto done;
++ }
++
++ /* Determine the time difference in femtoseconds */
++ delta_fs = counts * gb_timesync_fs_per_clock;
++
++ /* Convert to seconds */
++ sec = delta_fs;
++ do_div(sec, NSEC_PER_SEC);
++ do_div(sec, 1000000UL);
++
++ /* Get the nanosecond remainder */
++ nsec = do_div(delta_fs, sec);
++ do_div(nsec, 1000000UL);
++
++ if (add) {
++ /* Add the calculated offset - overflow nanoseconds upwards */
++ ts->tv_sec = timesync_svc->ktime_data.ts.tv_sec + sec;
++ ts->tv_nsec = timesync_svc->ktime_data.ts.tv_nsec + nsec;
++ if (ts->tv_nsec >= NSEC_PER_SEC) {
++ ts->tv_sec++;
++ ts->tv_nsec -= NSEC_PER_SEC;
++ }
++ } else {
++ /* Subtract the difference over/underflow as necessary */
++ if (nsec > timesync_svc->ktime_data.ts.tv_nsec) {
++ sec++;
++ nsec = nsec + timesync_svc->ktime_data.ts.tv_nsec;
++ nsec = do_div(nsec, NSEC_PER_SEC);
++ } else {
++ nsec = timesync_svc->ktime_data.ts.tv_nsec - nsec;
++ }
++ /* Cannot return a negative second value */
++ if (sec > timesync_svc->ktime_data.ts.tv_sec) {
++ ret = -EINVAL;
++ goto done;
++ }
++ ts->tv_sec = timesync_svc->ktime_data.ts.tv_sec - sec;
++ ts->tv_nsec = nsec;
++ }
++done:
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++ mutex_unlock(&timesync_svc->mutex);
++ return ret;
++}
++
++static size_t gb_timesync_log_frame_time(struct gb_timesync_svc *timesync_svc,
++ char *buf, size_t buflen)
++{
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_host_device *hd;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_interface *interface;
++ unsigned int len;
++ size_t off;
++
++ /* AP/SVC */
++ off = snprintf(buf, buflen, "%s frametime: ap=%llu %s=%llu ",
++ greybus_bus_type.name,
++ timesync_svc->ap_ping_frame_time, dev_name(&svc->dev),
++ timesync_svc->svc_ping_frame_time);
++ len = buflen - off;
++
++ /* APB/GPB */
++ if (len < buflen) {
++ hd = timesync_svc->timesync_hd->hd;
++ off += snprintf(&buf[off], len, "%s=%llu ", dev_name(&hd->dev),
++ timesync_svc->timesync_hd->ping_frame_time);
++ len = buflen - off;
++ }
++
++ list_for_each_entry(timesync_interface,
++ &timesync_svc->interface_list, list) {
++ if (len < buflen) {
++ interface = timesync_interface->interface;
++ off += snprintf(&buf[off], len, "%s=%llu ",
++ dev_name(&interface->dev),
++ timesync_interface->ping_frame_time);
++ len = buflen - off;
++ }
++ }
++ if (len < buflen)
++ off += snprintf(&buf[off], len, "\n");
++ return off;
++}
++
++static size_t gb_timesync_log_frame_ktime(struct gb_timesync_svc *timesync_svc,
++ char *buf, size_t buflen)
++{
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_host_device *hd;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_interface *interface;
++ struct timespec ts;
++ unsigned int len;
++ size_t off;
++
++ /* AP */
++ gb_timesync_to_timespec(timesync_svc, timesync_svc->ap_ping_frame_time,
++ &ts);
++ off = snprintf(buf, buflen, "%s frametime: ap=%lu.%lu ",
++ greybus_bus_type.name, ts.tv_sec, ts.tv_nsec);
++ len = buflen - off;
++ if (len >= buflen)
++ goto done;
++
++ /* SVC */
++ gb_timesync_to_timespec(timesync_svc, timesync_svc->svc_ping_frame_time,
++ &ts);
++ off += snprintf(&buf[off], len, "%s=%lu.%lu ", dev_name(&svc->dev),
++ ts.tv_sec, ts.tv_nsec);
++ len = buflen - off;
++ if (len >= buflen)
++ goto done;
++
++ /* APB/GPB */
++ hd = timesync_svc->timesync_hd->hd;
++ gb_timesync_to_timespec(timesync_svc,
++ timesync_svc->timesync_hd->ping_frame_time,
++ &ts);
++ off += snprintf(&buf[off], len, "%s=%lu.%lu ",
++ dev_name(&hd->dev),
++ ts.tv_sec, ts.tv_nsec);
++ len = buflen - off;
++ if (len >= buflen)
++ goto done;
++
++ list_for_each_entry(timesync_interface,
++ &timesync_svc->interface_list, list) {
++ interface = timesync_interface->interface;
++ gb_timesync_to_timespec(timesync_svc,
++ timesync_interface->ping_frame_time,
++ &ts);
++ off += snprintf(&buf[off], len, "%s=%lu.%lu ",
++ dev_name(&interface->dev),
++ ts.tv_sec, ts.tv_nsec);
++ len = buflen - off;
++ if (len >= buflen)
++ goto done;
++ }
++ off += snprintf(&buf[off], len, "\n");
++done:
++ return off;
++}
++
++/*
++ * Send an SVC initiated wake 'ping' to each TimeSync participant.
++ * Get the FrameTime from each participant associated with the wake
++ * ping.
++ */
++static void gb_timesync_ping(struct gb_timesync_svc *timesync_svc)
++{
++ struct gb_svc *svc = timesync_svc->svc;
++ struct gb_host_device *hd;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_control *control;
++ u64 *ping_frame_time;
++ int ret;
++
++ /* Get access to the wake pins in the AP and SVC */
++ ret = gb_timesync_platform_lock_bus(timesync_svc);
++ if (ret < 0) {
++ gb_timesync_platform_lock_bus_fail(timesync_svc, ret);
++ return;
++ }
++ ret = gb_svc_timesync_wake_pins_acquire(svc, timesync_svc->strobe_mask);
++ if (ret) {
++ dev_err(&svc->dev,
++ "gb_svc_timesync_wake_pins_acquire %d\n", ret);
++ gb_timesync_teardown(timesync_svc);
++ return;
++ }
++
++ /* Have SVC generate a timesync ping */
++ timesync_svc->capture_ping = true;
++ timesync_svc->svc_ping_frame_time = 0;
++ ret = gb_svc_timesync_ping(svc, &timesync_svc->svc_ping_frame_time);
++ timesync_svc->capture_ping = false;
++ if (ret) {
++ dev_err(&svc->dev,
++ "gb_svc_timesync_ping %d\n", ret);
++ gb_timesync_teardown(timesync_svc);
++ return;
++ }
++
++ /* Get the ping FrameTime from each APB/GPB */
++ hd = timesync_svc->timesync_hd->hd;
++ timesync_svc->timesync_hd->ping_frame_time = 0;
++ ret = hd->driver->timesync_get_last_event(hd,
++ &timesync_svc->timesync_hd->ping_frame_time);
++ if (ret)
++ dev_err(&hd->dev, "host timesync_get_last_event %d\n", ret);
++
++ list_for_each_entry(timesync_interface,
++ &timesync_svc->interface_list, list) {
++ control = timesync_interface->interface->control;
++ timesync_interface->ping_frame_time = 0;
++ ping_frame_time = &timesync_interface->ping_frame_time;
++ ret = gb_control_timesync_get_last_event(control,
++ ping_frame_time);
++ if (ret) {
++ dev_err(&timesync_interface->interface->dev,
++ "gb_control_timesync_get_last_event %d\n", ret);
++ }
++ }
++
++ /* Ping success - move to timesync active */
++ gb_svc_timesync_wake_pins_release(svc);
++ gb_timesync_platform_unlock_bus();
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_ACTIVE);
++}
++
++static void gb_timesync_log_ping_time(struct gb_timesync_svc *timesync_svc)
++{
++ char *buf;
++
++ if (!timesync_svc->print_ping)
++ return;
++
++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
++ if (buf) {
++ gb_timesync_log_frame_time(timesync_svc, buf, PAGE_SIZE);
++ dev_dbg(&timesync_svc->svc->dev, "%s", buf);
++ kfree(buf);
++ }
++}
++
++/*
++ * Perform the actual work of scheduled TimeSync logic.
++ */
++static void gb_timesync_worker(struct work_struct *work)
++{
++ struct delayed_work *delayed_work = to_delayed_work(work);
++ struct gb_timesync_svc *timesync_svc =
++ container_of(delayed_work, struct gb_timesync_svc, delayed_work);
++
++ mutex_lock(&timesync_svc->mutex);
++
++ switch (timesync_svc->state) {
++ case GB_TIMESYNC_STATE_INIT:
++ gb_timesync_enable(timesync_svc);
++ break;
++
++ case GB_TIMESYNC_STATE_WAIT_SVC:
++ dev_err(&timesync_svc->svc->dev,
++ "timeout SVC strobe completion %d/%d\n",
++ timesync_svc->strobe, GB_TIMESYNC_MAX_STROBES);
++ gb_timesync_teardown(timesync_svc);
++ break;
++
++ case GB_TIMESYNC_STATE_AUTHORITATIVE:
++ gb_timesync_authoritative(timesync_svc);
++ break;
++
++ case GB_TIMESYNC_STATE_PING:
++ gb_timesync_ping(timesync_svc);
++ gb_timesync_log_ping_time(timesync_svc);
++ break;
++
++ default:
++ pr_err("Invalid state %d for delayed work\n",
++ timesync_svc->state);
++ break;
++ }
++
++ mutex_unlock(&timesync_svc->mutex);
++}
++
++/*
++ * Schedule a new TimeSync INIT or PING operation serialized w/r to
++ * gb_timesync_worker().
++ */
++static int gb_timesync_schedule(struct gb_timesync_svc *timesync_svc, int state)
++{
++ int ret = 0;
++
++ if (state != GB_TIMESYNC_STATE_INIT && state != GB_TIMESYNC_STATE_PING)
++ return -EINVAL;
++
++ mutex_lock(&timesync_svc->mutex);
++ if (timesync_svc->state != GB_TIMESYNC_STATE_INVALID) {
++ gb_timesync_set_state_atomic(timesync_svc, state);
++ } else {
++ ret = -ENODEV;
++ }
++ mutex_unlock(&timesync_svc->mutex);
++ return ret;
++}
++
++static int __gb_timesync_schedule_synchronous(
++ struct gb_timesync_svc *timesync_svc, int state)
++{
++ unsigned long flags;
++ int ret;
++
++ ret = gb_timesync_schedule(timesync_svc, state);
++ if (ret)
++ return ret;
++
++ ret = wait_event_interruptible(timesync_svc->wait_queue,
++ (timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE ||
++ timesync_svc->state == GB_TIMESYNC_STATE_INACTIVE ||
++ timesync_svc->state == GB_TIMESYNC_STATE_INVALID));
++ if (ret)
++ return ret;
++
++ mutex_lock(&timesync_svc->mutex);
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++
++ ret = __gb_timesync_get_status(timesync_svc);
++
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++ mutex_unlock(&timesync_svc->mutex);
++
++ return ret;
++}
++
++static struct gb_timesync_svc *gb_timesync_find_timesync_svc(
++ struct gb_host_device *hd)
++{
++ struct gb_timesync_svc *timesync_svc;
++
++ list_for_each_entry(timesync_svc, &gb_timesync_svc_list, list) {
++ if (timesync_svc->svc == hd->svc)
++ return timesync_svc;
++ }
++ return NULL;
++}
++
++static struct gb_timesync_interface *gb_timesync_find_timesync_interface(
++ struct gb_timesync_svc *timesync_svc,
++ struct gb_interface *interface)
++{
++ struct gb_timesync_interface *timesync_interface;
++
++ list_for_each_entry(timesync_interface, &timesync_svc->interface_list, list) {
++ if (timesync_interface->interface == interface)
++ return timesync_interface;
++ }
++ return NULL;
++}
++
++int gb_timesync_schedule_synchronous(struct gb_interface *interface)
++{
++ int ret;
++ struct gb_timesync_svc *timesync_svc;
++ int retries;
++
++ if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
++ return 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ for (retries = 0; retries < GB_TIMESYNC_MAX_RETRIES; retries++) {
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc) {
++ ret = -ENODEV;
++ goto done;
++ }
++
++ ret = __gb_timesync_schedule_synchronous(timesync_svc,
++ GB_TIMESYNC_STATE_INIT);
++ if (!ret)
++ break;
++ }
++ if (ret && retries == GB_TIMESYNC_MAX_RETRIES)
++ ret = -ETIMEDOUT;
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_schedule_synchronous);
++
++void gb_timesync_schedule_asynchronous(struct gb_interface *interface)
++{
++ struct gb_timesync_svc *timesync_svc;
++
++ if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
++ return;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc)
++ goto done;
++
++ gb_timesync_schedule(timesync_svc, GB_TIMESYNC_STATE_INIT);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_schedule_asynchronous);
++
++static ssize_t gb_timesync_ping_read(struct file *file, char __user *ubuf,
++ size_t len, loff_t *offset, bool ktime)
++{
++ struct gb_timesync_svc *timesync_svc = file->f_inode->i_private;
++ char *buf;
++ ssize_t ret = 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ mutex_lock(&timesync_svc->mutex);
++ if (list_empty(&timesync_svc->interface_list))
++ ret = -ENODEV;
++ timesync_svc->print_ping = false;
++ mutex_unlock(&timesync_svc->mutex);
++ if (ret)
++ goto done;
++
++ ret = __gb_timesync_schedule_synchronous(timesync_svc,
++ GB_TIMESYNC_STATE_PING);
++ if (ret)
++ goto done;
++
++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
++ if (!buf) {
++ ret = -ENOMEM;
++ goto done;
++ }
++
++ if (ktime)
++ ret = gb_timesync_log_frame_ktime(timesync_svc, buf, PAGE_SIZE);
++ else
++ ret = gb_timesync_log_frame_time(timesync_svc, buf, PAGE_SIZE);
++ if (ret > 0)
++ ret = simple_read_from_buffer(ubuf, len, offset, buf, ret);
++ kfree(buf);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++
++static ssize_t gb_timesync_ping_read_frame_time(struct file *file,
++ char __user *buf,
++ size_t len, loff_t *offset)
++{
++ return gb_timesync_ping_read(file, buf, len, offset, false);
++}
++
++static ssize_t gb_timesync_ping_read_frame_ktime(struct file *file,
++ char __user *buf,
++ size_t len, loff_t *offset)
++{
++ return gb_timesync_ping_read(file, buf, len, offset, true);
++}
++
++static const struct file_operations gb_timesync_debugfs_frame_time_ops = {
++ .read = gb_timesync_ping_read_frame_time,
++};
++
++static const struct file_operations gb_timesync_debugfs_frame_ktime_ops = {
++ .read = gb_timesync_ping_read_frame_ktime,
++};
++
++static int gb_timesync_hd_add(struct gb_timesync_svc *timesync_svc,
++ struct gb_host_device *hd)
++{
++ struct gb_timesync_host_device *timesync_hd;
++
++ timesync_hd = kzalloc(sizeof(*timesync_hd), GFP_KERNEL);
++ if (!timesync_hd)
++ return -ENOMEM;
++
++ WARN_ON(timesync_svc->timesync_hd);
++ timesync_hd->hd = hd;
++ timesync_svc->timesync_hd = timesync_hd;
++
++ return 0;
++}
++
++static void gb_timesync_hd_remove(struct gb_timesync_svc *timesync_svc,
++ struct gb_host_device *hd)
++{
++ if (timesync_svc->timesync_hd->hd == hd) {
++ kfree(timesync_svc->timesync_hd);
++ timesync_svc->timesync_hd = NULL;
++ return;
++ }
++ WARN_ON(1);
++}
++
++int gb_timesync_svc_add(struct gb_svc *svc)
++{
++ struct gb_timesync_svc *timesync_svc;
++ int ret;
++
++ timesync_svc = kzalloc(sizeof(*timesync_svc), GFP_KERNEL);
++ if (!timesync_svc)
++ return -ENOMEM;
++
++ timesync_svc->work_queue =
++ create_singlethread_workqueue("gb-timesync-work_queue");
++
++ if (!timesync_svc->work_queue) {
++ kfree(timesync_svc);
++ return -ENOMEM;
++ }
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ INIT_LIST_HEAD(&timesync_svc->interface_list);
++ INIT_DELAYED_WORK(&timesync_svc->delayed_work, gb_timesync_worker);
++ mutex_init(&timesync_svc->mutex);
++ spin_lock_init(&timesync_svc->spinlock);
++ init_waitqueue_head(&timesync_svc->wait_queue);
++
++ timesync_svc->svc = svc;
++ timesync_svc->frame_time_offset = 0;
++ timesync_svc->capture_ping = false;
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
++
++ timesync_svc->frame_time_dentry =
++ debugfs_create_file("frame-time", S_IRUGO, svc->debugfs_dentry,
++ timesync_svc,
++ &gb_timesync_debugfs_frame_time_ops);
++ timesync_svc->frame_ktime_dentry =
++ debugfs_create_file("frame-ktime", S_IRUGO, svc->debugfs_dentry,
++ timesync_svc,
++ &gb_timesync_debugfs_frame_ktime_ops);
++
++ list_add(&timesync_svc->list, &gb_timesync_svc_list);
++ ret = gb_timesync_hd_add(timesync_svc, svc->hd);
++ if (ret) {
++ list_del(&timesync_svc->list);
++ debugfs_remove(timesync_svc->frame_ktime_dentry);
++ debugfs_remove(timesync_svc->frame_time_dentry);
++ destroy_workqueue(timesync_svc->work_queue);
++ kfree(timesync_svc);
++ goto done;
++ }
++
++ init_timer(&timesync_svc->ktime_timer);
++ timesync_svc->ktime_timer.function = gb_timesync_ktime_timer_fn;
++ timesync_svc->ktime_timer.expires = jiffies + GB_TIMESYNC_KTIME_UPDATE;
++ timesync_svc->ktime_timer.data = (unsigned long)timesync_svc;
++ add_timer(&timesync_svc->ktime_timer);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_svc_add);
++
++void gb_timesync_svc_remove(struct gb_svc *svc)
++{
++ struct gb_timesync_svc *timesync_svc;
++ struct gb_timesync_interface *timesync_interface;
++ struct gb_timesync_interface *next;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
++ if (!timesync_svc)
++ goto done;
++
++ cancel_delayed_work_sync(&timesync_svc->delayed_work);
++
++ mutex_lock(&timesync_svc->mutex);
++
++ gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INVALID);
++ del_timer_sync(&timesync_svc->ktime_timer);
++ gb_timesync_teardown(timesync_svc);
++
++ gb_timesync_hd_remove(timesync_svc, svc->hd);
++ list_for_each_entry_safe(timesync_interface, next,
++ &timesync_svc->interface_list, list) {
++ list_del(&timesync_interface->list);
++ kfree(timesync_interface);
++ }
++ debugfs_remove(timesync_svc->frame_ktime_dentry);
++ debugfs_remove(timesync_svc->frame_time_dentry);
++ destroy_workqueue(timesync_svc->work_queue);
++ list_del(&timesync_svc->list);
++
++ mutex_unlock(&timesync_svc->mutex);
++
++ kfree(timesync_svc);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++}
++EXPORT_SYMBOL_GPL(gb_timesync_svc_remove);
++
++/*
++ * Add a Greybus Interface to the set of TimeSync Interfaces.
++ */
++int gb_timesync_interface_add(struct gb_interface *interface)
++{
++ struct gb_timesync_svc *timesync_svc;
++ struct gb_timesync_interface *timesync_interface;
++ int ret = 0;
++
++ if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
++ return 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc) {
++ ret = -ENODEV;
++ goto done;
++ }
++
++ timesync_interface = kzalloc(sizeof(*timesync_interface), GFP_KERNEL);
++ if (!timesync_interface) {
++ ret = -ENOMEM;
++ goto done;
++ }
++
++ mutex_lock(&timesync_svc->mutex);
++ timesync_interface->interface = interface;
++ list_add(&timesync_interface->list, &timesync_svc->interface_list);
++ timesync_svc->strobe_mask |= 1 << interface->interface_id;
++ mutex_unlock(&timesync_svc->mutex);
++
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_interface_add);
++
++/*
++ * Remove a Greybus Interface from the set of TimeSync Interfaces.
++ */
++void gb_timesync_interface_remove(struct gb_interface *interface)
++{
++ struct gb_timesync_svc *timesync_svc;
++ struct gb_timesync_interface *timesync_interface;
++
++ if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
++ return;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc)
++ goto done;
++
++ timesync_interface = gb_timesync_find_timesync_interface(timesync_svc,
++ interface);
++ if (!timesync_interface)
++ goto done;
++
++ mutex_lock(&timesync_svc->mutex);
++ timesync_svc->strobe_mask &= ~(1 << interface->interface_id);
++ list_del(&timesync_interface->list);
++ kfree(timesync_interface);
++ mutex_unlock(&timesync_svc->mutex);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++}
++EXPORT_SYMBOL_GPL(gb_timesync_interface_remove);
++
++/*
++ * Give the authoritative FrameTime to the calling function. Returns zero if we
++ * are not in GB_TIMESYNC_STATE_ACTIVE.
++ */
++static u64 gb_timesync_get_frame_time(struct gb_timesync_svc *timesync_svc)
++{
++ unsigned long flags;
++ u64 ret;
++
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++ if (timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE)
++ ret = __gb_timesync_get_frame_time(timesync_svc);
++ else
++ ret = 0;
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++ return ret;
++}
++
++u64 gb_timesync_get_frame_time_by_interface(struct gb_interface *interface)
++{
++ struct gb_timesync_svc *timesync_svc;
++ u64 ret = 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc)
++ goto done;
++
++ ret = gb_timesync_get_frame_time(timesync_svc);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_get_frame_time_by_interface);
++
++u64 gb_timesync_get_frame_time_by_svc(struct gb_svc *svc)
++{
++ struct gb_timesync_svc *timesync_svc;
++ u64 ret = 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
++ if (!timesync_svc)
++ goto done;
++
++ ret = gb_timesync_get_frame_time(timesync_svc);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_get_frame_time_by_svc);
++
++/* Incrementally updates the conversion base from FrameTime to ktime */
++static void gb_timesync_ktime_timer_fn(unsigned long data)
++{
++ struct gb_timesync_svc *timesync_svc =
++ (struct gb_timesync_svc *)data;
++ unsigned long flags;
++ u64 frame_time;
++ struct timespec ts;
++
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++
++ if (timesync_svc->state != GB_TIMESYNC_STATE_ACTIVE)
++ goto done;
++
++ ktime_get_ts(&ts);
++ frame_time = __gb_timesync_get_frame_time(timesync_svc);
++ gb_timesync_store_ktime(timesync_svc, ts, frame_time);
++
++done:
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++ mod_timer(&timesync_svc->ktime_timer,
++ jiffies + GB_TIMESYNC_KTIME_UPDATE);
++}
++
++int gb_timesync_to_timespec_by_svc(struct gb_svc *svc, u64 frame_time,
++ struct timespec *ts)
++{
++ struct gb_timesync_svc *timesync_svc;
++ int ret = 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
++ if (!timesync_svc) {
++ ret = -ENODEV;
++ goto done;
++ }
++ ret = gb_timesync_to_timespec(timesync_svc, frame_time, ts);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_to_timespec_by_svc);
++
++int gb_timesync_to_timespec_by_interface(struct gb_interface *interface,
++ u64 frame_time, struct timespec *ts)
++{
++ struct gb_timesync_svc *timesync_svc;
++ int ret = 0;
++
++ mutex_lock(&gb_timesync_svc_list_mutex);
++ timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
++ if (!timesync_svc) {
++ ret = -ENODEV;
++ goto done;
++ }
++
++ ret = gb_timesync_to_timespec(timesync_svc, frame_time, ts);
++done:
++ mutex_unlock(&gb_timesync_svc_list_mutex);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(gb_timesync_to_timespec_by_interface);
++
++void gb_timesync_irq(struct gb_timesync_svc *timesync_svc)
++{
++ unsigned long flags;
++ u64 strobe_time;
++ bool strobe_is_ping = true;
++ struct timespec ts;
++
++ ktime_get_ts(&ts);
++ strobe_time = __gb_timesync_get_frame_time(timesync_svc);
++
++ spin_lock_irqsave(&timesync_svc->spinlock, flags);
++
++ if (timesync_svc->state == GB_TIMESYNC_STATE_PING) {
++ if (!timesync_svc->capture_ping)
++ goto done_nolog;
++ timesync_svc->ap_ping_frame_time = strobe_time;
++ goto done_log;
++ } else if (timesync_svc->state != GB_TIMESYNC_STATE_WAIT_SVC) {
++ goto done_nolog;
++ }
++
++ timesync_svc->strobe_data[timesync_svc->strobe].frame_time = strobe_time;
++ timesync_svc->strobe_data[timesync_svc->strobe].ts = ts;
++
++ if (++timesync_svc->strobe == GB_TIMESYNC_MAX_STROBES) {
++ gb_timesync_set_state(timesync_svc,
++ GB_TIMESYNC_STATE_AUTHORITATIVE);
++ }
++ strobe_is_ping = false;
++done_log:
++ trace_gb_timesync_irq(strobe_is_ping, timesync_svc->strobe,
++ GB_TIMESYNC_MAX_STROBES, strobe_time);
++done_nolog:
++ spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
++}
++EXPORT_SYMBOL(gb_timesync_irq);
++
++int __init gb_timesync_init(void)
++{
++ int ret = 0;
++
++ ret = gb_timesync_platform_init();
++ if (ret) {
++ pr_err("timesync platform init fail!\n");
++ return ret;
++ }
++
++ gb_timesync_clock_rate = gb_timesync_platform_get_clock_rate();
++
++ /* Calculate nanoseconds and femtoseconds per clock */
++ gb_timesync_fs_per_clock = FSEC_PER_SEC;
++ do_div(gb_timesync_fs_per_clock, gb_timesync_clock_rate);
++ gb_timesync_ns_per_clock = NSEC_PER_SEC;
++ do_div(gb_timesync_ns_per_clock, gb_timesync_clock_rate);
++
++ /* Calculate the maximum number of clocks we will convert to ktime */
++ gb_timesync_max_ktime_diff =
++ GB_TIMESYNC_MAX_KTIME_CONVERSION * gb_timesync_clock_rate;
++
++ pr_info("Time-Sync @ %lu Hz max ktime conversion +/- %d seconds\n",
++ gb_timesync_clock_rate, GB_TIMESYNC_MAX_KTIME_CONVERSION);
++ return 0;
++}
++
++void gb_timesync_exit(void)
++{
++ gb_timesync_platform_exit();
++}
+--- /dev/null
++++ b/drivers/greybus/timesync.h
+@@ -0,0 +1,45 @@
++/*
++ * TimeSync API driver.
++ *
++ * Copyright 2016 Google Inc.
++ * Copyright 2016 Linaro Ltd.
++ *
++ * Released under the GPLv2 only.
++ */
++
++#ifndef __TIMESYNC_H
++#define __TIMESYNC_H
++
++struct gb_svc;
++struct gb_interface;
++struct gb_timesync_svc;
++
++/* Platform */
++u64 gb_timesync_platform_get_counter(void);
++u32 gb_timesync_platform_get_clock_rate(void);
++int gb_timesync_platform_lock_bus(struct gb_timesync_svc *pdata);
++void gb_timesync_platform_unlock_bus(void);
++
++int gb_timesync_platform_init(void);
++void gb_timesync_platform_exit(void);
++
++/* Core API */
++int gb_timesync_interface_add(struct gb_interface *interface);
++void gb_timesync_interface_remove(struct gb_interface *interface);
++int gb_timesync_svc_add(struct gb_svc *svc);
++void gb_timesync_svc_remove(struct gb_svc *svc);
++
++u64 gb_timesync_get_frame_time_by_interface(struct gb_interface *interface);
++u64 gb_timesync_get_frame_time_by_svc(struct gb_svc *svc);
++int gb_timesync_to_timespec_by_svc(struct gb_svc *svc, u64 frame_time,
++ struct timespec *ts);
++int gb_timesync_to_timespec_by_interface(struct gb_interface *interface,
++ u64 frame_time, struct timespec *ts);
++
++int gb_timesync_schedule_synchronous(struct gb_interface *intf);
++void gb_timesync_schedule_asynchronous(struct gb_interface *intf);
++void gb_timesync_irq(struct gb_timesync_svc *timesync_svc);
++int gb_timesync_init(void);
++void gb_timesync_exit(void);
++
++#endif /* __TIMESYNC_H */
+--- /dev/null
++++ b/drivers/greybus/timesync_platform.c
+@@ -0,0 +1,77 @@
++/*
++ * TimeSync API driver.
++ *
++ * Copyright 2016 Google Inc.
++ * Copyright 2016 Linaro Ltd.
++ *
++ * Released under the GPLv2 only.
++ *
++ * This code reads directly from an ARMv7 memory-mapped timer that lives in
++ * MMIO space. Since this counter lives inside of MMIO space its shared between
++ * cores and that means we don't have to worry about issues like TSC on x86
++ * where each time-stamp-counter (TSC) is local to a particular core.
++ *
++ * Register-level access code is based on
++ * drivers/clocksource/arm_arch_timer.c
++ */
++#include <linux/cpufreq.h>
++#include <linux/of_platform.h>
++
++#include "greybus.h"
++#include "arche_platform.h"
++
++static u32 gb_timesync_clock_frequency;
++int (*arche_platform_change_state_cb)(enum arche_platform_state state,
++ struct gb_timesync_svc *pdata);
++EXPORT_SYMBOL_GPL(arche_platform_change_state_cb);
++
++u64 gb_timesync_platform_get_counter(void)
++{
++ return (u64)get_cycles();
++}
++
++u32 gb_timesync_platform_get_clock_rate(void)
++{
++ if (unlikely(!gb_timesync_clock_frequency))
++ return cpufreq_get(0);
++
++ return gb_timesync_clock_frequency;
++}
++
++int gb_timesync_platform_lock_bus(struct gb_timesync_svc *pdata)
++{
++ return arche_platform_change_state_cb(ARCHE_PLATFORM_STATE_TIME_SYNC,
++ pdata);
++}
++
++void gb_timesync_platform_unlock_bus(void)
++{
++ arche_platform_change_state_cb(ARCHE_PLATFORM_STATE_ACTIVE, NULL);
++}
++
++static const struct of_device_id arch_timer_of_match[] = {
++ { .compatible = "google,greybus-frame-time-counter", },
++ {},
++};
++
++int __init gb_timesync_platform_init(void)
++{
++ struct device_node *np;
++
++ np = of_find_matching_node(NULL, arch_timer_of_match);
++ if (!np) {
++ /* Tolerate not finding to allow BBB etc to continue */
++ pr_warn("Unable to find a compatible ARMv7 timer\n");
++ return 0;
++ }
++
++ if (of_property_read_u32(np, "clock-frequency",
++ &gb_timesync_clock_frequency)) {
++ pr_err("Unable to find timer clock-frequency\n");
++ return -ENODEV;
++ }
++
++ return 0;
++}
++
++void gb_timesync_platform_exit(void) {}