/*
   Copyright (c) 2024-2026 by Plexim GmbH
   All rights reserved.

   A free license is granted to anyone to use this software for any legal
   non safety-critical purpose, including commercial applications, provided
   that:
   1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
   2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
 */

#ifndef PLX_CMPSS_H_
#define PLX_CMPSS_H_

#include "includes.h"

#include "cmpss.h"
// #include "plx_cmpss_impl.h" // implementation specific

// CMPSS peripheral type, matching TI's classification:
#if defined(TARGET_2837x)
#  define PLX_CMPSS_TYPE 0
#elif defined(TARGET_28004x) || defined(TARGET_2838x)
#  define PLX_CMPSS_TYPE 1
#elif defined(TARGET_28003x)
#  define PLX_CMPSS_TYPE 2
#elif defined(TARGET_280013x)
#  define PLX_CMPSS_TYPE 3
#elif defined(TARGET_28p55x) || defined(TARGET_28p65x) || \
      defined(TARGET_29h85x) || defined(TARGET_29p58x)
#  define PLX_CMPSS_TYPE 6
#else
#  error "PLX_CMPSS_TYPE: unrecognized target, please add it to plx_cmpss.h"
#endif

// Defined when the driverlib provides per-side ramp functions
// (CMPSS_setRampReferenceHigh/Low, CMPSS_setRampDelayHigh/Low).
// Supported on: 28p55x, 28p65x, 29h85x.
// Other targets use the legacy CMPSS_setMaxRampValue / CMPSS_setRampDelayValue instead.
#if defined(TARGET_28p55x) || defined(TARGET_28p65x) || defined(TARGET_29h85x)
#  define PLX_CMPSS_HAS_HIGH_LOW_RAMP_API
#endif

// Defined when the hardware supports an independent low-side ramp generator.
#if PLX_CMPSS_TYPE >= 5
#  define PLX_CMPSS_HAS_LOW_SIDE_RAMP
#endif

// Defined when the hardware supports a ramp clock divider (RAMPCLKDIV).
#if PLX_CMPSS_TYPE >= 6
#  define PLX_CMPSS_HAS_RAMP_CLK_DIVIDER
#endif

// Default ramp clock divider (divide-by-1, no scaling).
#define PLX_CMPSS_RAMP_CLK_DIVIDER_DEFAULT 1
// Convert SysClk divisor (1–16) to CMPSS_RampClockDiv register value (0–15).
#define PLX_CMPSS_RAMP_CLK_DIV_TO_REG(div) ((CMPSS_RampClockDiv)((div) - 1U))

#define VDDA_VOLTAGE 3.3                          // Analog supply voltage
#define PLX_CMPSS_DAC_REFERENCE_VOLTAGE VDDA_VOLTAGE  // CMPSS DAC vref tied to VDDA

// CMPSS DAC configuration without ramp functionality (12-bit mode)
#define PLX_CMPSS_DAC_RESOLUTION 4096  // 12-bit DAC resolution (2^12)
#define PLX_CMPSS_DAC_MAX_COUNT 4095   // Maximum 12-bit DAC count value (2^12 - 1)
#define PLX_CMPSS_DAC_VOLTS_TO_COUNTS \
  ((float)(PLX_CMPSS_DAC_RESOLUTION) / (PLX_CMPSS_DAC_REFERENCE_VOLTAGE))

// CMPSS ramp configuration (16-bit DAC with delay extension)
#define PLX_CMPSS_RAMP_DELAY_MAX_COUNT 0x1FFF  // Maximum ramp delay count (13-bit)
#define PLX_CMPSS_RAMP_STEP_MIN 1  // Min ramp step value (prevent divide-by-zero)
#define PLX_CMPSS_RAMP_RESOLUTION 65536      // 16-bit ramp resolution (2^16)
#define PLX_CMPSS_RAMP_MAX_COUNT UINT16_MAX  // Max 16-bit ramp count (2^16 - 1)
#define PLX_CMPSS_RAMP_VOLTS_TO_COUNTS \
  ((float)(PLX_CMPSS_RAMP_RESOLUTION) / (PLX_CMPSS_DAC_REFERENCE_VOLTAGE))

typedef struct {
  uint16_t rampStepVal;
  uint16_t rampClkDivider;
} PLX_CMPSS_RampConfig_t;

typedef struct PLX_CMPSS_OBJ {
  uint32_t base;
  PLX_CMPSS_RampConfig_t compH;
  PLX_CMPSS_RampConfig_t compL;
} PLX_CMPSS_Obj_t;

typedef PLX_CMPSS_Obj_t* PLX_CMPSS_Handle_t;

typedef struct PLX_CMPSS_RampParams {
  uint16_t rampRef;
  uint16_t rampDelay;
} PLX_CMPSS_RampParams_t;

// Initialize CMPSS handle with provided memory.
// Returns NULL if insufficient memory is provided.
static inline PLX_CMPSS_Handle_t PLX_CMPSS_init(void* aMemory, const size_t aNumBytes) {
  PLX_CMPSS_Handle_t handle;

  if (aNumBytes < sizeof(PLX_CMPSS_Obj_t)) {
    return ((PLX_CMPSS_Handle_t)NULL);
  }

  // set handle
  handle = (PLX_CMPSS_Handle_t)aMemory;
  return handle;
}

// Configure CMPSS module with base address and ramp step value.
// Ramp step is clamped to PLX_CMPSS_RAMP_STEP_MIN to prevent divide-by-zero.
static inline void PLX_CMPSS_configure(PLX_CMPSS_Handle_t aHandle,
                         uint32_t aCmpssBase,
                         uint16_t aCompHRampStepVal,
                         uint16_t aCompLRampStepVal,
                         uint16_t aRampClkDividerH,
                         uint16_t aRampClkDividerL) {
  PLX_CMPSS_Obj_t* obj = (PLX_CMPSS_Obj_t*)aHandle;
  obj->base = aCmpssBase;

  // --- Ramp clock division ---
  if (aRampClkDividerH < PLX_CMPSS_RAMP_CLK_DIVIDER_DEFAULT) {
    // saturate to minimum allowed ramp clock divider
    aRampClkDividerH = PLX_CMPSS_RAMP_CLK_DIVIDER_DEFAULT;
  }

  if (aRampClkDividerL < PLX_CMPSS_RAMP_CLK_DIVIDER_DEFAULT) {
    // saturate to minimum allowed ramp clock divider
    aRampClkDividerL = PLX_CMPSS_RAMP_CLK_DIVIDER_DEFAULT;
  }

  obj->compH.rampClkDivider = aRampClkDividerH;
  obj->compL.rampClkDivider = aRampClkDividerL;

  // --- Ramp step config ---
  if (aCompHRampStepVal < PLX_CMPSS_RAMP_STEP_MIN) {
    // saturate to minimum allowed ramp
    obj->compH.rampStepVal = PLX_CMPSS_RAMP_STEP_MIN;
  } else {
    obj->compH.rampStepVal = aCompHRampStepVal;
  }

  if (aCompLRampStepVal < PLX_CMPSS_RAMP_STEP_MIN) {
    // saturate to minimum allowed ramp
    obj->compL.rampStepVal = PLX_CMPSS_RAMP_STEP_MIN;
  } else {
    obj->compL.rampStepVal = aCompLRampStepVal;
  }
}

// Convert voltage to 12-bit DAC counts with saturation to valid range.
static inline uint16_t PLX_CMPSS_voltsToDacCounts(float aVal) {
  float val = PLX_CMPSS_DAC_VOLTS_TO_COUNTS * aVal;
  uint16_t valInt = 0;
  if (val > PLX_CMPSS_DAC_MAX_COUNT) {
    valInt = PLX_CMPSS_DAC_MAX_COUNT;
  } else if (val > 0) {
    valInt = (uint16_t)val;
  }
  return valInt;
}

// Set the compH DAC threshold voltage for the CMPSS module without ramp
// functionality.
static inline void PLX_CMPSS_setCompHDacVal(PLX_CMPSS_Handle_t aHandle,
                                        float aVal) {
  PLX_CMPSS_Obj_t* obj = (PLX_CMPSS_Obj_t*)aHandle;
  uint32_t base = obj->base;
  CMPSS_setDACValueHigh(base, PLX_CMPSS_voltsToDacCounts(aVal));
}

// Set the compL DAC threshold voltage for the CMPSS module without ramp
// functionality.
static inline void PLX_CMPSS_setCompLDacVal(PLX_CMPSS_Handle_t aHandle,
                                        float aVal) {
  PLX_CMPSS_Obj_t* obj = (PLX_CMPSS_Obj_t*)aHandle;
  uint32_t base = obj->base;
  CMPSS_setDACValueLow(base, PLX_CMPSS_voltsToDacCounts(aVal));
}

// Convert peak voltage to 16-bit ramp reference and ramp delay,
// clamping to DAC range and compensating with delay for out-of-range values.
// PLX_CMPSS_configure enforces compH/compL.rampStepVal >= 1, preventing divide-by-zero.
//
// Note: no rampClkDivider correction is needed. aRampStepVal is scaled
// proportionally to rampClkDivider so that the target slope is achieved.
// This means the ramp delay count (overflow / aRampStepVal, in RAMPCLK cycles)
// is invariant to the choice of rampClkDivider.
static inline PLX_CMPSS_RampParams_t PLX_CMPSS_calcRampParams(float aPeak,
                                                               uint16_t aRampStepVal) {
  // Convert peak voltage to DAC counts with saturation protection to prevent
  // overflow
  float val = PLX_CMPSS_RAMP_VOLTS_TO_COUNTS * aPeak;
  int32_t valInt = 0;
  if (val > (float)INT32_MAX) {
    valInt = INT32_MAX;
  } else if (val < (float)INT32_MIN) {
    valInt = INT32_MIN;
  } else {
    valInt = (int32_t)val;
  }

  uint32_t rampDelay;

  // Clamp valInt to the DAC resolution and use a ramp delay to compensate
  // for the out-of-range portion. The ramp delay extends the time at the
  // clamped value to achieve the desired behavior.
  if (valInt > PLX_CMPSS_RAMP_MAX_COUNT) {
    rampDelay = (uint32_t)((valInt - PLX_CMPSS_RAMP_MAX_COUNT) / aRampStepVal);
    valInt = PLX_CMPSS_RAMP_MAX_COUNT;
  } else if (valInt < 0) {
    rampDelay = (uint32_t)((0 - valInt) / aRampStepVal);
    valInt = 0;
  } else {
    rampDelay = 0;
  }

  // Saturate ramp delay to maximum allowable value
  if (rampDelay > PLX_CMPSS_RAMP_DELAY_MAX_COUNT) {
    rampDelay = PLX_CMPSS_RAMP_DELAY_MAX_COUNT;
  }

  PLX_CMPSS_RampParams_t result = {
    .rampRef = (uint16_t)valInt,
    .rampDelay = (uint16_t)rampDelay
  };
  return result;
}

// Calculates and sets the peak ramp value, applying a ramp delay
// when the desired value exceeds the DAC range.
static inline void PLX_CMPSS_setCompHRampRef(PLX_CMPSS_Handle_t aHandle, float aPeak) {
  PLX_CMPSS_Obj_t* obj = (PLX_CMPSS_Obj_t*)aHandle;
  uint32_t base = obj->base;
  PLX_CMPSS_RampParams_t ramp = PLX_CMPSS_calcRampParams(aPeak, obj->compH.rampStepVal);
#ifdef PLX_CMPSS_HAS_HIGH_LOW_RAMP_API
  CMPSS_setRampDelayHigh(base, ramp.rampDelay);
  CMPSS_setRampReferenceHigh(base, ramp.rampRef);
#else
  CMPSS_setRampDelayValue(base, ramp.rampDelay);
  CMPSS_setMaxRampValue(base, ramp.rampRef);
#endif
}

// Calculates and sets the peak ramp value, applying a ramp delay
// when the desired value exceeds the DAC range.
// Low-side ramp requires PLX_CMPSS_HAS_LOW_SIDE_RAMP; no-op otherwise.
static inline void PLX_CMPSS_setCompLRampRef(PLX_CMPSS_Handle_t aHandle, float aPeak) {
#ifdef PLX_CMPSS_HAS_LOW_SIDE_RAMP
  PLX_CMPSS_Obj_t* obj = (PLX_CMPSS_Obj_t*)aHandle;
  uint32_t base = obj->base;
  PLX_CMPSS_RampParams_t ramp = PLX_CMPSS_calcRampParams(aPeak, obj->compL.rampStepVal);
  CMPSS_setRampDelayLow(base, ramp.rampDelay);
  CMPSS_setRampReferenceLow(base, ramp.rampRef);
#endif
}

#endif /* PLX_CMPSS_H_ */
