/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_PLATFORM_HUB75_EXT_PROCEDURE_H
#define ECXX_PLATFORM_HUB75_EXT_PROCEDURE_H


namespace eCxx {


/*
 * The CRTP base class for extended-procedure classes for GenericHUB75Driver<T>
 *
 * All derived (implementation) classes must implement this constant:
 *
 *     static constexpr uint8_t LatchCommandEndPulseCount_impl;

 * All derived (implementation) classes must implement this member function:
 *
 *     static inline void initialize_impl(const ControlPinType& ctlPin, const DataPinType& dataPin, const AddressPinType& addrPin);
 */
template <
    typename ImplementationT,
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
class HUB75ExtProcedure_CRTPBase : public Uninstantiable {
    public:
        using ControlPinType = HUB75ControlPinT;
        using DataPinType    = HUB75DataPinT;
        using AddressPinType = HUB75AddressPinT;

        using SendDataType   = HUB75SendDataT;

        static constexpr uint16_t RowPixelCount             = RowPixelCountN;
        static constexpr uint8_t  LatchCommandEndPulseCount = ImplementationT::LatchCommandEndPulseCount_impl;

    public:
        static inline void initialize(const ControlPinType& ctlPin, const DataPinType& dataPin, const AddressPinType& addrPin) __ECXX_ATTR_ALWAYS_INLINE
        { ImplementationT::initialize_impl(ctlPin, dataPin, addrPin); }
};


//
// No-operation extended-procedure class for GenericHUB75Driver<T>
//
template <
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
class HUB75ExtProcedure_NOP : public HUB75ExtProcedure_CRTPBase<
    HUB75ExtProcedure_NOP<HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>,
    HUB75ControlPinT,
    HUB75DataPinT,
    HUB75AddressPinT,
    HUB75SendDataT,
    RowPixelCountN
> {
    public:
        using HUB75ExtProcedureType = HUB75ExtProcedure_CRTPBase<HUB75ExtProcedure_NOP, HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>;

        using ControlPinType        = typename HUB75ExtProcedureType::ControlPinType;
        using DataPinType           = typename HUB75ExtProcedureType::DataPinType;
        using AddressPinType        = typename HUB75ExtProcedureType::AddressPinType;

        using SendDataType          = typename HUB75ExtProcedureType::SendDataType;

        static constexpr uint16_t RowPixelCount                  = HUB75ExtProcedureType::RowPixelCount;
        static constexpr uint8_t  LatchCommandEndPulseCount_impl = 0;

    public:
        static inline void initialize_impl(const ControlPinType&, const DataPinType&, const AddressPinType&) __ECXX_ATTR_ALWAYS_INLINE
        {}
};


//
// FM6126 (also FM6124 and ICN2038) extended-procedure class for GenericHUB75Driver<T>
//
// Based on : http://bobdavis321.blogspot.com/2019/02/p3-64x32-hub75e-led-matrix-panels-with.html
//            https://pdf1.alldatasheet.com/datasheet-pdf/view/1145498/FUMAN/FM6126A.html
//
template <
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
class HUB75ExtProcedure_FM6126 : public HUB75ExtProcedure_CRTPBase<
    HUB75ExtProcedure_FM6126<HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>,
    HUB75ControlPinT,
    HUB75DataPinT,
    HUB75AddressPinT,
    HUB75SendDataT,
    RowPixelCountN
> {
    public:
        using HUB75ExtProcedureType = HUB75ExtProcedure_CRTPBase<HUB75ExtProcedure_FM6126, HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>;

        using ControlPinType        = typename HUB75ExtProcedureType::ControlPinType;
        using DataPinType           = typename HUB75ExtProcedureType::DataPinType;
        using AddressPinType        = typename HUB75ExtProcedureType::AddressPinType;

        using SendDataType          = typename HUB75ExtProcedureType::SendDataType;

        static constexpr uint16_t RowPixelCount                  = HUB75ExtProcedureType::RowPixelCount;
        static constexpr uint8_t  LatchCommandEndPulseCount_impl = 3;

    private:
        static constexpr uint8_t WriteConfigReg1EndPulseCount = 11;
        static constexpr uint8_t WriteConfigReg2EndPulseCount = 12;

    public:
        static inline void initialize_impl(const ControlPinType& ctlPin, const DataPinType& dataPin, const AddressPinType& addrPin)
        {
            // Assert the address pin A
            addrPin.a.digitalWrite(AddressPinType::AddressH);

            // Write to configuration register 1
            const uint8_t reg1Value[2] = { 0b01111111, 0b11111111 };

            SendDataType::template writeRegister< sizeof(reg1Value) > (
                reg1Value, ctlPin, dataPin, WriteConfigReg1EndPulseCount
            );

            // Write to configuration register 2
            const uint8_t reg2Value[2] = { 0b00000000, 0b01000000 };

            SendDataType::template writeRegister< sizeof(reg2Value) > (
                reg2Value, ctlPin, dataPin, WriteConfigReg2EndPulseCount
            );

            // Deassert the address pin A
            addrPin.a.digitalWrite(AddressPinType::AddressL);
        }
};

template <
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
using HUB75ExtProcedure_FM6124 = HUB75ExtProcedure_FM6126<HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>;

template <
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
using HUB75ExtProcedure_ICN2038 = HUB75ExtProcedure_FM6126<HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>;


//
// FM6127 extended-procedure class for GenericHUB75Driver<T>
//
// Based on : https://github.com/hzeller/rpi-rgb-led-matrix/files/4140096/FM6127.txt
//            http://superchip.cn/Private/ProductFiles/63694649249960578960607011.pdf
//
template <
    typename HUB75ControlPinT,
    typename HUB75DataPinT,
    typename HUB75AddressPinT,
    typename HUB75SendDataT,
    uint16_t RowPixelCountN
>
class HUB75ExtProcedure_FM6127 : public HUB75ExtProcedure_CRTPBase<
    HUB75ExtProcedure_FM6127<HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>,
    HUB75ControlPinT,
    HUB75DataPinT,
    HUB75AddressPinT,
    HUB75SendDataT,
    RowPixelCountN
> {
    public:
        using HUB75ExtProcedureType = HUB75ExtProcedure_CRTPBase<HUB75ExtProcedure_FM6127, HUB75ControlPinT, HUB75DataPinT, HUB75AddressPinT, HUB75SendDataT, RowPixelCountN>;

        using ControlPinType        = typename HUB75ExtProcedureType::ControlPinType;
        using DataPinType           = typename HUB75ExtProcedureType::DataPinType;
        using AddressPinType        = typename HUB75ExtProcedureType::AddressPinType;

        using SendDataType          = typename HUB75ExtProcedureType::SendDataType;

        static constexpr uint16_t RowPixelCount                  = HUB75ExtProcedureType::RowPixelCount;
        static constexpr uint8_t  LatchCommandEndPulseCount_impl = 3;

    private:
        static constexpr uint8_t WriteConfigReg1EndPulseCount = 11;
        static constexpr uint8_t WriteConfigReg2EndPulseCount = 12;
        static constexpr uint8_t WriteConfigReg3EndPulseCount = 10;

    public:
        static inline void initialize_impl(const ControlPinType& ctlPin, const DataPinType& dataPin, const AddressPinType& addrPin)
        {
            // Assert the address pin A
            addrPin.a.digitalWrite(AddressPinType::AddressH);

            // Write to configuration register 1
            const uint8_t reg1Value[2] = { 0b11111111, 0b11001110 };

            SendDataType::template writeRegister< sizeof(reg1Value) > (
                reg1Value, ctlPin, dataPin, WriteConfigReg1EndPulseCount
            );

            // Write to configuration register 2
            const uint8_t reg2Value[2] = { 0b11100000, 0b01100010 };

            SendDataType::template writeRegister< sizeof(reg2Value) > (
                reg2Value, ctlPin, dataPin, WriteConfigReg2EndPulseCount
            );

            // Write to configuration register 3
            const uint8_t reg3Value[2] = { 0b01011111, 0b00000000 };

            SendDataType::template writeRegister< sizeof(reg3Value) > (
                reg3Value, ctlPin, dataPin, WriteConfigReg3EndPulseCount
            );

            // Deassert the address pin A
            addrPin.a.digitalWrite(AddressPinType::AddressL);
        }
};


} // namespace eCxx


#endif
