blob: 2e68af7dea6d51ad8c3ae5657e0af9debaf4a285 [file] [log] [blame]
/*
* 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();
}