/**
 * fan_motor_control.c
 *
 * Fan power control and speed sensing.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <asf.h>
#include "fan_motor_control.h"

Fan_State_T fan_state;
uint32_t fan_speed_hz; 
uint32_t fan_speed_rpm;

void init_fan(void) {
	// Startup the Timer/Counter module
	sysclk_enable_peripheral_clock(ID_TC0);
	sysclk_enable_peripheral_clock(ID_TC1);
	// Set input / output pin modes
	ioport_set_pin_mode(PIN_FAN_PWM, PINMODE_FAN_PWM);
	ioport_disable_pin(PIN_FAN_PWM);
	ioport_set_pin_mode(PIN_FAN_FREQ, PINMODE_FAN_FREQ | IOPORT_MODE_PULLUP);
	ioport_disable_pin(PIN_FAN_FREQ);
	//pio_set_peripheral(FAN_PIO, PINMODE_FAN_FREQ, FAN_FG_PIN_MASK);
	//pio_set_peripheral(FAN_PIO, PINMODE_FAN_PWM, PIN_FAN_PWM);
	
	// Set pullup on FG pin. The fan's speed output is open drain
	//FAN_FG_PORT->PIO_PUER |= FAN_FG_PIN_MASK;
	
	// Enable PCK3 for 1MHz
	PMC->PMC_PCK[3] = PCK3_SRC + PCK3_DIV; // Main clock divided by 32
	PMC->PMC_SCER = PMC_SCER_PCK3; // Enable PCK3
	
	// Channel 0 used for counting (input)
	TC0->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK5 // PCK3 (set to PLL0 / 98, about 1MHz)
							// Overflow will occur after 2^16 - 1 or 65535 counts, which is 262ms (about 1/4 second)
							+ TC_CMR_ETRGEDG_FALLING // external trigger on falling edges
							+ TC_CMR_ABETRG // external trigger is 'A' input
							+ TC_CMR_LDRA_RISING // rising edges load the capture 'A' register
							+ TC_CMR_LDRB_FALLING; // falling edges load the capture 'B' register (and resets, from ext. trigger)
	TC0->TC_CHANNEL[0].TC_IER = TC_IER_COVFS + TC_IER_LDRBS; // Interrupt after two captures (B was loaded, following A load) or overflow
	TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN + TC_CCR_SWTRG; // Enable the clock
	
	// Channel 1 used for PWM output
	// Upcounting mode, counts up to value of RC and then resets.
	// The frequency is then TimerClk / (RC+1)
	// Output B is set when counter is at zero (at RC match), and is reset
	// at RB match. This makes it easy to set PWM duty cycle. Duty cycle 
	// is simply the value of RB / RC.
	TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // MCK / 8 = 98.3MHz / 8 = 12.288MHz
							+ TC_CMR_WAVE // Waveform output mode
							+ TC_CMR_WAVSEL_UP_RC // Counts up to RC and then resets to zero, counting up again
							+ TC_CMR_BCPB_CLEAR // Output B clears on RB match
							+ TC_CMR_BCPC_CLEAR // Output B also clears on RC match. This gets changed to "set on RC match" whenever
												// duty cycle isn't zero. If it's on "set", the output is 100% high even at zero duty
							+ TC_CMR_EEVT_XC0; // Anything except B as ext trig. Otherwise, B is set as input
	TC0->TC_CHANNEL[1].TC_RC = FAN_PWM_RC;	
	TC0->TC_CHANNEL[1].TC_RB = 0; // Start with 0% duty
	TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN + TC_CCR_SWTRG; // Enable the clock
	fan_state = FAN_STOPPED;
	
	// Enable capture and overflow interrupts
	NVIC_EnableIRQ(TC0_IRQn);
	NVIC_SetPriority(TC0_IRQn, FAN_INTERRUPT_PRIO); // Lower priority than USB, that's all that matters probably.
}

void disable_fan(void) {
	NVIC_DisableIRQ(TC0_IRQn);
	ioport_set_pin_mode(PIN_FAN_PWM, 0);
	ioport_enable_pin(PIN_FAN_PWM);
	ioport_set_pin_dir(PIN_FAN_PWM, IOPORT_DIR_OUTPUT);
	ioport_set_pin_level(PIN_FAN_PWM, false);
	sysclk_disable_peripheral_clock(ID_TC0);
	sysclk_disable_peripheral_clock(ID_TC1);
}

void set_fan_pwm(uint16_t duty_cycle) {
	// Convert 0 to 100 duty cycle into timer counts
	if(duty_cycle > 100) {
		duty_cycle = 100;
	}
	uint32_t max_duty = TC0->TC_CHANNEL[1].TC_RC;
	uint32_t new_counts = duty_cycle*max_duty / 100;
	TC0->TC_CHANNEL[1].TC_RB = new_counts;
	if(duty_cycle == 0) {
		// Change Set on RC match to Clear on RC match
		// Otherwise the output is 100% high, and we don't want that
		TC0->TC_CHANNEL[1].TC_CMR &= ~(TC_CMR_BCPC_Msk);
		TC0->TC_CHANNEL[1].TC_CMR |= TC_CMR_BCPC_CLEAR;
	} else {
		// Change back to normal
		TC0->TC_CHANNEL[1].TC_CMR &= ~(TC_CMR_BCPC_Msk);
		TC0->TC_CHANNEL[1].TC_CMR |= TC_CMR_BCPC_SET;
	}
}

uint16_t get_fan_speed(void) {
	return (uint16_t) fan_speed_rpm;
}

void TC0_Handler(void) {
	// Get status register - this clears all interrupts, so we better service anything that was set
	uint32_t tcstatus = TC0->TC_CHANNEL[0].TC_SR;
	
	if((tcstatus & TC_SR_COVFS)!=0) {
		// Overflow occurred. Means fan is stopped for at least 1/4 second
		fan_state = FAN_STOPPED;
		fan_speed_hz = 0;
		fan_speed_rpm = 0;
	}
	if((tcstatus & TC_SR_LDRBS) != 0) {
		// Capture B happened, so we got a rising and falling edge in a row! Fan is spinnin
		fan_state = FAN_RUNNING;
		// Do the math on how fast it's going
		// RB is two captures in, a high period then a low period
		// This is 1/3 of a full fan rotation, according to our friends at Delta Electronics
		// BSB0205HA5-00HSF
		fan_speed_hz = FAN_FG_TIMERCLK / 3 / (TC0->TC_CHANNEL[0].TC_RB);
		fan_speed_rpm = fan_speed_hz * 60;
	}
}
