/* 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/safety-adc.h>
#include <reflow-controller/periph-config/safety-adc-hwcfg.h>

#include <stm-periph/clock-enable-manager.h>

void safety_adc_init()
{
	rcc_manager_enable_clock(&RCC->APB2ENR, BITMASK_TO_BITNO(SAFETY_ADC_ADC_RCC_MASK));

	/* Enable temperature and VREFINT measurement */
	ADC->CCR |= ADC_CCR_TSVREFE;

	/* Set sample time for channels 16 and 17 */
	SAFETY_ADC_ADC_PERIPHERAL->SMPR1 |= ADC_SMPR1_SMP17 | ADC_SMPR1_SMP16;

	/* Standard sequence. One measurement */
	SAFETY_ADC_ADC_PERIPHERAL->SQR1 = 0UL;
}


void safety_adc_deinit()
{
	SAFETY_ADC_ADC_PERIPHERAL->CR1 = 0UL;
	SAFETY_ADC_ADC_PERIPHERAL->CR2 = 0UL;
	SAFETY_ADC_ADC_PERIPHERAL->SMPR1  = 0UL;
	rcc_manager_enable_clock(&RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB2ENR_ADC2EN));
}

enum safety_adc_check_result safety_adc_check_results(uint16_t vref_result, uint16_t temp_result,
						      float *vref_calculated, float *temp_calculated)
{
	enum safety_adc_check_result res = SAFETY_ADC_CHECK_OK;


	if (vref_calculated) {
		*vref_calculated = (SAFETY_ADC_INT_REF_MV * 4095.0f) / (float)vref_result;
	}

	if (temp_calculated) {
		*temp_calculated = (((float)temp_result / 4095.0f * 2500.0f -
				     SAFETY_ADC_TEMP_NOM_MV) / SAFETY_ADC_TEMP_MV_SLOPE) + SAFETY_ADC_TEMP_NOM;
	}

	/* TODO: Implement safety ADC checking */

	return res;
}

int safety_adc_poll_result(uint16_t *adc_result)
{
	int ret = 0;

	if (!adc_result)
		return -1000;

	if (!(SAFETY_ADC_ADC_PERIPHERAL->CR2 & ADC_CR2_ADON)) {
		return -1;
	}

	if (SAFETY_ADC_ADC_PERIPHERAL->SR & ADC_SR_EOC) {
		*adc_result = (uint16_t)SAFETY_ADC_ADC_PERIPHERAL->DR;
		SAFETY_ADC_ADC_PERIPHERAL->CR2 &= ~ADC_CR2_ADON;
		ret = 1;
	}

	return ret;
}

void safety_adc_trigger_meas(enum safety_adc_meas_channel measurement)
{
	switch (measurement) {
	case SAFETY_ADC_MEAS_TEMP:
		SAFETY_ADC_ADC_PERIPHERAL->SQR3 = TEMP_CHANNEL_NUM;
		break;
	case SAFETY_ADC_MEAS_VREF:
		SAFETY_ADC_ADC_PERIPHERAL->SQR3 = INT_REF_CHANNEL_NUM;
		break;
	default:
		return;
	}

	SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_ADON;
	SAFETY_ADC_ADC_PERIPHERAL->CR2 |= ADC_CR2_SWSTART;
}

static volatile uint16_t safety_vref_meas_raw;
static volatile bool safety_vref_valid = false;
static volatile uint16_t safety_temp_meas_raw;
static volatile bool safety_temp_valid = false;
static float safety_vref;
static float safety_temp;

enum safety_adc_check_result handle_safety_adc()
{
	static enum safety_adc_meas_channel safety_meas_channel = SAFETY_ADC_MEAS_VREF;
	uint16_t result;
	int poll_status;

	poll_status = safety_adc_poll_result(&result);

	if (poll_status < 0) {
		safety_adc_trigger_meas(safety_meas_channel);
	} else if (poll_status > 0) {
		switch (safety_meas_channel) {
		case SAFETY_ADC_MEAS_TEMP:
			safety_temp_meas_raw = result;
			safety_temp_valid = true;
			safety_meas_channel = SAFETY_ADC_MEAS_VREF;
			break;
		case SAFETY_ADC_MEAS_VREF:
			safety_vref_meas_raw = result;
			safety_vref_valid = true;
			safety_meas_channel = SAFETY_ADC_MEAS_TEMP;
			break;
		default:
			safety_meas_channel = SAFETY_ADC_MEAS_VREF;
			return SAFETY_ADC_INTERNAL_ERROR;
		}
	}

	if (safety_temp_valid && safety_vref_valid) {
		return safety_adc_check_results(safety_vref_meas_raw, safety_temp_meas_raw, &safety_vref, &safety_temp);
	} else {
		return SAFETY_ADC_CHECK_OK;
	}
}

float safety_adc_get_temp()
{
	return safety_temp;
}

float safety_adc_get_vref()
{
	return safety_vref;
}
