/* Reflow Oven Controller
 *
 * Copyright (C) 2020  Mario Hüttel <mario.huettel@gmx.net>
 *
 * This file is part of the Reflow Oven Controller Project.
 *
 * The reflow oven controller is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the reflow oven controller project.
 * If not, see <http://www.gnu.org/licenses/>.
 */

#include <reflow-controller/adc-meas.h>
#include <stm32/stm32f4xx.h>
#include <cmsis/core_cm4.h>
#include <stm-periph/stm32-gpio-macros.h>
#include <stdlib.h>
#include <stm-periph/clock-enable-manager.h>

static float pt1000_offset;
static float pt1000_sens_dev;
static bool calibration_active;
static float filter_alpha;
static volatile float pt1000_res_raw_lf;
static volatile bool filter_ready;
static volatile enum adc_pt1000_error pt1000_error = ADC_PT1000_INACTIVE;
static volatile int * volatile streaming_flag_ptr = NULL;
static uint32_t filter_startup_cnt;
static volatile float adc_pt1000_raw_reading_hf;
static volatile uint16_t dma_sample_buffer[ADC_PT1000_DMA_AVG_SAMPLES];
static volatile uint32_t adc_watchdog_counter = 0UL;

volatile float * volatile stream_buffer = NULL;
volatile uint32_t stream_count;
volatile uint32_t stream_pos;

#define ADC_TO_RES(adc) ((float)(adc) / 4096.0f * 2500.0f)

static inline void adc_pt1000_stop_sample_frequency_timer()
{
	TIM2->CR1 &= ~TIM_CR1_CEN;
	rcc_manager_disable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_TIM2EN));
}

static inline void adc_pt1000_setup_sample_frequency_timer()
{
	rcc_manager_enable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_TIM2EN));

	/* Divide 42 MHz peripheral clock by 42 */
	TIM2->PSC = (42UL-1UL);

	/* Reload value */
	TIM2->ARR = ADC_PT1000_SAMPLE_CNT_DELAY;

	/* Trigger output at update event */
	TIM2->CR2 = TIM_CR2_MMS_1;

	/* Start Timer in downcounting mode with autoreload */
	TIM2->CR1 = TIM_CR1_DIR | TIM_CR1_CEN;

}

static inline void adc_pt1000_disable_adc()
{
	ADC_PT1000_PERIPH->CR2 &= ~ADC_CR2_ADON;
	DMA2_Stream0->CR = 0;

	pt1000_error |= ADC_PT1000_INACTIVE;

	rcc_manager_disable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC3EN));
	rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(ADC_PT1000_PORT_RCC_MASK));
}

/**
 * @brief Enable DMA Stream for ADC
 *
 * DMA2 Stream 0 is used. It will capture @ref ADC_PT1000_DMA_AVG_SAMPLES measurement values,
 * delete the two most extreme values
 * and calculate the avereage over the remaining values. This ensures, that one time errors are
 * not included in the measurement.
 *
 * After that, the moving average filter is fed with the values.
 *
 */
static inline void adc_pt1000_enable_dma_stream()
{
	/* Enable peripheral clock for DMA2 */
	rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_DMA2EN));

	/* Destination is the DMA sample buffer */
	DMA2_Stream0->M0AR = (uint32_t)dma_sample_buffer;

	/* Source is the ADC data register */
	DMA2_Stream0->PAR = (uint32_t)&ADC_PT1000_PERIPH->DR;

	/* Transfer size is ADC_PT1000_DMA_AVG_SAMPLES */
	DMA2_Stream0->NDTR = ADC_PT1000_DMA_AVG_SAMPLES;

	NVIC_EnableIRQ(DMA2_Stream0_IRQn);

	/* Enable the stream in Peripheral-to-Memory mode with 16 bit data and a circular destination buffer
	 * Enable interrupt generation on transfer complete
	 *
	 * Todo: Maybe use twice as big of a buffer and also use half-fill interrupt in order to prevent overruns
	 */
	DMA2_Stream0->CR = DMA_SxCR_PL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC |
			DMA_SxCR_CIRC | DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_EN | ((ADC_PT1000_CHANNEL  & 0x7)<<25);
}

static inline void adc_pt1000_disable_dma_stream()
{
	/* Disable the stream */
	DMA2_Stream0->CR = 0;

	/* Disable clock if necessary */
	rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(RCC_AHB1ENR_DMA2EN));

	/* Disable interrupt */
	NVIC_DisableIRQ(DMA2_Stream0_IRQn);
}

void adc_pt1000_setup_meas()
{
	rcc_manager_enable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC3EN));
	rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(ADC_PT1000_PORT_RCC_MASK));

	ADC_PT1000_PORT->MODER |= ANALOG(ADC_PT1000_PIN);

	/* Set S&H time for PT1000 ADC channel */
#if ADC_PT1000_CHANNEL < 10
	ADC_PT1000_PERIPH->SMPR2 |= (7U << (3*ADC_PT1000_CHANNEL));
#else
	ADC_PT1000_PERIPH->SMPR1 |= (7U << (3*(ADC_PT1000_CHANNEL-10)));
#endif

	ADC->CCR |= (0x3<<16);

	/* Set watchdog limits */
	ADC_PT1000_PERIPH->HTR = ADC_PT1000_UPPER_WATCHDOG;
	ADC_PT1000_PERIPH->LTR = ADC_PT1000_LOWER_WATCHDOG;

	/* Set length of sequence to 1 */
	ADC_PT1000_PERIPH->SQR1 = (0UL<<20);

	/* Set channel as 1st element in sequence */
	ADC_PT1000_PERIPH->SQR3 = (ADC_PT1000_CHANNEL<<0);

	ADC_PT1000_PERIPH->CR1 = ADC_CR1_OVRIE | ADC_CR1_AWDEN | ADC_CR1_AWDIE;
	ADC_PT1000_PERIPH->CR2 = ADC_CR2_EXTEN_0 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1 | ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS;

	adc_pt1000_set_moving_average_filter_param(ADC_PT1000_FILTER_WEIGHT);
	adc_pt1000_set_resistance_calibration(0, 0, false);
	pt1000_res_raw_lf = 0.0f;

	NVIC_EnableIRQ(ADC_IRQn);

	adc_pt1000_enable_dma_stream();

	adc_pt1000_setup_sample_frequency_timer();

	pt1000_error &= ~ADC_PT1000_INACTIVE;
}

void adc_pt1000_set_moving_average_filter_param(float alpha)
{
	filter_alpha = alpha;
	filter_ready = false;
	filter_startup_cnt = ADC_FILTER_STARTUP_CYCLES;
}

void adc_pt1000_set_resistance_calibration(float offset, float sensitivity_deviation, bool active)
{
	pt1000_offset = offset;
	pt1000_sens_dev = sensitivity_deviation;
	calibration_active = active;
}

void adc_pt1000_get_resistance_calibration(float *offset, float *sensitivity_deviation, bool *active)
{
	if (!offset || !sensitivity_deviation || !active)
		return;

	*offset = pt1000_offset;
	*sensitivity_deviation = pt1000_sens_dev;
	*active = calibration_active;
}

static inline float adc_pt1000_apply_calibration(float raw_resistance)
{
	if (calibration_active)
		return pt1000_res_raw_lf * (1.0f + pt1000_sens_dev) + pt1000_offset;
	else
		return raw_resistance;

}

int adc_pt1000_get_current_resistance(float *resistance)
{
	int ret_val = 0;

	if (!resistance)
		return -1001;

	*resistance = adc_pt1000_apply_calibration(pt1000_res_raw_lf);

	if (adc_pt1000_check_error()) {
		ret_val = -100;
		goto return_value;
	}

	if (!filter_ready) {
		ret_val = 2;
		goto return_value;
	}

return_value:
	return ret_val;
}

int adc_pt1000_stream_raw_value_to_memory(volatile float *adc_array, uint32_t length, volatile int *flag_to_set)
{
	int ret = 0;

	if (!flag_to_set)
		return -1;

	stream_buffer = adc_array;
	stream_count = length;
	stream_pos = 0U;

	if (adc_array) {
		*flag_to_set = 0;
		streaming_flag_ptr = flag_to_set;
	} else {
		ret = -2;
	}

	return ret;
}

void adc_pt1000_convert_raw_value_array_to_resistance(float *resistance_dest, float *raw_source, uint32_t count)
{
	uint32_t i;

	if (!resistance_dest)
		resistance_dest = raw_source;

	if (!raw_source || !count)
		return;

	for (i = 0; i < count; i++)
		resistance_dest[i] = ADC_TO_RES(raw_source[i]);
}

enum adc_pt1000_error adc_pt1000_check_error()
{
	return pt1000_error;
}

void adc_pt1000_clear_error()
{
	pt1000_error &= ~ADC_PT1000_OVERFLOW & ~ADC_PT1000_WATCHDOG_ERROR;
}

void adc_pt1000_disable()
{
	adc_pt1000_disable_adc();
	adc_pt1000_stop_sample_frequency_timer();
	adc_pt1000_disable_dma_stream();

	filter_ready = false;
	pt1000_res_raw_lf = 0.0f;
	pt1000_error |= ADC_PT1000_INACTIVE;

	if (streaming_flag_ptr) {
		*streaming_flag_ptr = -3;
		streaming_flag_ptr = NULL;
	}
}

static inline __attribute__((optimize("O3"))) void adc_pt1000_filter(float adc_prefiltered_value)
{
	if (!filter_ready && --filter_startup_cnt <= 0)
		filter_ready = true;

	pt1000_res_raw_lf = (1.0f-filter_alpha) * pt1000_res_raw_lf + filter_alpha * ADC_TO_RES(adc_prefiltered_value);
}

static inline __attribute__((optimize("O3"))) float adc_pt1000_dma_avg_pre_filter()
{
	unsigned int i;
	uint32_t sum = 0;
	uint16_t max_val = 0U;
	uint16_t min_val = 65535U;
	uint16_t sample;

	for (i = 0; i < ADC_PT1000_DMA_AVG_SAMPLES; i++) {
		sample = dma_sample_buffer[i];

		/* Update min and max trackers */
		max_val = (sample > max_val ? sample : max_val);
		min_val = (sample < min_val ? sample : min_val);

		/* Sum up all values (for average) */
		sum += sample;
	}

	/* Delete max and min vals from sum */
	sum = sum - (uint32_t)max_val - (uint32_t)min_val;

	/* Divide to get average and return */
	return (float)sum / (float)(ADC_PT1000_DMA_AVG_SAMPLES-2);
}

void ADC_IRQHandler(void)
{
	uint32_t adc1_sr;

	adc1_sr = ADC_PT1000_PERIPH->SR;

	if (adc1_sr & ADC_SR_OVR) {
		ADC_PT1000_PERIPH->SR &= ~ADC_SR_OVR;
		pt1000_error |= ADC_PT1000_OVERFLOW;
		/* Disable ADC  in case of overrrun*/
		adc_pt1000_disable();
	}

	if (adc1_sr & ADC_SR_AWD) {
		ADC_PT1000_PERIPH->SR &= ~ADC_SR_AWD;
		adc_watchdog_counter++;
		if (adc_watchdog_counter >= ADC_PT1000_WATCHDOG_SAMPLE_COUNT)
			pt1000_error |= ADC_PT1000_WATCHDOG_ERROR;
	}
}

static void append_stream_buffer(float val)
{
	if (!stream_buffer || !streaming_flag_ptr)
		return;

	if (stream_pos < stream_count)
		stream_buffer[stream_pos++] = val;

	if (stream_pos >= stream_count) {
		*streaming_flag_ptr = 1;
		streaming_flag_ptr = NULL;
	}

}

void DMA2_Stream0_IRQHandler()
{
	uint32_t lisr;
	float adc_val;

	lisr = DMA2->LISR;
	DMA2->LIFCR = lisr;

	if (lisr & DMA_LISR_TCIF0) {
		/* Samples Transfered */
		adc_val = adc_pt1000_dma_avg_pre_filter();
		adc_pt1000_raw_reading_hf = adc_val;

		if (streaming_flag_ptr)
			append_stream_buffer(adc_val);

		if (adc_watchdog_counter > 0UL)
			adc_watchdog_counter--;

		/* Call moving average filter */
		adc_pt1000_filter(adc_val);
	}

	if (lisr & DMA_LISR_TEIF0) {
		adc_pt1000_disable();
	}

}
