/*
 * Copyright (c) 2021 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#ifndef HPM_PTPC_DRV_H
#define HPM_PTPC_DRV_H

#include "hpm_common.h"
#include "hpm_ptpc_regs.h"

/**
 * @brief PTPC driver
 * @defgroup ptpc_interface PTPC driver APIs
 * @ingroup timer_interfaces Timers
 * @{
 */

#define PTPC_EVENT_COMPARE0_MASK    PTPC_INT_STS_COMP_INT_STS0_MASK
#define PTPC_EVENT_CAPTURE0_MASK    PTPC_INT_STS_CAPTURE_INT_STS0_MASK
#define PTPC_EVENT_PPS0_MASK        PTPC_INT_STS_PPS_INT_STS0_MASK
#define PTPC_EVENT_COMPARE1_MASK    PTPC_INT_STS_COMP_INT_STS1_MASK
#define PTPC_EVENT_CAPTURE1_MASK    PTPC_INT_STS_CAPTURE_INT_STS1_MASK
#define PTPC_EVENT_PPS1_MASK        PTPC_INT_STS_PPS_INT_STS1_MASK

#define PTPC_MAX_NS_COUNTER (0x3B9ACA00UL)

/**
 * @brief Counter types
 */
typedef enum ptpc_ns_counter_rollover_type {
    ptpc_ns_counter_rollover_binary = 0, /**< binary mode, resolution ~0.466ns, overflow at 0x7FFFFFFF */
    ptpc_ns_counter_rollover_digital = 1, /**< digital mode, resolution 1ns, overflow at 0x3B9ACA00 */
} ptpc_ns_counter_rollover_type_t;

/**
 * @brief Capture trigger types
 */
typedef enum ptpc_capture_trigger_type {
    ptpc_capture_trigger_none = 0,
    ptpc_capture_trigger_on_rising_edge = PTPC_PTPC_CTRL0_CAPT_SNAP_POS_EN_MASK,
    ptpc_capture_trigger_on_failing_edge = PTPC_PTPC_CTRL0_CAPT_SNAP_NEG_EN_MASK,
    ptpc_capture_trigger_on_both_edges = PTPC_PTPC_CTRL0_CAPT_SNAP_POS_EN_MASK
                                        | PTPC_PTPC_CTRL0_CAPT_SNAP_NEG_EN_MASK,
} ptpc_capture_trigger_type_t;

/**
 * @brief Capture trigger types
 */
typedef enum ptpc_counting_mode {
    ptpc_counting_increment = 0,            /**< Increment the counter */
    ptpc_counting_decrement = 1,            /**< Decrement the counter */
} ptpc_counting_mode;

/**
 * @brief Timer config
 */
typedef struct {
    ptpc_capture_trigger_type_t capture_trigger;        /**< Capture trigger */
    ptpc_ns_counter_rollover_type_t ns_rollover_mode;   /**< Ns rollover mode */
    bool coarse_increment;                              /**< Set false to use fine increment */
    bool capture_keep;                                  /**< Keep captured timer counter */
    uint32_t src_frequency;                             /**< Source frequency */
} ptpc_config_t;

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief   Configure ns counter rollover mode
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Index of target counter
 * @param[in] rollover Rollover type
 */
static inline void ptpc_set_ns_counter_rollover(PTPC_Type *ptr, uint8_t index,
                                ptpc_ns_counter_rollover_type_t rollover)
{
    ptr->PTPC[index].CTRL0 = (ptr->PTPC[index].CTRL0 & ~PTPC_PTPC_CTRL0_SUBSEC_DIGITAL_ROLLOVER_MASK)
        | PTPC_PTPC_CTRL0_SUBSEC_DIGITAL_ROLLOVER_SET(rollover);
}

/**
 * @brief   Enable capture keep
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 */
static inline void ptpc_enable_capture_keep(PTPC_Type *ptr, uint8_t index)
{
    ptr->PTPC[index].CTRL0 |= PTPC_PTPC_CTRL0_CAPT_SNAP_KEEP_MASK;
}

/**
 * @brief   Disable capture keep
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 */
static inline void ptpc_disable_capture_keep(PTPC_Type *ptr, uint8_t index)
{
    ptr->PTPC[index].CTRL0 &= ~PTPC_PTPC_CTRL0_CAPT_SNAP_KEEP_MASK;
}

/**
 * @brief   Update ns counter update type
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Index of target counter
 * @param[in] coarse_update Counter will be increment by 1 on
 */
static inline void ptpc_set_ns_counter_update_type(PTPC_Type *ptr, uint8_t index, bool coarse_update)
{
    ptr->PTPC[index].CTRL0 = (ptr->PTPC[index].CTRL0 & ~PTPC_PTPC_CTRL0_FINE_COARSE_SEL_MASK)
        | PTPC_PTPC_CTRL0_FINE_COARSE_SEL_SET(coarse_update);
}

/**
 * @brief   Set ns counter increment step
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] ns_step Increment step
 */
static inline void ptpc_set_ns_counter_step(PTPC_Type *ptr, uint8_t index, uint32_t ns_step)
{
    ptr->PTPC[index].CTRL1 = PTPC_PTPC_CTRL1_SS_INCR_SET(ns_step);
}

/**
 * @brief   Set Second portion update value
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] sec Second value
 */
static inline void ptpc_set_second_update(PTPC_Type *ptr, uint8_t index, uint32_t sec)
{
    ptr->PTPC[index].TS_UPDTH = PTPC_PTPC_TS_UPDTH_SEC_UPDATE_SET(sec);
}

/**
 * @brief   Set ns portion update value
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] ns Ns value (31 bits, 0x3B9ACA00 max)
 * @param[in] mode Counting mode
 *
 * @return status_success if everything is okay
 */
hpm_stat_t ptpc_set_ns_update(PTPC_Type *ptr, uint8_t index, uint32_t ns, ptpc_counting_mode mode);

/**
 * @brief   Get current timestamp Second portion
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 *
 * @return Seconds value
 */
static inline uint32_t ptpc_get_timestamp_second(PTPC_Type *ptr, uint8_t index)
{
    return PTPC_PTPC_TIMEH_TIMESTAMP_HIGH_GET(ptr->PTPC[index].TIMEH);
}

/**
 * @brief   Get timestamp ns portion
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 *
 * @return ns value
 */
static inline uint32_t ptpc_get_timestamp_ns(PTPC_Type *ptr, uint8_t index)
{
    return PTPC_PTPC_TIMEL_TIMESTAMP_LOW_GET(ptr->PTPC[index].TIMEL);
}

/**
 * @brief   Config compare
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] sec Target second
 * @param[in] ns Target ns
 */
static inline void ptpc_config_compare(PTPC_Type *ptr, uint8_t index, uint32_t sec, uint32_t ns)
{
    ptr->PTPC[index].TARH = PTPC_PTPC_TARH_TARGET_TIME_HIGH_SET(sec);
    ptr->PTPC[index].TARL = PTPC_PTPC_TARL_TARGET_TIME_LOW_SET(ns);
    ptr->PTPC[index].CTRL0 |= PTPC_PTPC_CTRL0_COMP_EN_MASK;
}

/**
 * @brief   Update Second portion
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] sec Target second
 */
static inline void ptpc_update_timestamp_second(PTPC_Type *ptr, uint8_t index, uint32_t sec)
{
    ptr->PTPC[index].TS_UPDTH = PTPC_PTPC_TS_UPDTH_SEC_UPDATE_SET(sec);
}

/**
 * @brief   Update Ns portion
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] ns Target ns
 * @param[in] mode Counting mode
 */
static inline void ptpc_update_timestamp_ns(PTPC_Type *ptr, uint8_t index, uint32_t ns, ptpc_counting_mode mode)
{
    ptr->PTPC[index].TS_UPDTL = PTPC_PTPC_TS_UPDTL_NS_UPDATE_SET(ns) | PTPC_PTPC_TS_UPDTL_ADD_SUB_SET(mode);
}

/**
 * @brief   Enable timer
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 */
static inline void ptpc_enable_timer(PTPC_Type *ptr, uint8_t index)
{
    ptr->PTPC[index].CTRL0 |= PTPC_PTPC_CTRL0_TIMER_ENABLE_MASK;
}

/**
 * @brief   Disable timer
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 */
static inline void ptpc_disable_timer(PTPC_Type *ptr, uint8_t index)
{
    ptr->PTPC[index].CTRL0 &= ~PTPC_PTPC_CTRL0_TIMER_ENABLE_MASK;
}

/**
 * @brief   Config capture trigger event
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] trigger Capture trigger type
 */
static inline void ptpc_config_capture(PTPC_Type *ptr, uint8_t index,
                                ptpc_capture_trigger_type_t trigger)
{
    ptr->PTPC[index].CTRL0 = (ptr->PTPC[index].CTRL0 & ~(PTPC_PTPC_CTRL0_CAPT_SNAP_POS_EN_MASK
                | PTPC_PTPC_CTRL0_CAPT_SNAP_NEG_EN_MASK)) | trigger;
}

/**
 * @brief   Get snapped ns value
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 *
 * @return ns portion on capture
 */
static inline uint32_t ptpc_get_capture_ns(PTPC_Type *ptr, uint8_t index)
{
    return PTPC_PTPC_CAPT_SNAPL_CAPT_SNAP_LOW_GET(ptr->PTPC[index].CAPT_SNAPL);
}

/**
 * @brief   Get captured second
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 *
 * @return second portion on capture
 */
static inline uint32_t ptpc_get_capture_second(PTPC_Type *ptr, uint8_t index)
{
    return PTPC_PTPC_CAPT_SNAPH_CAPT_SNAP_HIGH_GET(ptr->PTPC[index].CAPT_SNAPH);
}

/**
 * @brief   Clear interrupt status
 *
 * @param[in] ptr PTPC base address
 * @param[in] mask Mask of interrupts to be cleared
 */
static inline void ptpc_clear_irq_status(PTPC_Type *ptr, uint32_t mask)
{
    ptr->INT_STS = mask;
}

/**
 * @brief   Get interrupt status
 *
 * @param[in] ptr PTPC base address
 *
 * @return interrupt status mask
 */
static inline uint32_t ptpc_get_irq_status(PTPC_Type *ptr)
{
    return ptr->INT_STS;
}

/**
 * @brief   Disable interrupt with mask
 *
 * @param[in] ptr PTPC base address
 * @param[in] mask Mask of interrupts to be disabled
 */
static inline void ptpc_irq_disable(PTPC_Type *ptr, uint32_t mask)
{
    ptr->INT_EN &= ~mask;
}

/**
 * @brief   Enable interrupt with mask
 *
 * @param[in] ptr PTPC base address
 * @param[in] mask Mask of interrupts to be enabled
 */
static inline void ptpc_irq_enable(PTPC_Type *ptr, uint32_t mask)
{
    ptr->INT_EN |= mask;
}

/**
 * @brief   Initialize PTPC module
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] config Pointer to configuration struct
 *
 * @return status_success if everything is okay
 */
hpm_stat_t ptpc_init(PTPC_Type *ptr, uint8_t index, ptpc_config_t *config);

/**
 * @brief   Set PTPC output destination
 *
 * @param[in] ptr PTPC base address
 * @param[in] can_index Target CAN instance
 * @param[in] use_ptpc1 Use PTPC1 if set to true, otherwise PTPC0
 *
 * @return status_success if everything is okay
 */
hpm_stat_t ptpc_set_timer_output(PTPC_Type *ptr, uint8_t can_index, bool use_ptpc1);

/**
 * @brief   Get default config
 *
 * @param[in] ptr PTPC base address
 * @param[out] config Pointer of config struct
 */
void ptpc_get_default_config(PTPC_Type *ptr, ptpc_config_t *config);

/**
 * @brief   Initialize timer to count from 0
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 *
 */
void ptpc_init_timer(PTPC_Type *ptr, uint8_t index);

/**
 * @brief   Initialize timer with initial values
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] sec Seconds
 * @param[in] ns Ns
 * @param[in] mode Counting mode
 *
 * @return status_success if everything is okay
 */
hpm_stat_t ptpc_init_timer_with_initial(PTPC_Type *ptr, uint8_t index, uint32_t sec, uint32_t ns, ptpc_counting_mode mode);

/**
 * @brief   Update timestamp counter
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] sec Seconds
 * @param[in] ns Ns
 * @param[in] mode Counting mode
 * @return status_success if everything is okay
 */
hpm_stat_t ptpc_update_timer(PTPC_Type *ptr, uint8_t index, uint32_t sec, uint32_t ns, ptpc_counting_mode mode);

/**
 * @brief   Set period of pulse generated per second
 *
 * @param[in] ptr PTPC base address
 * @param[in] index Target index
 * @param[in] p (2^power Hz, from [0 - 15])
 *
 * @return true if requested frequency can be set
 * @return false if requested frequency can not be set
 */
hpm_stat_t ptpc_set_pps(PTPC_Type *ptr, uint8_t index, uint8_t p);

#ifdef __cplusplus
}
#endif
/**
 * @}
 */

#endif /* HPM_PTPC_DRV_H */
