/* delay.h - delay loop for HCS12
 * Copyright 2008 Colin D. Bennett <colin@gibibit.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 3 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, see <http://www.gnu.org/licenses/>.
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * The delay_ms() function uses an assembly language delay loop to
 * implement the delay.  You should call delay_ms() with a compile time
 * constant value so that the floating point arithmetic operations are done
 * at compile time, not at run time.  This way no floating point libraries
 * or any other overhead is added to the compiled program.
 */
#include <inttypes.h>

/* If the CPU clock frequency is not known, the delay loops cannot calculate
 * proper timings.  It should be defined in your program's build system
 * (e.g., in your Makefile). */
#ifndef F_CPU
# error "F_CPU not defined for delay.h -- must be set to CPU clock frequency in Hz"
#endif

/* Compiler optimizations are necessary to do the right compile time
 * calculations and dead code elmination, etc. */
#ifndef __OPTIMIZE__
# warning "Compiler optimizations disabled; functions from delay won't work as designed"
#endif


/**
 * Delay for the specified time in milliseconds. 
 * The parameter 'ms' must be a compile time constant for this function
 * to work as designed.
 */
static inline void 
delay_ms(double ms)
__attribute__((always_inline));


/**
 * Delay for the specified number of loops.
 * The number of cycles per loop is defined by DELAY_CYCLES_PER_LOOP.
 * Normally you will want to use delay_ms() instead.
 */
static inline void 
delay_cycles(uint32_t loops)
__attribute__((always_inline));


/**
 * Delay function for up to 2^16 * DELAY_CYCLES_PER_LOOP cycles.
 * If loops == 0, then executes 65536 times.
 */
static inline void 
_delay_cycles_16(uint16_t loops)
__attribute__((always_inline));


/* #define DELAY_CYCLES_PER_LOOP 8     HC11 */
#define DELAY_CYCLES_PER_LOOP 5     /* HC12/HCS12 */


static inline void 
delay_ms(double ms)
{
	/* cycles/sec * loops/cycle * sec = loops */
	/* F_CPU / 1e3 / DELAY_CYCLES_PER_LOOP * ms = loops */

	double loops = (F_CPU) / 1e3 / DELAY_CYCLES_PER_LOOP * ms;
	if (loops < 1.0)
		loops = 1;

	delay_cycles((uint32_t) loops);
}


static inline void 
delay_cycles(uint32_t loops)
{
	uint16_t repeats = loops >> 16;
	while (repeats--)
		_delay_cycles_16(0);    /* Loop 65536 times. */
	if ((uint16_t) loops)
		_delay_cycles_16((uint16_t) loops);
}


/**
 * Execute a delay loop 'loops' times.
 * If loops == 0, then executes 65536 times.
 */
static inline void 
_delay_cycles_16(uint16_t loops)
{
	/* Total:  
	 *   HC11: 8 cycles per loop. 
	 *   HC12/HCS12: 5 cycles per loop. */
	asm volatile (
		"1: nop\n\t"  /* (HC11:2, HC(S)12: 1) No-op. */
		"dex\n\t"     /* (HC11:3, HC(S)12: 1) Decrement X. */
		"bne 1b"      /* (HC11:3, HC(S)12: 3/1) Loop if X != 0. */
		: /* No outputs.*/
		: "x" (loops)  /* Input: X := loops */
	);

}

