/*
PXI.c:
    PXI I/O functions.

(c) TuxSH, 2016-2020
This is part of 3ds_pxi, which is licensed under the MIT license (see LICENSE for details).
*/

#include "PXI.h"

void PXIReset(void)
{
    REG_PXI_SYNC = 0;
    REG_PXI_CNT = CNT_CLEAR_SEND_FIFO;

    for(u32 i = 0; i < 16; i += 2)
    {
        REG_PXI_RECV;
        REG_PXI_RECV;
    }

    REG_PXI_CNT = 0;
    REG_PXI_CNT = CNT_ENABLE_FIFOs | CNT_ACKNOWLEDGE_FIFO_ERROR | CNT_CLEAR_SEND_FIFO;
}

void PXITriggerSync9IRQ(void)
{
    REG_PXI_INTERRUPT_CNT |= SYNC_TRIGGER_SYNC9_IRQ;
}

bool PXIIsSendFIFOFull(void)
{
    return (REG_PXI_CNT & CNT_SEND_FIFO_FULL_STATUS) != 0;
}

void PXISendByte(u8 byte)
{
    REG_PXI_BYTE_SENT_TO_REMOTE = byte;
}

void PXISendWord(u32 word)
{
    while(REG_PXI_CNT & CNT_SEND_FIFO_FULL_STATUS);
    REG_PXI_SEND = word;
}

void PXISendBuffer(const u32 *buffer, u32 nbWords)
{
    for(; nbWords > 0; nbWords--)
    {
        while(REG_PXI_CNT & CNT_SEND_FIFO_FULL_STATUS);
        REG_PXI_SEND = *buffer++;
    }
}

bool PXIIsReceiveFIFOEmpty(void)
{
    return (REG_PXI_CNT & CNT_RECEIVE_FIFO_EMPTY_STATUS) != 0;
}

u8 PXIReceiveByte(void)
{
    return REG_PXI_BYTE_RECEIVED_FROM_REMOTE;
}

u32 PXIReceiveWord(void)
{
    while(REG_PXI_CNT & CNT_RECEIVE_FIFO_EMPTY_STATUS);
    return REG_PXI_RECV;
}

void PXIReceiveBuffer(u32 *buffer, u32 nbWords)
{
    for(; nbWords > 0; nbWords--)
    {
        while(REG_PXI_CNT & CNT_RECEIVE_FIFO_EMPTY_STATUS);
        *buffer++ = REG_PXI_RECV;
    }
}

Result bindPXIInterrupts(Handle *syncInterrupt, Handle *receiveFIFONotEmptyInterrupt, Handle *sendFIFOEmptyInterrupt)
{
    Result res = 0;
    u32 mask = CNT_ENABLE_FIFOs | CNT_ENABLE_RECEIVE_FIFO_NOT_EMPTY_IRQ | CNT_ENABLE_SEND_FIFO_EMPTY_IRQ;
    if(receiveFIFONotEmptyInterrupt != NULL)
    {
        res = svcBindInterrupt(0x53, *receiveFIFONotEmptyInterrupt, 0, false);
        if(R_FAILED(res))
        {
            unbindPXIInterrupts(syncInterrupt, receiveFIFONotEmptyInterrupt, sendFIFOEmptyInterrupt);
            return res;
        }
        REG_PXI_CNT = (REG_PXI_CNT & mask) | CNT_ENABLE_RECEIVE_FIFO_NOT_EMPTY_IRQ;
    }

    if(sendFIFOEmptyInterrupt != NULL)
    {
        res = svcBindInterrupt(0x52, *sendFIFOEmptyInterrupt, 0, false);
        if(R_FAILED(res))
        {
            unbindPXIInterrupts(syncInterrupt, receiveFIFONotEmptyInterrupt, sendFIFOEmptyInterrupt);
            return res;
        }
        REG_PXI_CNT = (REG_PXI_CNT & mask) | CNT_ENABLE_SEND_FIFO_EMPTY_IRQ;
    }

    if(syncInterrupt != NULL)
    {
        res = svcBindInterrupt(0x50, *syncInterrupt, 0, false);
        if(R_FAILED(res))
        {
            unbindPXIInterrupts(syncInterrupt, receiveFIFONotEmptyInterrupt, sendFIFOEmptyInterrupt);
            return res;
        }
        REG_PXI_INTERRUPT_CNT |= SYNC_ENABLE_SYNC11_IRQ;
    }

    return res;
}

void unbindPXIInterrupts(Handle *syncInterrupt, Handle *receiveFIFONotEmptyInterrupt, Handle *sendFIFOEmptyInterrupt)
{
    if(receiveFIFONotEmptyInterrupt != NULL)
    {
        REG_PXI_CNT &= CNT_ENABLE_FIFOs | CNT_ENABLE_SEND_FIFO_EMPTY_IRQ;
        svcUnbindInterrupt(0x53, *receiveFIFONotEmptyInterrupt);
    }

    if(sendFIFOEmptyInterrupt != NULL)
    {
        REG_PXI_CNT &= CNT_ENABLE_FIFOs | CNT_ENABLE_RECEIVE_FIFO_NOT_EMPTY_IRQ;
        svcUnbindInterrupt(0x52, *sendFIFOEmptyInterrupt);
    }
    if(syncInterrupt != NULL)
    {
        REG_PXI_INTERRUPT_CNT &= ~SYNC_ENABLE_SYNC11_IRQ;
        svcUnbindInterrupt(0x50, *syncInterrupt);
    }
}