#include "x502api.h"
#include "x502api_private.h"
#include "x502_fpga_regs.h"

#include <string.h>
#include <stdlib.h>


static const double f_x502_scales[] = {10., 5.,         2.,           1., 0.5, 0.2};
// В E16 только 4 диапазона!
static const double f_e16_scales[] = {10., 2.5, 0.625, 0.15625, 0., 0.};


X502_EXPORT(double const*) E16_GetAdcRanges(uint32_t *ranges_len) {
    if (ranges_len) {
        *ranges_len = E16_ADC_RANGE_CNT;
    }
    return f_e16_scales;
}

X502_EXPORT(double const*) E502_GetAdcRanges(uint32_t *ranges_len) {
    if (ranges_len) {
        *ranges_len = X502_ADC_RANGE_CNT;
    }
    return f_x502_scales;
}

X502_EXPORT(double const*) X502_GetAdcRanges(t_x502_hnd hnd, uint32_t *ranges_len) {
    int32_t err  = X502_CHECK_HND(hnd);
    if (err != X502_ERR_OK) {
        if (x502_is_E16(hnd)) {
            return E16_GetAdcRanges(ranges_len);
        } else {
            return E502_GetAdcRanges(ranges_len);
        }
    }
    return NULL;
}


X502_EXPORT(t_x502_hnd) X502_Create(void) {
    t_x502_hnd hnd = calloc(sizeof(t_x502), 1);
    if (hnd != NULL) {
        hnd->sign = X502_SIGN;
    }

    return hnd;
}

X502_EXPORT(int32_t) X502_Free(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND(hnd);
    if (err == X502_ERR_OK) {
        if (hnd->flags & PRIV_FLAGS_OPENED)
            err = X502_Close(hnd);

        hnd->sign = 0;
        free(hnd);
    }
    return err;
}



X502_EXPORT(int32_t) X502_Close(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND(hnd);
    if ((err == X502_ERR_OK) && (hnd->flags  & PRIV_FLAGS_OPENED)) {
        int32_t stop_err;
        if (hnd->flags & PRIV_FLAGS_STREAM_RUN) {
            /* остановка потока */
            err = X502_StreamsStop(hnd);
        }

        hnd->flags &= ~PRIV_FLAGS_OPENED;


        stop_err = hnd->iface_hnd->close(hnd);
        if (err == X502_ERR_OK)
            err = stop_err;

        if (hnd->mutex_cfg!=OSSPEC_INVALID_MUTEX) {
            stop_err = osspec_mutex_destroy(hnd->mutex_cfg);
            hnd->mutex_cfg = OSSPEC_INVALID_MUTEX;
            if (err == X502_ERR_OK)
                err = stop_err;
        }

        if (hnd->mutex_bf!=OSSPEC_INVALID_MUTEX) {
            stop_err = osspec_mutex_destroy(hnd->mutex_bf);
            hnd->mutex_bf = OSSPEC_INVALID_MUTEX;
            if (err == X502_ERR_OK)
                err = stop_err;
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_IsOpened(t_x502_hnd hnd) {
    int32_t err  = X502_CHECK_HND(hnd);
    if ((err == X502_ERR_OK) && !(hnd->flags & PRIV_FLAGS_OPENED)) {
        err = X502_ERR_DEVICE_NOT_OPENED;
    }
    return err;
}



X502_EXPORT(int32_t) X502_OpenByDevRecord(t_x502* hnd, const t_x502_devrec *devrec) {
    int32_t err  = X502_CHECK_HND(hnd);
    if ((err == X502_ERR_OK) && ((devrec==NULL) || (devrec->sign!=X502_DEVREC_SIGN)))
        err = X502_ERR_INVALID_DEVICE_RECORD;
    if (X502_IsOpened(hnd) == X502_ERR_OK) {
        X502_Close(hnd);
    }
    if (err == X502_ERR_OK) {
        hnd->iface_hnd = (const t_x502_dev_iface *)(devrec->internal->iface);
        memcpy(hnd->info.serial, devrec->serial, X502_SERIAL_SIZE);
        memcpy(hnd->info.name, devrec->devname, X502_DEVNAME_SIZE);
        hnd->iface = devrec->iface;
        hnd->info.devflags = devrec->flags;
        err = hnd->iface_hnd->open(hnd, devrec);
        if (err == X502_ERR_OK) {
            X502_SetLChannel(hnd, 0, 0, X502_LCH_MODE_COMM, X502_ADC_RANGE_10, 0);
            hnd->set.lch_cnt = 1;
            hnd->set.adc_frame_delay = 0;
            hnd->set.sync_mode = X502_SYNC_INTERNAL;
            hnd->set.sync_start_mode = X502_SYNC_INTERNAL;


            hnd->set.ext_ref_freq = 0;
            hnd->streams = 0;

            if (x502_is_E16(hnd)) {
                hnd->set.ref_freq = E16_REF_FREQ_48000KHZ;
                hnd->set.dac_range = E16_DAC_RANGE;
                hnd->set.dac_code_max = E16_DAC_SCALE_CODE_MAX;
                hnd->set.adc_code_max = E16_ADC_SCALE_CODE_MAX;
                hnd->set.f_scales = f_e16_scales;

                hnd->set.adc_freq_div = E16_REF_FREQ_48000KHZ / E16_ADC_FREQ_DEFAULT;
                hnd->set.din_freq_div = E16_REF_FREQ_48000KHZ / E16_DIN_FREQ_DEFAULT;
                hnd->set.out_freq_div = E16_REF_FREQ_48000KHZ / E16_OUT_FREQ_DEFAULT;
            } else {
                hnd->set.ref_freq = X502_REF_FREQ_2000KHZ;
                hnd->set.dac_range = X502_DAC_RANGE;
                hnd->set.dac_code_max = X502_DAC_SCALE_CODE_MAX;
                hnd->set.adc_code_max = X502_ADC_SCALE_CODE_MAX;
                hnd->set.f_scales = f_x502_scales;

                hnd->set.adc_freq_div = 1;
                hnd->set.din_freq_div = 1;
                hnd->set.out_freq_div = 2;
            }

            hnd->flags = PRIV_FLAGS_OPENED;


            if (err == X502_ERR_OK) {
                hnd->mutex_bf = osspec_mutex_create();
                if (hnd->mutex_bf == OSSPEC_INVALID_MUTEX)
                    err = X502_ERR_MUTEX_CREATE;
            } else {
                hnd->mutex_bf = OSSPEC_INVALID_MUTEX;
            }

            if (err == X502_ERR_OK) {
                hnd->mutex_cfg = osspec_mutex_create();
                if (hnd->mutex_cfg == OSSPEC_INVALID_MUTEX)
                    err = X502_ERR_MUTEX_CREATE;
            }

            if (err == X502_ERR_OK) {
                if (!(hnd->info.devflags & X502_DEVFLAGS_FPGA_LOADED)) {
                    err = X502_ERR_FPGA_NOT_LOADED;
                } else {


                    /* определяем - в каком режиме работаем (BF или FPGA) */
                    uint32_t bf_ctl;
                    err = hnd->iface_hnd->fpga_reg_read(hnd, X502_REGS_BF_CTL, &bf_ctl);
                    if (err == X502_ERR_OK) {
                        uint32_t mode = bf_ctl;
                        if (mode & X502_REGBIT_BF_CTL_DBG_MODE_Msk) {
                            hnd->mode = X502_MODE_DEBUG;
                        } else if (mode & X502_REGBIT_BF_CTL_DSP_MODE_Msk) {
                            hnd->mode = X502_MODE_DSP;
                        } else {
                            hnd->mode = X502_MODE_FPGA;
                        }


                        if (hnd->mode==X502_MODE_DSP) {
                            /* если blackfin находится в состоянии сброса, то возвращаемся в режим
                               FPGA, т.к. все команды к HostDMA все равно не выполнятся */
                            if (!(bf_ctl & X502_REGBIT_BF_CTL_BF_RESET_Msk)) {
                                err = X502_SetMode(hnd, X502_MODE_FPGA);
                            } else {
                                err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_BF_CMD, X502_BF_CMD_HDMA_RST);
                                /** @todo Для BlackFin проверить наличие прошивки */
                            }
                        }
                    }

                    /* проверка захвата PLL (сейчас невозможна в режиме DSP) */
                    if ((err == X502_ERR_OK) && (hnd->mode!=X502_MODE_DSP)) {
                        uint32_t val;
                        err =  hnd->iface_hnd->fpga_reg_read(hnd, X502_REGS_IOHARD_IO_MODE, &val);
                        if (err == X502_ERR_OK) {
                            if (!(val & X502_REGBIT_ADC_SLV_CLK_LOCK_Msk)) {
                                err = X502_ERR_REF_FREQ_NOT_LOCKED;
                            }
                        }
                    }

                    /* читаем информацию о версии прошивки ПЛИС'ов и наличии опций */
                    if (err == X502_ERR_OK) {
                        uint32_t hard_id=0;
                        int32_t id_err = hnd->iface_hnd->fpga_reg_read(hnd, hnd->iface_hnd->id_reg_addr, &hard_id);
                        if (id_err == X502_ERR_OK) {
                            hnd->info.fpga_ver = (hard_id >> 16) & 0x7FFF;
                            hnd->info.plda_ver = (hard_id >> 4) & 0xF;
                            hnd->info.board_rev  = (hard_id >> 8) & 0xF;
                            FILL_HARD_ID_FLAGS(hnd->info.devflags, hard_id);
                        }
                    }

                    /* если был запущен сбор - то останавливаем его */
                    if ((err == X502_ERR_OK) && (hnd->mode==X502_MODE_FPGA)) {
                        hnd->last_dout = 0;

                        if (hnd->iface_hnd->fpga_mode_init != NULL)
                            err = hnd->iface_hnd->fpga_mode_init(hnd);

                        if (err == X502_ERR_OK)
                            err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_GO_SYNC_IO, 0);

                        if (err == X502_ERR_OK)
                            err =  hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_OUTSWAP_BFCTL, 0);

                        if ((err == X502_ERR_OK) && (hnd->iface_hnd->stream_running != NULL)) {
                            int32_t running;
                            unsigned ch;
                            for (ch=0; (ch < X502_STREAM_CH_CNT) && (err == X502_ERR_OK); ch++) {
                                err = hnd->iface_hnd->stream_running(hnd, ch, &running);
                                if ((err == X502_ERR_OK) && running) {
                                    err = hnd->iface_hnd->stream_stop(hnd, ch, 0);
                                }
                            }
                        }
                    }
                }
            }
        }
        /* читаем информацию из EEPROM (в первую очередь -
           калибровочные коэффициенты) */
        if (err == X502_ERR_OK || err == X502_ERR_FPGA_NOT_LOADED) {
            int i;

            for (i=0; i < X502_ADC_RANGE_CNT; i++) {
                hnd->info.cbr.adc[i].offs = 0;
                hnd->info.cbr.adc[i].k = 1.;
            }

            for (i=0; i < X502_DAC_CH_CNT; i++) {
                hnd->info.cbr.dac[i].offs = 0;
                hnd->info.cbr.dac[i].k = 1;
            }

            x502_check_eeprom(hnd, 0);
        }

        /* записываем конфигурацию по умолчанию, чтобы быть уверенным,
           что установлена нужная конфигурация */
        if ((err == X502_ERR_OK) && (hnd->mode==X502_MODE_FPGA))
            err = X502_Configure(hnd, 0);
        //if (!err)
        //    err = _fpga_reg_write(hnd, L502_REGS_IOHARD_PRELOAD_ADC, 1);

        if ((err != X502_ERR_OK) && (err != X502_ERR_FPGA_NOT_LOADED) && (err != X502_ERR_REF_FREQ_NOT_LOCKED))
            X502_Close(hnd);
    }

    return err;
}


X502_EXPORT(int32_t) X502_Open(t_x502_hnd hnd, const char* serial,
                                const char *devname, t_x502_get_devinfo_list_cb get_list) {
    int32_t err = X502_CHECK_HND(hnd);
    int32_t get_info_res = 0;
    uint32_t fnd_cnt;

    if (err == X502_ERR_OK) {
        get_info_res = get_list(NULL, 0, 0, &fnd_cnt);
        if (get_info_res < 0) {
            err = get_info_res;
        } else if (!fnd_cnt) {
            err = X502_ERR_DEVICE_NOT_FOUND;
        }
    }

    if (err == X502_ERR_OK) {
        t_x502_devrec *info_list = malloc(sizeof(t_x502_devrec)*fnd_cnt);
        if (info_list==NULL) {
            err = X502_ERR_MEMORY_ALLOC;
        } else {
            /* получаем информацию по всем устройствам драйвера lpcie */
            get_info_res = get_list(info_list, fnd_cnt, 0, NULL);
            if (get_info_res < 0) {
                err = get_info_res;
            } else {
                int32_t i, ser_size=0, fnd=0, open_err = 0;

                if (serial!=NULL) {
                    /* если серийный задан - смотрим его размер, отсекая
                     * все что начинается с признака кноца строки */
                    for (ser_size=0; (ser_size < X502_SERIAL_SIZE) &&
                         !((serial[ser_size]=='\0') ||
                           (serial[ser_size]=='\n') ||
                           (serial[ser_size]=='\r')); ser_size++)
                    {}
                }

                for (i=0; !fnd && (i < get_info_res); i++)  {
                    /* ищем устройство L502 с совпадающим серийным */
                    if (((devname==NULL) || !strcmp(info_list[i].devname, devname)) &&
                            ((ser_size==0) || !strncmp(serial, info_list[i].serial,
                                                        ser_size))) {
                        /* пробуем открыть устройство */
                        err = X502_OpenByDevRecord(hnd, &info_list[i]);

                        /* если серийный номер не был указан, то сохраняем
                           код ошибки и идем дальше, пробовать открыть
                           следующее устройство */
                        if (err && (ser_size==0)) {
                            open_err = err;
                            err = 0;
                        } else {
                            /* иначе заканчиваем поиск */
                            fnd = 1;
                        }
                    }
                }

                /* если не нашли устройство - устанавливаем соотвествующий код
                   ошибки */
                if (!fnd) {
                    err = open_err ? open_err : X502_ERR_DEVICE_NOT_FOUND;
                }
            }


            X502_FreeDevRecordList(info_list, fnd_cnt);
            free(info_list);
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_GetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size,
                                        uint32_t flags, uint32_t *devcnt, const char *devname,
                                        t_x502_get_devinfo_list_cb get_list) {
    uint32_t fnd_cnt=0, put_cnt=0;
    /* получаем количество устройств, поддерживаемых драйвером lpcie */
    int32_t res = get_list(NULL, 0, flags, &fnd_cnt);
    if ((res>=0) && fnd_cnt) {
        t_x502_devrec *info_list = malloc(sizeof(t_x502_devrec)*fnd_cnt);
        if (info_list==NULL) {
            res = X502_ERR_MEMORY_ALLOC;
        } else {
            /* получаем информацию по всем устройствам драйвера lpcie */
            res = get_list(info_list, fnd_cnt, flags, NULL);
            if (res>0) {
                int32_t i;
                for (i=0; i < res; i++) {
                    /* проверяем, что это устройство - E502 */
                    if (!strcmp(info_list[i].devname, devname)) {
                        /* если есть место в списке, то сохраняем серийный номер
                           устройства */
                        if (put_cnt < size) {
                            memcpy(serials[put_cnt], info_list[i].serial,
                                   X502_SERIAL_SIZE);
                        }
                        put_cnt++;
                    }
                }
            }

            X502_FreeDevRecordList(info_list, fnd_cnt);
            free(info_list);
        }
    }

    if (devcnt != NULL)
        *devcnt = put_cnt;

    return res < 0 ? res : put_cnt > size ? (int32_t)size : (int32_t)put_cnt;
}

X502_EXPORT(int32_t) X502_FreeDevRecordList(t_x502_devrec *list, uint32_t size) {
    uint32_t i;
    int32_t err = X502_ERR_OK;
    if (list!=NULL) {
        for (i=0; (i < size) && (err==X502_ERR_OK); i++) {
            if (list[i].sign!=X502_DEVREC_SIGN) {
                err = X502_ERR_INVALID_DEVICE_RECORD;
            } else {
                t_x502_devrec_inptr *devinfo_ptr = (t_x502_devrec_inptr*)list[i].internal;
                if (devinfo_ptr != NULL) {
                    const t_x502_dev_iface *iface = (const t_x502_dev_iface *)devinfo_ptr->iface;
                    if (iface!=NULL)
                        iface->free_devinfo_ptr(devinfo_ptr);

                }
                list[i].internal = NULL;
            }
        }
    }
    return err;
}


X502_EXPORT(int32_t) X502_GetDevInfo(t_x502_hnd hnd, t_x502_info* info) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (info==NULL))
        err = X502_ERR_INVALID_POINTER;

    if (err == X502_ERR_OK)
        *info = hnd->info;

    return err;
}




X502_EXPORT(int32_t) X502_GetNextExpectedLchNum(t_x502_hnd hnd, uint32_t *lch) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (lch==NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK)
        *lch = hnd->proc_adc_ch;
    return err;
}

#define STREAM_IN_WRD_TYPE(wrd) wrd & 0x80000000 ? STREAM_IN_WRD_ADC : \
      (wrd & 0xFF000000) == 0x0 ? STREAM_IN_WRD_DIN : \
    ((wrd & 0xFF000000)>>24) == 0x01 ? STREAM_IN_WRD_MSG : STREAM_IN_WRD_USR


X502_EXPORT(int32_t) X502_ProcessAdcData(t_x502_hnd hnd, const uint32_t* src,  double *dest,
                            uint32_t *size, uint32_t flags) {
    if (size == NULL)
        return X502_ERR_INVALID_POINTER;
    return X502_ProcessDataWithUserExt(hnd, src, *size, flags, dest, size,
                                        NULL, NULL, NULL, NULL);
}

X502_EXPORT(int32_t) X502_ProcessData(t_x502_hnd hnd, const uint32_t *src, uint32_t size, uint32_t flags,
                           double *adc_data, uint32_t *adc_data_size,
                           uint32_t *din_data, uint32_t *din_data_size) {
    return X502_ProcessDataWithUserExt(hnd, src, size, flags, adc_data, adc_data_size,
                                      din_data, din_data_size, NULL, NULL);
}

X502_EXPORT(int32_t) X502_ProcessDataWithUserExt(t_x502_hnd hnd, const uint32_t* src, uint32_t size,
                                   uint32_t flags, double *adc_data,
                                   uint32_t *adc_data_size, uint32_t *din_data, uint32_t *din_data_size,
                                   uint32_t *usr_data, uint32_t *usr_data_size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (adc_data_size==NULL) && (adc_data!=NULL))
        err = X502_ERR_INVALID_POINTER;
    if ((err == X502_ERR_OK) && (din_data_size==NULL) && (din_data!=NULL))
        err = X502_ERR_INVALID_POINTER;
    if ((err == X502_ERR_OK) && (usr_data_size==NULL) && (usr_data!=NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK) {
        uint32_t adc_cnt = 0, din_cnt=0, usr_cnt = 0;
        uint32_t i;

        for (i=0; (i<size) && (err == X502_ERR_OK); i++) {
            register uint32_t wrd = src[i];
            t_stream_in_wrd_type type = STREAM_IN_WRD_TYPE(wrd);
            /* проверяем - это данные от АЦП или цифровых входов */
            switch (type) {
                case STREAM_IN_WRD_ADC: {
                    uint32_t ch_num = (wrd >> 24) & 0xF;
                    uint32_t ch_mode = (wrd >> 28) & 0x3;
                    int32_t val;
                    uint32_t range;

                    /* проверяем совпадение каналов */
                    switch (ch_mode) {
                        case 0:
                            ch_mode = X502_LCH_MODE_DIFF;
                            break;
                        case 1:
                            ch_mode = X502_LCH_MODE_COMM;
                            break;
                        case 2:
                            ch_mode = X502_LCH_MODE_COMM;
                            ch_num+=16;
                            break;
                        case 3:
                            ch_mode = X502_LCH_MODE_ZERO;
                            break;
                    }

                    if (!(flags & X502_PROC_FLAGS_DONT_CHECK_CH) &&
                            ((hnd->set.lch[hnd->proc_adc_ch].mode != ch_mode) ||
                            (hnd->set.lch[hnd->proc_adc_ch].ch != ch_num))) {
                        err = X502_ERR_PROC_INVALID_CH_NUM;
                    } else {
                        /* проверяем формат - пришло откалиброванное 24-битное слово,
                          или неоткалиброванное 16-битное */
                        if (wrd & 0x40000000) {
                            val = wrd & 0xFFFFFF;
                            if (wrd & 0x800000)
                                val |= 0xFF000000;

                            range = hnd->set.lch[hnd->proc_adc_ch].range;
                        } else {
                            range = (wrd >> 16) & 0x7;
                            if (!(flags & X502_PROC_FLAGS_DONT_CHECK_CH) &&
                                    (range != hnd->set.lch[hnd->proc_adc_ch].range)) {
                                err = X502_ERR_PROC_INVALID_CH_RANGE;
                            } else {
                                val = wrd & 0xFFFF;
                                if (wrd & 0x8000)
                                    val |= 0xFFFF0000;
                            }
                        }
                    }

                    if (err == X502_ERR_OK) {
                        double res_val = val;
                        if (flags & X502_PROC_FLAGS_VOLT) {
                            res_val = hnd->set.f_scales[range]*res_val/hnd->set.adc_code_max;
                        }


                        if ((adc_data!=NULL) && (adc_cnt<*adc_data_size)) {
                            *adc_data++ = res_val;
                            adc_cnt++;
                        } else if (adc_data==NULL) {
                            adc_cnt++;
                        }

                        if (++hnd->proc_adc_ch ==hnd->set.lch_cnt)
                            hnd->proc_adc_ch = 0;
                    }
                }
                break;
            case STREAM_IN_WRD_DIN:
                if ((din_data!=NULL) && (din_cnt<*din_data_size)) {
                    *din_data++ = wrd & 0x3FFFF;
                    din_cnt++;
                } else if (din_data==NULL) {
                    din_cnt++;
                }
                break;
            case STREAM_IN_WRD_MSG:
                err = wrd==X502_STREAM_IN_MSG_OVERFLOW ? X502_ERR_STREAM_OVERFLOW :
                                                         X502_ERR_UNSUP_STREAM_MSG;
                break;
            case STREAM_IN_WRD_USR:
                if ((usr_data!=NULL) && (usr_cnt<*usr_data_size)) {
                    *usr_data++ = wrd;
                    usr_cnt++;
                } else if (usr_data==NULL) {
                    usr_cnt++;
                }
                break;
            default:
                break;
            }
        }


        if (adc_data_size!=NULL)
            *adc_data_size = adc_cnt;
        if (din_data_size!=NULL)
            *din_data_size = din_cnt;
        if (usr_data_size!=NULL)
            *usr_data_size = usr_cnt;
    }

    return err;
}

uint32_t prepare_dac_wrd(t_x502_hnd hnd, double val, uint32_t flags, const t_x502_cbr_coef* coef) {
    uint16_t wrd = 0;
    if (flags & X502_DAC_FLAGS_VOLT) {
        val = (val / hnd->set.dac_range) * hnd->set.dac_code_max;
    }
    if (flags & X502_DAC_FLAGS_CALIBR) {
        val = (val + coef->offs) * coef->k;
    }
    if (val > SHRT_MAX) {
        wrd = SHRT_MAX;
    } else
    if (val < SHRT_MIN) {
        wrd = SHRT_MIN;
    } else {
        wrd = val;
    }
    return wrd;
}

X502_EXPORT(int32_t) X502_PrepareData(t_x502_hnd hnd, const double* dac1, const double* dac2,
                            const uint32_t* digout, uint32_t size, int32_t flags,
                            uint32_t* out_buf) {
    int err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (out_buf==NULL))
        err = X502_ERR_INVALID_POINTER;
    if ((err == X502_ERR_OK) && ((dac1==NULL) && (dac2==NULL) && (digout==NULL)))
        err = X502_ERR_INVALID_POINTER;


    if (err == X502_ERR_OK) {
        uint32_t i;
        for (i = 0; (i < size) && (err == X502_ERR_OK); i++) {
            if ((dac1 != NULL) && (hnd->streams & X502_STREAM_DAC1)) {
                uint32_t wrd = prepare_dac_wrd(hnd, *dac1++, flags, &hnd->info.cbr.dac[0]);
                *out_buf++ = wrd | X502_STREAM_OUT_WORD_TYPE_DAC1;
            }
            if ((dac2 != NULL) && (hnd->streams & X502_STREAM_DAC2)) {
                uint32_t wrd = prepare_dac_wrd(hnd, *dac2++, flags, &hnd->info.cbr.dac[1]);
                *out_buf++ = wrd | X502_STREAM_OUT_WORD_TYPE_DAC2;
            }
            if ((digout != NULL) && (hnd->streams & X502_STREAM_DOUT)) {
                uint32_t wrd = *digout++;
                *out_buf++ = (wrd &0x3FFFF)
                             | X502_STREAM_OUT_WORD_TYPE_DOUT;
            }
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_FpgaRegWrite(t_x502_hnd hnd, uint32_t reg, uint32_t val) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->fpga_reg_write(hnd, reg & 0xFFFF, val);
    }
    return err;
}

X502_EXPORT(int32_t) X502_FpgaRegRead(t_x502_hnd hnd, uint32_t reg, uint32_t *val) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->fpga_reg_read(hnd, reg, val);
    }
    return err;
}


X502_EXPORT(uint32_t) X502_GetLibraryVersion(void) {
    return (X502API_VER_MAJOR << 24) | (X502API_VER_MINOR<<16) |
            (X502API_VER_PATCH << 8);
}


X502_EXPORT(int32_t) X502_DevRecordInit(t_x502_devrec *rec) {
    if (rec!=NULL) {
        memset(rec, 0, sizeof(t_x502_devrec));
        rec->sign = X502_DEVREC_SIGN;
    }
    return X502_ERR_OK;
}

X502_EXPORT(int32_t) X502_LedBlink(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        int32_t err2;
        err = hnd->mode == X502_MODE_DSP ? bf_fpga_reg_wr(hnd, X502_REGS_IOHARD_LED, 0) :
                                           X502_FpgaRegWrite(hnd, X502_REGS_IOHARD_LED, 0);
        SLEEP_MS(200);
        err2 = hnd->mode == X502_MODE_DSP ? bf_fpga_reg_wr(hnd, X502_REGS_IOHARD_LED, 1) :
                                            X502_FpgaRegWrite(hnd, X502_REGS_IOHARD_LED, 1);
        if (err == X502_ERR_OK)
            err = err2;
    }
    return err;
}

X502_EXPORT(int32_t) X502_SetDigInPullup(t_x502_hnd hnd, uint32_t pullups) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        uint32_t val = 0;
        if (pullups & X502_PULLUPS_DI_L)
            val |= 0x1;
        if (pullups & X502_PULLUPS_DI_H)
            val |= 0x2;
        if (pullups & X502_PULLUPS_DI_SYN1)
            val |= 0x4;
        if (pullups & X502_PULLUPS_DI_SYN2)
            val |= 0x8;
        if (pullups & X502_PULLDOWN_CONV_IN)
            val |= 0x10;
        if (pullups & X502_PULLDOWN_START_IN)
            val |= 0x20;
        if (pullups & E16_MODE_RELAY_ON)
            val |= 0x40;

        err = hnd->mode == X502_MODE_DSP ? bf_fpga_reg_wr(hnd, X502_REGS_IOHARD_DIGIN_PULLUP, val) :
                                           X502_FpgaRegWrite(hnd, X502_REGS_IOHARD_DIGIN_PULLUP, val);
    }
    return err;
}


X502_EXPORT(int32_t) X502_CheckFeature(t_x502_hnd hnd, uint32_t feature) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->check_feature(hnd, feature);
    }
    return err;
}

#ifdef _WIN32
BOOL WINAPI DllMain(HINSTANCE hmod, DWORD reason, LPVOID resvd) {
    switch (reason) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
#endif
