#include "common.h"
#include "bus/pwm.h"

int PWM_Init(void)
{
	// PD4: PWM NF-12 Fan Peltier Hot Side
	// PD5: PWM NF-A8 Fan Peltier Cold Side
	// PD7: PWM NF-R8 Fan Heating Element

	// ATMega32A does not have more than two outputs for the
	// 16-bit timer and the other 8-bit timers don't have modes
	// where the value of TOP can be changed. We can only get
	// 25 KHz with software PWM on this chip as far as I know.

	// The 328P would allow us to use OCR2A as top but with
	// 8-bit this gives us a really low duty step size of 2.5%.
	// Ideal would be two 16-bit timers with two outputs each.

	DDRD |= BIT(PD4) | BIT(PD5) | BIT(PD7);
	// PORTD &= ~BIT(PD7); // Turn off PD7

	// TCCR1A Timer1 Control Register A
	// 7       6       5       4       3      2      1      0
	// COM1A1  COM1A0  COM1B1  COM1B0  FOC1A  FOC1B  WGM11  WGM10

	// TCCR1B Timer1 Control Register B
	// 7       6       5       4       3      2      1      0
	// CNC1    ICES1   –       WGM13   WGM12  CS12   CS11   CS10

	// The COM1A1:0 and COM1B1:0 control the Output
	// Compare pins (OC1A and OC1B respectively) behavior.
	// If one or both of the COM1A1:0 bits are written to
	// one, the OC1A output overrides the normal port
	// functionality of the I/O pin it is connected to.

	// If one or both of the COM1B1:0 bit are written to
	// one, the OC1B output overrides the normal port
	// functionality of the I/O pin it is connected to.
	// However, note that the Data Direction Register
	// (DDR) bit corresponding to the OC1A or OC1B pin
	// must be set in order to enable the output driver.

	// COM1B1:0: Compare Output Mode, Fast PWM
	// COM1A1/   COM1A0/   Description
	// COM1B1    COM1B0
	// 0         0         OC1A/OC1B disconnected.
	// 0         1         WGM13:0 = 15: Toggle OC1A on match,
	//                     OC1B disconnected. For other WGM13:0
	//                     settings OC1A/OC1B disconnected.
	// 1         0         Clear OC1A/OC1B on match and set at
	//                     BOTTOM (non-inverting).
	// 1         1         Set OC1A/OC1B on match and clear at
	//                     BOTTOM (inverting mode).

	// A special case occurs when OCR1A/OCR1B equals TOP
	// and COM1A1/COM1B1 is set. In this case the compare
	// match is ignored, but the set or clear is done at
	// BOTTOM.

	// FOC1A/FOC1B: Force Output Compare for Compare unit A/B
	// For ensuring compatibility with future devices, these
	// bits must be set to zero when TCCR1A is written when
	// operating in a PWM mode.

	// WGM11:0: Waveform Generation Mode
	// Combined with the WGM13:2 bits found in the TCCR1B
	// Register, these bits control the counting sequence of
	// the counter, the source for maximum (TOP) counter
	// value, and what type of waveform generation to be used.
	// See page 115 of the ATMega32A data sheet for all modes.

	// Mode  WGM13 WGM12 WGM11 WGM10  Timer Mode   TOP
	// 14    1     1     1     0      Fast PWM     ICR1

	// CS12:0: Clock Select

	// CS12  CS11  CS10  Description
	// 0     0     0     No clock source.
	// 0     0     1     clk I/O /1 no prescaling.
	// 0     1     0     clk I/O /8 from prescaler.
	// 0     1     1     clk I/O /64 from prescaler.
	// 1     0     0     clk I/O /256 from prescaler.
	// 1     0     1     clk I/O /1024 from prescaler.
	// 1     1     0     External clock on T1 falling edge.
	// 1     1     1     External clock on T1 rising edge.

	// TIMER1: Fast mode, non-inverting, top=ICR1, prescale /1

	TCCR1A = BIT(WGM11) | BIT(COM1A1) | BIT(COM1B1);
	TCCR1B = BIT(WGM12) | BIT(WGM13) | BIT(CS10);
	ICR1 = PWM_CYCLE_TOP; // 8000 MHz / 25000 KHz

	OCR1B = FAN01_MIN_DUTY; // PD4
	OCR1A = FAN02_MIN_DUTY; // PD5

	// TIMER2: Fast mode, non-inverting, top=0xFF, prescale /8
	// TOP set to 8000000 (f_cpu) / 8 (prescale) / 25000 (f_pwm) - 1

	TCCR2 = BIT(WGM20) | BIT(WGM21) | BIT(COM21) | BIT(CS21);
	OCR2 = 255; // XXX: OCR2A=TOP OCR2B=duty

	// TCCR2 = 0; // Normal operation
	// OCR2 = FAN03_MIN_DUTY; // PD7
	// PORTD &= ~BIT(PD7);  // Low

	return 0;
}

// Value in range 0-100 is expected
void PWM_SetValue(int port, int value)
{
	int n;

	if (port != FAN01 && port != FAN02 && port != FAN03)
		return; // Invalid port

	n = CLAMP(value, 100, 0) * (PWM_CYCLE_TOP / 100.0f);

	Info("Setting duty cycle for %s to %d/%d...",
		(port == FAN01) ? "FAN01" :
		(port == FAN02) ? "FAN02" :
		(port == FAN03) ? "FAN03" :
		"UNKNOWN", n, PWM_CYCLE_TOP);

	switch (port) {
	case PD4: OCR1B = n; break;
	case PD5: OCR1A = n; break;
	case PD7: OCR2  = n; break;
	}
}
