/*
 * This file is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Code by Andrew Tridgell and Siddharth Bharat Purohit
 */
#include "shared_dma.h"

/*
  code to handle sharing of DMA channels between peripherals
 */

#if CH_CFG_USE_SEMAPHORES == TRUE && STM32_DMA_ADVANCED

using namespace ChibiOS;

Shared_DMA::dma_lock Shared_DMA::locks[SHARED_DMA_MAX_STREAM_ID];

void Shared_DMA::init(void)
{
    for (uint8_t i=0; i<SHARED_DMA_MAX_STREAM_ID; i++) {
        chBSemObjectInit(&locks[i].semaphore, false);
    }
}

// constructor
Shared_DMA::Shared_DMA(uint8_t _stream_id1,
                       uint8_t _stream_id2,
                       dma_allocate_fn_t _allocate,
                       dma_deallocate_fn_t _deallocate)
{
    stream_id1 = _stream_id1;
    stream_id2 = _stream_id2;
    allocate = _allocate;
    deallocate = _deallocate;
}

//remove any assigned deallocator or allocator
void Shared_DMA::unregister()
{
    if (locks[stream_id1].obj == this) {
        locks[stream_id1].deallocate(this);
        locks[stream_id1].obj = nullptr;
    }

    if (locks[stream_id2].obj == this) {
        locks[stream_id2].deallocate(this);
        locks[stream_id2].obj = nullptr;
    }
}

// lock the DMA channels
void Shared_DMA::lock_core(void)
{
    // see if another driver has DMA allocated. If so, call their
    // deallocation function
    if (stream_id1 != SHARED_DMA_NONE &&
        locks[stream_id1].obj && locks[stream_id1].obj != this) {
        locks[stream_id1].deallocate(locks[stream_id1].obj);
        locks[stream_id1].obj = nullptr;
    }
    if (stream_id2 != SHARED_DMA_NONE &&
        locks[stream_id2].obj && locks[stream_id2].obj != this) {
        locks[stream_id2].deallocate(locks[stream_id2].obj);
        locks[stream_id2].obj = nullptr;
    }
    if ((stream_id1 != SHARED_DMA_NONE && locks[stream_id1].obj == nullptr) ||
        (stream_id2 != SHARED_DMA_NONE && locks[stream_id2].obj == nullptr)) {
        // allocate the DMA channels and put our deallocation function in place
        allocate(this);
        if (stream_id1 != SHARED_DMA_NONE) {
            locks[stream_id1].deallocate = deallocate;
            locks[stream_id1].obj = this;
        }
        if (stream_id2 != SHARED_DMA_NONE) {
            locks[stream_id2].deallocate = deallocate;
            locks[stream_id2].obj = this;
        }
    }
    have_lock = true;
}

// lock the DMA channels, blocking method
void Shared_DMA::lock(void)
{
    if (stream_id1 != SHARED_DMA_NONE) {
        chBSemWait(&locks[stream_id1].semaphore);
    }
    if (stream_id2 != SHARED_DMA_NONE) {
        chBSemWait(&locks[stream_id2].semaphore);
    }
    lock_core();
}

// lock the DMA channels, non-blocking
bool Shared_DMA::lock_nonblock(void)
{
    if (stream_id1 != SHARED_DMA_NONE) {
        if (chBSemWaitTimeout(&locks[stream_id1].semaphore, 1) != MSG_OK) {
            chSysDisable();
            if (locks[stream_id1].obj != nullptr && locks[stream_id1].obj != this) {
                locks[stream_id1].obj->contention = true;
            }
            chSysEnable();
            contention = true;
            return false;
        }
    }
    if (stream_id2 != SHARED_DMA_NONE) {
        if (chBSemWaitTimeout(&locks[stream_id2].semaphore, 1) != MSG_OK) {
            if (stream_id1 != SHARED_DMA_NONE) {
                chBSemSignal(&locks[stream_id1].semaphore);
            }
            chSysDisable();
            if (locks[stream_id2].obj != nullptr && locks[stream_id2].obj != this) {
                locks[stream_id2].obj->contention = true;
            }
            chSysEnable();
            contention = true;
            return false;
        }
    }
    lock_core();
    return true;
}

// unlock the DMA channels
void Shared_DMA::unlock(void)
{
    osalDbgAssert(have_lock, "must have lock");
    if (stream_id2 != SHARED_DMA_NONE) {
        chBSemSignal(&locks[stream_id2].semaphore);        
    }
    if (stream_id1 != SHARED_DMA_NONE) {
        chBSemSignal(&locks[stream_id1].semaphore);
    }
    have_lock = false;
}

// unlock the DMA channels from a lock zone
void Shared_DMA::unlock_from_lockzone(void)
{
    osalDbgAssert(have_lock, "must have lock");
    if (stream_id2 != SHARED_DMA_NONE) {
        chBSemSignalI(&locks[stream_id2].semaphore);
        chSchRescheduleS();
    }
    if (stream_id1 != SHARED_DMA_NONE) {
        chBSemSignalI(&locks[stream_id1].semaphore);
        chSchRescheduleS();
    }
    have_lock = false;
}

// unlock the DMA channels from an IRQ
void Shared_DMA::unlock_from_IRQ(void)
{
    osalDbgAssert(have_lock, "must have lock");
    if (stream_id2 != SHARED_DMA_NONE) {
        chBSemSignalI(&locks[stream_id2].semaphore);        
    }
    if (stream_id1 != SHARED_DMA_NONE) {
        chBSemSignalI(&locks[stream_id1].semaphore);
    }
    have_lock = false;
}

/*
  lock all channels - used on reboot to ensure no sensor DMA is in
  progress
 */
void Shared_DMA::lock_all(void)
{
    for (uint8_t i=0; i<SHARED_DMA_MAX_STREAM_ID; i++) {
        chBSemWait(&locks[i].semaphore);
    }
}

#endif // CH_CFG_USE_SEMAPHORES && STM32_DMA_ADVANCED