/*
 * ll_crc_handler.cpp
 *
 *  Created on: Oct 26, 2019
 *      Author: compi
 */

#include <f4ll/crc_handler.h>

namespace f4ll {

crc_handler::crc_handler(DMA_TypeDef *dma, uint32_t stream)
    : m_dma(dma, stream)
{
    LL_DMA_EnableIT_TC(dma, stream);
    LL_DMA_EnableIT_TE(dma, stream);
    LL_DMA_SetM2MDstAddress(dma, stream, (uint32_t)&CRC->DR);
}

void crc_handler::attach_slot(slot_base &slot)
{
    for (unsigned int i = 0; i < slot.m_task_count; ++i) {
        auto &task(slot[i]);
        task.m_address = nullptr;
        task.m_word_count = 0;
        task.m_callback = nullptr;
        task.m_callback_param = 0;
    }
    uint32_t prim = __get_PRIMASK();
    __disable_irq();
    slot.m_next = m_first_slot;
    m_first_slot = &slot;
    __set_PRIMASK(prim);
}

bool crc_handler::enqueue(slot_base &slot, uint8_t task, void const *address, uint16_t len, icallback *cb, uintptr_t cb_param)
{
    uint32_t prim = __get_PRIMASK();
    bool immediate;

    // TODO: do we need sanity check here? (is slot attached, is task in range,
    // etc...?)

    while (is_active(slot, task))
        ;
    __disable_irq();
    immediate = m_active_slot == nullptr;
    slot[task].m_address = (!immediate) ? address : nullptr;
    slot[task].m_word_count = (len + 3) / 4;
    slot[task].m_callback = cb;
    slot[task].m_callback_param = cb_param;
    if (immediate) {
        m_active_slot = &slot;
        m_active_task = task;
    }
    __set_PRIMASK(prim);

    if (immediate) {
        CRC->CR = 1;
        LL_DMA_SetM2MSrcAddress(m_dma.get_dma(), m_dma.get_stream(), (uint32_t)address);
        LL_DMA_SetDataLength(m_dma.get_dma(), m_dma.get_stream(), (len + 3) / 4);
        LL_DMA_EnableStream(m_dma.get_dma(), m_dma.get_stream());
    }
    return immediate;
}

bool crc_handler::is_active(slot_base &slot, uint8_t task) const
{
    return task < slot.m_task_count && slot[task].m_word_count != 0;
}

bool crc_handler::is_queued(slot_base &slot, uint8_t task) const
{
    return task < slot.m_task_count && slot[task].m_address != nullptr;
}

bool crc_handler::is_running(slot_base &slot, uint8_t task) const
{
    return task < slot.m_task_count && slot[task].m_word_count && !slot[task].m_address;
}

void crc_handler::dma_transfer_completed(void)
{
    if (*m_dma.get_is_reg() & m_dma.get_tc_mask()) { // DMA transfer complete
        *m_dma.get_ifc_reg() = m_dma.get_tc_mask();
        LL_DMA_DisableStream(m_dma.get_dma(), m_dma.get_stream());
        if (m_active_slot) {
            if ((*m_active_slot)[m_active_task].m_callback) {
                (*m_active_slot)[m_active_task].m_callback->crc_succeeded(
                    (*m_active_slot)[m_active_task].m_callback_param, CRC->DR, m_active_task);
            } else if ((*m_active_slot)[m_active_task].m_callback_param) {
                *reinterpret_cast<uint32_t *>((*m_active_slot)[m_active_task].m_callback_param) = CRC->DR;
            }
        }
    } else if (*m_dma.get_is_reg() & m_dma.get_te_mask()) { // DMA transfer error
        *m_dma.get_ifc_reg() = m_dma.get_te_mask();
        LL_DMA_DisableStream(m_dma.get_dma(), m_dma.get_stream());
        if (m_active_slot) {
            if ((*m_active_slot)[m_active_task].m_callback) {
                (*m_active_slot)[m_active_task].m_callback->crc_failed(
                    (*m_active_slot)[m_active_task].m_callback_param, CRC->DR, m_active_task);
            } else if ((*m_active_slot)[m_active_task].m_callback_param) {
                *reinterpret_cast<uint32_t *>((*m_active_slot)[m_active_task].m_callback_param) = -1;
            }
        }
    }
    (*m_active_slot)[m_active_task].m_callback = nullptr;
    (*m_active_slot)[m_active_task].m_callback_param = 0;
    (*m_active_slot)[m_active_task].m_word_count = 0;
    start_next_task();
}

void crc_handler::start_next_task(void)
{
    bool more_tasks;
    uint8_t index = 0;

    do {
        slot_base volatile *slot = m_first_slot;
        more_tasks = false;
        while (slot) {
            if (index < slot->m_task_count) {
                if ((*slot)[index].m_address) {
                    m_active_slot = slot;
                    m_active_task = index;
                    CRC->CR = 1;
                    LL_DMA_SetM2MSrcAddress(m_dma.get_dma(), m_dma.get_stream(), reinterpret_cast<uint32_t>((*slot)[index].m_address));
                    LL_DMA_SetDataLength(m_dma.get_dma(), m_dma.get_stream(), (*slot)[index].m_word_count);
                    LL_DMA_EnableStream(m_dma.get_dma(), m_dma.get_stream());
                    (*slot)[index].m_address = nullptr; // marking as started
                    return;
                }
                if (index + 1 < slot->m_task_count) {
                    more_tasks = true;
                }
            }
            slot = slot->m_next;
        }
        ++index;
    } while (more_tasks);
    m_active_slot = nullptr;
}

void crc_handler::wait_results(slot_base &slot, uint8_t task) const
{
    while (is_queued(slot, task))
        ;
    while (is_active(slot, task))
        ;
}

uint32_t crc_handler::compute(slot_base &slot, uint8_t task, void const *address, uint16_t len)
{
    uint32_t result;
    enqueue(slot, task, address, len, nullptr, reinterpret_cast<uintptr_t>(&result));
    while (is_active(slot, task))
        ;
    return result;
}

} // namespace f4ll
