//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program 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 program 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.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// The main USART code, for serial communications over FPC connector
//-----------------------------------------------------------------------------
#include "usart.h"
#include "proxmark3_arm.h"

#define Dbprintf_usb(...) {\
        bool tmpfpc = g_reply_via_fpc;\
        bool tmpusb = g_reply_via_usb;\
        g_reply_via_fpc = false;\
        g_reply_via_usb = true;\
        Dbprintf(__VA_ARGS__);\
        g_reply_via_fpc = tmpfpc;\
        g_reply_via_usb = tmpusb;}

#define Dbprintf_fpc(...) {\
        bool tmpfpc = g_reply_via_fpc;\
        bool tmpusb = g_reply_via_usb;\
        g_reply_via_fpc = true;\
        g_reply_via_usb = false;\
        Dbprintf(__VA_ARGS__);\
        g_reply_via_fpc = tmpfpc;\
        g_reply_via_usb = tmpusb;}

#define Dbprintf_all(...) {\
        bool tmpfpc = g_reply_via_fpc;\
        bool tmpusb = g_reply_via_usb;\
        g_reply_via_fpc = true;\
        g_reply_via_usb = true;\
        Dbprintf(__VA_ARGS__);\
        g_reply_via_fpc = tmpfpc;\
        g_reply_via_usb = tmpusb;}


static volatile AT91PS_USART pUS1 = AT91C_BASE_US1;
static volatile AT91PS_PIO pPIO   = AT91C_BASE_PIOA;
static volatile AT91PS_PDC pPDC   = AT91C_BASE_PDC_US1;

uint32_t g_usart_baudrate = 0;
uint8_t g_usart_parity = 0;
/*
void usart_close(void) {
    // Reset the USART mode
    pUS1->US_MR = 0;

    // Reset the baud rate divisor register
    pUS1->US_BRGR = 0;

    // Reset the Timeguard Register
    pUS1->US_TTGR = 0;

    // Disable all interrupts
    pUS1->US_IDR = 0xFFFFFFFF;

    // Abort the Peripheral Data Transfers
    pUS1->US_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS;

    // Disable receiver and transmitter and stop any activity immediately
    pUS1->US_CR = AT91C_US_TXDIS | AT91C_US_RXDIS | AT91C_US_RSTTX | AT91C_US_RSTRX;
}
*/

static uint8_t us_in_a[USART_BUFFLEN];
static uint8_t us_in_b[USART_BUFFLEN];
static uint8_t *usart_cur_inbuf = NULL;
static uint16_t usart_cur_inbuf_off = 0;
static uint8_t us_rxfifo[USART_FIFOLEN];
static size_t us_rxfifo_low = 0;
static size_t us_rxfifo_high = 0;


static void usart_fill_rxfifo(void) {

    uint16_t rxfifo_free;

    if (pUS1->US_RNCR == 0) { // One buffer got filled, backup buffer being used

        if (us_rxfifo_low > us_rxfifo_high) {
            rxfifo_free = us_rxfifo_low - us_rxfifo_high;
        } else {
            rxfifo_free = sizeof(us_rxfifo) - us_rxfifo_high + us_rxfifo_low;
        }

        uint16_t available = USART_BUFFLEN - usart_cur_inbuf_off;

        if (available <= rxfifo_free) {

            for (uint16_t i = 0; i < available; i++) {
                us_rxfifo[us_rxfifo_high++] = usart_cur_inbuf[usart_cur_inbuf_off + i];
                if (us_rxfifo_high == sizeof(us_rxfifo)) {
                    us_rxfifo_high = 0;
                }
            }

            // Give next buffer
            pUS1->US_RNPR = (uint32_t)usart_cur_inbuf;
            pUS1->US_RNCR = USART_BUFFLEN;

            // Swap current buff
            if (usart_cur_inbuf == us_in_a) {
                usart_cur_inbuf = us_in_b;
            } else {
                usart_cur_inbuf = us_in_a;
            }

            usart_cur_inbuf_off = 0;
        } else {
            // Take only what we have room for
            available = rxfifo_free;
            for (uint16_t i = 0; i < available; i++) {

                us_rxfifo[us_rxfifo_high++] = usart_cur_inbuf[usart_cur_inbuf_off + i];

                if (us_rxfifo_high == sizeof(us_rxfifo)) {
                    us_rxfifo_high = 0;
                }
            }
            usart_cur_inbuf_off += available;
            return;
        }
    }

    if (pUS1->US_RCR < USART_BUFFLEN - usart_cur_inbuf_off) { // Current buffer partially filled

        if (us_rxfifo_low > us_rxfifo_high) {
            rxfifo_free = (us_rxfifo_low - us_rxfifo_high);
        } else {
            rxfifo_free = (sizeof(us_rxfifo) - us_rxfifo_high + us_rxfifo_low);
        }

        uint16_t available = (USART_BUFFLEN - pUS1->US_RCR - usart_cur_inbuf_off);

        if (available > rxfifo_free) {
            available = rxfifo_free;
        }

        for (uint16_t i = 0; i < available; i++) {
            us_rxfifo[us_rxfifo_high++] = usart_cur_inbuf[usart_cur_inbuf_off + i];
            if (us_rxfifo_high == sizeof(us_rxfifo)) {
                us_rxfifo_high = 0;
            }
        }
        usart_cur_inbuf_off += available;
    }
}

uint16_t usart_rxdata_available(void) {
    usart_fill_rxfifo();
    if (us_rxfifo_low <= us_rxfifo_high) {
        return (us_rxfifo_high - us_rxfifo_low);
    } else {
        return (sizeof(us_rxfifo) - us_rxfifo_low + us_rxfifo_high);
    }
}

uint32_t usart_read_ng(uint8_t *data, size_t len) {

    if (len == 0) {
        return 0;
    }

    uint32_t bytes_rcv = 0;
    uint32_t try = 0;
//    uint32_t highest_observed_try = 0;
    // Empirical max try observed: 3000000 / USART_BAUD_RATE
    // Let's take 10x

    uint32_t tryconstant = 0;
#ifdef USART_SLOW_LINK
    // Experienced up to 13200 tries on BT link even at 460800
    tryconstant = 50000;
#endif

    uint32_t maxtry = 10 * (3000000 / USART_BAUD_RATE) + tryconstant;

    while (len) {

        uint32_t available = usart_rxdata_available();
        uint32_t packetSize = MIN(available, len);

        if (available > 0) {
//            Dbprintf_usb("Dbg USART ask %d bytes, available %d bytes, packetsize %d bytes", len, available, packetSize);
//            highest_observed_try = MAX(highest_observed_try, try);
            try = 0;
        }

        len -= packetSize;

        while (packetSize--) {
            if (us_rxfifo_low == sizeof(us_rxfifo)) {
                us_rxfifo_low = 0;
            }
            data[bytes_rcv++] = us_rxfifo[us_rxfifo_low++];
        }

        if (try++ == maxtry) {
//            Dbprintf_usb("Dbg USART TIMEOUT");
                break;
            }
    }
//    highest_observed_try = MAX(highest_observed_try, try);
//    Dbprintf_usb("Dbg USART max observed try %i", highest_observed_try);
    return bytes_rcv;
}

// transfer from device to client
int usart_writebuffer_sync(uint8_t *data, size_t len) {

    // Wait for current PDC bank to be free
    // (and check next bank too, in case there will be a usart_writebuffer_async)
    while (pUS1->US_TNCR || pUS1->US_TCR) {};
    pUS1->US_TPR = (uint32_t)data;
    pUS1->US_TCR = len;
    // Wait until finishing all transfers to make sure "data" buffer can be discarded
    // (if we don't wait here, bulk send as e.g. "hw status" will fail)
    while (pUS1->US_TNCR || pUS1->US_TCR) {};
    return PM3_SUCCESS;
}

void usart_init(uint32_t baudrate, uint8_t parity) {

    if (baudrate != 0) {
        g_usart_baudrate = baudrate;
    }

    if ((parity == 'N') || (parity == 'O') || (parity == 'E')) {
        g_usart_parity = parity;
    }

    // For a nice detailed sample, interrupt driven but still relevant.
    // See https://www.sparkfun.com/datasheets/DevTools/SAM7/at91sam7%20serial%20communications.pdf

    // disable & reset receiver / transmitter for configuration
    pUS1->US_CR = (AT91C_US_RSTRX | AT91C_US_RSTTX | AT91C_US_RXDIS | AT91C_US_TXDIS);

    //enable the USART1 Peripheral clock
    AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_US1);

    // disable PIO control of receive / transmit pins
    pPIO->PIO_PDR |= (AT91C_PA21_RXD1 | AT91C_PA22_TXD1);

    // enable peripheral mode A on receive / transmit pins
    pPIO->PIO_ASR |= (AT91C_PA21_RXD1 | AT91C_PA22_TXD1);
    pPIO->PIO_BSR = 0;

    // enable pull-up on receive / transmit pins (see 31.5.1 I/O Lines)
    pPIO->PIO_PPUER |= (AT91C_PA21_RXD1 | AT91C_PA22_TXD1);

    // set mode
    uint32_t mode = AT91C_US_USMODE_NORMAL |         // normal mode
                    AT91C_US_CLKS_CLOCK |            // MCK (48MHz)
                    AT91C_US_OVER |                  // oversampling
                    AT91C_US_CHRL_8_BITS |           // 8 bits
                    AT91C_US_NBSTOP_1_BIT |          // 1 stop bit
                    AT91C_US_CHMODE_NORMAL;          // channel mode: normal

    switch (g_usart_parity) {
        case 'N':
            mode |= AT91C_US_PAR_NONE;               // parity: none
            break;
        case 'O':
            mode |= AT91C_US_PAR_ODD;                // parity: odd
            break;
        case 'E':
            mode |= AT91C_US_PAR_EVEN;               // parity: even
            break;
    }
    pUS1->US_MR = mode;

    // all interrupts disabled
    pUS1->US_IDR = 0xFFFF;

    // http://ww1.microchip.com/downloads/en/DeviceDoc/doc6175.pdf
    // note that for very large baudrates, error is not neglectible:
    // b921600  => 8.6%
    // b1382400 => 8.6%
    // FP, Fractional Part  (Datasheet p402, Supported in AT91SAM512 / 256) (31.6.1.3)
    // FP = 0 disabled;
    // FP = 1-7 Baudrate resolution,
    // CD, Clock divider,
    //    sync == 0 , (async?)
    //       OVER = 0,  -no
    //          baudrate == selected clock/16/CD
    //       OVER = 1,  -yes we are oversampling
    //          baudrate == selected clock/8/CD    --> this is ours
    //
    uint32_t brgr = MCK / (g_usart_baudrate << 3);
    // doing fp = round((mck / (g_usart_baudrate << 3) - brgr) * 8) with integers:
    uint32_t fp = ((16 * MCK / (g_usart_baudrate << 3) - 16 * brgr) + 1) / 2;

    pUS1->US_BRGR = (fp << 16) | brgr;

    // Write the Timeguard Register
    pUS1->US_TTGR = 0;
    pUS1->US_RTOR = 0;
    pUS1->US_FIDI = 0;
    pUS1->US_IF = 0;

    // Initialize DMA buffers
    pUS1->US_TPR = (uint32_t)0;
    pUS1->US_TCR = 0;
    pUS1->US_TNPR = (uint32_t)0;
    pUS1->US_TNCR = 0;
    pUS1->US_RPR = (uint32_t)us_in_a;
    pUS1->US_RCR = USART_BUFFLEN;
    usart_cur_inbuf = us_in_a;
    usart_cur_inbuf_off = 0;
    pUS1->US_RNPR = (uint32_t)us_in_b;
    pUS1->US_RNCR = USART_BUFFLEN;

    // Initialize our fifo
    us_rxfifo_low = 0;
    us_rxfifo_high = 0;

    // re-enable receiver / transmitter
    pUS1->US_CR = (AT91C_US_RXEN | AT91C_US_TXEN);

    // ready to receive and transmit
    pUS1->US_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;
}
