/*
 * bim_device.hpp
 *
 *  Created on: 8 нояб. 2018 г.
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
 * ------------------------------------------------------
 *  реализация  АПИ блока БИМ на КАН контролере миландра.
 *  особенности:
// политика учета ошибок КАН сделана толерантной к отказам -
//      успешный обмен ценнее чем отказ, и линия считается
//      нормальной сразу после успеха
*/

#ifndef HAL_MDR32F9QX_CAN_DEVICE_HPP_
#define HAL_MDR32F9QX_CAN_DEVICE_HPP_

#include <mcu_nvic.h>
#include <mcu_can.h>
#include <mcu_gpio.h>
#include <can_hal.hpp>
#include <project-conf.h>

#ifndef CAN_CONNECTIONS_LIMIT
// by default provide all messagebox support
#define CAN_CONNECTIONS_LIMIT   (CAN_BUFFER_LIMIT /2)
#endif

#if ( CAN_CONNECTIONS_LIMIT > (CAN_BUFFER_LIMIT /2) )
#error "Milanr CAN driver CAN_CONNECTIONS_LIMIT too much"
#endif

class MDR32_CANDevice :public CAN_Device
{
    public:
        typedef CAN_Device inherited;
    public:
        struct PORT_INIT {
            CAN_TypeDef*                CANx;
            const NVIC_InitTypeDef*     NVIC_Init;
            uint32_t                    RCC_Perifery;

            GPIOFUNC_INIT               GPIOPINS;
            // нужен для unhang_poll
            uint8_t                     PINID_RX;
        };
        struct DEV_INIT {
            const PORT_INIT*            port;
            const CAN_InitTypeDef*      CAN_Init;
            unsigned                    baud;
        };
    public:

        MDR32_CANDevice();
        //CANDevice(dev_name name);

        int init(const DEV_INIT* x);

        using inherited::writeData;
        virtual DevResult connect(addr_t address, recv_handle ev_recv, void* ev_self);
        // \arg data == NULL - abort all sending on specified adress
        virtual DevResult writeData(addr_t address, const void *data, size_t size);
        //  abort all sends on <address>. and cleanup error counts.
        virtual  DevResult writeAbort(addr_t address);
        //  abort all sends - terminate all transmitions. and cleanup error counts.
        virtual DevResult writeAbort(void);

        void IRQ();
        CAN_TypeDef* port() {return io;};

        // \sa 1986BE9x and MDR32F9Qx Series Errata Notice
        //      0010 останов передатчика КАН при помехе в линии приема
        //      при приеме помехи по линии связи может произойти останов
        //      приемника и передатчика.
        //  для устранения состояния останова должен прийти любой исправный бит
        //  по линии, или надо внести в линию доминант более чем 1бит
        //  Эта функция переводит порт приемника в GPIO и дергает его для внесения
        //      доминанта в приемник
        //  !!!WARN: на практике применение этого метода частое дает зависание девайса
        //      которое лечится толко холодным выкл/вкл.  ненадо это применять!!!
        void    unhang_poll();

    protected:
        CAN_TypeDef*                io;
        const DEV_INIT*             port_def;
        //длительность бита [мкс], unhang_poll внедряет такой пильс чтобы
        //  взбодрить приемник
        unsigned                    bit1_us;

        //virtual
        int init();
        int deinit();

        int can_init();
        int can_down();

        enum {
            bufidNONE = CAN_BUFFER_NUMBER
        };

        // configure slot <bufid> for receive on single <adress>
        void assign_filter(unsigned bufid, unsigned adress);
        // \return - allocated and configured new slot for send
        unsigned alloc_recvbuf(unsigned adress, recv_handle ev_recv, void* ev_self);
        unsigned alloc_sendbuf();
        // \return  - sending slot for <adress>
        unsigned send_bufid(unsigned adress);

        // каждму слоту приемника сопоставляется обработчик
        struct slot_h{
            recv_handle on_recv;
            void*       arg;
            unsigned    adr;
            int         err_cnt;
        };
        enum {slots_limit = CAN_CONNECTIONS_LIMIT*2};
        slot_h  on_slot[CAN_BUFFER_NUMBER];

        bool is_slot_active(unsigned bufid) const{
            unsigned st = io->BUF_CON[bufid];
            return (st&CAN_STATUS_EN) != 0;
        };

        bool is_slot_send(unsigned bufid) const{
            unsigned st = io->BUF_CON[bufid];
            const unsigned send_active_st =  (CAN_STATUS_EN | CAN_STATUS_RX_TXn);
            return (st & send_active_st) == CAN_STATUS_EN;
        };

        bool is_sending(unsigned bufid) const {
            unsigned st = io->BUF_CON[bufid];
            const unsigned send_active_st =  (CAN_STATUS_EN | CAN_STATUS_TX_REQ | CAN_STATUS_RX_TXn);
            if ( (st & send_active_st) != CAN_STATUS_EN)
                return true;
            return false;
        };

    protected:
        // политика учета ошибок КАН сделана толерантной к отказам -
        //      успешный обмен ценнее чем отказ, и линия считается
        //      нормальной сразу после успеха
        // контроль ошибок КАН веду самостоятельно, ибо контроллер стандартный
        //  дает обратную политику:
        //  - любое удачное получение фрейма с КАН откатывает счетчик ошибок на err_good_price
        //  - линия cчитается мертвой при счетчике >= err_switch_limit
        //  - линия считается хорошей при счетчике < err_good_level
        int      can_err_cnt;
        enum {
            // порог ошибок до переключения шины
              err_switch_limit = 15
              // успешная активность КАН восстанавливает некоторый запас ошибок
            , err_good_price = 4
            , err_good_level = err_good_price*2
            // уровень ошибок, требующий отмены текущих слотов отсылок. Не используется
            , err_abort_limit = 8
        };

        void IRQ_buf_recv(unsigned bufid);
        virtual void on_error(unsigned err);
        // успешная активность КАН, должна както влиять на счетчики ошибок
        void on_good(unsigned err);
        void can_send_err(unsigned bufid);
        void can_abort(unsigned bufid);
};

// HAL предлагает этот тип как дефолтный драйвер КАН
typedef MDR32_CANDevice     CANDevice;

extern const CAN_InitTypeDef                CAN80M_500K;
extern const MDR32_CANDevice::PORT_INIT     CAN1C_init;
extern const MDR32_CANDevice::PORT_INIT     CAN2C_init;
extern const MDR32_CANDevice::PORT_INIT     CAN1D_init;
extern const MDR32_CANDevice::PORT_INIT     CAN2D_init;

#endif /* HAL_MDR32F9QX_CAN_DEVICE_HPP_ */
