/*
 * \brief   Utilities for timer drivers
 * \author  Martin Stein
 * \date    2017-08-23
 */

/*
 * Copyright (C) 2017 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _DRIVERS__TIMER__UTIL_H_
#define _DRIVERS__TIMER__UTIL_H_

namespace Genode {

	enum { TIMER_MIN_TICKS_PER_MS = 1000ULL };

	/*
	 * Translate timer ticks to microseconds without losing precicision
	 *
	 * There are hardware timers whose frequency can't be expressed as
	 * ticks-per-microsecond integer-value because only a ticks-per-millisecond
	 * integer-value is precise enough. We don't want to use expensive
	 * floating-point values here but nonetheless want to translate from ticks
	 * to time with microseconds precision. Thus, we split the input in two and
	 * translate both parts separately. This way, we can raise precision by
	 * shifting the values to their optimal bit position. Afterwards, the
	 * results are shifted back and merged together again. Note, the remainder
	 * of the upper-half division is factored into the lower-half calculation
	 * (as upper-half value).
	 *
	 * Please ensure that the assertion
	 * "ticks_per_ms >= TIMER_MIN_TICKS_PER_MS" is true when calling this
	 * method!
	 */
	template <typename RESULT_T, typename TICS_PER_MS_T>
	RESULT_T timer_ticks_to_us(RESULT_T      const ticks,
	                           TICS_PER_MS_T const ticks_per_ms)
	{
		enum:RESULT_T {
			HALF_WIDTH = (sizeof(RESULT_T) << 2),
			MSB_MASK  = (~(RESULT_T)0) << HALF_WIDTH,
			LSB_MASK  = (~(RESULT_T)0) >> HALF_WIDTH,
			MSB_RSHIFT = 10,
			LSB_LSHIFT = HALF_WIDTH - MSB_RSHIFT,
		};
		/* upper half */
		RESULT_T const msb0 = ((ticks & MSB_MASK) >> MSB_RSHIFT) * 1000;
		RESULT_T const msb  = (msb0 / ticks_per_ms) << MSB_RSHIFT;
		RESULT_T const rem  = (msb0 % ticks_per_ms) << MSB_RSHIFT;
		/* lower half */
		RESULT_T const lsb0 = ((ticks & LSB_MASK) << LSB_LSHIFT) * 1000
		                    + (rem << LSB_LSHIFT);
		RESULT_T const lsb  = (lsb0 / ticks_per_ms) >> LSB_LSHIFT;

		return msb + lsb;
	}
}

#endif /* _DRIVERS__TIMER__UTIL_H_ */
