#ifdef _WIN32
    #include <ntddk.h>
    #include <wdf.h>
#else
    #include <linux/module.h>
#endif

#include "lpcie_drv.h"
#include "lpcie_trace.h"
#include "lpcie_eeprom.h"
#include "l502_fpga_regs.h"
#include "fast_crc.h"

#ifdef _WIN32
    #include "lpcie_eeprom.tmh"
#endif

static t_status f_lpcie_eeprom_read(t_lpcie_dev_context *dev, unsigned int addr,
                              unsigned char* data, unsigned int size);

static t_status f_lpcie_eeprom_read(t_lpcie_dev_context* dev, unsigned int addr,
                              unsigned char* data, unsigned int size) {
    unsigned int last_addr, val;
    t_status status = STATUS_SUCCESS;

    for (last_addr = addr+size; (addr < last_addr) && IS_SUCCESS(status); addr+=sizeof(val)) {
        status = lpcie_fpga_reg_write(dev, L502_REGS_EEPROM_SET_RD_ADDR, (addr & 0xFFFFFF) << 8);
        if (IS_SUCCESS(status))
            status = lpcie_fpga_reg_read(dev, L502_REGS_EEPROM_RD_DWORD, &val);
        if (IS_SUCCESS(status)) {
            unsigned int i;
            for (i=0; (i < sizeof(val)) && size; i++, size--) {
                *data++ = val & 0xFF;
                val >>= 8;
            }
        }
    }
    return status;
}


t_status lpcie_read_descr(t_lpcie_dev_context *dev) {
    t_l5xx_eeprom_hdr hdr;
    t_status status = STATUS_SUCCESS;
    memcpy(dev->name, "L502", sizeof("L502"));
    memset(dev->sn, 0, sizeof(dev->sn));
    dev->devflags = 0;

    /* читаем заголовок с информацией о устройстве из Flash-памяти */
    status = f_lpcie_eeprom_read(dev, L502_EEPROM_ADDR_DESCR, (unsigned char*)&hdr, sizeof(hdr));
    if (IS_SUCCESS(status)) {
        /* проверяем правильность заголовка по сигнатуре, размеру и формату */
        if ((hdr.sign!=L5XX_EEPROM_SIGN) || (hdr.format != L5XX_EEPROM_FORMAT) ||
                (hdr.size >= L502_DESCR_MAX_SIZE) || (hdr.size < L5XX_DESCR_MIN_SIZE)) {
            status = STATUS_UNSUCCESSFUL;
            TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
                        "Invalid EEPROM data format");
        } else {
            /* проверка CRC */

            FASTCRC_U32_TYPE cur_crc, crc, pos=sizeof(hdr);

            /* читаем CRC - последние 4 байта информации во Flash-памяти */
            status = f_lpcie_eeprom_read(dev, L502_EEPROM_ADDR_DESCR+hdr.size-sizeof(crc),
                                     (unsigned char*)&crc, sizeof(crc));
            /* сперва рассчитаем CRC по считанному заголовку */
            cur_crc = CRC32_Block8(0, (FASTCRC_U8_TYPE*)&hdr, sizeof(hdr));
            /* затем по слову читаем оставшуюся информацию (кроме заголовка) и обновляем рассчитанное CRC */
            while ((pos < (hdr.size-4)) && IS_SUCCESS(status)) {
                unsigned wrd, upd_size = sizeof(wrd);
                status = f_lpcie_eeprom_read(dev, L502_EEPROM_ADDR_DESCR+pos,
                                       (unsigned char*)&wrd, sizeof(wrd));
                if (IS_SUCCESS(status)) {
                    /* для случае когда размер не кратен слову, в последний
                     * раз читаем не полное слово меньше */
                    if ((hdr.size - 4 - pos) < upd_size)
                        upd_size = hdr.size - 4 - pos;
                    cur_crc = CRC32_Block8(cur_crc, (FASTCRC_U8_TYPE*)&wrd, upd_size);
                    pos+=upd_size;
                }
            }

            if (IS_SUCCESS(status)) {
                /* сравниваем прочитанную CRC и рассчитанное значение */
                if (crc!=cur_crc) {
                    TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
                                "Invalid EEPROM data CRC");
                    status = STATUS_UNSUCCESSFUL;
                } else {
                    memcpy(dev->name, hdr.name, sizeof(dev->name));
                    memcpy(dev->sn, hdr.serial, sizeof(dev->name));

                    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT,
                                "EEPROM params: name = %s, serial = %s", hdr.name, hdr.serial);
                }
            }
        }
    }

    /* читаем регистр hard_id, чтобы определить, какие опции присутствуют */
    if (IS_SUCCESS(status)) {
        uint32_t hard_id;
        status = lpcie_fpga_reg_read(dev, L502_REGS_HARD_ID, &hard_id);
        if (IS_SUCCESS(status)) {
            if (hard_id & 0x01)
                dev->devflags |= LPCIE_DEV_FLAGS_DAC;
            if (hard_id & 0x02)
                dev->devflags |= LPCIE_DEV_FLAGS_GAL;
            if (hard_id & 0x04)
                dev->devflags |= LPCIE_DEV_FLAGS_BF;
        }
    }
    return status;
}
