| /* |
| * 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, |
| ×ync_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(×ync_svc->wait_queue); |
| break; |
| case GB_TIMESYNC_STATE_INACTIVE: |
| timesync_svc->state = state; |
| wake_up(×ync_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(×ync_svc->delayed_work); |
| queue_delayed_work(timesync_svc->work_queue, |
| ×ync_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(×ync_svc->delayed_work); |
| queue_delayed_work(timesync_svc->work_queue, |
| ×ync_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, |
| ×ync_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(×ync_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(×ync_svc->spinlock, flags); |
| gb_timesync_set_state(timesync_svc, state); |
| spin_unlock_irqrestore(×ync_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, |
| ×ync_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, ×ync_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, |
| ×ync_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(×ync_svc->mutex); |
| spin_lock_irqsave(×ync_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(×ync_svc->spinlock, flags); |
| mutex_unlock(×ync_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, |
| ×ync_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, |
| ×ync_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, ×ync_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, |
| ×ync_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, |
| ×ync_svc->interface_list, list) { |
| control = timesync_interface->interface->control; |
| timesync_interface->ping_frame_time = 0; |
| ping_frame_time = ×ync_interface->ping_frame_time; |
| ret = gb_control_timesync_get_last_event(control, |
| ping_frame_time); |
| if (ret) { |
| dev_err(×ync_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(×ync_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(×ync_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(×ync_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(×ync_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(×ync_svc->mutex); |
| if (timesync_svc->state != GB_TIMESYNC_STATE_INVALID) |
| gb_timesync_set_state_atomic(timesync_svc, state); |
| else |
| ret = -ENODEV; |
| |
| mutex_unlock(×ync_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(×ync_svc->mutex); |
| spin_lock_irqsave(×ync_svc->spinlock, flags); |
| |
| ret = __gb_timesync_get_status(timesync_svc); |
| |
| spin_unlock_irqrestore(×ync_svc->spinlock, flags); |
| mutex_unlock(×ync_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, ×ync_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_inode(file)->i_private; |
| char *buf; |
| ssize_t ret = 0; |
| |
| mutex_lock(&gb_timesync_svc_list_mutex); |
| mutex_lock(×ync_svc->mutex); |
| if (list_empty(×ync_svc->interface_list)) |
| ret = -ENODEV; |
| timesync_svc->print_ping = false; |
| mutex_unlock(×ync_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(×ync_svc->interface_list); |
| INIT_DELAYED_WORK(×ync_svc->delayed_work, gb_timesync_worker); |
| mutex_init(×ync_svc->mutex); |
| spin_lock_init(×ync_svc->spinlock); |
| init_waitqueue_head(×ync_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(×ync_svc->list, &gb_timesync_svc_list); |
| ret = gb_timesync_hd_add(timesync_svc, svc->hd); |
| if (ret) { |
| list_del(×ync_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(×ync_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(×ync_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(×ync_svc->delayed_work); |
| |
| mutex_lock(×ync_svc->mutex); |
| |
| gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INVALID); |
| del_timer_sync(×ync_svc->ktime_timer); |
| gb_timesync_teardown(timesync_svc); |
| |
| gb_timesync_hd_remove(timesync_svc, svc->hd); |
| list_for_each_entry_safe(timesync_interface, next, |
| ×ync_svc->interface_list, list) { |
| list_del(×ync_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(×ync_svc->list); |
| |
| mutex_unlock(×ync_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(×ync_svc->mutex); |
| timesync_interface->interface = interface; |
| list_add(×ync_interface->list, ×ync_svc->interface_list); |
| timesync_svc->strobe_mask |= 1 << interface->interface_id; |
| mutex_unlock(×ync_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(×ync_svc->mutex); |
| timesync_svc->strobe_mask &= ~(1 << interface->interface_id); |
| list_del(×ync_interface->list); |
| kfree(timesync_interface); |
| mutex_unlock(×ync_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(×ync_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(×ync_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(×ync_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(×ync_svc->spinlock, flags); |
| mod_timer(×ync_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(×ync_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(×ync_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(); |
| } |