/*
 * Copyright 2009 Ettus Research LLC
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

#include "global.h"
#include "power.h"
#include "debug.h"
#include "error.h"
#include "ltc3675.h"
#ifdef CHARGER_TI
#include "bq24190.h"
#else
#include "ltc4155.h"
#endif // CHARGER_TI

#define AUTO_POWER_ON

#define INITIAL_DELAY	250	// ms

FUSES = {	// FIXME: & FUSE_CKSEL1 for low power 128 kHz clock
	.low = (FUSE_CKSEL0 & FUSE_SUT0 & FUSE_CKDIV8),	// Internal 8MHz Oscillator, Slowly rising power (start-up time), Divide Clock by 8
	.high = (FUSE_EESAVE & FUSE_SPIEN),	// Save EEPROM between flashes	// FIXME: Leave SPIEN for programming enabled?
};	// Not using watchdog as it runs during sleep and consumes power

volatile STATE _state;

/*
    - Main/shared variables must be volatile
	- Port pins are tri-stated on reset
	* AVR_IRQ PD(5)
	- Enable pull-ups on all O.D. outputs from regulator chip
	* PS_POR/SRST should be driven HIGH by ATTiny?
	- AVR_RESET -> RESET pin - don't configure fuse (this would disable this functionality and prohibit serial programming)
	* Ship-and-store mode for charge controller?
	* cli before I2C calls
	* PS_TX
	- en5-clk, en2-data
	* Instruction following SEI is executed before interrupts
	* LTC3675 real-time status doesn't contain UV/OT
	* LTC3675 PGOOD -> power down (no point in checking blink state)
	* On WALL, use TX, on battery use OTG switcher
	* PRR - Power Reduction Register (p40)
	- 100% -> 50% battery charge limit
	* Check latched status for UV/OT in 3675
	* If blink error reset, get latest charge status from 4155
	* Fix UV status check from 3675/4155 as they currently share the same error 
	* Use charger termination at 8hr or Vc/x
	* Check PGood on all regs after power on before setting powered=true
	* Re-init 4155 on soft-power on
	- Re-set 3A limit in 4155 after external power connection
	- Removing power when running on battery, 4155GO 0xA0 - but WALL has been removed
	- Why is charger reporting Constant Current when power is removed
	* ltc3675_is_power_button_depressed: check if any reg is on, otherwise value will be invalid
	* When e.g. 3.3V doesn't come up, blink code is correctly 4 but there's a very short blink before re-starting the sequence
	- Vprog<Vc/x
*/

bool pmc_mask_irqs(bool mask)
{
	if (_state.interrupts_enabled == false)
		return false;
	
	if (mask)
	{
		if (_state.interrupt_depth == 0)
			cli();
		++_state.interrupt_depth;
	}		
	else
	{
		if (_state.interrupt_depth == 0)
			return false;
		
		--_state.interrupt_depth;
		if (_state.interrupt_depth == 0)
			sei();
	}
	
	return true;
}

int main(void)
{
	_delay_ms(INITIAL_DELAY);
	
	///////////////////////////////////////////////////////////////////////////
	
	memset((void*)&_state, 0x00, sizeof(STATE));
	
	debug_init();
	debug_blink(1);
	
	//debug_log("#");	// Will not boot if this is 21 chars long?!
	debug_log("Hello world");
	
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // SLEEP_MODE_PWR_SAVE combination is documented as Reserved
	
//ltc4155_dump();

    // FIXME: Init as SPI slave (FPGA is master)
	
	// 8-bit timer for blinking errors on charge LED
	TCCR0A = _BV(CTC0);		// CTC mode
	OCR0A = 244;			// 250ms with 1024 prescale
	TIMSK0 = _BV(OCIE0A);	// Enable CTC on Timer 0

	bool init_result = power_init();
	debug_log_ex("Init", false);
	_debug_log(init_result ? "+" : "-");
	debug_blink(2);
	//debug_blink_rev(6);
	
	///////////////////////////////////
#ifdef AUTO_POWER_ON
	power_on(); // Turn on immediately. Need to de-press power button to turn off.
	debug_log("Power");
	debug_blink(3);
	//debug_blink_rev(10);

	//debug_wait();
	
//ltc4155_dump();
#endif // AUTO_POWER_ON
	_state.interrupts_enabled = true;
	sei();	// Enable interrupts

	asm("nop");

	_state.wake_up = false;	// This will fire the first time the regs are turned on
	
	bool one_more = false;
	
	while (true)
	{
		one_more = false;
#ifdef CHARGER_TI
		if (_state.bq24190_irq)
		{
			bq24190_handle_irq();
			
			_state.bq24190_irq = false;
		}
#else
		if ((_state.ltc4155_irq)/* || ltc4155_has_interrupt()*/)	// [Don't know why PCINT ISR misses LTC4155 IRQ on power up, so double-check state of line]
		{
			ltc4155_handle_irq();
//ltc4155_dump();
			_state.ltc4155_irq = false;
		}
#endif // !CHARGER_TI
		if (_state.core_power_bad)	// FIXME: Check whether it's supposed to be on
		{
			if (power_is_subsys_on(PS_FPGA))
			{
				_delay_ms(1);	// Seeing weird 120us drop in PGOOD during boot from flash (no apparent drop in 1.0V though)
				
				if (tps54478_is_power_good() == false)
				{
					debug_log("ML:FPGA!");
			
					//power_off();
					_state.power_off = true;
			
					/*while (_state.wake_up == false)
					{
						blink_error_sequence(1);
					}*/
					pmc_set_blink_error(BlinkError_FPGA_Power);	// [If a blink error was set in power_off, this will supercede it]
				}
			}			
			
			_state.core_power_bad = false;
		}
		
		if ((_state.ltc3675_irq)/* || ltc3675_has_interrupt()*/)	// This is fired on initial power up
		{
			debug_log("ML:3675+");
			
			ltc3675_handle_irq();
			
			if (ltc3675_is_power_good(ltc3675_get_last_status()) == false)
			{
				debug_log("ML:3675!");
				
				//power_off();
				_state.power_off = true;
			}
			
			_state.ltc3675_irq = false;
		}
		
		if (_state.power_off)
		{
			debug_log("ML:Off..");
			
			power_off();
			
			_state.power_off = false;
			_state.wake_up = false;
		}
		else if (_state.wake_up)
		{
			_delay_ms(1);	// Tapping 3.1 ohm load ing 4155 in dev setup causes transient on this line and causes power on sequence to begin again
			
			//if (_state.powered == false)	// Don't check in case button is held long enough to force LTC3675 shutdown (will not change 'powered' value)
			if (ltc3675_is_waking_up())
			{
				debug_log("ML:On..");
				
				power_on();
			}
			
			_state.wake_up = false;
		}
		
		// Check to see if the error state has resolved itself at the end of each sequence of the current blink error
		
		if ((_state.blink_error != BlinkError_None) && (_state.blink_last_loop != _state.blink_loops))
		{
			// [Check IRQs periodically]
			
			bool ltc3675_use_last_status = false;
			/*if (ltc3675_has_interrupt())
			{
				//debug_set(IO_PB(6), ((_state.blink_loops % 2) == 0));
				ltc3675_use_last_status = true;
				ltc3675_handle_irq();
			}*/
			
			///////////////////////////
			
			switch (_state.blink_error)
			{
				case BlinkError_LTC3675_UnderVoltage:
				case BlinkError_LTC3675_OverTemperature:
				case BlinkError_DRAM_Power:
				case BlinkError_3_3V_Peripherals_Power:
				case BlinkError_1_8V_Peripherals_Power:
				case BlinkError_TX_Power:
					if (((ltc3675_use_last_status) && (ltc3675_status_to_error(ltc3675_get_last_status()) != BlinkError_None)) || 
						((ltc3675_use_last_status == false) && (ltc3675_check_status() != BlinkError_None)))
						break;
					debug_log("BE:3675-");
					goto cancel_blink_error;
				case BlinkError_FPGA_Power:
					if (tps54478_is_power_good() == false)
						break;
					debug_log("BE:FPGA-");
					goto cancel_blink_error;
				default:
cancel_blink_error:				
					//debug_set(IO_PB(7), true);
					pmc_set_blink_error(BlinkError_None);
			}
			
			////////////////////////////////////
			
			// More periodic checks
			// Need to do this has some interrupts are on PCINT, and while GIE is disabled, might change & change back
			//	E.g. LTC3675 IRQ due to UV, reset IRQ, re-asserts UV
#ifndef CHARGER_TI
			if (ltc4155_has_interrupt())
			{
				debug_log("BE:4155");
				
				_state.ltc4155_irq = true;
				one_more = true;
			}
#endif // !CHARGER_TI
			if (ltc3675_has_interrupt())
			{
				debug_log("BE:3675");
				
				_state.ltc3675_irq = true;
				one_more = true;
			}
			
			if (power_is_subsys_on(PS_FPGA))
			{
				if (tps54478_is_power_good() == false)
				{
					debug_log("BE:FPGA!");
				
					_state.core_power_bad = true;
					one_more = true;
				}
			}
			
			////////////////////////////////////
			
			_state.blink_last_loop = _state.blink_loops;
		}
		
		//if (_state.timers_running == false)
		if ((_state.active_timers == 0) && (one_more == false))
		{
			debug_log("^");
			sleep_mode();
			debug_log("$");
		}			
	}

	return 0;
}

uint8_t pmc_get_blink_error(void)
{
	return _state.blink_error;
}

void pmc_set_blink_error(uint8_t count)
{
	if ((_state.blink_error != BlinkError_None) && (count /*> _state.blink_error*/!= BlinkError_None))	// [Prioritise] Always keep first sequence running
		return;
	else if (_state.blink_error == count)	// Don't restart if the same
		return;
	
	if (count == BlinkError_None)
	{
		debug_log("BLNK-");
		_state.blink_stop = true;
		return;
	}
	
	//char msg[25];
	//sprintf(msg, "Blink code = %i\n", count);
	//debug_log(msg);
	debug_log_ex("BLNK ", false);
	debug_log_byte(count);
	
	_state.blink_error = count;
	_state.blink_loops = 0;
	_state.blink_last_loop = 0;
	_state.blinker_state = 0;
	_state.blink_stop = false;

	charge_set_led(false);
	
	TCNT0 = 0;
	if ((TCCR0A & 0x07) == 0x00)	// Might already be active with existing error
		_state.active_timers++;
	TCCR0A |= 0x05;	// Start with 1024 prescale
}

ISR(TIMER0_COMPA_vect)	// Blink the sequence, and leave one slot at the beginning and end where the LED is off so one can get a sense of how many blinks occurred
{
	pmc_mask_irqs(true);
	
	if (_state.blinker_state < (2 * _state.blink_error + 1))
		charge_set_led((_state.blinker_state % 2) == 1);
	
	_state.blinker_state++;
	
	if (_state.blinker_state == (2 * _state.blink_error + 1 + 1))
	{
		_state.blinker_state = 0;
		
		if (_state.blink_stop)
		{
			if ((TCCR0A & 0x07) != 0x00)
				_state.active_timers--;
			TCCR0A &= ~0x07;
			
			_state.blink_error = BlinkError_None;
			
			debug_log("BLNK.");
		}
		else
		{
			_state.blink_loops++;
		}
	}
	
	pmc_mask_irqs(false);
}