// ---------------------------------------------------------------------------
// @file       jtag.c
// @brief      JTAG basic commands
// 
// @author     Laurent Saint-Marcel (lstmarcel@yahoo.fr)
// @date       2009/09/25
// ---------------------------------------------------------------------------

// Thanks to http://www.fpga4fun.com/JTAG4.html for the JTAG protocol description!

#include <avr/io.h>
#include <util/delay.h>

#include "util.h"
#include "jtag.h"

#define TMS_0 0
#define TMS_1 1

#define TDI_0 0
#define TDI_1 1

// pin configuration
#define JTAG_DDR DDRB
#define TMS_PORT PORTB
#define TMS_PIN      1
#define TDI_PORT PORTB
#define TDI_PIN      3
#define TDO_PORT PINB
#define TDO_PIN      2
#define TCLK_PORT PORTB
#define TCLK_PIN     0

// Additional pin used to force the reset of the target AVR. It is connected
// to the reset pin of the target
#define JTAG_RESET_DDR  DDRA
#define JTAG_RESET_PORT PORTA
#define JTAG_RESET_PIN  3


// ---------------------------------------------------------------------------
// Initialize the IO port of the atmel
// ---------------------------------------------------------------------------
void
        jtagInit()
{
    // reset pin output
    sbi(JTAG_RESET_DDR,  JTAG_RESET_PIN);
    sbi(JTAG_RESET_PORT, JTAG_RESET_PIN);
    // outputs
    sbi(JTAG_DDR, TMS_PIN);
    sbi(JTAG_DDR, TDI_PIN);
    sbi(JTAG_DDR, TCLK_PIN);
    // inputs
    cbi(JTAG_DDR, TDO_PIN);
}

// ---------------------------------------------------------------------------
// Set the Jtag Reset pin state
// ---------------------------------------------------------------------------
void 
        jtagReset(unsigned char reset)
{
    if (reset) {
        cbi(JTAG_RESET_PORT, JTAG_RESET_PIN);
    } else {
        sbi(JTAG_RESET_PORT, JTAG_RESET_PIN);
    }
}

// ---------------------------------------------------------------------------
// send one byte to the jtag with one clock pulse, return the TDO bit value
// ---------------------------------------------------------------------------
unsigned char 
        jtagClockPulse(unsigned char tms,
                       unsigned char tdi)
{
    if (tms != 0) sbi(TMS_PORT, TMS_PIN);
    else          cbi(TMS_PORT, TMS_PIN);
    if (tdi != 0) sbi(TDI_PORT, TDI_PIN);
    else          cbi(TDI_PORT, TDI_PIN);
    // clock raising edge
    sbi(TCLK_PORT, TCLK_PIN);
    // clock falling edge
    cbi(TCLK_PORT, TCLK_PIN);
    return bit_is_set(TDO_PORT, TDO_PIN);
}


// ---------------------------------------------------------------------------
// set the JTAG in the Run-Test/Idle state from any state
// ---------------------------------------------------------------------------
void 
        jtagTestLogicReset()
{
    unsigned char i;
    // first sync everybody to the test-logic-reset state
    for(i=0; i<5; i++) {
        jtagClockPulse(TMS_1, TDI_0);
    }
}

// ---------------------------------------------------------------------------
// set the JTAG in the Shift IR state
// ---------------------------------------------------------------------------
void 
        jtagShiftIR()
{
    jtagClockPulse(TMS_0, TDI_0);
    jtagClockPulse(TMS_1, TDI_0);
    jtagClockPulse(TMS_1, TDI_0);
    jtagClockPulse(TMS_0, TDI_0);
    jtagClockPulse(TMS_0, TDI_0);
}

// ---------------------------------------------------------------------------
// Prerequisit: must be in the ShiftIR state
// write 4 bits in the IR register of the JTAG and go in the update IR state
// ---------------------------------------------------------------------------
void 
        jtagWriteIR(unsigned char data)
{
    unsigned char bit;
    for(bit =0; bit<4; ++bit) {
        jtagClockPulse(bit == 3/*tms*/, // go in the update DR state at last bit
                       (data & (1 << bit))/*tdi*/);
    }
}

// ---------------------------------------------------------------------------
// set the JTAG in the Shift DR state
// ---------------------------------------------------------------------------
void 
        jtagShiftDR()
{
    jtagClockPulse(TMS_0, TDI_0);
    jtagClockPulse(TMS_1, TDI_0);
    jtagClockPulse(TMS_0, TDI_0);
    jtagClockPulse(TMS_0, TDI_0);
}

// ---------------------------------------------------------------------------
// Prerequisit: must be in the ShiftIR state
// write 'length" bits of 'data' in the DR register of the JTAG 
// Go in the update DR state at last bit if exitAtLast != 0 or remain in the
// Shift DR state
// ---------------------------------------------------------------------------
unsigned char 
        jtagWriteDR(unsigned char data,
                    unsigned char length,
                    unsigned char exitAtLast)
{
    //_delay_us(10);
    unsigned char result = 0;
    unsigned char bit;
    for(bit =0; bit < length; ++bit) {
        unsigned char tms = TMS_0;
        if ((exitAtLast != 0) && (bit == (length-1))) {
            tms = TMS_1; // to exit the Shift-DR state after last bit is set
        }
        if ((jtagClockPulse(tms, (data & (1 << bit)) ) != 0)) {
            result |= (1 << bit);
        }
    }
    return result;
}

// ---------------------------------------------------------------------------
// set the JTAG in the RunTestIdle state after being in IR or DR state
// ---------------------------------------------------------------------------
void jtagRunTestIdle()
{
    jtagClockPulse(TMS_1, TDI_0);
    jtagClockPulse(TMS_0, TDI_0);
}