/* Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 */

#include "ARMCM0.h"
#include "adc.h"
#include "bsp/dp32g030/irq.h"
#include "bsp/dp32g030/saradc.h"
#include "bsp/dp32g030/syscon.h"

uint8_t ADC_GetChannelNumber(ADC_CH_MASK Mask)
{
	if (Mask & ADC_CH15) return 15U;
	if (Mask & ADC_CH14) return 14U;
	if (Mask & ADC_CH13) return 13U;
	if (Mask & ADC_CH12) return 12U;
	if (Mask & ADC_CH11) return 11U;
	if (Mask & ADC_CH10) return 10U;
	if (Mask & ADC_CH9) return 9U;
	if (Mask & ADC_CH8) return 8U;
	if (Mask & ADC_CH7) return 7U;
	if (Mask & ADC_CH6) return 6U;
	if (Mask & ADC_CH5) return 5U;
	if (Mask & ADC_CH4) return 4U;
	if (Mask & ADC_CH3) return 3U;
	if (Mask & ADC_CH2) return 2U;
	if (Mask & ADC_CH1) return 1U;
	if (Mask & ADC_CH0) return 0U;

	return 0U;
}

void ADC_Disable(void)
{
	SARADC_CFG = (SARADC_CFG & ~SARADC_CFG_ADC_EN_MASK) | SARADC_CFG_ADC_EN_BITS_DISABLE;
}

void ADC_Enable(void)
{
	SARADC_CFG = (SARADC_CFG & ~SARADC_CFG_ADC_EN_MASK) | SARADC_CFG_ADC_EN_BITS_ENABLE;
}

void ADC_SoftReset(void)
{
	SARADC_START = (SARADC_START & ~SARADC_START_SOFT_RESET_MASK) | SARADC_START_SOFT_RESET_BITS_ASSERT;
	SARADC_START = (SARADC_START & ~SARADC_START_SOFT_RESET_MASK) | SARADC_START_SOFT_RESET_BITS_DEASSERT;
}

// The firmware thinks W_SARADC_SMPL_CLK_SEL is at [8:7] but the TRM says it's at [10:9]
#define FW_R_SARADC_SMPL_SHIFT 7
#define FW_R_SARADC_SMPL_MASK (3U << FW_R_SARADC_SMPL_SHIFT)

uint32_t ADC_GetClockConfig(void)
{
	uint32_t Value;

	Value = SYSCON_CLK_SEL;

	Value = 0
		| (Value & ~(SYSCON_CLK_SEL_R_PLL_MASK | FW_R_SARADC_SMPL_MASK))
		| (((Value & SYSCON_CLK_SEL_R_PLL_MASK) >> SYSCON_CLK_SEL_R_PLL_SHIFT) << SYSCON_CLK_SEL_W_PLL_SHIFT)
		| (((Value & FW_R_SARADC_SMPL_MASK) >> FW_R_SARADC_SMPL_SHIFT) << SYSCON_CLK_SEL_W_SARADC_SMPL_SHIFT)
		;

	return Value;
}

void ADC_Configure(ADC_Config_t *pAdc)
{
	SYSCON_DEV_CLK_GATE = (SYSCON_DEV_CLK_GATE & ~SYSCON_DEV_CLK_GATE_SARADC_MASK) | SYSCON_DEV_CLK_GATE_SARADC_BITS_ENABLE;

	ADC_Disable();

	SYSCON_CLK_SEL = (ADC_GetClockConfig() & ~SYSCON_CLK_SEL_W_SARADC_SMPL_MASK) | ((pAdc->CLK_SEL << SYSCON_CLK_SEL_W_SARADC_SMPL_SHIFT) & SYSCON_CLK_SEL_W_SARADC_SMPL_MASK);

	SARADC_CFG = 0
		| (SARADC_CFG & ~(0
			| SARADC_CFG_CH_SEL_MASK
			| SARADC_CFG_AVG_MASK
			| SARADC_CFG_CONT_MASK
			| SARADC_CFG_SMPL_SETUP_MASK
			| SARADC_CFG_MEM_MODE_MASK
			| SARADC_CFG_SMPL_CLK_MASK
			| SARADC_CFG_SMPL_WIN_MASK
			| SARADC_CFG_ADC_TRIG_MASK
			| SARADC_CFG_DMA_EN_MASK
			))
		| ((pAdc->CH_SEL     << SARADC_CFG_CH_SEL_SHIFT)     & SARADC_CFG_CH_SEL_MASK)
		| ((pAdc->AVG        << SARADC_CFG_AVG_SHIFT)        & SARADC_CFG_AVG_MASK)
		| ((pAdc->CONT       << SARADC_CFG_CONT_SHIFT)       & SARADC_CFG_CONT_MASK)
		| ((pAdc->SMPL_SETUP << SARADC_CFG_SMPL_SETUP_SHIFT) & SARADC_CFG_SMPL_SETUP_MASK)
		| ((pAdc->MEM_MODE   << SARADC_CFG_MEM_MODE_SHIFT)   & SARADC_CFG_MEM_MODE_MASK)
		| ((pAdc->SMPL_CLK   << SARADC_CFG_SMPL_CLK_SHIFT)   & SARADC_CFG_SMPL_CLK_MASK)
		| ((pAdc->SMPL_WIN   << SARADC_CFG_SMPL_WIN_SHIFT)   & SARADC_CFG_SMPL_WIN_MASK)
		| ((pAdc->ADC_TRIG   << SARADC_CFG_ADC_TRIG_SHIFT)   & SARADC_CFG_ADC_TRIG_MASK)
		| ((pAdc->DMA_EN     << SARADC_CFG_DMA_EN_SHIFT)     & SARADC_CFG_DMA_EN_MASK)
		;

	SARADC_EXTTRIG_SEL = pAdc->EXTTRIG_SEL;

	if (pAdc->CALIB_OFFSET_VALID) {
		SARADC_CALIB_OFFSET = (SARADC_CALIB_OFFSET & ~SARADC_CALIB_OFFSET_VALID_MASK) | SARADC_CALIB_OFFSET_VALID_BITS_YES;
	} else {
		SARADC_CALIB_OFFSET = (SARADC_CALIB_OFFSET & ~SARADC_CALIB_OFFSET_VALID_MASK) | SARADC_CALIB_OFFSET_VALID_BITS_NO;
	}
	if (pAdc->CALIB_KD_VALID) {
		SARADC_CALIB_KD = (SARADC_CALIB_KD & ~SARADC_CALIB_KD_VALID_MASK) | SARADC_CALIB_KD_VALID_BITS_YES;
	} else {
		SARADC_CALIB_KD = (SARADC_CALIB_KD & ~SARADC_CALIB_KD_VALID_MASK) | SARADC_CALIB_KD_VALID_BITS_NO;
	}

	SARADC_IF = 0xFFFFFFFF;
	SARADC_IE = 0
		| (SARADC_IE & ~(0
			| SARADC_IE_CHx_EOC_MASK
			| SARADC_IE_FIFO_FULL_MASK
			| SARADC_IE_FIFO_HFULL_MASK
			))
		| ((pAdc->IE_CHx_EOC    << SARADC_IE_CHx_EOC_SHIFT)    & SARADC_IE_CHx_EOC_MASK)
		| ((pAdc->IE_FIFO_FULL  << SARADC_IE_FIFO_FULL_SHIFT)  & SARADC_IE_FIFO_FULL_MASK)
		| ((pAdc->IE_FIFO_HFULL << SARADC_IE_FIFO_HFULL_SHIFT) & SARADC_IE_FIFO_HFULL_MASK)
		;

	if (SARADC_IE == 0) {
		NVIC_DisableIRQ((IRQn_Type)DP32_SARADC_IRQn);
	} else {
		NVIC_EnableIRQ((IRQn_Type)DP32_SARADC_IRQn);
	}
}

void ADC_Start(void)
{
	SARADC_START = (SARADC_START & ~SARADC_START_START_MASK) | SARADC_START_START_BITS_ENABLE;
}

bool ADC_CheckEndOfConversion(ADC_CH_MASK Mask)
{
	volatile ADC_Channel_t *pChannels = (volatile ADC_Channel_t *)&SARADC_CH0;
	uint8_t Channel = ADC_GetChannelNumber(Mask);

	return (pChannels[Channel].STAT & ADC_CHx_STAT_EOC_MASK) >> ADC_CHx_STAT_EOC_SHIFT;
}

uint16_t ADC_GetValue(ADC_CH_MASK Mask)
{
	volatile ADC_Channel_t *pChannels = (volatile ADC_Channel_t *)&SARADC_CH0;
	uint8_t Channel = ADC_GetChannelNumber(Mask);

	SARADC_IF = 1 << Channel; // TODO: Or just use 'Mask'

	return (pChannels[Channel].DATA & ADC_CHx_DATA_DATA_MASK) >> ADC_CHx_DATA_DATA_SHIFT;
}