#include "x502api_private.h"
#include "x502_fpga_regs.h"
#include "l502_bf_cmd_defs.h"
#include "ltimer.h"

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


/* разрешение/запрещение потоков ввода вывода в соответствии с полем hnd->streams */
static int32_t f_set_streams(t_x502_hnd hnd, uint32_t streams) {
    int32_t err = X502_ERR_OK;
    if (hnd->mode == X502_MODE_FPGA)  {
        err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOARITH_IN_STREAM_ENABLE,
                                        (streams & X502_STREAM_ADC ? 0x01 : 0) |
                                        (streams & X502_STREAM_DIN ? 0x02 : 0));
    }

    if (err == X502_ERR_OK) {
        hnd->streams = streams;
    }
    return err;
}



/* Функция автоматического рассчета параметов DMA для входного потока
   на основе частоты сбора данных */
static int32_t f_stream_in_cfg(t_x502 *hnd) {
    int32_t err = X502_ERR_OK;


    t_x502_stream_ch_params params;
    double din_freq = 0;
    uint32_t total_size = 0;
    double ref_freq;

    memset(&params, 0, sizeof(params));

    X502_GetRefFreqValue(hnd, &ref_freq);

    /* рассчитываем частоту сбора для потоков АЦП и DIN */
    if (hnd->streams & X502_STREAM_ADC) {
        double f_frame;
        X502_GetAdcFreq(hnd, NULL, &f_frame);
        din_freq = f_frame*hnd->set.lch_cnt;
    }

    if (hnd->streams & X502_STREAM_DIN) {
        din_freq+=ref_freq/hnd->set.din_freq_div;
    }

    /* размер полного буфера определяем таким, чтобы его хватило на
       L502_DMA_IN_BUF_FOR_SEC секунд постоянного сбора, но не меньше минимального
       размера */
    total_size = (uint32_t)(X502_DMA_IN_BUF_FOR_SEC*din_freq);
    if (total_size < hnd->iface_hnd->in_stream_buf_min)
        total_size = hnd->iface_hnd->in_stream_buf_min;






    /* рассчитываем IRQ_STEP, чтобы он был  L502_DMA_IN_MAX_IRQ_PER_SEC
       в секунду */

    params.step = hnd->stream_pars[X502_STREAM_CH_IN].step ?
                hnd->stream_pars[X502_STREAM_CH_IN].step :
                (din_freq>X502_DMA_IN_MAX_IRQ_PER_SEC) ?
                (uint32_t)(din_freq/X502_DMA_IN_MAX_IRQ_PER_SEC) : 1;

    /* для эффиктивности делаем размер буфера кратным irq_step */
    total_size = ((total_size+params.step-1)/params.step)*params.step;

    params.buf_size = hnd->stream_pars[X502_STREAM_CH_IN].buf_size ?
                hnd->stream_pars[X502_STREAM_CH_IN].buf_size : total_size;

    err = hnd->iface_hnd->stream_cfg(hnd, X502_STREAM_CH_IN, &params);

    if ((err == X502_ERR_OK) && (hnd->mode == X502_MODE_DSP)) {
        /* для BlackFin нужно так же установить еще шаг прерываний для
           приема данных от SPORT'а */
        uint32_t size;
        err = x502_bf_get_par(hnd, L502_BF_PARAM_IN_BUF_SIZE, &size, 1);
        if (err == X502_ERR_OK) {
            uint32_t instep = params.step;
            if (instep>size/4) {
                instep=size/4;
            }
            if (instep > 0x8000)
                instep = 0x8000;
            err = x502_bf_set_par(hnd, L502_BF_PARAM_IN_STEP_SIZE, &instep, 1);
        }
    }
    return err;
}





static int32_t f_out_stream_preload(t_x502 *hnd) {
    int32_t err;
    STREAM_OUT_CFG(hnd, err);
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->stream_start(hnd, X502_STREAM_CH_OUT, 0);

    if ((err == X502_ERR_OK) && (hnd->mode == X502_MODE_DSP)) {
        err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_PRELOAD, 0, NULL, 0,
                             NULL, 0, X502_BF_REQ_TOUT, NULL);
    }

    if (err == X502_ERR_OK) {
        hnd->flags |= PRIV_FLAGS_PRELOAD_DONE;
    }
    return err;
}





X502_EXPORT(int32_t) X502_StreamsEnable(t_x502_hnd hnd, uint32_t streams) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
    if (err == X502_ERR_OK) {
        if (hnd->mode == X502_MODE_DSP) {
            err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_STREAM_EN, streams,
                                 NULL, 0, NULL, 0, X502_BF_REQ_TOUT, NULL);
            if (err == X502_ERR_OK)
                hnd->streams |= streams;
        } else {
            if (hnd->flags & PRIV_FLAGS_STREAM_RUN) {
                uint32_t old_streams = hnd->streams;
                hnd->streams |= streams;

                /* если не было разрешено потока на ввод до этого,
                   а при вызове стал разрешен => инициализируем его */
                if ((err == X502_ERR_OK) && !(old_streams & X502_STREAM_ALL_IN) &&
                        (streams & X502_STREAM_ALL_IN)) {
                    err = f_stream_in_cfg(hnd);
                    if (err == X502_ERR_OK)
                        err = hnd->iface_hnd->stream_start(hnd, X502_STREAM_CH_IN, 0);
                } else if ((err == X502_ERR_OK) && ((old_streams & X502_STREAM_ALL_IN) !=
                                  (streams & X502_STREAM_ALL_IN))) {
                    err =f_stream_in_cfg(hnd);
                }


                /* Запись в регистры плиса имеет смысл делать только перед запуском
                   или во время запуска синхронного сбора, т.к. пока сбор не запущен
                   - не вляют. но в старых версиях ПЛИС могли приводит к сдвигу по
                   времени первого отсчета АЦП.
                   Разрешение потока в ПЛИС следует делать уже после stream_start,
                   чтобы ПК был готов принимать данные */
                err = f_set_streams(hnd, hnd->streams | streams);
            } else {
                hnd->streams |= streams;
            }
        }
        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}

X502_EXPORT(int32_t) X502_StreamsDisable(t_x502_hnd hnd, uint32_t streams) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);

    if (err == X502_ERR_OK) {
        if (hnd->mode == X502_MODE_DSP) {
            err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_STREAM_DIS, streams,
                                 NULL, 0, NULL, 0, X502_BF_REQ_TOUT, NULL);
            if (err == X502_ERR_OK)
                hnd->streams &= ~streams;

        } else {


            if (hnd->flags & PRIV_FLAGS_STREAM_RUN) {
                uint32_t old_streams = hnd->streams;
                err = f_set_streams(hnd, hnd->streams & ~streams);
                /* если все потоки на ввод были запрещены, то
                   останавливаем их */
                if ((err == X502_ERR_OK) && (old_streams & X502_STREAM_ALL_IN) &&
                        !(hnd->streams & X502_STREAM_ALL_IN)) {
                    err = hnd->iface_hnd->stream_stop(hnd, X502_STREAM_CH_IN, 0);
                }

                if ((err == X502_ERR_OK) && (old_streams & X502_STREAM_ALL_OUT) &&
                        !(hnd->streams & X502_STREAM_ALL_OUT)) {
                    err = hnd->iface_hnd->stream_stop(hnd, X502_STREAM_CH_OUT, 0);
                    if (err == X502_ERR_OK) {
                        hnd->flags &= ~PRIV_FLAGS_PRELOAD_DONE;
                    }
                }
            } else {
                hnd->streams &= ~streams;
            }
        }

        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}

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



X502_EXPORT(int32_t) X502_StreamsStart(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (hnd->flags & PRIV_FLAGS_STREAM_RUN))
        err = X502_ERR_STREAM_IS_RUNNING;

    if ((err == X502_ERR_OK) && (hnd->mode==X502_MODE_FPGA)) {
        uint32_t reg;
        err = hnd->iface_hnd->fpga_reg_read(hnd, X502_REGS_IOHARD_IO_MODE, &reg);
        if ((err == X502_ERR_OK) && !(reg & X502_REGBIT_ADC_SLV_CLK_LOCK_Msk))
            err = X502_ERR_REF_FREQ_NOT_LOCKED;
    }

    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);

    if (err == X502_ERR_OK) {
        int in_started = 0;
        f_set_streams(hnd, hnd->streams);

        /** @todo корректные действия, если сбор в Bf не остановлен */

        /* выполняем предзагрузку первого слова выходного потока и
           коммутатора АЦП (при наличии DSP это делает DSP) */
        if ((err == X502_ERR_OK) && (hnd->mode==X502_MODE_FPGA)) {
            /* предзагрузку значения на вывод делаем только если реально данные уже были
             * предзагружены в буфер платы */
            if ((hnd->streams & X502_STREAM_ALL_OUT) &&
                    (hnd->flags & (PRIV_FLAGS_PRELOAD_DONE | PRIV_FLAGS_CYCLE_MODE))) {
                err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_OUTSWAP_BFCTL, 1);
            }


            /* Предзагрузку АЦП должны делать всегда, так как по этой функции
               выполняется часть инициализации параметров синхронного сбора!

               Так как конвейер автомата управления входной коммутации АЦП состоит
                из 2-х стадий, для корректного синхронного старта необходимо в
                ыполнить два раза предзагрузку. В противном случае,
                время момента первого отсчета может не совпадать с моментом
                запуска синхронизации
            */
            if (err == X502_ERR_OK)
                err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_PRELOAD_ADC, 1);
            if (err == X502_ERR_OK)
                err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_PRELOAD_ADC, 1);
            SLEEP_MS(20);
        }


        /* запуск потока на ввод выполняем при запуске синхронного ввода-вывода */
        if ((err == X502_ERR_OK) && (hnd->streams & X502_STREAM_ALL_IN)) {
            err = f_stream_in_cfg(hnd);
            if (err == X502_ERR_OK)
                err = hnd->iface_hnd->stream_start(hnd, X502_STREAM_CH_IN, 0);
            if (err == X502_ERR_OK)
                in_started = 1;
        }

        if (err == X502_ERR_OK) {
            if (hnd->mode == X502_MODE_FPGA) {
                /* взводим сигнал GO, указывающий что запущен синхронных ввод-вывод */
                err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_GO_SYNC_IO, 1);
            } else if (hnd->mode == X502_MODE_DSP) {
                err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_STREAM_START, 0,
                                  NULL, 0, NULL, 0, X502_BF_CMD_DEFAULT_TOUT, NULL);
            } else {
                err = X502_ERR_INVALID_MODE;
            }
        }

        if (err && in_started) {
            hnd->iface_hnd->stream_free(hnd, X502_STREAM_CH_IN, 0);
        }


        if (err == X502_ERR_OK) {
            hnd->flags |= PRIV_FLAGS_STREAM_RUN;
            hnd->proc_adc_ch = 0;
        }

        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}


X502_EXPORT(int32_t) X502_StreamsStop(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
    if (err == X502_ERR_OK) {
         int32_t stop_err1, stop_err2;

        if (hnd->mode==X502_MODE_FPGA) {
            err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_GO_SYNC_IO, 0);
        } else if (hnd->mode == X502_MODE_DSP) {           
            err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_STREAM_STOP, 0,
                              NULL, 0, NULL, 0, X502_BF_CMD_DEFAULT_TOUT, NULL);
        }

        stop_err1 = hnd->iface_hnd->stream_free(hnd, X502_STREAM_CH_IN, 0);
        stop_err2 = hnd->iface_hnd->stream_free(hnd, X502_STREAM_CH_OUT, 0);
        if (err == X502_ERR_OK)
            err = stop_err1;
        if (err == X502_ERR_OK)
            err = stop_err2;

        hnd->flags &= ~(PRIV_FLAGS_STREAM_RUN | PRIV_FLAGS_PRELOAD_DONE | PRIV_FLAGS_CYCLE_MODE);

        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}

X502_EXPORT(int32_t) X502_IsRunning(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    uint32_t bf_mode=0;
    if ((err == X502_ERR_OK) && (hnd->mode==X502_MODE_DSP)) {
        err = x502_bf_get_par(hnd, L502_BF_PARAM_STREAM_MODE, &bf_mode, 1);
    }

    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
    if (err == X502_ERR_OK) {
        if (hnd->mode==X502_MODE_DSP) {
            if (bf_mode==L502_BF_MODE_IDLE) {
                hnd->flags &= ~PRIV_FLAGS_STREAM_RUN;
            } else {
                hnd->flags |= PRIV_FLAGS_STREAM_RUN;
            }
        }

        if (!(hnd->flags & PRIV_FLAGS_STREAM_RUN))
            err = X502_ERR_STREAM_IS_NOT_RUNNING;

        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}

X502_EXPORT(int32_t) X502_Recv(t_x502_hnd hnd, uint32_t* buf, uint32_t size, uint32_t tout) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (buf==NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->stream_read(hnd, buf, size, tout);
    }
    return err;
}

X502_EXPORT(int32_t) X502_Send(t_x502_hnd hnd, const uint32_t* buf, uint32_t size, uint32_t tout) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (buf==NULL))
        err = X502_ERR_INVALID_POINTER;

    /* если разрешен синхронный вывод, но не было
     * вызова X502_PreloadStart() или не был установлен синхронных режим, то
     * делаем запуск потока вывода при первой записи */
    if ((err == X502_ERR_OK) && (hnd->streams & X502_STREAM_ALL_OUT)) {
        if (err == X502_ERR_OK)
            err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
        if (err == X502_ERR_OK) {
            if (!(hnd->flags & (PRIV_FLAGS_PRELOAD_DONE | PRIV_FLAGS_CYCLE_MODE))) {
                err = f_out_stream_preload(hnd);
            }
            osspec_mutex_release(hnd->mutex_cfg);
        }
    }

    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->stream_write(hnd, buf, size, tout);
    }
    return err;
}

X502_EXPORT(int32_t)X502_PreloadStart(t_x502_hnd hnd) {
    int err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
    if (err == X502_ERR_OK) {
        err = f_out_stream_preload(hnd);
        osspec_mutex_release(hnd->mutex_cfg);
    }
    return err;
}

X502_EXPORT(int32_t) X502_GetRecvReadyCount(t_x502_hnd hnd, uint32_t *rdy_cnt) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (rdy_cnt==NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->stream_get_rdy_cnt(hnd, X502_STREAM_CH_IN, rdy_cnt);
    return err;
}

X502_EXPORT(int32_t) X502_GetSendReadyCount(t_x502_hnd hnd, uint32_t *rdy_cnt) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (rdy_cnt==NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->stream_get_rdy_cnt(hnd, X502_STREAM_CH_OUT, rdy_cnt);
    return err;
}


static int32_t f_check_stream_ch_par_en(t_x502_hnd hnd, uint32_t stream_ch) {
    int32_t err = 0;
    if (stream_ch == X502_STREAM_CH_IN) {
        if ((hnd->flags & PRIV_FLAGS_STREAM_RUN)
                && (hnd->streams & X502_STREAM_ALL_IN)) {
            err = X502_ERR_STREAM_IS_RUNNING;
        }
    } else if (stream_ch == X502_STREAM_CH_OUT) {
        if ((hnd->flags & (PRIV_FLAGS_PRELOAD_DONE | PRIV_FLAGS_STREAM_RUN))
                && (hnd->streams & X502_STREAM_ALL_OUT)) {
            err = X502_ERR_STREAM_IS_RUNNING;
        }
    } else {
        err = X502_ERR_INVALID_STREAM_CH;
    }
    return err;
}

X502_EXPORT(int32_t) X502_SetStreamBufSize(t_x502_hnd hnd, uint32_t ch, uint32_t size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = f_check_stream_ch_par_en(hnd, ch);
    if (err == X502_ERR_OK)
        hnd->stream_pars[ch].buf_size = size;
    return err;
}

X502_EXPORT(int32_t) X502_SetStreamStep(t_x502_hnd hnd, uint32_t ch, uint32_t step) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = f_check_stream_ch_par_en(hnd, ch);
    if (err == X502_ERR_OK)
        hnd->stream_pars[ch].step = step;
    return err;
}


X502_EXPORT(int32_t) X502_OutCycleLoadStart(t_x502_hnd hnd, uint32_t size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
        if (err == X502_ERR_OK) {
            /** @todo проверить правильность момента вызова */
            err = hnd->iface_hnd->cycle_load_start(hnd, size);

            /* если еще не была выполнена предзагрузка сигнала, то посылаем DSP
               команду, чтобы успел подгрузить данные до старта выдачи */
            if ((err == X502_ERR_OK) && !(hnd->flags & (PRIV_FLAGS_CYCLE_MODE | PRIV_FLAGS_PRELOAD_DONE))) {
                if (hnd->mode == X502_MODE_DSP) {
                    err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_PRELOAD, 0, NULL, 0,
                                         NULL, 0, X502_BF_REQ_TOUT, NULL);
                }
            }

            if (err == X502_ERR_OK)
                hnd->flags |= PRIV_FLAGS_CYCLE_MODE;

            osspec_mutex_release(hnd->mutex_cfg);
        }
    }
    return err;
}


static int32_t f_cycle_setup_wait(t_x502_hnd hnd, uint32_t tout) {
    int32_t err = X502_ERR_OK;
    t_ltimer tmr;
    uint32_t done = 0;
    int first = 1;
    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));

    do {
        if (!first) {
            SLEEP_MS(5);
        } else {
            first = 0;
        }

        err = X502_OutCycleCheckSetupDone(hnd, &done);        
    } while (!done && !ltimer_expired(&tmr) && (err == X502_ERR_OK));

    if ((err == X502_ERR_NOT_SUP_BY_DRIVER) || (err == X502_ERR_NOT_SUP_BY_FIRMWARE)) {
        done = 1;
        err = X502_ERR_OK;
    }

    if ((err == X502_ERR_OK) && !done) {
        err = X502_ERR_OUT_CYCLE_SETUP_TOUT;
    }

    return err;

}



X502_EXPORT(int32_t) X502_OutCycleSetup(t_x502_hnd hnd, uint32_t flags) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->cycle_setup(hnd, flags);
    if ((err == X502_ERR_OK) && (flags & X502_OUT_CYCLE_FLAGS_WAIT_DONE))
        err = f_cycle_setup_wait(hnd, X502_OUT_CYCLE_WAIT_TOUT);
    return err;
}



X502_EXPORT(int32_t) X502_OutCycleStop(t_x502_hnd hnd, uint32_t flags) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->cycle_stop(hnd, flags);
    if (err == X502_ERR_OK) {
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
        if (err == X502_ERR_OK) {
            hnd->flags &= ~PRIV_FLAGS_CYCLE_MODE;
            osspec_mutex_release(hnd->mutex_cfg);
        }
    }

    if ((err == X502_ERR_OK) && (flags & X502_OUT_CYCLE_FLAGS_WAIT_DONE))
        err = f_cycle_setup_wait(hnd, X502_OUT_CYCLE_WAIT_TOUT);

    return err;
}

X502_EXPORT(int32_t) X502_OutCycleCheckSetupDone(t_x502_hnd hnd, uint32_t *done) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->cycle_check_setup(hnd, done);
    }
    return err;
}

X502_EXPORT(int32_t) X502_OutGetStatusFlags(t_x502_hnd hnd, uint32_t *status) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK) {
        err = hnd->iface_hnd->check_feature(hnd, X502_FEATURE_OUT_STATUS_FLAGS);
    }
    if (err == X502_ERR_OK) {
        if (hnd->mode == X502_MODE_DSP) {
            if (hnd->bf_features & L502_BF_FEATURE_OUT_STATUS_FLAGS) {
                uint32_t recvd;
                err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_GET_OUT_STATUS,
                                     0, NULL, 0, status, 1, X502_BF_CMD_DEFAULT_TOUT,
                                     &recvd);
                if ((err == X502_ERR_OK) && (recvd < 1)) {
                    err = X502_ERR_BF_CMD_RETURN_INSUF_DATA;
                }
            } else {
                err = X502_ERR_NOT_IMPLEMENTED;
            }

        } else {
            err = X502_FpgaRegRead(hnd, X502_REGS_IOHARD_OUTSWAP_ERROR, status);
        }
    }
    return err;
}


X502_EXPORT(int32_t) X502_AsyncGetAdcFrame(t_x502_hnd hnd, uint32_t flags,
                                            uint32_t tout, double* data) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err == X502_ERR_OK) && (data==NULL))
        err = X502_ERR_INVALID_POINTER;
    if (err == X502_ERR_OK)
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
    if (err == X502_ERR_OK) {
        if (hnd->mode == X502_MODE_FPGA) {
            /* если запущен хоть один поток на ввод синхронно, то ввод кадра
             * не возможне */
            if ((hnd->flags & PRIV_FLAGS_STREAM_RUN) && (hnd->streams & X502_STREAM_ALL_IN)) {
                err = X502_ERR_STREAM_IS_RUNNING;
            }

            if (err == X502_ERR_OK) {
                int need_stop = 0;
                int old_streams = hnd->streams;
                uint32_t *wrds = NULL;
                int32_t rcv_size = hnd->set.lch_cnt;

                /* разрешаем поток для АЦП */
                err = f_set_streams(hnd, (hnd->streams & ~X502_STREAM_ALL_IN)
                                    | X502_STREAM_ADC);

                hnd->proc_adc_ch = 0;

                if (err == X502_ERR_OK) {
                     /* инициализируем буфер для приема - достаточно всего на один кадр */
                    t_x502_stream_ch_params par = hnd->stream_pars[X502_STREAM_CH_IN];
                    hnd->stream_pars[X502_STREAM_CH_IN].buf_size = hnd->iface_hnd->in_stream_buf_min;
                    hnd->stream_pars[X502_STREAM_CH_IN].step = hnd->set.lch_cnt;
                    err = f_stream_in_cfg(hnd);

                    /* восстанавливаем параметры в структуре, что были заданы
                      до этой функции */
                    hnd->stream_pars[X502_STREAM_CH_IN] = par;
                }

                if (err == X502_ERR_OK) {
                    /* выделяем массив для необработанных отсчетов */
                    wrds = malloc(sizeof(wrds[0])*rcv_size);
                    if (wrds==NULL)
                        err = X502_ERR_MEMORY_ALLOC;
                }

                /* предзагрузка логической таблицы для АЦП */
                if (err == X502_ERR_OK)
                    err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_PRELOAD_ADC, 1);

                /* запуск канала DMA на прием данных */
                if (err == X502_ERR_OK) {
                    err = hnd->iface_hnd->stream_start(hnd, X502_STREAM_CH_IN,
                                                       X502_STREAM_FLAG_SINGLE);
                }

                /* если общий синхронный ввод не был запущен  - разрешаем его */
                if (err == X502_ERR_OK) {
                    if (!(hnd->flags & PRIV_FLAGS_STREAM_RUN)) {
                        err = hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_GO_SYNC_IO, 1);
                        if (err == X502_ERR_OK)
                            need_stop = 1;
                    }
                }

                if (err == X502_ERR_OK) {
                    /* принимаем отсчеты от одного кадра */
                    int32_t rcv = X502_Recv(hnd, wrds, rcv_size, tout);
                    if (rcv < 0) {
                        err = rcv;
                    } else if (rcv!=rcv_size) {
                        err = X502_ERR_RECV_INSUFFICIENT_WORDS;
                    } else {
                        err = X502_ProcessAdcData(hnd, wrds, data, (uint32_t*)&rcv_size, flags);
                    }
                }

                /* если в этой функции запустили синхронный сбор, то останвливаем его */
                if (need_stop)
                    hnd->iface_hnd->fpga_reg_write(hnd, X502_REGS_IOHARD_GO_SYNC_IO, 0);

                hnd->proc_adc_ch = 0;
                hnd->iface_hnd->stream_free(hnd, X502_STREAM_CH_IN, 0);
                /* восстанавливаем те потоки, которые были разрешены */
                f_set_streams(hnd, old_streams);
                free(wrds);
            }
        } else if (hnd->mode == X502_MODE_DSP) {
            err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_ADC_GET_FRAME,
                                 0, NULL, 0, (uint32_t*)data, 2*hnd->set.lch_cnt,
                                 X502_BF_CMD_DEFAULT_TOUT, NULL);
        } else {
            err = X502_ERR_INVALID_MODE;
        }
        osspec_mutex_release(hnd->mutex_cfg);
    }

    return err;
}


X502_EXPORT(int32_t) X502_ManualStreamStart(t_x502_hnd hnd, uint32_t stream_ch, uint32_t flags) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = X502_StreamsEnable(hnd, stream_ch == X502_STREAM_CH_IN ? X502_STREAM_ALL_IN : X502_STREAM_ALL_OUT );
    if (err == X502_ERR_OK) {
        if (stream_ch == X502_STREAM_CH_IN) {
            err = f_stream_in_cfg(hnd);
        } else {
            STREAM_OUT_CFG(hnd, err);
        }
    }
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->stream_start(hnd, stream_ch, flags);
    if ((err == X502_ERR_OK) && (stream_ch == X502_STREAM_CH_OUT)) {
        err = osspec_mutex_lock(hnd->mutex_cfg, X502_MUTEX_CFG_LOCK_TOUT);
        if (err==X502_ERR_OK) {
            hnd->flags |= PRIV_FLAGS_PRELOAD_DONE;
            osspec_mutex_release(hnd->mutex_cfg);
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_ManualStreamStop(t_x502_hnd hnd, uint32_t stream_ch) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err == X502_ERR_OK)
        err = X502_StreamsDisable(hnd, stream_ch == X502_STREAM_CH_IN ? X502_STREAM_ALL_IN : X502_STREAM_ALL_OUT );
    if (err == X502_ERR_OK)
        err = hnd->iface_hnd->stream_free(hnd, stream_ch, 0);
    return err;
}
