/**
 * \addtogroup BSP
 * \{
 * \addtogroup DEVICES
 * \{
 * \addtogroup OTPC
 * \{
 */

/**
 ****************************************************************************************
 *
 * @file hw_otpc.c
 *
 * @brief Implementation of the OTP Controller Low Level Driver.
 *
 * Copyright (c) 2016, Dialog Semiconductor
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************************
 */

#if dg_configUSE_HW_OTPC



#include <stdint.h>
#include "hw_otpc.h"


/*
 * Local variables
 */

/*
 * 1MHz: 1 cycle = 1us ==>
 *    25ns : 1us,   200ns : 1us,   500ns : 1us,   1us : 2us,     5us : 5us, 2us : 3us,     blanc : 1us
 *
 * 2MHz: 1 cycle = 500ns ==>
 *    25ns : 500ns, 200ns : 500ns, 500ns : 1us,   1us : 1.5us,   5us : 5us, 2us : 2.5us,   blanc : 500ns
 *
 * 3MHz: 1 cycle = 333ns ==>
 *    25ns : 333ns, 200ns : 333ns, 500ns : 666ns, 1us : 1.33us,  5us : 5us, 2us : 2.33us,  blanc : 333ns
 *
 * 4MHz: 1 cycle = 250ns ==>
 *    25ns : 250ns, 200ns : 250ns, 500ns : 750ns, 1us : 1.25ns,  5us : 5us, 2us : 2.25ns,  blanc : 250ns
 *
 * 6MHz: 1 cycle = 167ns ==>
 *    25ns : 167ns, 200ns : 333ns, 500ns : 667ns, 1us : 1.167ns, 5us : 5us, 2us : 2.167ns, blanc : 167ns
 *
 * 8MHz: 1 cycle = 125ns ==>
 *    25ns : 125ns, 200ns : 250ns, 500ns : 625ns, 1us : 1.125ns, 5us : 5us, 2us : 2.125ns, blanc : 125ns
 *
 * 12MHz: 1 cycle = 83.33ns ==>
 *    25ns : 83ns, 200ns : 250ns,  500ns : 583ns, 1us : 1.083ns, 5us : 5us, 2us : 2.083ns, blanc : 167ns
 *
 * 16MHz: 1 cycle = 62.5ns ==>
 *    25ns : 62ns, 200ns : 250ns,  500ns : 562ns, 1us : 1.062ns, 5us : 5us, 2us : 2.062ns, blanc : 125ns
 *
 * 24MHz: 1 cycle = 41.67ns ==>
 *    25ns : 41ns, 200ns : 208ns,  500ns : 541ns, 1us : 1.041ns, 5us : 5us, 2us : 2.041ns, blanc : 125ns
 *
 * 32MHz: 1 cycle = 31.25ns ==>
 *    25ns : 31ns, 200ns : 219ns,  500ns : 531ns, 1us : 1.031ns, 5us : 5us, 2us : 2.031ns, blanc : 125ns
 *
 * 48MHz: 1 cycle = 41.67ns ==>
 *    25ns : 41ns, 200ns : 208ns,  500ns : 521ns, 1us : 1.021ns, 5us : 5us, 2us : 2.021ns, blanc : 125ns
 *
 */
const uint32_t tim1[11] =
{
    /* 1 MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (4 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (2 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 2MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (2 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (9 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (4 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 3MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (3 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (14 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (6 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 4MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (2 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (4 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (19 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (8 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 6MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (3 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (6 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (29 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (12 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 8MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (4 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (8 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (39 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (16 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 12MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (2 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (6 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (12 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (59 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (24 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 16MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (3 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (8 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (16 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (79 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (32 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 24MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (4 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (12 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (24 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (119 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (48 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 32MHz */
    (0 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (6 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (16 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (32 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (159 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (64 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos),
    /* 48MHz */
    (1 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_25NS_Pos) |
    (9 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_200NS_Pos) |
    (24 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_500NS_Pos) |
    (48 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_1US_Pos) |
    (239 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_PW_Pos) |
    (96 << OTPC_OTPC_TIM1_REG_OTPC_TIM1_CC_T_CADX_Pos)
};

static const uint8_t tim2[HW_OTPC_SYS_CLK_FREQ_48 + 1] =
{ 0, 0, 0, 0, 0, 0,  1,  1,  2,  3,  5 };


/*
 * Forward declarations
 */



/*
 * Inline helpers
 */

/**
 * Wait for programming to finish.
 */
__STATIC_INLINE void wait_for_prog_done(void)
{
    while (!(OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, PRDY)))
        ;
}

/**
 * Wait for AREAD or APROG to finish.
 */
__STATIC_INLINE void wait_for_auto_done(void)
{
    while (!(OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, ARDY)))
        ;
}

/**
 * Check for correctable or uncorrectable programming error.
 */
__STATIC_INLINE bool have_prog_error(void)
{
    return OTPC->OTPC_STAT_REG &
           (HW_OTPC_FIELD_VAL(STAT, PERR_UNC, 1) | HW_OTPC_FIELD_VAL(STAT, PERR_COR, 1));
}

/*
 * Assertion macros
 */

/*
 * Make sure that the OTP clock is enabled
 */
#define ASSERT_WARNING_OTP_CLK_ENABLED \
        ASSERT_WARNING(CRG_TOP->CLK_AMBA_REG & REG_MSK(CRG_TOP, CLK_AMBA_REG, OTP_ENABLE))

/*
 * Make sure that the OTPC is in given state
 */
#define ASSERT_WARNING_OTPC_MODE(s) \
        ASSERT_WARNING((OTPC->OTPC_MODE_REG & HW_OTPC_REG_FIELD_MASK(MODE, MODE)) \
                                == HW_OTPC_FIELD_VAL(MODE, MODE, s))

/*
 * Make sure that the cell address is valid
 */
#define ASSERT_CELL_OFFSET_VALID(off) \
        ASSERT_WARNING(off < 0x2000)

/*
 * Make sure val is non-zero and less than maximum
 */
#define ASSERT_WARNING_NONZERO_RANGE(val, maximum) \
        do { \
                ASSERT_WARNING(val); \
                ASSERT_WARNING((val) < (maximum)); \
        } while (0)


/*
 * Function definitions
 */

HW_OTPC_SYS_CLK_FREQ hw_otpc_convert_sys_clk_mhz(uint32_t clk_freq)
{
    HW_OTPC_SYS_CLK_FREQ f;

    /* Value to convert must be at most 48 MHz */
    ASSERT_WARNING(clk_freq <= 48);

    if (--clk_freq < 4)
    {
        /*
         * cover 1, 2, 3, 4
         */
        f = (HW_OTPC_SYS_CLK_FREQ)clk_freq;
    }
    else
    {
        clk_freq -= 5;
        /*
         * remaining valid values:
         *  -  0 (initially 6)
         *  -  2 (initially 8)
         *  -  6 (initially 12)
         *  - 10 (initially 16)
         *  - 18 (initially 24)
         *  - 26 (initially 32)
         *  - 42 (initially 48)
         */

        /* no odd valid values any more */
        ASSERT_WARNING(!(clk_freq & 1));

        clk_freq >>= 1;

        /*
         * remaining valid values:
         *  -  0 (initially 6)
         *  -  1 (initially 8)
         *  -  3 (initially 12)
         *  -  5 (initially 16)
         *  -  9 (initially 24)
         *  - 13 (initially 32)
         *  - 21 (initially 48)
         */
        if (clk_freq > 8)
        {
            /*
             * remaining valid values:
             *  -  9 (initially 24)
             *  - 13 (initially 32)
             *  - 21 (initially 48)
             */
            if (clk_freq > 16)
            {
                ASSERT_WARNING(clk_freq == 21);

                f = HW_OTPC_SYS_CLK_FREQ_48;
            }
            else
            {
                clk_freq -= 8;
                /*
                 * remaining valid values:
                 *  -  1 (initially 24)
                 *  -  5 (initially 32)
                 */
                ASSERT_WARNING((clk_freq == 1) || (clk_freq == 5));

                if (clk_freq < 4)
                {
                    f = (HW_OTPC_SYS_CLK_FREQ)HW_OTPC_SYS_CLK_FREQ_24;
                }
                else
                {
                    f = (HW_OTPC_SYS_CLK_FREQ)HW_OTPC_SYS_CLK_FREQ_32;
                }
            }
        }
        else
        {
            /*
             * remaining valid values:
             *  -  0 (initially 6)
             *  -  1 (initially 8)
             *  -  3 (initially 12)
             *  -  5 (initially 16)
             */
            if (clk_freq > 2)
            {
                ASSERT_WARNING((clk_freq == 3) || (clk_freq == 5));

                if (clk_freq > 4)
                {
                    f = (HW_OTPC_SYS_CLK_FREQ)HW_OTPC_SYS_CLK_FREQ_16;
                }
                else
                {
                    f = (HW_OTPC_SYS_CLK_FREQ)HW_OTPC_SYS_CLK_FREQ_12;
                }
            }
            else
            {
                f = (HW_OTPC_SYS_CLK_FREQ)(HW_OTPC_SYS_CLK_FREQ_6 + clk_freq);
            }
        }
    }

    return f;
}


void hw_otpc_disable(void)
{
    /*
     * Enable OTPC clock
     */
    hw_otpc_init();

    /*
     * set OTPC to stand-by mode
     */
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);

    /*
     * Disable OTPC clock
     */
    hw_otpc_close();
}


void hw_otpc_set_speed(HW_OTPC_SYS_CLK_FREQ clk_speed)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    /*
     * Set access speed
     */
    ASSERT_WARNING(clk_speed <= HW_OTPC_SYS_CLK_FREQ_48);

    OTPC->OTPC_TIM1_REG = tim1[clk_speed];
    HW_OTPC_REG_SETF(TIM2, CC_T_BCHK, tim2[clk_speed]);
}


void hw_otpc_power_save(uint32_t inactivity_period)
{
    /* Only go to power save for an inactivity_period < 1024 */
    ASSERT_WARNING(inactivity_period < 1024);

    HW_OTPC_REG_SETF(TIM2, CC_STBY_THR, inactivity_period);
}


uint32_t hw_otpc_num_of_rr(void)
{
    unsigned int i;

    volatile uint32_t *ptr = (volatile uint32_t *)MEMORY_OTP_BASE;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    // STBY mode
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);

    // The access will be performed in the spare rows
    HW_OTPC_REG_SETF(MODE, USE_SP_ROWS, 1);

    // MREAD mode
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_MREAD);

    // Read Records
    i = 0;

    while ((i < MAX_RR_AVAIL) && (ptr[0x9e - 4 * i] & 0x1))
    {
        i++;
    }

    // STBY mode
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);

    // Select the normal memory array
    HW_OTPC_REG_SETF(MODE, USE_SP_ROWS, 0);

    return i;
}

static bool manual_prog_verify(uint32_t cell_offset, uint32_t pword_l, uint32_t pword_h)
{
    uint32_t val_l, val_h, old_mode = OTPC->OTPC_MODE_REG;
    uint32_t *addr = hw_otpc_cell_to_mem(cell_offset);

    /* we have to go through standby mode first */
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);
    __DMB();
    HW_OTPC_REG_SETF(MODE, ERR_RESP_DIS, 1);
    __DMB();
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_MREAD);

    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, RERROR))
    {
        HW_OTPC_REG_SETF(STAT, RERROR,
                         1); //This bit need to be cleared manually (can only happen if a previous read has not checked/cleared it)
    }

    /*
     * Read cell in manual mode, as 2 32-bit values (little-endian)
     */
    val_l = addr[0];
    val_h = addr[1];
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);
    __DMB();
    OTPC->OTPC_MODE_REG = old_mode;

    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, RERROR))
    {
        HW_OTPC_REG_SETF(STAT, RERROR, 1); //This bit need to be cleared manually
        return false;
    }

    return (pword_h == val_h) && (pword_l == val_l);
}

/*
 * Set to 1 to manually verify the programmed cell value, if the auto-verification fails.
 */
#define MANUAL_PROG_VERIFICATION        1

bool hw_otpc_manual_word_prog(uint32_t cell_offset, uint32_t pword_l, uint32_t pword_h, bool use_rr)
{
    int i;
    bool ret = true;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    /*
     * Program the data regs
     */
    OTPC->OTPC_PWORDL_REG = pword_l;
    OTPC->OTPC_PWORDH_REG = pword_h;

    /*
     * Start programming
     */
    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_MPROG);
    OTPC->OTPC_PCTRL_REG =
        HW_OTPC_FIELD_VAL(PCTRL, WADDR, cell_offset) |
        HW_OTPC_FIELD_VAL(PCTRL, PSTART, 1);
    wait_for_prog_done();

    /*
     * Check and retry up to 10 times
     */
    i = 0;

    while (have_prog_error())
    {
        if (i == 10)
        {
            break;
        }

        i = i + 1;

        OTPC->OTPC_PCTRL_REG =
            HW_OTPC_FIELD_VAL(PCTRL, WADDR, cell_offset) |
            HW_OTPC_FIELD_VAL(PCTRL, PSTART, 1) |
            HW_OTPC_FIELD_VAL(PCTRL, PRETRY, 1);

        wait_for_prog_done();
    }

    if (i == 10)
    {
        ret = false;

        if (MANUAL_PROG_VERIFICATION)
        {
            ret = manual_prog_verify(cell_offset, pword_l, pword_h);
        }

        if (use_rr && !ret)
        {
            do
            {
                // Reset state
                HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);

                // Abort if the writing was done in the spare area
                if (OTPC->OTPC_MODE_REG & HW_OTPC_REG_FIELD_MASK(MODE, USE_SP_ROWS))
                {
                    break;
                }

                // Write the repair record to the spare area
                if (!hw_otpc_write_rr(cell_offset, pword_l, pword_h))
                {
                    break;
                }

                HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);
                // force reloading
                HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_MREAD);

                ret = true;
                break;
            }
            while (0);
        }
    }

    HW_OTPC_REG_SETF(MODE, MODE, HW_OTPC_MODE_STBY);

    return ret;
}

bool hw_otpc_manual_prog(const uint32_t *p_data, uint32_t cell_offset, HW_OTPC_WORD cell_word,
                         uint32_t num_of_words, bool use_rr)
{
    uint32_t w, c, ncells, off, val;
    bool ret = true;
    const uint32_t *addr;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    if (num_of_words == 0)
    {
        return true;    // early exit
    }

    /* index in p_data[] */
    w = 0;
    off = cell_offset;

    if (cell_word == HW_OTPC_WORD_HIGH)
    {
        /*
         * read low 32-bit word so that we program the same value
         */
        addr = hw_otpc_cell_to_mem(cell_offset);

        hw_otpc_manual_read_on(use_rr);
        /* little-endian */
        val = addr[0];
        hw_otpc_manual_read_off();

        if (!hw_otpc_manual_word_prog(off++, val, p_data[w++], use_rr))
        {
            return false;
        }

        ncells = (num_of_words - 1) >> 1;
    }
    else
    {
        ncells = num_of_words >> 1;
    }

    for (c = 0; c < ncells; c++, off++, w += 2)
    {
        if (!hw_otpc_manual_word_prog(off, p_data[w], p_data[w + 1], use_rr))
        {
            return false;
        }
    }

    if (w < num_of_words)
    {
        /*
         * read high 32-bit word so that we program the same value
         */
        addr = hw_otpc_cell_to_mem(off);

        hw_otpc_manual_read_on(use_rr);
        /* little-endian */
        val = addr[1];
        hw_otpc_manual_read_off();

        if (!hw_otpc_manual_word_prog(off, p_data[w++], val, use_rr))
        {
            return false;
        }
    }

    ASSERT_WARNING(w == num_of_words);

    return ret;
}

bool hw_otpc_write_rr(uint32_t cell_addr, uint32_t pword_l, uint32_t pword_h)
{
    uint32_t repair_cnt;
    bool ret = false;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    // Get the number of used Repair Records
    repair_cnt = hw_otpc_num_of_rr();

    // Abort if all repair records are being used
    if (repair_cnt < MAX_RR_AVAIL)
    {
        do
        {
            // The access will be performed in the spare rows
            HW_OTPC_REG_SETF(MODE, USE_SP_ROWS, 1);

            // Write the data to the spare area
            if (!hw_otpc_manual_word_prog(0x4F - repair_cnt - 1,
                                          pword_l, pword_h, false))
            {
                break;
            }

            // Write the header of the repair record to the spare area
            if (!hw_otpc_manual_word_prog(0x4F - repair_cnt,
                                          1 | (cell_addr << 1), 0x00000000, false))
            {
                break;
            }

            ret = true;
        }
        while (0);

        // Return to the normal memory array
        HW_OTPC_REG_SETF(MODE, USE_SP_ROWS, 0);

        if (ret)
        {
            /*
             * Request the reloading of the repair records at the next
             * enabling of the OTP cell
             */
            HW_OTPC_REG_SETF(MODE, RLD_RR_REQ, 1);
        }
    }

    return ret;
}


void hw_otpc_manual_read_on(bool spare_rows)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    /*
     * Place the OTPC in manual read mode
     */
    if (spare_rows)
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_MREAD) |
            HW_OTPC_FIELD_VAL(MODE, USE_SP_ROWS, 1);
    }
    else
    {
        OTPC->OTPC_MODE_REG = HW_OTPC_MODE_MREAD;
    }
}


void hw_otpc_manual_read_off(void)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_MREAD);

    /*
     * Place the OTPC in STBY mode
     */
    OTPC->OTPC_MODE_REG = HW_OTPC_MODE_STBY;
}


bool hw_otpc_dma_prog(const uint32_t *p_data, uint32_t cell_offset, HW_OTPC_WORD cell_word,
                      uint32_t num_of_words, bool spare_rows)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    /*
     * Make sure that the size is valid
     */
    ASSERT_WARNING(num_of_words);
    ASSERT_WARNING(num_of_words < 16384);

    /*
     * Set up DMA
     */
    unsigned int remap_type = REG_GETF(CRG_TOP, SYS_CTRL_REG, REMAP_ADR0);

    if (IS_SYSRAM_ADDRESS(p_data) ||
        (IS_REMAPPED_ADDRESS(p_data) && (remap_type == 0x3)))
    {
        OTPC->OTPC_AHBADR_REG = black_orca_phy_addr((uint32_t)p_data);
    }
    else
    {
        /* Only RAM addresses are accepted */
        ASSERT_WARNING(0);
    }

    OTPC->OTPC_CELADR_REG = (cell_offset << 1) | (cell_word == HW_OTPC_WORD_HIGH);
    OTPC->OTPC_NWORDS_REG = num_of_words - 1;

    /*
     * Start DMA programming
     */
    if (spare_rows)
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_APROG) |
            HW_OTPC_FIELD_VAL(MODE, USE_DMA, 1) |
            HW_OTPC_FIELD_VAL(MODE, USE_SP_ROWS, 1);
    }
    else
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_APROG) |
            HW_OTPC_FIELD_VAL(MODE, USE_DMA, 1);
    }

    wait_for_auto_done();

    /*
     * Check result
     */
    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, PERR_UNC))
    {
        return false;
    }

    return true;
}


void hw_otpc_dma_read(uint32_t *p_data, uint32_t cell_offset, HW_OTPC_WORD cell_word,
                      uint32_t num_of_words, bool spare_rows)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    /*
     * Make sure that the size is valid
     */
    ASSERT_WARNING(num_of_words);
    ASSERT_WARNING(num_of_words < 16384);

    /*
     * Set up DMA
     */
    unsigned int remap_type = REG_GETF(CRG_TOP, SYS_CTRL_REG, REMAP_ADR0);

    if (IS_SYSRAM_ADDRESS(p_data) ||
        (IS_REMAPPED_ADDRESS(p_data) && (remap_type == 0x3)))
    {
        OTPC->OTPC_AHBADR_REG = black_orca_phy_addr((uint32_t)p_data);
    }
    else
    {
        /* Only RAM addresses are accepted */
        ASSERT_WARNING(0);
    }

    OTPC->OTPC_CELADR_REG = (cell_offset << 1) | (cell_word == HW_OTPC_WORD_HIGH);
    OTPC->OTPC_NWORDS_REG = num_of_words - 1;

    /*
     * Start DMA programming
     */
    if (spare_rows)
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_AREAD) |
            HW_OTPC_FIELD_VAL(MODE, USE_DMA, 1) |
            HW_OTPC_FIELD_VAL(MODE, USE_SP_ROWS, 1);
    }
    else
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_AREAD) |
            HW_OTPC_FIELD_VAL(MODE, USE_DMA, 1);
    }

    wait_for_auto_done();
}


bool hw_otpc_fifo_prog(const uint32_t *p_data, uint32_t cell_offset, HW_OTPC_WORD cell_word,
                       uint32_t num_of_words, bool spare_rows)
{
    unsigned int i;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    ASSERT_WARNING_NONZERO_RANGE(num_of_words, 16384);

    /*
     * Set up FIFO
     */
    OTPC->OTPC_CELADR_REG = (cell_offset << 1) | (cell_word == HW_OTPC_WORD_HIGH);
    OTPC->OTPC_NWORDS_REG = num_of_words - 1;

    /*
     * Perform programming via FIFO
     */
    if (spare_rows)
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_APROG) |
            HW_OTPC_FIELD_VAL(MODE, USE_SP_ROWS, 1);
    }
    else
    {
        OTPC->OTPC_MODE_REG = HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_APROG);
    }

    for (i = 0; i < num_of_words; i++)
    {
        while (HW_OTPC_REG_GETF(STAT, FWORDS) == 8)
            ;

        OTPC->OTPC_FFPRT_REG = p_data[i]; // Write FIFO data
    }

    /*
     * Wait for completion
     */
    wait_for_auto_done();

    /*
     * Check result
     */
    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, PERR_UNC))
    {
        return false;
    }

    return true;
}


bool hw_otpc_fifo_read(uint32_t *p_data, uint32_t cell_offset, HW_OTPC_WORD cell_word,
                       uint32_t num_of_words, bool spare_rows)
{
    unsigned int i;

    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_CELL_OFFSET_VALID(cell_offset);

    ASSERT_WARNING_NONZERO_RANGE(num_of_words, 16384);

    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, RERROR))
    {
        HW_OTPC_REG_SETF(STAT, RERROR,
                         1); //This bit need to be cleared manually (can only happen if a previous read has not checked/cleared it)
    }

    /*
     * Set up FIFO
     */
    OTPC->OTPC_CELADR_REG = (cell_offset << 1) | (cell_word == HW_OTPC_WORD_HIGH);
    OTPC->OTPC_NWORDS_REG = num_of_words - 1;

    /*
     * Perform reading via FIFO
     */
    if (spare_rows)
    {
        OTPC->OTPC_MODE_REG =
            HW_OTPC_FIELD_VAL(MODE, MODE, HW_OTPC_MODE_AREAD) |
            HW_OTPC_FIELD_VAL(MODE, USE_SP_ROWS, 1);
    }
    else
    {
        OTPC->OTPC_MODE_REG = HW_OTPC_MODE_AREAD;
    }

    for (i = 0; i < num_of_words; i++)
    {
        while (HW_OTPC_REG_GETF(STAT, FWORDS) == 0)
            ;

        p_data[i] = OTPC->OTPC_FFPRT_REG;
    }

    /*
     * Wait for completion
     */
    wait_for_auto_done();

    /*
     * Check result
     */
    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, RERROR))
    {
        HW_OTPC_REG_SETF(STAT, RERROR, 1); //This bit need to be cleared manually
        return false;
    }

    return true;
}


void hw_otpc_prepare(uint32_t num_of_bytes)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    ASSERT_WARNING_NONZERO_RANGE(num_of_bytes, 65536);

    /*
     * Set up image size
     */
    OTPC->OTPC_NWORDS_REG = ((num_of_bytes + 3) >> 2) - 1;

    /*
     * Enable OTP_COPY
     */
    CRG_TOP->SYS_CTRL_REG |= 1 << REG_POS(CRG_TOP, SYS_CTRL_REG, OTP_COPY);
}


void hw_otpc_cancel_prepare(void)
{
    ASSERT_WARNING_OTP_CLK_ENABLED;

    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    /*
     * Disable OTP_COPY
     */
    CRG_TOP->SYS_CTRL_REG &= ~REG_MSK(CRG_TOP, SYS_CTRL_REG, OTP_COPY);
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// TEST FUNCTIONALITY
////////////////////////////////////////////////////////////////////////////////////////////////////


/**
 * \brief Execution of the test.
 *
 * \return The test result.
 *         <ul>
 *             <li> 0, if the test succeeded
 *             <li> 1, if the test 1 failed
 *         </ul>
 *
 */
static int hw_otpc_core_test(int mode)
{
    ASSERT_WARNING_OTPC_MODE(HW_OTPC_MODE_STBY);

    /*
     * Put OTPC in the proper test mode
     */
    HW_OTPC_REG_SETF(MODE, MODE, mode);

    /*
     * Wait end of test
     */
    while (!(OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, TRDY)))
        ;


    /*
     * Check result
     */
    if (OTPC->OTPC_STAT_REG & HW_OTPC_REG_FIELD_MASK(STAT, TERROR))
    {
        return 1;
    }

    return 0;
}


int hw_otpc_blank(void)
{
    return hw_otpc_core_test(HW_OTPC_MODE_TBLANK);
}


int hw_otpc_tdec(void)
{
    return hw_otpc_core_test(HW_OTPC_MODE_TDEC);
}


int hw_otpc_twr(void)
{
    return hw_otpc_core_test(HW_OTPC_MODE_TWR);
}


#endif /* dg_configUSE_HW_OTPC */
/**
 * \}
 * \}
 * \}
 */
