//************************************************************************
//**
//** Project......: Write ATtiny45 Fuse bits
//**
//** Platform.....: ATTiny2313/AT90S2313, 8MHz,RC,Div8, Clock 1MHz
//**
//** Licence......: This software is freely available for non-commercial 
//**                use - i.e. for research and experimentation only!
//**
//** Programmer...: F.W. Krom, PE0FKO
//** 
//** Description..: Write the fuse bits of a ATtiny45 to enable/disable the
//**				RSTDISBL fuse bit. It also will erase and default the
//**				ATtiny45 to the factory default.
//**				Inspiration: http://cappels.org/dproj/t12fp/t12f.htm
//**				             ATtiny12 fuse restorer, Dick Cappels
//**
//** History......: 30/12/2008 V0.1 First complete working version.
//**
//**************************************************************************
//
//
//        Host                    Target
//        ATtiny2313              ATtiny45
//        +--+-+--+//             +--+-+--+
// !RESET |  |_|  | VCC    !RESET |  |_|  | VCC
//    PD0 |       | PB7       PB3 |       | PB2
//    PD1 |       | PB6       PB4 |       | PB1
//  XTAL2 |       | PB5       GND |       | PB0
//  XTAL1 |       | PB4           +-------+
//    PD2 |       | PB3
//    PD3 |       | PB2
//    PD4 |       | PB1
//    PD5 |       | PB0
//    GND |       | PD6
//        +-------+
//
// Host fuse bits: 8MHz internal RC + divide by 8
// hFuse = 0xDF
// lFuse = 0x64
//
// -------------------------
// ATtiny	ATtiny
// 2313		45	
// -------------------------
// PB0	O	PB0	I	SDI
// PB1	O	PB1	I	SII
// PB2	O/I	PB2	I/O	SDO
// PB3	O	PB3	I	SCI
// PB4	O	VCC
// PB5	O	*PB5	/RST	Target reset to ground
// PB6	O	*PB5	+12V	Target reset to +12V
// PD2	I+			USRP1	Lock/Unlock
// PD3	I+			USRP2	not used
// PD4	I+			USRP3	Erase default
// PD6	O			LED
// -------------------------
// 

/*
 * Fuse bits for a ATtiny45 target cpu!
 * ------------------------------------
 * Fuse bit information:
 * Fuse high byte:
 * 0xdd =1/0 1 0 1   1 1 0 1
 *        ^  ^ ^ ^   ^ \-+-/ 
 *        |  | | |   |   +------ BODLEVEL 2..0 (brownout trigger level -> 2.7V)
 *        |  | | |   +---------- EESAVE (preserve EEPROM on Chip Erase -> not preserved)
 *        |  | | +-------------- WDTON (watchdog timer always on -> disable)
 *        |  | +---------------- SPIEN (enable serial programming -> enabled)
 *        |  +------------------ DWEN (debug wire enable -> disabled?)
 *        +--------------------- RSTDISBL (disable external reset -> disabled)
 *
 * Fuse low byte:
 * 0xe1 = 1 1 1 0   0 0 0 1
 *        ^ ^ \+/   \--+--/
 *        | |  |       +------- CKSEL 3..0 (clock selection -> HF PLL)
 *        | |  +--------------- SUT 1..0 (BOD enabled, fast rising power)
 *        | +------------------ CKOUT (clock output on CKOUT pin -> disabled)
 *        +-------------------- CKDIV8 (divide clock by 8 -> don't divide) 
 */

#include <stdbool.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <avr/pgmspace.h>

#define	V12		PB6		// +12V to target reset pin (PB5)
#define	RST		PB5		// Ground reset pin (PB5) on target
#define	VCC		PB4		// VCC to target
#define	SCI		PB3		// PB3 Clock in to targtet
#define	SDO		PB2		// PB2 Data out from target
#define	SII		PB1		// PB1 Serail instructions in to target
#define	SDI		PB0		// PB0 Serial data in to target
#define	LED		PD6		// High to light LED

#define	cbi(port,bit)	port &= ~(1 << bit)
#define	sbi(port,bit)	port |=  (1 << bit)

#define	USRDDR	DDRD
#define	USRPIN	PIND
#define	USRPRT	PORTD
#define	USRP1	PD2
#define	USRP2	PD3
#define	USRP3	PD4

static	bool ok = true;

typedef struct {
	uint8_t		Signature[3];
	uint8_t		FuseLow;
	uint8_t		FuseHigh;
	uint8_t		FuseExt;
} CPU_t;

static	CPU_t	PROGMEM	cpu[] = 
{
	{	// ATtiny25
		.Signature	= { 0x1E, 0x91, 0x08 },
		.FuseLow	= 0x62,
		.FuseHigh	= 0xDF,
		.FuseExt	= 0xFF,
	},
	{	// ATtiny45
		.Signature	= { 0x1E, 0x92, 0x06 },
		.FuseLow	= 0x62,
		.FuseHigh	= 0xDF,
		.FuseExt	= 0xFF,
	},
	{	// ATtiny85
		.Signature	= { 0x1E, 0x93, 0x0B },
		.FuseLow	= 0x62,
		.FuseHigh	= 0xDF,
		.FuseExt	= 0xFF,
	},
	{	// ATtiny13
		.Signature	= { 0x1E, 0x90, 0x07 },
		.FuseLow	= 0x6A,
		.FuseHigh	= 0xFF,
		.FuseExt	= 0,
	},
	{	// ATtiny12, not verified
		.Signature	= { 0x1E, 0x90, 0x04 },
		.FuseLow	= 0x52,
		.FuseHigh	= 0,
		.FuseExt	= 0,
	},
	{	// ATtiny11, not verified
		.Signature	= { 0x1E, 0x90, 0x05 },
		.FuseLow	= 0x1C,
		.FuseHigh	= 0,
		.FuseExt	= 0,
	},
	{ { 0,0,0 }, 0, 0, 0 },
};

#define	CpuEnd(i)	(pgm_read_byte(&cpu[i].Signature[0]) == 0)


// Bug in sleep.h for set_sleep_mode cpu at90s2313, used __BV not _BV!
#if defined(__AVR_AT90S2313__)
#define __BV(bit) (1 << (bit))
#endif

// The minimum period for the Serial Clock Input (SCI) during High-voltage Serial Programming is 220 ns.
static void
clockit()
{
	sbi(PORTB, SCI);
	asm volatile ("nop"::);
	cbi(PORTB, SCI);
}

static uint8_t
sendByte(uint8_t data, uint8_t instr)
{
	uint8_t   r,i;

	cbi(PORTB, SDI);
	cbi(PORTB, SII);

    // First zero
	clockit();

	for(r=i=0; i<8; i++)
	{
        r <<= 1;
		r |= (PINB & (1<<SDO)) != 0 ? 1:0;

        if(data & 0x80)
			sbi(PORTB, SDI);
		else
			cbi(PORTB, SDI);

        if(instr & 0x80)
			sbi(PORTB, SII);
		else
			cbi(PORTB, SII);

        data <<= 1;
        instr <<= 1;

		clockit();
    }

	cbi(PORTB, SDI);
	cbi(PORTB, SII);

    // Last 2 zero's
	clockit();
	clockit();

    return r;
}

static void
poll()
{
	int i;
	for(i=0; i < 1000; i++)
		if ((PINB & _BV(SDO)) != 0) 
			return;
		else
			_delay_us(20);

	ok = false;
}

static void
ChipErase(void)
{
	sendByte(0x80, 0x4C);
	sendByte(0x00, 0x64);
	sendByte(0x00, 0x6C);
	poll();

	// Intsruction NOP
	sendByte(0x00, 0x4C);
	poll();
}

static void
WriteFuseLowBits(uint8_t code)		// Default ATtiny45 = 0x62
{
	sendByte(0x40, 0x4C);
	sendByte(code, 0x2C);
	sendByte(0x00, 0x64);
	sendByte(0x00, 0x6C);
	poll();

	// Intsruction NOP
	sendByte(0x00, 0x4C);
	poll();
}

static void
WriteFuseHighBits(uint8_t code)		// Default ATtiny45 = 0xDF
{
	sendByte(0x40, 0x4C);
	sendByte(code, 0x2C);
	sendByte(0x00, 0x74);
	sendByte(0x00, 0x7C);
	poll();

	// Intsruction NOP
	sendByte(0x00, 0x4C);
	poll();
}

static void
WriteFuseExtendedBits(uint8_t code)	// Default ATtiny45 = 0x01
{
	sendByte(0x40, 0x4C);
	sendByte(code, 0x2C);
	sendByte(0x00, 0x66);
	sendByte(0x00, 0x6E);
	poll();

	// Intsruction NOP
	sendByte(0x00, 0x4C);
	poll();
}

static uint8_t
GetSignature(uint8_t code)
{
	sendByte(0x08, 0x4C);
	sendByte(code, 0x0C);
	sendByte(0x00, 0x68);
	return sendByte(0x00, 0x6C);
}


void __attribute__((naked)) __attribute__((section(".init3"))) 
setIO(void)
{
	PORTB = (0<<V12)|(0<<VCC)|(1<<RST);
	DDRB  = (1<<SDI)|(1<<SII)|(1<<SDO)|(1<<SCI)|(1<<VCC)|(1<<RST)|(1<<V12);

	USRPRT = (1<<USRP1)|(1<<USRP2)|(1<<USRP3)|(0<<LED);
	USRDDR = (0<<USRP1)|(0<<USRP2)|(0<<USRP3)|(1<<LED);
}

int //__attribute__((noreturn)) 
main(void)
{
	uint8_t Code,device;
	uint8_t FuseLowBits;
	uint8_t FuseHighBits;
	uint8_t	FuseExtendedBits;

/*	The following algorithm puts the device in High-voltage Serial Programming mode:
	1. Set Prog_enable pins listed in Table 22-13 to “000”, RESET pin and VCC to 0V.
	2. Apply 4.5 - 5.5V between VCC and GND.
	Ensure that VCC reaches at least 1.8V within the next 20 ěs.
	3. Wait 20 - 60 ěs, and apply 11.5 - 12.5V to RESET.
	4. Keep the Prog_enable pins unchanged for at least 10 ěs after the High-voltage has been
	applied to ensure the Prog_enable Signature has been latched.
	5. Release the Prog_enable[2] pin to avoid drive contention on the Prog_enable[2]/SDO
	pin.
	6. Wait at least 300 ěs before giving any serial instructions on SDI/SII.
	7. Exit Programming mode by power the device down or by bringing RESET pin to 0V.
*/
	_delay_us(50);

	PORTB=(0<<V12)|(1<<VCC)|(1<<RST);	//2: Apply VCC to target

	_delay_us(40);
	PORTB=(1<<V12)|(1<<VCC)|(0<<RST);	//3: Apply +12V to target

	_delay_us(15);						//4: Wait > 10us
	cbi(DDRB, SDO);						//5: Switch to input
	_delay_us(300);						//6: Delay 300us


	for (device = 0; !CpuEnd(device); device++)
	{
		if (GetSignature(0) == pgm_read_byte(&cpu[device].Signature[0]) && 
			GetSignature(1) == pgm_read_byte(&cpu[device].Signature[1]) && 
			GetSignature(2) == pgm_read_byte(&cpu[device].Signature[2])		)
		{
			break;
		}
	}

	if (!CpuEnd(device))
	{
		// New default ATtiny45
		// http://www.engbedded.com/cgi-bin/fc.cgi
		FuseLowBits			= pgm_read_byte(&cpu[device].FuseLow);
		FuseHighBits		= pgm_read_byte(&cpu[device].FuseHigh);
		FuseExtendedBits	= pgm_read_byte(&cpu[device].FuseExt);

		Code = USRPIN;

		if ((Code & _BV(USRP3)) == 0 || device >= 3)
		{
			ChipErase();
		}
		else if ((Code & _BV(USRP1)) == 0)
		{
			FuseLowBits = 0xE1;
			FuseHighBits = 0x5D;
		}
		else
		{
			FuseLowBits = 0xE1;
			FuseHighBits = 0xDD;
		}

		if (FuseLowBits != 0)
			WriteFuseLowBits(FuseLowBits);

		if (FuseHighBits != 0)
			WriteFuseHighBits(FuseHighBits);

		if (FuseExtendedBits != 0)
			WriteFuseExtendedBits(FuseExtendedBits);
	}

	// Power-off sequence
	_delay_us(20);
	PORTB=(0<<V12)|(1<<VCC)|(1<<RST);	// Prog down
	_delay_us(5);
	PORTB=(0<<V12)|(0<<VCC)|(1<<RST);	// Shutdown


	sbi(PORTD,LED);						// Light the LED
	if (!ok)
	{
		_delay_ms(1000);
	}
	else if (CpuEnd(device))
	{
		_delay_ms(300);
	}
	else 
	{
		_delay_ms(100);
	}
	cbi(PORTD,LED);						// Off the LED

	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	while(1)
	{
		sleep_mode();
	}
}

