#include <AP_HAL/AP_HAL.h>

#if CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_NAVIO
#include "GPIO.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <assert.h>
#include "RCInput_Navio.h"

//Parametres
#define RCIN_NAVIO_BUFFER_LENGTH   8
#define RCIN_NAVIO_SAMPLE_FREQ     500
#define RCIN_NAVIO_DMA_CHANNEL     0
#define RCIN_NAVIO_MAX_COUNTER     1300
#define PPM_INPUT_NAVIO RPI_GPIO_4
#define RCIN_NAVIO_MAX_SIZE_LINE   50

//Memory Addresses
#define RCIN_NAVIO_RPI1_DMA_BASE 0x20007000
#define RCIN_NAVIO_RPI1_CLK_BASE 0x20101000
#define RCIN_NAVIO_RPI1_PCM_BASE 0x20203000

#define RCIN_NAVIO_RPI2_DMA_BASE 0x3F007000
#define RCIN_NAVIO_RPI2_CLK_BASE 0x3F101000
#define RCIN_NAVIO_RPI2_PCM_BASE 0x3F203000

#define RCIN_NAVIO_GPIO_LEV0_ADDR  0x7e200034
#define RCIN_NAVIO_DMA_LEN         0x1000
#define RCIN_NAVIO_CLK_LEN         0xA8
#define RCIN_NAVIO_PCM_LEN         0x24
#define RCIN_NAVIO_TIMER_BASE      0x7e003004

#define RCIN_NAVIO_DMA_SRC_INC     (1<<8)
#define RCIN_NAVIO_DMA_DEST_INC    (1<<4) 
#define RCIN_NAVIO_DMA_NO_WIDE_BURSTS  (1<<26)
#define RCIN_NAVIO_DMA_WAIT_RESP   (1<<3)
#define RCIN_NAVIO_DMA_D_DREQ      (1<<6)
#define RCIN_NAVIO_DMA_PER_MAP(x)  ((x)<<16)
#define RCIN_NAVIO_DMA_END         (1<<1)
#define RCIN_NAVIO_DMA_RESET       (1<<31)
#define RCIN_NAVIO_DMA_INT         (1<<2)

#define RCIN_NAVIO_DMA_CS          (0x00/4)
#define RCIN_NAVIO_DMA_CONBLK_AD   (0x04/4)
#define RCIN_NAVIO_DMA_DEBUG       (0x20/4)

#define RCIN_NAVIO_PCM_CS_A        (0x00/4)
#define RCIN_NAVIO_PCM_FIFO_A      (0x04/4)
#define RCIN_NAVIO_PCM_MODE_A      (0x08/4)
#define RCIN_NAVIO_PCM_RXC_A       (0x0c/4)
#define RCIN_NAVIO_PCM_TXC_A       (0x10/4)
#define RCIN_NAVIO_PCM_DREQ_A      (0x14/4)
#define RCIN_NAVIO_PCM_INTEN_A     (0x18/4)
#define RCIN_NAVIO_PCM_INT_STC_A   (0x1c/4)
#define RCIN_NAVIO_PCM_GRAY        (0x20/4)

#define RCIN_NAVIO_PCMCLK_CNTL     38
#define RCIN_NAVIO_PCMCLK_DIV      39


extern const AP_HAL::HAL& hal;

using namespace Linux;


volatile uint32_t *LinuxRCInput_Navio::pcm_reg;
volatile uint32_t *LinuxRCInput_Navio::clk_reg;
volatile uint32_t *LinuxRCInput_Navio::dma_reg;

Memory_table::Memory_table()
{
    _page_count = 0;
}

//Init Memory table
Memory_table::Memory_table(uint32_t page_count, int version)
{
    uint32_t i;
    int fdMem, file;
    //Cache coherent adresses depends on RPI's version
    uint32_t bus = version == 1 ? 0x40000000 : 0xC0000000;
    uint64_t pageInfo;
    void* offset;

    _virt_pages = (void**)malloc(page_count * sizeof(void*));
    _phys_pages = (void**)malloc(page_count * sizeof(void*));
    _page_count = page_count;
 
    if ((fdMem = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        fprintf(stderr,"Failed to open /dev/mem\n");
        exit(-1);
    }

    if ((file = open("/proc/self/pagemap", O_RDWR | O_SYNC)) < 0) {
        fprintf(stderr,"Failed to open /proc/self/pagemap\n");
        exit(-1);
    }

    //Magic to determine the physical address for this page:
    offset = mmap(0, _page_count*PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,-1,0);
    lseek(file, ((uint32_t)offset)/PAGE_SIZE*8, SEEK_SET);

    //Get list of available cache coherent physical addresses
    for (i = 0; i < _page_count; i++) {
        _virt_pages[i]  =  mmap(0, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,-1,0);
        ::read(file, &pageInfo, 8); 
        _phys_pages[i] = (void*)((uint32_t)(pageInfo*PAGE_SIZE) | bus);
    }

    //Map physical addresses to virtual memory
    for (i = 0; i < _page_count; i++) {
        munmap(_virt_pages[i], PAGE_SIZE);
        _virt_pages[i]  = mmap(_virt_pages[i], PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_NORESERVE|MAP_LOCKED, fdMem, ((uint32_t)_phys_pages[i] & (version == 1 ? 0xFFFFFFFF : ~bus)));
        memset(_virt_pages[i], 0xee, PAGE_SIZE);
    }
    close(file);
    close(fdMem);
}

Memory_table::~Memory_table()
{
    free(_virt_pages);
    free(_phys_pages);
}

// This function returns physical address with help of pointer, which is offset from the beginning of the buffer.
void* Memory_table::get_page(void** const pages, uint32_t addr) const
{
    if (addr >= PAGE_SIZE * _page_count) {
        return NULL;   
    }
    return (uint8_t*)pages[(uint32_t) addr / 4096] + addr % 4096;
}

//Get virtual address from the corresponding physical address from memory_table.
void* Memory_table::get_virt_addr(const uint32_t phys_addr) const
{
    // FIXME: Can't the address be calculated directly?
    // FIXME: if the address room  in _phys_pages is not fragmented one may avoid a complete loop ..
    uint32_t i = 0;
    for (; i < _page_count; i++) {
        if ((uint32_t) _phys_pages[i] == (((uint32_t) phys_addr) & 0xFFFFF000)) {
            return (void*) ((uint32_t) _virt_pages[i] + (phys_addr & 0xFFF));
        }
    }
    return NULL;
}

// FIXME: in-congruent function style see above
// This function returns offset from the beginning of the buffer using virtual address and memory_table.
uint32_t Memory_table::get_offset(void ** const pages, const uint32_t addr) const
{
    uint32_t i = 0;
    for (; i < _page_count; i++) {
        if ((uint32_t) pages[i] == (addr & 0xFFFFF000) ) {
            return (i*PAGE_SIZE + (addr & 0xFFF));
        }
    }
    return -1;
}

//How many bytes are available for reading in circle buffer?
uint32_t Memory_table::bytes_available(const uint32_t read_addr, const uint32_t write_addr) const
{
    if (write_addr > read_addr) {
        return (write_addr - read_addr);
    }
    else {
        return _page_count * PAGE_SIZE - (read_addr - write_addr);
    }
}

uint32_t Memory_table::get_page_count() const
{
    return _page_count;
}

// More memory mapping
int get_raspberry_pi_version()
{
    char buffer[RCIN_NAVIO_MAX_SIZE_LINE];
    const char* hardware_description_entry = "Hardware";
    const char* v1 = "BCM2708";
    const char* v2 = "BCM2709";
    char* flag;
    FILE* fd;

    fd = fopen("/proc/cpuinfo", "r");

    while (fgets(buffer, RCIN_NAVIO_MAX_SIZE_LINE, fd) != NULL) {
        flag = strstr(buffer, hardware_description_entry);

        if (flag != NULL) {
            if (strstr(buffer, v2) != NULL) {
                printf("Raspberry Pi 2 with BCM2709!\n");
                fclose(fd);
                return 2;
            } 
            else if (strstr(buffer, v1) != NULL) {
                printf("Raspberry Pi 1 with BCM2708!\n");
                fclose(fd);
                return 1;
            }
        }
    }
    return 0;
}

//Physical addresses of peripheral depends on Raspberry Pi's version
void LinuxRCInput_Navio::set_physical_addresses(int version)
{
    if (version == 1) {
        dma_base = RCIN_NAVIO_RPI1_DMA_BASE;
        clk_base = RCIN_NAVIO_RPI1_CLK_BASE;
        pcm_base = RCIN_NAVIO_RPI1_PCM_BASE;
    }                                                                                                                                     
    else if (version == 2) {
        dma_base = RCIN_NAVIO_RPI2_DMA_BASE;
        clk_base = RCIN_NAVIO_RPI2_CLK_BASE;
        pcm_base = RCIN_NAVIO_RPI2_PCM_BASE;
    }
}

//Map peripheral to virtual memory
void* LinuxRCInput_Navio::map_peripheral(uint32_t base, uint32_t len)
{
    int fd = open("/dev/mem", O_RDWR);
    void * vaddr;

    if (fd < 0) {
        printf("Failed to open /dev/mem: %m\n");
        return NULL;
    }
    vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base);
    if (vaddr == MAP_FAILED) {
        printf("rpio-pwm: Failed to map peripheral at 0x%08x: %m\n", base);
    }

    close(fd);
    return vaddr;
}

//Method to init DMA control block
void LinuxRCInput_Navio::init_dma_cb(dma_cb_t** cbp, uint32_t mode, uint32_t source, uint32_t dest, uint32_t length, uint32_t stride, uint32_t next_cb)
{
    (*cbp)->info = mode;
    (*cbp)->src = source;
    (*cbp)->dst = dest;
    (*cbp)->length = length;
    (*cbp)->next = next_cb;
    (*cbp)->stride = stride;
}

void LinuxRCInput_Navio::stop_dma()
{
    dma_reg[RCIN_NAVIO_DMA_CS | RCIN_NAVIO_DMA_CHANNEL << 8] = 0;
}

/* We need to be sure that the DMA is stopped upon termination */
void LinuxRCInput_Navio::termination_handler(int signum)
{
    stop_dma();
    hal.scheduler->panic("Interrupted");
}


//This function is used to init DMA control blocks (setting sampling GPIO register, destination adresses, synchronization)
void LinuxRCInput_Navio::init_ctrl_data()
{
    uint32_t phys_fifo_addr;
    uint32_t dest = 0;
    uint32_t cbp = 0;
    dma_cb_t* cbp_curr;
    //Set fifo addr (for delay)
    phys_fifo_addr = ((pcm_base + 0x04) & 0x00FFFFFF) | 0x7e000000;  
    
    //Init dma control blocks.
    /*We are transferring 1 byte of GPIO register. Every 56th iteration we are 
      sampling TIMER register, which length is 8 bytes. So, for every 56 samples of GPIO we need 
      56 * 1 + 8 = 64 bytes of buffer. Value 56 was selected specially to have a 64-byte "block" 
      TIMER - GPIO. So, we have integer count of such "blocks" at one virtual page. (4096 / 64 = 64 
      "blocks" per page. As minimum, we must have 2 virtual pages of buffer (to have integer count of 
      vitual pages for control blocks): for every 56 iterations (64 bytes of buffer) we need 56 control blocks for GPIO
      sampling, 56 control blocks for setting frequency and 1 control block for sampling timer, so,
      we need 56 + 56 + 1 = 113 control blocks. For integer value, we need 113 pages of control blocks.
      Each control block length is 32 bytes. In 113 pages we will have (113 * 4096 / 32) = 113 * 128 control
      blocks. 113 * 128 control blocks = 64 * 128 bytes of buffer = 2 pages of buffer.
      So, for 56 * 64 * 2 iteration we init DMA for sampling GPIO
      and timer to (64 * 64 * 2) = 8192 bytes = 2 pages of buffer.
    */
    //    fprintf(stderr, "ERROR SEARCH1\n");

  uint32_t i = 0;
  for (i = 0; i < 56 * 128 * RCIN_NAVIO_BUFFER_LENGTH; i++) // 8 * 56 * 128 == 57344
	{
      //Transfer timer every 56th sample
      if(i % 56 == 0) {
          cbp_curr = (dma_cb_t*)con_blocks->get_page(con_blocks->_virt_pages, cbp);

          init_dma_cb(&cbp_curr, RCIN_NAVIO_DMA_NO_WIDE_BURSTS | RCIN_NAVIO_DMA_WAIT_RESP | RCIN_NAVIO_DMA_DEST_INC | RCIN_NAVIO_DMA_SRC_INC, RCIN_NAVIO_TIMER_BASE, (uint32_t) circle_buffer->get_page(circle_buffer->_phys_pages, dest), 8, 0, (uint32_t) con_blocks->get_page(con_blocks->_phys_pages, cbp + sizeof(dma_cb_t)));
          dest += 8;
          cbp += sizeof(dma_cb_t);
      } 

	    // Transfer GPIO (1 byte)
	    cbp_curr = (dma_cb_t*)con_blocks->get_page(con_blocks->_virt_pages, cbp);
	    init_dma_cb(&cbp_curr, RCIN_NAVIO_DMA_NO_WIDE_BURSTS | RCIN_NAVIO_DMA_WAIT_RESP, RCIN_NAVIO_GPIO_LEV0_ADDR, (uint32_t) circle_buffer->get_page(circle_buffer->_phys_pages, dest), 1, 0, (uint32_t) con_blocks->get_page(con_blocks->_phys_pages, cbp + sizeof(dma_cb_t)));
	    dest += 1;
	    cbp += sizeof(dma_cb_t);	  

	    // Delay (for setting sampling frequency)
	    /* DMA is waiting data request signal (DREQ) from PCM. PCM is set for 1 MhZ freqency, so,
	       each sample of GPIO is limited by writing to PCA queue.
	    */
	    cbp_curr = (dma_cb_t*)con_blocks->get_page(con_blocks->_virt_pages, cbp);
	    init_dma_cb(&cbp_curr, RCIN_NAVIO_DMA_NO_WIDE_BURSTS | RCIN_NAVIO_DMA_WAIT_RESP | RCIN_NAVIO_DMA_D_DREQ | RCIN_NAVIO_DMA_PER_MAP(2), RCIN_NAVIO_TIMER_BASE, phys_fifo_addr, 4, 0, (uint32_t)  con_blocks->get_page(con_blocks->_phys_pages, cbp + sizeof(dma_cb_t)));
	    cbp += sizeof(dma_cb_t);
	}
    //Make last control block point to the first (to make circle) 
    cbp -= sizeof(dma_cb_t);
    ((dma_cb_t*)con_blocks->get_page(con_blocks->_virt_pages, cbp))->next = (uint32_t) con_blocks->get_page(con_blocks->_phys_pages, 0);
}


/*Initialise PCM
  See BCM2835 documentation:
  http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
*/  
void LinuxRCInput_Navio::init_PCM()
{
    pcm_reg[RCIN_NAVIO_PCM_CS_A] = 1;                                          // Disable Rx+Tx, Enable PCM block
    hal.scheduler->delay_microseconds(100);
    clk_reg[RCIN_NAVIO_PCMCLK_CNTL] = 0x5A000006;                              // Source=PLLD (500MHz)
    hal.scheduler->delay_microseconds(100);
    clk_reg[RCIN_NAVIO_PCMCLK_DIV] = 0x5A000000 | ((50000/RCIN_NAVIO_SAMPLE_FREQ)<<12);   // Set pcm div. If we need to configure DMA frequency.
    hal.scheduler->delay_microseconds(100);
    clk_reg[RCIN_NAVIO_PCMCLK_CNTL] = 0x5A000016;                              // Source=PLLD and enable
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_TXC_A] = 0<<31 | 1<<30 | 0<<20 | 0<<16;             // 1 channel, 8 bits
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_MODE_A] = (10 - 1) << 10;                           //PCM mode
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_CS_A] |= 1<<4 | 1<<3;                               // Clear FIFOs
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_DREQ_A] = 64<<24 | 64<<8;                           // DMA Req when one slot is free?
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_CS_A] |= 1<<9;                                      // Enable DMA
    hal.scheduler->delay_microseconds(100);
    pcm_reg[RCIN_NAVIO_PCM_CS_A] |= 1<<2;                                      // Enable Tx
    hal.scheduler->delay_microseconds(100);
}

/*Initialise DMA
  See BCM2835 documentation:
  http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
*/  
void LinuxRCInput_Navio::init_DMA()
{
    dma_reg[RCIN_NAVIO_DMA_CS | RCIN_NAVIO_DMA_CHANNEL << 8] = RCIN_NAVIO_DMA_RESET;                 //Reset DMA
    hal.scheduler->delay_microseconds(100);
    dma_reg[RCIN_NAVIO_DMA_CS | RCIN_NAVIO_DMA_CHANNEL << 8] = RCIN_NAVIO_DMA_INT | RCIN_NAVIO_DMA_END;
    dma_reg[RCIN_NAVIO_DMA_CONBLK_AD | RCIN_NAVIO_DMA_CHANNEL << 8] = reinterpret_cast<uint32_t>(con_blocks->get_page(con_blocks->_phys_pages, 0));//Set first control block address
    dma_reg[RCIN_NAVIO_DMA_DEBUG | RCIN_NAVIO_DMA_CHANNEL << 8] = 7;                      // clear debug error flags
    dma_reg[RCIN_NAVIO_DMA_CS | RCIN_NAVIO_DMA_CHANNEL << 8] = 0x10880001;                // go, mid priority, wait for outstanding writes    
}


//We must stop DMA when the process is killed
void LinuxRCInput_Navio::set_sigaction()
{
    for (int i = 0; i < 64; i++) { 
        //catch all signals (like ctrl+c, ctrl+z, ...) to ensure DMA is disabled
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = LinuxRCInput_Navio::termination_handler;
        sigaction(i, &sa, NULL);
    }
}

//Initial setup of variables
LinuxRCInput_Navio::LinuxRCInput_Navio():
    prev_tick(0),
    delta_time(0),
    curr_tick_inc(1000/RCIN_NAVIO_SAMPLE_FREQ),
    curr_pointer(0),
    curr_channel(0),
    width_s0(0),
    curr_signal(0),
    last_signal(228),
    state(RCIN_NAVIO_INITIAL_STATE)
{
    int version = get_raspberry_pi_version();
    set_physical_addresses(version);

    //Init memory for buffer and for DMA control blocks. See comments in "init_ctrl_data()" to understand values "2" and "113"
    circle_buffer = new Memory_table(RCIN_NAVIO_BUFFER_LENGTH * 2, version);
    con_blocks = new Memory_table(RCIN_NAVIO_BUFFER_LENGTH * 113, version);
}

LinuxRCInput_Navio::~LinuxRCInput_Navio()
{
    delete circle_buffer;
    delete con_blocks;
}

void LinuxRCInput_Navio::deinit()
{
    stop_dma();
}

//Initializing necessary registers
void LinuxRCInput_Navio::init_registers()
{
    dma_reg = (uint32_t*)map_peripheral(dma_base, RCIN_NAVIO_DMA_LEN);    
    pcm_reg = (uint32_t*)map_peripheral(pcm_base, RCIN_NAVIO_PCM_LEN);
    clk_reg = (uint32_t*)map_peripheral(clk_base, RCIN_NAVIO_CLK_LEN);
}

void LinuxRCInput_Navio::init(void*)
{
    
    init_registers();
    
    //Enable PPM input
    enable_pin = hal.gpio->channel(PPM_INPUT_NAVIO);
    enable_pin->mode(HAL_GPIO_INPUT);

    //Configuration
    set_sigaction();
    init_ctrl_data();
    init_PCM();
    init_DMA();

    //wait a bit to let DMA fill queues and come to stable sampling
    hal.scheduler->delay(300);

    //Reading first sample
    curr_tick = *((uint64_t*) circle_buffer->get_page(circle_buffer->_virt_pages, curr_pointer));
    prev_tick = curr_tick;
    curr_pointer += 8;
    curr_signal = *((uint8_t*) circle_buffer->get_page(circle_buffer->_virt_pages, curr_pointer)) & 0x10 ? 1 : 0;
    last_signal = curr_signal;
    curr_pointer++;
}


//Processing signal
void LinuxRCInput_Navio::_timer_tick()
{
    int j;
    void* x;

    //Now we are getting address in which DMAC is writing at current moment
    dma_cb_t* ad = (dma_cb_t*) con_blocks->get_virt_addr(dma_reg[RCIN_NAVIO_DMA_CONBLK_AD | RCIN_NAVIO_DMA_CHANNEL << 8]);
    for(j = 1; j >= -1; j--){
    x = circle_buffer->get_virt_addr((ad + j)->dst);
    if(x != NULL) {
        break;}
    }
    
    //How many bytes have DMA transfered (and we can process)?
    counter = circle_buffer->bytes_available(curr_pointer, circle_buffer->get_offset(circle_buffer->_virt_pages, (uint32_t)x));
    //We can't stay in method for a long time, because it may lead to delays
    if (counter > RCIN_NAVIO_MAX_COUNTER) {
        counter = RCIN_NAVIO_MAX_COUNTER;
    }

    //Processing ready bytes
    for (;counter > 0x40;counter--) {
        //Is it timer samle?
        if (curr_pointer %  (64) == 0) {
            curr_tick = *((uint64_t*) circle_buffer->get_page(circle_buffer->_virt_pages, curr_pointer));
            curr_pointer+=8;
            counter-=8;
        }
        //Reading required bit
        curr_signal = *((uint8_t*) circle_buffer->get_page(circle_buffer->_virt_pages, curr_pointer)) & 0x10 ? 1 : 0;
        //If the signal changed
        if (curr_signal != last_signal) {
            delta_time = curr_tick - prev_tick;
            prev_tick = curr_tick;
            switch (state) {
            case RCIN_NAVIO_INITIAL_STATE:
                state = RCIN_NAVIO_ZERO_STATE;
                break;
            case RCIN_NAVIO_ZERO_STATE:
                if (curr_signal == 0) {
                    width_s0 = (uint16_t) delta_time;
                    state = RCIN_NAVIO_ONE_STATE;
                    break;
                }
                else 
                    break;
            case RCIN_NAVIO_ONE_STATE:
                if (curr_signal == 1) {
                    width_s1 = (uint16_t) delta_time;
                    state = RCIN_NAVIO_ZERO_STATE;
                    _process_rc_pulse(width_s0, width_s1);
                    break;
                }
                else 
                    break;
            }
        }
        last_signal = curr_signal;
        curr_pointer++;
        if (curr_pointer >= circle_buffer->get_page_count()*PAGE_SIZE) {
            curr_pointer = 0;
        }
        curr_tick+=curr_tick_inc;
    }
}
#endif // CONFIG_HAL_BOARD_SUBTYPE