| /* | 
 |  * Copyright (C) 2009 Intel Corporation. | 
 |  * Author: Patrick Ohly <patrick.ohly@intel.com> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License | 
 |  * along with this program; if not, write to the Free Software | 
 |  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
 |  */ | 
 |  | 
 | #include <linux/timecompare.h> | 
 | #include <linux/module.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/math64.h> | 
 | #include <linux/kernel.h> | 
 |  | 
 | /* | 
 |  * fixed point arithmetic scale factor for skew | 
 |  * | 
 |  * Usually one would measure skew in ppb (parts per billion, 1e9), but | 
 |  * using a factor of 2 simplifies the math. | 
 |  */ | 
 | #define TIMECOMPARE_SKEW_RESOLUTION (((s64)1)<<30) | 
 |  | 
 | ktime_t timecompare_transform(struct timecompare *sync, | 
 | 			      u64 source_tstamp) | 
 | { | 
 | 	u64 nsec; | 
 |  | 
 | 	nsec = source_tstamp + sync->offset; | 
 | 	nsec += (s64)(source_tstamp - sync->last_update) * sync->skew / | 
 | 		TIMECOMPARE_SKEW_RESOLUTION; | 
 |  | 
 | 	return ns_to_ktime(nsec); | 
 | } | 
 | EXPORT_SYMBOL_GPL(timecompare_transform); | 
 |  | 
 | int timecompare_offset(struct timecompare *sync, | 
 | 		       s64 *offset, | 
 | 		       u64 *source_tstamp) | 
 | { | 
 | 	u64 start_source = 0, end_source = 0; | 
 | 	struct { | 
 | 		s64 offset; | 
 | 		s64 duration_target; | 
 | 	} buffer[10], sample, *samples; | 
 | 	int counter = 0, i; | 
 | 	int used; | 
 | 	int index; | 
 | 	int num_samples = sync->num_samples; | 
 |  | 
 | 	if (num_samples > ARRAY_SIZE(buffer)) { | 
 | 		samples = kmalloc(sizeof(*samples) * num_samples, GFP_ATOMIC); | 
 | 		if (!samples) { | 
 | 			samples = buffer; | 
 | 			num_samples = ARRAY_SIZE(buffer); | 
 | 		} | 
 | 	} else { | 
 | 		samples = buffer; | 
 | 	} | 
 |  | 
 | 	/* run until we have enough valid samples, but do not try forever */ | 
 | 	i = 0; | 
 | 	counter = 0; | 
 | 	while (1) { | 
 | 		u64 ts; | 
 | 		ktime_t start, end; | 
 |  | 
 | 		start = sync->target(); | 
 | 		ts = timecounter_read(sync->source); | 
 | 		end = sync->target(); | 
 |  | 
 | 		if (!i) | 
 | 			start_source = ts; | 
 |  | 
 | 		/* ignore negative durations */ | 
 | 		sample.duration_target = ktime_to_ns(ktime_sub(end, start)); | 
 | 		if (sample.duration_target >= 0) { | 
 | 			/* | 
 | 			 * assume symetric delay to and from source: | 
 | 			 * average target time corresponds to measured | 
 | 			 * source time | 
 | 			 */ | 
 | 			sample.offset = | 
 | 				(ktime_to_ns(end) + ktime_to_ns(start)) / 2 - | 
 | 				ts; | 
 |  | 
 | 			/* simple insertion sort based on duration */ | 
 | 			index = counter - 1; | 
 | 			while (index >= 0) { | 
 | 				if (samples[index].duration_target < | 
 | 				    sample.duration_target) | 
 | 					break; | 
 | 				samples[index + 1] = samples[index]; | 
 | 				index--; | 
 | 			} | 
 | 			samples[index + 1] = sample; | 
 | 			counter++; | 
 | 		} | 
 |  | 
 | 		i++; | 
 | 		if (counter >= num_samples || i >= 100000) { | 
 | 			end_source = ts; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	*source_tstamp = (end_source + start_source) / 2; | 
 |  | 
 | 	/* remove outliers by only using 75% of the samples */ | 
 | 	used = counter * 3 / 4; | 
 | 	if (!used) | 
 | 		used = counter; | 
 | 	if (used) { | 
 | 		/* calculate average */ | 
 | 		s64 off = 0; | 
 | 		for (index = 0; index < used; index++) | 
 | 			off += samples[index].offset; | 
 | 		*offset = div_s64(off, used); | 
 | 	} | 
 |  | 
 | 	if (samples && samples != buffer) | 
 | 		kfree(samples); | 
 |  | 
 | 	return used; | 
 | } | 
 | EXPORT_SYMBOL_GPL(timecompare_offset); | 
 |  | 
 | void __timecompare_update(struct timecompare *sync, | 
 | 			  u64 source_tstamp) | 
 | { | 
 | 	s64 offset; | 
 | 	u64 average_time; | 
 |  | 
 | 	if (!timecompare_offset(sync, &offset, &average_time)) | 
 | 		return; | 
 |  | 
 | 	if (!sync->last_update) { | 
 | 		sync->last_update = average_time; | 
 | 		sync->offset = offset; | 
 | 		sync->skew = 0; | 
 | 	} else { | 
 | 		s64 delta_nsec = average_time - sync->last_update; | 
 |  | 
 | 		/* avoid division by negative or small deltas */ | 
 | 		if (delta_nsec >= 10000) { | 
 | 			s64 delta_offset_nsec = offset - sync->offset; | 
 | 			s64 skew; /* delta_offset_nsec * | 
 | 				     TIMECOMPARE_SKEW_RESOLUTION / | 
 | 				     delta_nsec */ | 
 | 			u64 divisor; | 
 |  | 
 | 			/* div_s64() is limited to 32 bit divisor */ | 
 | 			skew = delta_offset_nsec * TIMECOMPARE_SKEW_RESOLUTION; | 
 | 			divisor = delta_nsec; | 
 | 			while (unlikely(divisor >= ((s64)1) << 32)) { | 
 | 				/* divide both by 2; beware, right shift | 
 | 				   of negative value has undefined | 
 | 				   behavior and can only be used for | 
 | 				   the positive divisor */ | 
 | 				skew = div_s64(skew, 2); | 
 | 				divisor >>= 1; | 
 | 			} | 
 | 			skew = div_s64(skew, divisor); | 
 |  | 
 | 			/* | 
 | 			 * Calculate new overall skew as 4/16 the | 
 | 			 * old value and 12/16 the new one. This is | 
 | 			 * a rather arbitrary tradeoff between | 
 | 			 * only using the latest measurement (0/16 and | 
 | 			 * 16/16) and even more weight on past measurements. | 
 | 			 */ | 
 | #define TIMECOMPARE_NEW_SKEW_PER_16 12 | 
 | 			sync->skew = | 
 | 				div_s64((16 - TIMECOMPARE_NEW_SKEW_PER_16) * | 
 | 					sync->skew + | 
 | 					TIMECOMPARE_NEW_SKEW_PER_16 * skew, | 
 | 					16); | 
 | 			sync->last_update = average_time; | 
 | 			sync->offset = offset; | 
 | 		} | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL_GPL(__timecompare_update); |