/*
 * This file is part of Cleanflight.
 *
 * Cleanflight 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.
 *
 * Cleanflight 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 Cleanflight.  If not, see <http://www.gnu.org/licenses/>.
 * Author: 4712
 * have a look at https://github.com/sim-/tgy/blob/master/boot.inc
 * for info about the stk500v2 implementation
 */

#include <stdbool.h>
#include <stdint.h>

#include "platform.h"

#ifdef  USE_SERIAL_4WAY_BLHELI_INTERFACE

#include "drivers/io.h"
#include "drivers/serial.h"
#include "drivers/time.h"

#include "io/serial.h"
#include "io/serial_4way.h"
#include "io/serial_4way_impl.h"
#include "io/serial_4way_stk500v2.h"

#ifdef USE_SERIAL_4WAY_SK_BOOTLOADER

#define BIT_LO_US (32) //32uS
#define BIT_HI_US (2*BIT_LO_US)

static uint8_t StkInBuf[16];

#define STK_BIT_TIMEOUT 250 // micro seconds
#define STK_WAIT_TICKS (1000 / STK_BIT_TIMEOUT)    // per ms
#define STK_WAITCYLCES (STK_WAIT_TICKS * 35)       // 35ms
#define STK_WAITCYLCES_START (STK_WAIT_TICKS / 2)  // 0.5 ms
#define STK_WAITCYLCES_EXT (STK_WAIT_TICKS * 5000) //5 s

#define  WaitPinLo  while (ESC_IS_HI) {if (micros() > timeout_timer) goto timeout;}
#define  WaitPinHi  while (ESC_IS_LO) {if (micros() > timeout_timer) goto timeout;}

static uint32_t LastBitTime;
static uint32_t HiLoTsh;

static uint8_t SeqNumber;
static uint8_t StkCmd;
static uint8_t ckSumIn;
static uint8_t ckSumOut;

// used STK message constants from ATMEL AVR - Application note
#define MESSAGE_START           0x1B
#define TOKEN                   0x0E

#define CMD_SIGN_ON             0x01
#define CMD_LOAD_ADDRESS        0x06
#define CMD_CHIP_ERASE_ISP      0x12
#define CMD_PROGRAM_FLASH_ISP   0x13
#define CMD_READ_FLASH_ISP      0x14
#define CMD_PROGRAM_EEPROM_ISP  0x15
#define CMD_READ_EEPROM_ISP     0x16
#define CMD_READ_SIGNATURE_ISP  0x1B
#define CMD_SPI_MULTI           0x1D

#define STATUS_CMD_OK           0x00

#define CmdFlashEepromRead 0xA0
#define EnterIspCmd1 0xAC
#define EnterIspCmd2 0x53
#define signature_r  0x30

#define delay_us(x) delayMicroseconds(x)
#define IRQ_OFF // dummy
#define IRQ_ON  // dummy

static void StkSendByte(uint8_t dat)
{
    ckSumOut ^= dat;
    for (uint8_t i = 0; i < 8; i++)    {
        if (dat & 0x01) {
            // 1-bits are encoded as 64.0us high, 72.8us low (135.8us total).
            ESC_SET_HI;
            delay_us(BIT_HI_US);
            ESC_SET_LO;
            delay_us(BIT_HI_US);
        } else {
            // 0-bits are encoded as 27.8us high, 34.5us low, 34.4us high, 37.9 low (134.6us total)
            ESC_SET_HI;
            delay_us(BIT_LO_US);
            ESC_SET_LO;
            delay_us(BIT_LO_US);
            ESC_SET_HI;
            delay_us(BIT_LO_US);
            ESC_SET_LO;
            delay_us(BIT_LO_US);
        }
        dat >>= 1;
    }
}

static void StkSendPacketHeader(void)
{
    IRQ_OFF;
    ESC_OUTPUT;
    StkSendByte(0xFF);
    StkSendByte(0xFF);
    StkSendByte(0x7F);
    ckSumOut = 0;
    StkSendByte(MESSAGE_START);
    StkSendByte(++SeqNumber);
}

static void StkSendPacketFooter(void)
{
    StkSendByte(ckSumOut);
    ESC_SET_HI;
    delay_us(BIT_LO_US);
    ESC_INPUT;
    IRQ_ON;
}



static int8_t ReadBit(void)
{
    uint32_t btimer = micros();
    uint32_t timeout_timer = btimer + STK_BIT_TIMEOUT;
    WaitPinLo;
    WaitPinHi;
    LastBitTime = micros() - btimer;
    if (LastBitTime <= HiLoTsh) {
        timeout_timer = timeout_timer + STK_BIT_TIMEOUT;
        WaitPinLo;
        WaitPinHi;
        //lo-bit
        return 0;
    } else {
        return 1;
    }
timeout:
    return -1;
}

static uint8_t ReadByte(uint8_t *bt)
{
    *bt = 0;
    for (uint8_t i = 0; i < 8; i++) {
        int8_t bit = ReadBit();
        if (bit == -1) goto timeout;
        if (bit == 1) {
            *bt |= (1 << i);
        }
    }
    ckSumIn ^=*bt;
    return 1;
timeout:
    return 0;
}

static uint8_t StkReadLeader(void)
{

    // Reset learned timing
    HiLoTsh = BIT_HI_US + BIT_LO_US;

    // Wait for the first bit
    uint32_t waitcycl; //250uS each

    if ((StkCmd == CMD_PROGRAM_EEPROM_ISP) || (StkCmd == CMD_CHIP_ERASE_ISP)) {
         waitcycl = STK_WAITCYLCES_EXT;
    } else if (StkCmd == CMD_SIGN_ON) {
        waitcycl = STK_WAITCYLCES_START;
    } else {
        waitcycl= STK_WAITCYLCES;
    }
    for ( ; waitcycl >0 ; waitcycl--) {
        //check is not timeout
        if (ReadBit() >- 1) break;
    }

    //Skip the first bits
    if (waitcycl == 0) {
        goto timeout;
    }

    for (uint8_t i = 0; i < 10; i++) {
        if (ReadBit() == -1) goto timeout;
    }

    // learn timing
    HiLoTsh = (LastBitTime >> 1) + (LastBitTime >> 2);

    // Read until we get a 0 bit
    int8_t bit;
    do {
        bit = ReadBit();
        if (bit == -1) goto timeout;
    } while (bit > 0);
    return 1;
timeout:
    return 0;
}

static uint8_t StkRcvPacket(uint8_t *pstring)
{
    uint8_t bt = 0;
    uint8_16_u Len;

    IRQ_OFF;
    if (!StkReadLeader()) goto Err;
    ckSumIn=0;
    if (!ReadByte(&bt) || (bt != MESSAGE_START)) goto Err;
    if (!ReadByte(&bt) || (bt != SeqNumber)) goto Err;
    ReadByte(&Len.bytes[1]);
    if (Len.bytes[1] > 1) goto Err;
    ReadByte(&Len.bytes[0]);
    if (Len.bytes[0] < 1) goto Err;
    if (!ReadByte(&bt) || (bt != TOKEN)) goto Err;
    if (!ReadByte(&bt) || (bt != StkCmd)) goto Err;
    if (!ReadByte(&bt) || (bt != STATUS_CMD_OK)) goto Err;
    for (uint16_t i = 0; i < (Len.word - 2); i++)
    {
         if (!ReadByte(pstring)) goto Err;
         pstring++;
    }
    ReadByte(&bt);
    if (ckSumIn != 0) goto Err;
    IRQ_ON;
    return 1;
Err:
    IRQ_ON;
    return 0;
}

static uint8_t _CMD_SPI_MULTI_EX(volatile uint8_t * ResByte,uint8_t Cmd,uint8_t AdrHi,uint8_t AdrLo)
{
    StkCmd= CMD_SPI_MULTI;
    StkSendPacketHeader();
    StkSendByte(0); // hi byte Msg len
    StkSendByte(8); // lo byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(CMD_SPI_MULTI);
    StkSendByte(4); // NumTX
    StkSendByte(4); // NumRX
    StkSendByte(0); // RxStartAdr
    StkSendByte(Cmd);    // {TxData} Cmd
    StkSendByte(AdrHi); // {TxData} AdrHi
    StkSendByte(AdrLo);    // {TxData} AdrLoch
    StkSendByte(0); // {TxData} 0
    StkSendPacketFooter();
    if (StkRcvPacket((void *)StkInBuf)) { // NumRX + 3
         if ((StkInBuf[0] == 0x00) && ((StkInBuf[1] == Cmd)||(StkInBuf[1] == 0x00)/* ignore  zero returns */) &&(StkInBuf[2] == 0x00)) {
            *ResByte = StkInBuf[3];
         }
         return 1;
    }
    return 0;
}

static uint8_t _CMD_LOAD_ADDRESS(ioMem_t *pMem)
{
    // ignore 0xFFFF
    // assume address is set before and we read or write the immediately following package
    if ((pMem->D_FLASH_ADDR_H == 0xFF) && (pMem->D_FLASH_ADDR_L == 0xFF)) return 1;
    StkCmd = CMD_LOAD_ADDRESS;
    StkSendPacketHeader();
    StkSendByte(0); // hi byte Msg len
    StkSendByte(5); // lo byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(CMD_LOAD_ADDRESS);
    StkSendByte(0);
    StkSendByte(0);
    StkSendByte(pMem->D_FLASH_ADDR_H);
    StkSendByte(pMem->D_FLASH_ADDR_L);
    StkSendPacketFooter();
    return (StkRcvPacket((void *)StkInBuf));
}

static uint8_t _CMD_READ_MEM_ISP(ioMem_t *pMem)
{
    uint8_t LenHi;
    if (pMem->D_NUM_BYTES>0) {
        LenHi=0;
    } else {
        LenHi=1;
    }
    StkSendPacketHeader();
    StkSendByte(0); // hi byte Msg len
    StkSendByte(4); // lo byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(StkCmd);
    StkSendByte(LenHi);
    StkSendByte(pMem->D_NUM_BYTES);
    StkSendByte(CmdFlashEepromRead);
    StkSendPacketFooter();
    return (StkRcvPacket(pMem->D_PTR_I));
}

static uint8_t _CMD_PROGRAM_MEM_ISP(ioMem_t *pMem)
{
    uint8_16_u Len;
    uint8_t LenLo = pMem->D_NUM_BYTES;
    uint8_t LenHi;
    if (LenLo) {
        LenHi = 0;
        Len.word = LenLo + 10;
    } else {
        LenHi = 1;
        Len.word = 256 + 10;
    }
    StkSendPacketHeader();
    StkSendByte(Len.bytes[1]); // high byte Msg len
    StkSendByte(Len.bytes[0]); // low byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(StkCmd);
    StkSendByte(LenHi);
    StkSendByte(LenLo);
    StkSendByte(0); // mode
    StkSendByte(0); // delay
    StkSendByte(0); // cmd1
    StkSendByte(0); // cmd2
    StkSendByte(0); // cmd3
    StkSendByte(0); // poll1
    StkSendByte(0); // poll2
    do {
        StkSendByte(*pMem->D_PTR_I);
        pMem->D_PTR_I++;
        LenLo--;
    } while (LenLo);
    StkSendPacketFooter();
    return StkRcvPacket((void *)StkInBuf);
}

uint8_t Stk_SignOn(void)
{
    StkCmd=CMD_SIGN_ON;
    StkSendPacketHeader();
    StkSendByte(0); // hi byte Msg len
    StkSendByte(1); // lo byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(CMD_SIGN_ON);
    StkSendPacketFooter();
    return (StkRcvPacket((void *) StkInBuf));
}

uint8_t Stk_ConnectEx(uint8_32_u *pDeviceInfo)
{
    if (Stk_SignOn()) {
        if (_CMD_SPI_MULTI_EX(&pDeviceInfo->bytes[1], signature_r,0,1)) {
            if (_CMD_SPI_MULTI_EX(&pDeviceInfo->bytes[0], signature_r,0,2)) {
                return 1;
            }
        }
    }
    return 0;
}

uint8_t Stk_Chip_Erase(void)
{
    StkCmd = CMD_CHIP_ERASE_ISP;
    StkSendPacketHeader();
    StkSendByte(0); // high byte Msg len
    StkSendByte(7); // low byte Msg len
    StkSendByte(TOKEN);
    StkSendByte(CMD_CHIP_ERASE_ISP);
    StkSendByte(20); // ChipErase_eraseDelay atmega8
    StkSendByte(0); // ChipErase_pollMethod atmega8
    StkSendByte(0xAC);
    StkSendByte(0x88);
    StkSendByte(0x13);
    StkSendByte(0x76);
    StkSendPacketFooter();
    return (StkRcvPacket(StkInBuf));
}

uint8_t Stk_ReadFlash(ioMem_t *pMem)
{
    if (_CMD_LOAD_ADDRESS(pMem)) {
        StkCmd = CMD_READ_FLASH_ISP;
        return (_CMD_READ_MEM_ISP(pMem));
    }
    return 0;
}


uint8_t Stk_ReadEEprom(ioMem_t *pMem)
{
    if (_CMD_LOAD_ADDRESS(pMem)) {
        StkCmd = CMD_READ_EEPROM_ISP;
        return (_CMD_READ_MEM_ISP(pMem));
    }
    return 0;
}

uint8_t Stk_WriteFlash(ioMem_t *pMem)
{
    if (_CMD_LOAD_ADDRESS(pMem)) {
        StkCmd = CMD_PROGRAM_FLASH_ISP;
        return (_CMD_PROGRAM_MEM_ISP(pMem));
    }
    return 0;
}

uint8_t Stk_WriteEEprom(ioMem_t *pMem)
{
    if (_CMD_LOAD_ADDRESS(pMem)) {
        StkCmd = CMD_PROGRAM_EEPROM_ISP;
        return (_CMD_PROGRAM_MEM_ISP(pMem));
    }
    return 0;
}
#endif
#endif
