/** \file
 *
 * \brief  Internal RC Oscillator Calibration for ATtiny4/5/9/10/20/40
 *
 * This assembly file contains the calibration firmware which performs 
 * calibration of the internal 8MHz RC oscillator based on 32kHz signal
 * applied on TPICLK pin of the target device.
 *
 * \par Application note:
 * AVR057: Internal RC Oscillator Calibration for ATtiny4/5/9/10/20/40
 *
 * \author
 * Atmel Corporation: http://www.atmel.com \n
 * Support email: avr@atmel.com
 *
 * Copyright (C) 2010 Atmel Corporation. All rights reserved.
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */


 ; Target Device Being Used. Please specify the target device here!
 .equ ATTINY = 4
 .include "defines.inc"

 ; Variables used in the firmware assigned to GP registers
 .def FLAG_REG = R17
 .def STEP_SIZE = R18
 .def CPU_TICKS_LOW = R19
 .def CPU_TICKS_HIGH = R20
 .def OSCCAL_VALUE = R21
 .def CYCLE_COUNTER = R22
 .def MAX_CALIB_CYCLES = R23
 .def CALIB_STATUS = R24

 ; Target internal RC oscillator frequency
 .equ TARGET_FREQ = 8000000

 ; Calibration reference clock frequency. 
 ; Please measure the frequency on the TPICLK
 ; line of the TPI programmer (PD4 on ATmega324) and update the value.
 ; Clock signal can be obtained on PD4 pin of ATmega324 
 ; with the following command on AVROSP
 ; avrosp.exe -dattiny10 -Y
 ; Change device name for respective devices
 .equ CALIB_CLOCK_FREQ = 31800

 ; Accuracy of calibration. 1 means +/-1% accuracy
 ; Specify minimum value of 1 (not less than 1)
 .equ ACCURACY = 1

 ; Internal RC oscillator frequency limits for +/-1% accuracy
 .equ TARGET_UP_LIMIT = (TARGET_FREQ + ((TARGET_FREQ*ACCURACY)/100))
 .equ TARGET_DOWN_LIMIT = (TARGET_FREQ - ((TARGET_FREQ*ACCURACY)/100))

 ; Internal RC clock tick limits for 40 calibration cycles 
 .equ EXPECTED_CPU_CYCLES = ((40*TARGET_FREQ)/CALIB_CLOCK_FREQ)
 .equ MAX_CPU_CYCLES = ((40*TARGET_UP_LIMIT)/CALIB_CLOCK_FREQ)
 .equ MIN_CPU_CYCLES = ((40*TARGET_DOWN_LIMIT)/CALIB_CLOCK_FREQ)

 ; Initial Step Size is half the maximum value (i.e., 255)
 .equ START_STEP_SIZE = 127

 ; Main Code Starts Here
 .org 0x000
		RJMP START
 .org INT_VECT
		RJMP PCINT_vect

 START: 
    ; Load CCP register with change protection value 
    LDI R16,0xD8
		OUT CCP,R16
    ; Select internal 8MHz RC Oscillator as clock source
		LDI R16,0x00
		OUT CLKMSR,R16	

    ; Load CCP register with change protection value 
		LDI R16,0xD8
		OUT CCP,R16
    ; Reset system prescalar to Divide by 1. Default is 8
		LDI R16,0x00
		OUT CLKPSR,R16	
    
    ; Initialize stack pointer
		LDI R16,high(RAMEND)
	  OUT SPH,R16		
    LDI R16,low(RAMEND)
    OUT SPL,R16

		; Configure TPIDATA as output, TPICLK as input with pull-up
    SBI TDIR,TDATA_PIN
    SBI TPORT,TDATA_PIN
    SBI TPORT,TCLK_PIN

    ; Enable pin change interrupt on TPICLK pin
    SBI INT_CTRL_REG,INT_CTRL_BIT
    SBI INT_MASK_REG,INT_MASK_BIT

    ; Load OSCCAL with initial step size (127)
    LDI STEP_SIZE,START_STEP_SIZE
		LDI OSCCAL_VALUE,START_STEP_SIZE
		OUT OSCCAL,OSCCAL_VALUE

    ; Initialize variables
		LDI CYCLE_COUNTER,0x00
		LDI MAX_CALIB_CYCLES,40
    INC MAX_CALIB_CYCLES
		LDI CALIB_STATUS,0x00
    
    ; Enable global interrupts
		SEI

    ; Wait until calibration is done. That is until
    ; CALIB_STATUS is non-zero
loop:	SBRC CALIB_STATUS,0
		RCALL SEND_OSCCAL
		RJMP loop

PCINT_vect:
		; On pin change interrupt check for falling edge
    ; If rising edge then return
    SBIC TPIN,TCLK_PIN
		RETI
    ; If falling edge then increment cycle counter
		INC CYCLE_COUNTER
    ; Check if cycle counter reached max value (40)
		CLZ
		CP MAX_CALIB_CYCLES,CYCLE_COUNTER
    ; If max value reached then stop timer
		BREQ STOP_TIMER
    ; Else start the timer
		LDI R16,TIMER_CTRL_VAL
		OUT TIMER_CTRL_REG,R16
		RETI

STOP_TIMER:
		LDI R16,0x00
		OUT TIMER_CTRL_REG,R16
		LDI CYCLE_COUNTER,0x00
    ; Once timer is stopped check whether calibration is over
		RCALL CALIB_CHECK
		RETI

CALIB_CHECK:
    ;Step size divide by 2 for binary search
		LSR STEP_SIZE	

    ; Load timer values to variables and clear TCNT registers
		IN CPU_TICKS_LOW,TIMER_CNT_LO
		IN CPU_TICKS_HIGH,TIMER_CNT_HI
		LDI R16,0x00
		OUT TIMER_CNT_HI,R16
		OUT TIMER_CNT_LO,R16

    ; Check if CPU ticks is within limits
    CLC 
    CLZ
    MOV R30,CPU_TICKS_LOW
    MOV R31,CPU_TICKS_HIGH
    SUBI R30,low(MAX_CPU_CYCLES)
    SBCI R31,high(MAX_CPU_CYCLES)
    BRCS CHECK_LOW
    BRCC REDUCE_OSCCAL

CHECK_LOW:
    CLC
    CLZ
    MOV R30,CPU_TICKS_LOW
    MOV R31,CPU_TICKS_HIGH
    SUBI R30,low(MIN_CPU_CYCLES)
    SBCI R31,high(MIN_CPU_CYCLES)
    BRCS INCREASE_OSCCAL
    BRCC END_CALIB

    ; If CPU ticks is too high then reduce OSCCAL value by step size
REDUCE_OSCCAL:
		SUB OSCCAL_VALUE,STEP_SIZE
		OUT OSCCAL,OSCCAL_VALUE
		RET

    ; If CPU ticks is too low then increase OSCCAL value by step size
INCREASE_OSCCAL:
		ADD OSCCAL_VALUE,STEP_SIZE
		OUT OSCCAL,OSCCAL_VALUE
		RET
    
    ; If CPU ticks is within limits stop calibration 
    ; by disabling interrupt and loading CALIB_STATUS with non-zero value
END_CALIB:
		LDI CALIB_STATUS,0x01
    CBI TPORT,TDATA_PIN
		RET

    ; Once calibration is done listen for high to low change on TPICLK pin
    ; and send OSCCAL value on TPIDATA line bit by bit for every high to low
    ; change
SEND_OSCCAL:
    CLI
loop1:
    IN R16,TPIN
    SBRS R16,TCLK_PIN
    RJMP SEND_BIT
    RJMP loop1
SEND_BIT:
    SBRC OSCCAL_VALUE,7
    SBI TPORT,TDATA_PIN
    SBRS OSCCAL_VALUE,7
    CBI TPORT,TDATA_PIN
    LSL OSCCAL_VALUE
loop2:
    IN R16,TPIN
    SBRC R16,TCLK_PIN
    RJMP loop1
    RJMP loop2

/* End of File */