#include "ldevusbu.h"
//////////////////////////////////////////////////////////////////////////////////////////

#define E2010_PLD_INFO_SIZE_MAX    512
#define E2010_PLD_INFO_SIZE_REVB   510

typedef struct {
    CHAR McuName[25];   // название микроконтроллера
    CHAR Version[10];   // версия драйвера микроконтроллера
    CHAR Created[14];   // дата сборки драйвера микроконтроллера
    CHAR Manufacturer[25];  // производитель драйвера микроконтроллера
    CHAR Author[25];    // автор драйвера микроконтроллера
    CHAR Comment[128];  // строка комментария
} t_e2010_mcu_descriptor;


typedef struct {
    char board_rev;
    struct {
        u8 h, l;
    } avr_ver;
    struct {
        u16 size;
        u8 data[E2010_PLD_INFO_SIZE_MAX];
    } pld_info;
} t_e2010_ctx;


#define E2010_CHECK_MCU_VER(vh, vl, req_h, req_l) (((vh) > (req_h)) || (((vh) == (req_h)) && ((vl) >= (req_l))))
#define E2010_CHECK_MCU_VER2(vh, vl, req_h1, req_l1, req_h2, req_l2) \
    (((vh) == (req_h1)) ? E2010_CHECK_MCU_VER(vh, vl, req_h1, req_l1) : \
                         E2010_CHECK_MCU_VER(vh, vl, req_h2, req_l2))

#define E2010_CTX(dev)                 ((t_e2010_ctx *)((dev)->devspec))
#define E2010_CHECK_DIG_SYNCIN(dev)    E2010_CHECK_MCU_VER(E2010_CTX(dev)->avr_ver.h, E2010_CTX(dev)->avr_ver.l, 2, 4)
#define E2010_CHECK_FPGA_LOAD_REQ(dev) ((E2010_CTX(dev)->board_rev == 'A') || (E2010_CTX(dev)->board_rev == 'B'))


// Cmd - vendor code in this case
static int ldev_e2010_ioctl_tx(ldevusb *dev, u8 cmd, u16 par1, u16 par2, void *data, u16 size, int silent) {
    int status = 0;
    int ioctl_res  = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
                                     cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
                                     par1, par2, data, size, HZ*10);
    if (ioctl_res < 0) {
        status = ioctl_res;
        if (!silent) {
            printk(KERN_ERR "ldevusb: e2010 tx ioctl (%02X,%04X,%04X,%04X) failed! status = %d\n",
                    cmd, par1, par2, size, ioctl_res);
        }
    } else if (ioctl_res != size) {
        status = -EIO;
        if (!silent) {
            printk(KERN_ERR "ldevusb: e2010 tx ioctl (%02X,%04X,%04X,%04X) transmit insufficient bytes: %d\n",
               cmd, par1, par2, size, ioctl_res);
        }
    }
    return status;
}

static int ldev_e2010_ioctl_rx(ldevusb *dev, u8 cmd, u16 par1, u16 par2, void *data, u16 size, int silent) {
    int status = 0;
    int ioctl_res  = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
                                     cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
                                     par1, par2, data, size, HZ*10);
    if (ioctl_res < 0) {
        status = ioctl_res;
        if (!silent) {
            printk(KERN_ERR "ldevusb: e2010 rx ioctl (%02X,%04X,%04X,%04X) failed! status = %d\n",
                   cmd, par1, par2, size, ioctl_res);
        }
    } else if (ioctl_res != size) {
        status = -EIO;
        if (!silent) {
            printk(KERN_ERR "ldevusb: e2010 tx ioctl (%02X,%04X,%04X,%04X) receive insufficient bytes: %d\n",
                   cmd, par1, par2, size, ioctl_res);
        }
    }
    return status;
}


static int ldev_e2010_cmd_exec(ldevusb *dev, u16 cmd, u16 par1, u16 par2) {
    return ldev_e2010_ioctl_tx(dev, cmd, par1, par2, NULL, 0, 0);
}


static int ldev_e2010_get_array(ldevusb *dev, u32 addr, u8 *data, u32 size, int silent) {
    int status = 0;
    /* Для ревизий A и B нельзя получить информацию о версии PLD явным запросом,
     * поэтому по обращению к PLD_INFO_ADDRESS возвращаем сохраненные
     * во время инициализации ПЛИС данные */
    if ((addr == PLD_INFO_ADDRESS) && (E2010_CHECK_FPGA_LOAD_REQ(dev))) {
        if (size <= E2010_CTX(dev)->pld_info.size) {
            memcpy(data, E2010_CTX(dev)->pld_info.data, size);
        } else {
            status = -EFAULT;
        }
    } else {
        u8 *tmp_buf = (u8 *)kmalloc(size > PAGE_SIZE ? PAGE_SIZE : size, GFP_KERNEL);
        if (tmp_buf == NULL) {
            status = -ENOMEM;
        } else {
            while ((status == 0) && (size > 0)) {
                u32 transf_size = size > PAGE_SIZE ? PAGE_SIZE : size;
                status = ldev_e2010_ioctl_rx(dev, V_GET_ARRAY_E2010,
                                              addr&0xFFFF, (addr >> 16) & 0xFFFF,
                                              tmp_buf, transf_size, silent);
                if (status == 0) {
                    memcpy(data, tmp_buf, transf_size);
                    size -= transf_size;
                    addr += transf_size;
                    data += transf_size;
                }
            }
            kfree(tmp_buf);
        }
    }
    return status;
}


static int ldev_e2010_put_array(ldevusb *dev, u32 addr, const u8 *data, u32 size, int silent) {
    int status = 0;
    u8 *tmp_buf = (u8 *)kmalloc(size > PAGE_SIZE ? PAGE_SIZE : size, GFP_KERNEL);
    if (tmp_buf == NULL) {
        status = -ENOMEM;
    } else {
        while ((status == 0) && (size > 0)) {
            u32 transf_size = size > PAGE_SIZE ? PAGE_SIZE : size;
            memcpy(tmp_buf, data, transf_size);
            status = ldev_e2010_ioctl_tx(dev, V_PUT_ARRAY_E2010,
                                          addr&0xFFFF, (addr >> 16) & 0xFFFF,
                                          tmp_buf, transf_size, silent);
            if (status == 0) {
                size -= transf_size;
                addr += transf_size;
                data += transf_size;
            }
        }
        kfree(tmp_buf);
    }
    return status;
}



// count in byte, 2 - adrr
int ldev_e2010_get_data_memory(ldevusb *dev, u8 *Data, u32 Count, u32 Addr) {
    return ldev_e2010_get_array(dev, Addr, Data, Count, 0);
}

// count in byte, 2 - adrr
int ldev_e2010_put_data_memory(ldevusb *dev, u8 *Data, u32 Count, u32 Addr) {
    return ldev_e2010_put_array(dev, Addr, Data, Count, 0);
}


int ldev_e2010_get_dm_word(ldevusb *dev, u32 Addr, u16 *pword) {
    return ldev_e2010_get_array(dev, Addr, (u8*)pword, sizeof(*pword), 0);
}

int ldev_e2010_put_dm_word(ldevusb *dev, u32 Addr, u16 Data) {
   return ldev_e2010_put_array(dev, Addr, (u8*)&Data, sizeof(Data), 0);
}


int ldev_e2010_get_pm_word(ldevusb *dev, u32 Addr, u8 *pword) {
    return ldev_e2010_get_array(dev, Addr, (u8*)pword, sizeof(*pword), 0);
}

int ldev_e2010_put_pm_word(ldevusb *dev, u32 Addr, u8 Data) {
    return ldev_e2010_put_array(dev, Addr, &Data, 1, 0);
}

int ldev_e2010_put_pm_word_silent(ldevusb *dev, u32 Addr, u8 Data) {
    return ldev_e2010_put_array(dev, Addr, &Data, 1, 1);
}

int ldev_e2010_read_plata_descr(ldevusb *dev, u16 *data, u16 size) {
    int status = 0;
    /* сперва пробуем прочитать новую версию дескриптора для ревизии C, если
     * не удастся - повторное чтение старой структуры (ревизии B и ниже).
     * Для совместимости в ревизии C и выше по старому адресу лежит старая
     * структура с указанием ревзии B, т.к. на это был завязан старый драйвер */
    status = ldev_e2010_get_array(dev, NEW_MODULE_DESCRIPTOR_ADDRESS, (u8*)data, size * sizeof(data[0]), 1);
    if (status != 0) {
        status = ldev_e2010_get_array(dev, MODULE_DESCRIPTOR_ADDRESS, (u8*)data, size * sizeof(data[0]), 0);
    }
    return status;
}

int ldev_e2010_init(ldevusb *dev) {
    PLATA_DESCR_E2010 pd;
    int status = 0;

    kfree(dev->devspec);
    dev->devspec = kmalloc(sizeof(t_e2010_ctx), GFP_KERNEL);
    if (!dev->devspec) {
        status = -ENOMEM;
    } else {
        memset(dev->devspec, 0, sizeof(t_e2010_ctx));
    }

    if (status == 0)
        status = ldev_e2010_cmd_exec(dev, V_CALL_APPLICATION_E2010, (BOOT_LOADER_START_ADDRESS&0xFFFF), 0);
    if (status == 0)
        status = ldev_e2010_cmd_exec(dev, V_CALL_APPLICATION_E2010, (FIRMWARE_START_ADDRESS&0xFFFF), 1);

    if (status == 0) {
        status = ldev_e2010_read_plata_descr(dev, (u16*)&pd, sizeof(pd)/sizeof(u16));
        if (status == 0) {
            /* тип E2010B используется для всех ревизий E20-10, кроме 'A' (для совместимости старших ревизий) */
            if (pd.Rev != 'A') {
                dev->Type = E2010B;
            }
            E2010_CTX(dev)->board_rev = pd.Rev;
        }
    }
    if (status == 0) {
        t_e2010_mcu_descriptor mcu_descr;
        status = ldev_e2010_get_array(dev, FIRMWARE_DESCRIPTOR_ADDRESS, (u8*)&mcu_descr, sizeof(mcu_descr), 0);
        if (status == 0) {
            unsigned mcu_ver_h, mcu_ver_l;
            if (sscanf(mcu_descr.Version, "%d.%d", &mcu_ver_h, &mcu_ver_l) == 2) {
                E2010_CTX(dev)->avr_ver.h =  mcu_ver_h & 0xFF;
                E2010_CTX(dev)->avr_ver.l  = mcu_ver_l & 0xFF;
            }
        }
    }
    return status;
}

// пишет всю флеш...  */
int e2010_write_plate_descr(ldevusb *dev, u16 *Data, u16 Size) {
   int status = 0, status2;

   status = ldev_e2010_cmd_exec(dev, V_CALL_APPLICATION_E2010, (BOOT_LOADER_START_ADDRESS&0xFFFF), 0);
   if (status == 0) {
       /** @todo (учесть ревзию C!) */
       status = ldev_e2010_put_array(dev, MODULE_DESCRIPTOR_ADDRESS, (u8*)Data, Size *sizeof(Data[0]), 0);
   }
   status2 = ldev_e2010_cmd_exec(dev, V_CALL_APPLICATION_E2010, (FIRMWARE_START_ADDRESS&0xFFFF), 0);
   if (status == 0)
       status = status2;
   return status;
}



int ldev_e2010_load_bios(ldevusb *dev, u8 *Data, u32 size) {
   int count;
   int status = 0;
   int fpga_load_req = E2010_CHECK_FPGA_LOAD_REQ(dev);


   if (status == 0) {
       status=usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr));
   }
   if (status == 0) {
       status = ldev_e2010_cmd_exec(dev,V_RESET_FPGA_E2010, 0,0); // reset fpga
       if (status != 0) {
            printk(KERN_ERR "ldevusb: e2010 fpga reset error: %d\n", status);
       }
   }


   if ((status == 0) && fpga_load_req) {
       status = ldev_e2010_put_data_memory(dev, Data, size, SEL_PLD_DATA); // how words
   }

   if (status == 0) {
       /* для Rev C и выше указываем флаг, что не нужно отвечать информацией
        * о PLD по Bulk, т.к. мы можем эту информацию считать явно, когда
        * нам будет удобно */
       status = ldev_e2010_cmd_exec(dev,V_INIT_FPGA_E2010, fpga_load_req ? 0 : 1, 0); // init fpga
       if (status != 0) {
            printk(KERN_ERR "ldevusb: e2010 fpga start error: %d\n", status);
       }
   }


   if ((status == 0) && fpga_load_req) {
       // read fpga answer
       u32 rep_sz = E2010_PLD_INFO_SIZE_MAX;
       if(dev->Type==E2010B) {
           rep_sz = E2010_PLD_INFO_SIZE_REVB;
           status = ldev_e2010_put_data_memory(dev,(PUCHAR)&rep_sz, 4, SEL_BULK_REQ_SIZE);
       }

       if (status == 0)  {
            status = usb_bulk_msg(dev->udev,
                                usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                                E2010_CTX(dev)->pld_info.data, rep_sz, &count, HZ*10);
            if (status != 0)  {
                printk(KERN_ERR "ldevusb: e2010 fpga read bulk info error: %d\n", status);
            } else if (count == 0) {
                status = -EFAULT;
                printk(KERN_ERR "ldevusb: no e2010 fpga read bulk info!\n");
            } else {
                E2010_CTX(dev)->pld_info.size = count;
            }
       }
   }
   return status;
}

int ldev_e2010_enable(ldevusb *dev, int en) {
   int status = 0;
   ADC_PAR_E2010_PACK_U ap;
   ADC_PAR_EXTRA_E2010_PACK_U app;
   
   if(en) { // start ADC
       int i;
      // pack adc parameters

      for(i=0; i < 128; i++)
          ap.t1.Chn[i] = (u8)dev->adcPar.t4.Chn[i];

      ap.t1.Rate = (u8)dev->adcPar.t4.Rate;
      ap.t1.NCh = dev->adcPar.t4.NCh-1;
      ap.t1.Kadr = (u16)dev->adcPar.t4.Kadr;
      ap.t1.ChanMode=dev->adcPar.t4.AdcIMask;
      ap.t1.SyncMode=(u8)dev->adcPar.t4.SynchroType;

      status = ldev_e2010_put_data_memory(dev, (u8*)&ap.bi, 263, SEL_ADC_PARAM);

      if((status == 0) && (dev->Type == E2010B)) {
         app.t1.StartCnt = dev->adcPar.t4.StartCnt;
         app.t1.StopCnt = dev->adcPar.t4.StopCnt;
         app.t1.SynchroMode = (u16)dev->adcPar.t4.SynchroMode;
         app.t1.AdPorog = (u16)dev->adcPar.t4.AdPorog;
         app.t1.DM_Ena = (u8)dev->adcPar.t4.DM_Ena;

         status = ldev_e2010_put_data_memory(dev, (u8*)&app.bi, 13, EXTRA_SYNCHRO_PARS_ADDRESS);
      }

      if ((status == 0) && (dev->Type == E2010B)) {
         status = ldev_e2010_put_pm_word(dev, LUSBAPI_OR_LCOMP_ADDRESS, 1);
         if (status == 0)
            status = ldev_e2010_put_pm_word(dev, EXT_SYNC_REG_ADDRESS, (u8)((dev->adcPar.t4.SynchroMode>>24)&0xFF));
         if (status == 0)
            status = ldev_e2010_put_pm_word(dev, EXT_START_REG_ADDRESS, (u8)((dev->adcPar.t4.SynchroMode>>16)&0xFF));

         /** для версии AVR до 2.4 запрос вернет ошибку */
         if ((status == 0) && E2010_CHECK_DIG_SYNCIN(dev)) {
            status = ldev_e2010_put_pm_word_silent(dev, SEL_DIG_RATE, dev->adcPar.t4.DigRate); // digital stream divider (for chan  4 5 6 in control table)
         }
      }

      if ((status == 0) && (dev->Type == E2010B)) {
         u32 rep_sz = dev->wIrqStep*sizeof(u16);
         ldev_e2010_put_data_memory(dev,(u8*)&rep_sz,4,SEL_BULK_REQ_SIZE);
      }

      if (status == 0)
        status = usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr));
      
      if (status == 0)
        status = ldev_e2010_cmd_exec(dev, V_START_ADC_E2010,0,1);
   } else  {
      status = ldev_e2010_cmd_exec(dev, V_STOP_ADC_E2010,0,1);
   }
   return status;
}
