/*
   Copyright (c) 2014-2022 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_PWM_IMPL_H_
#define PLX_PWM_IMPL_H_

typedef enum PLX_PWM_UNIT {
  PLX_PWM_EPWM_NONE = 0,
  PLX_PWM_EPWM1,
  PLX_PWM_EPWM2,
  PLX_PWM_EPWM3,
  PLX_PWM_EPWM4,
  PLX_PWM_EPWM5,
  PLX_PWM_EPWM6,
  PLX_PWM_EPWM_MAX_PLUS_ONE
} PLX_PWM_Unit_t;

typedef struct PLX_PWM_REG_PARAMS {
  Uint16 TBPRD;
  union TBPHS_HRPWM_GROUP TBPHS;
  union TBCTL_REG TBCTL;
  union CMPCTL_REG CMPCTL;
  union AQCTL_REG AQCTLA;
  union DBCTL_REG DBCTL;
  Uint16 DBFED;
  Uint16 DBRED;
  union TZCTL_REG TZCTL;
  union ETSEL_REG ETSEL;
  union TZSEL_REG TZSEL;
  union ETPS_REG ETPS;
} PLX_PWM_RegParams_t;

typedef enum PLX_PWM_OUTPUT_MODE {
  PLX_PWM_OUTPUT_MODE_DUAL = 0,
  PLX_PWM_OUTPUT_MODE_SINGLE,
  PLX_PWM_OUTPUT_MODE_DISABLED
} PLX_PWM_OutputMode_t;

typedef struct PLX_PWM_PARAMS {
  PLX_PWM_RegParams_t reg;
  PLX_PWM_OutputMode_t outMode;
} PLX_PWM_Params_t;

typedef struct PLX_PWM_OBJ {
  volatile struct EPWM_REGS *pwm;
  uint16_t nomTBPRD;
  uint16_t sequence;
} PLX_PWM_Obj_t;

typedef PLX_PWM_Obj_t *PLX_PWM_Handle_t;

extern void PLX_PWM_getRegisterBase(PLX_PWM_Unit_t aPwmChannel,
                                    volatile struct EPWM_REGS **aReg);

inline uint32_t PLX_PWM_getFullDutyCompare(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  if (obj->pwm->TBCTL.bit.CTRMODE == TB_COUNT_UPDOWN) {
    // up-down
    return obj->pwm->TBPRD;
  } else {
    // triangle
    return (uint32_t)obj->pwm->TBPRD + 1;
  }
}

inline uint32_t PLX_PWM_getCounter(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  return obj->pwm->TBCTR;
}

inline bool PLX_PWM_getCountDirection(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  return obj->pwm->TBSTS.bit.CTRDIR;
}

inline void PLX_PWM_enableOut(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  EALLOW;
  obj->pwm->TZCLR.bit.OST = 1;
  EDIS;
}

inline bool PLX_PWM_pwmOutputIsEnabled(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  return (obj->pwm->TZFLG.bit.OST == 0);
}

inline void PLX_PWM_disableOut(PLX_PWM_Handle_t aHandle) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  EALLOW;
  obj->pwm->TZFRC.bit.OST = 1;
  EDIS;
}

inline void PLX_PWM_setTZSafe(PLX_PWM_Handle_t aHandle, uint16_t aSafe) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->TZCTL.bit.TZA = aSafe;
  obj->pwm->TZCTL.bit.TZB = aSafe;
}

inline void PLX_PWM_setDeadTimeCounts(PLX_PWM_Handle_t aHandle,
                                      float aRisingEdgeDelay,
                                      float aFallingEdgeDelay) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->DBFED = (uint16_t)(aFallingEdgeDelay + 0.5f) & 0x3FF;
  obj->pwm->DBRED = (uint16_t)(aRisingEdgeDelay + 0.5f) & 0x3FF;
}

inline void PLX_PWM_setDeadTimeCountsRising(PLX_PWM_Handle_t aHandle,
                                            float aRisingEdgeDelay) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->DBRED = (uint16_t)(aRisingEdgeDelay + 0.5f) & 0x3FF;
}

inline void PLX_PWM_setDeadTimeCountsFalling(PLX_PWM_Handle_t aHandle,
                                             float aFallingEdgeDelay) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->DBFED = (uint16_t)(aFallingEdgeDelay + 0.5f) & 0x3FF;
}

inline void PLX_PWM_setOutToPassive(PLX_PWM_Handle_t aHandle) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_setOutToOperational(PLX_PWM_Handle_t aHandle) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_prepareSetOutToXTransition(PLX_PWM_Handle_t aHandle) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_setSequenceAq(PLX_PWM_Handle_t aHandle,
                                  uint32_t aSequenceAq) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;

  // this function allows setting the AQ actions directly from a sequence mask -
  // note that AQCTLA and AQCTLB are configured identically, with the exception
  // of the CMP events - for those, we assign the CAs to AQCTLA and the CBs to
  // AQCTLB
  obj->pwm->AQCTLA.all = aSequenceAq & 0xFF;  // [CAD CAU PRD ZRO]
  obj->pwm->AQCTLB.all = (aSequenceAq & 0x0F)
                         | (aSequenceAq & 0xF00);  // [CBD CBU X X PRD ZRO]

  obj->sequence = 1;  // ensures that the duty cycle is not inverted
}

inline void PLX_PWM_setSequence(PLX_PWM_Handle_t aHandle, uint16_t aSequence) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;

  obj->sequence = aSequence & 1;
  uint16_t zeroTransition, firstCompareTransition;

  if (obj->sequence == 0) {
    // negative sequence
    zeroTransition = 1;          // clear
    firstCompareTransition = 2;  // set
  } else {
    // positive sequence
    zeroTransition = 2;          // set
    firstCompareTransition = 1;  // clear
  }

  // configure channel A compare events
  obj->pwm->AQCTLA.bit.CAU = firstCompareTransition;
  obj->pwm->AQCTLA.bit.CAD = zeroTransition;  // only relevant for
                                              // symmetrical carrier

  // configure channel A zero and period events
  obj->pwm->AQCTLA.bit.ZRO = zeroTransition;
  if (obj->pwm->TBCTL.bit.CTRMODE != TB_COUNT_UP) {
    // only force period for symmetrical carrier
    obj->pwm->AQCTLA.bit.PRD = firstCompareTransition;
  } else {
    obj->pwm->AQCTLA.bit.PRD = 0;
  }

  // channel B has equivalent actions as channel A
  obj->pwm->AQCTLB.bit.CBU = obj->pwm->AQCTLA.bit.CAU;
  obj->pwm->AQCTLB.bit.CBD = obj->pwm->AQCTLA.bit.CAD;
  obj->pwm->AQCTLB.bit.ZRO = obj->pwm->AQCTLA.bit.ZRO;
  obj->pwm->AQCTLB.bit.PRD = obj->pwm->AQCTLA.bit.PRD;
}

inline uint16_t PLX_PWM_calcCompareValue(PLX_PWM_Handle_t aHandle,
                                         float aDuty) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  float duty = aDuty;

  if (duty > 1.0) {
    duty = 1.0;
  } else if (duty < 0.0) {
    duty = 0.0;
  }

  if (obj->sequence == 0) {
    duty = 1.0 - duty;
  }

  float cmpF = duty * (float)PLX_PWM_getFullDutyCompare(aHandle);
  if (cmpF > 65535.0) {
    cmpF = 65535.0;
  }
  return (uint16_t)cmpF;
}

inline void PLX_PWM_setPwmDutyCSmallerThanFull(PLX_PWM_Handle_t aHandle,
                                               float aDuty) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_setPwmDutyCGreaterThanZero(PLX_PWM_Handle_t aHandle,
                                               float aDuty) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_setPwmDuty(PLX_PWM_Handle_t aHandle, float aDuty) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->CMPA.half.CMPA = PLX_PWM_calcCompareValue(aHandle, aDuty);
}

inline void PLX_PWM_setPwmDutyA(PLX_PWM_Handle_t aHandle, float aDuty) {
  PLX_PWM_setPwmDuty(aHandle, aDuty);
}

inline void PLX_PWM_setPwmDutyB(PLX_PWM_Handle_t aHandle, float aDuty) {
  PLX_PWM_Obj_t *obj = (PLX_PWM_Obj_t *)aHandle;
  obj->pwm->CMPB = PLX_PWM_calcCompareValue(aHandle, aDuty);
}

inline void PLX_PWM_enableOutputSwap(PLX_PWM_Handle_t aHandle, bool aEnableSwap) {
  PLX_ASSERT(0);  // not supported by this chip
}

inline void PLX_PWM_enableAllClocks() {
  EALLOW;
  SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1;
  SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;
  SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1;
  SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1;
  SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 1;
  SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 1;
  EDIS;
}

inline void PLX_PWM_disableAllClocks() {
  EALLOW;
  SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 0;
  SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;
  SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 0;
  SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 0;
  SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 0;
  SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 0;
  EDIS;
}

#endif /* PLX_PWM_IMPL_H_ */
