/*
 * dsk324sr.h
 *
 *  Created on: 8/05/2021
 *      Author: alexrayne <alexraynepe196@gmail.com>
 * -----------------------------------------------------------------------------------------------------------
 *      DSK324SR i2c RTC driver
 */

#ifndef RTC_DSK324SR_DSK324SR_H_
#define RTC_DSK324SR_DSK324SR_H_

#include <stdint.h>
#include <stdbool.h>


enum DSK324Addr{
    // I2C bus 7bit adress
    DSK324ADDR  = 0x32,
};

enum DSK324RegAddr{
    DSK324REG_SEC   = 0,
    DSK324REG_MIMUTE= 1,
    DSK324REG_HOUR  = 2,
    DSK324REG_WEEK  = 3,
    DSK324REG_DAY   = 4,
    DSK324REG_MONTH = 5,
    DSK324REG_YEAR  = 6,

    DSK324REG_DATETIME_TOTAL = 7,

    DSK324REG_ALARM_MIMUTE= 7,
    DSK324REG_ALARM_HOUR  = 8,
    DSK324REG_ALARM_WEEK  = 9,  DSK324REG_ALARM_DAY   = DSK324REG_ALARM_WEEK,

    DSK324REG_TIMER = 10,

    DSK324REG_SELECT= 11,
    DSK324REG_FLAGS = 12,
    DSK324REG_CTRL  = 13,
    DSK324REG_TOTAL,

    //< when alarm not used, ones registers can use as generic ram
    DSK324REG_RAM1  = 7,
    DSK324REG_RAM2  = 8,
    DSK324REG_RAM3  = 9,
    //< when timer not used, ones registers can use as generic ram
    DSK324REG_RAM4 = 10,
};

enum DSK324AlarmFlag{
    //< When the AE bit (Alarm Enable) of bit 7 in a register is set to “1”, the alarm is set to be triggered
    //  after every increment (every minute, every hour, or every day) of the corresponding register.
    DSK324ALARM_EVERY   = 0x80,

    //< DSK324REG_ALARM_HOUR/DAY have this bit for app general purpose
    DSK324ALARM_RAM     = 0x40,


    //< DSK324REG_ALARM_WEEK bits
    DSK324ALARM_SUN =   1,
    DSK324ALARM_MON =   2,
    DSK324ALARM_TUE =   4,
    DSK324ALARM_WED =   8,
    DSK324ALARM_THU =   0x10,
    DSK324ALARM_FRI =   0x20,
    DSK324ALARM_SAT =   0x40,
};

enum DSK324SelectFlag{
    //< (Update Time Select) The UTS bit selects the timing for generating time update interrupts.
    DSK324SEL_UTS       = 1,    //< (Update Time Select)
    DSK324SEL_UTS_SEC   = 0,    //< Seconds digits update
    DSK324SEL_UTS_MIN   = 1,    //< Minutes digits update

    //< (Alarm Select) The AS bit selects day of week alarm or day alarm
    DSK324SEL_AS        = 2,
    DSK324SEL_AS_WEEK   = 0,                //< Day of week alarm
    DSK324SEL_AS_DAY    = DSK324SEL_AS,     //< Day alarm

    //< (Timer Source Clock Select) The TSS bits select the fixed-cycle timer source clock
    DSK324SEL_TSS       = 0xC,
    DSK324SEL_TSS_4096HZ= 0,
    DSK324SEL_TSS_64HZ  = 0x4,
    DSK324SEL_TSS_1HZ   = 0x8,
    DSK324SEL_TSS_60SEC = 0xC,
    DSK324SEL_TSS_MINUTE= DSK324SEL_TSS_60SEC,

    //<  (Output Frequency Select) The CFS bits select the output frequency of Output terminal
    DSK324SEL_CFS       = 0x30,

    //<  (Temperature Compensation Select) The TCS bits select the operating interval of temperature compensation.
    //      Temperature compensation operates in sync with the clock register timing.
    DSK324SEL_TCS       = 0xC0,
};

enum DSK324SelectCFS {
    DSK324SEL_CFS_32K   = 0,
    DSK324SEL_CFS_1024  = 0x10,
    DSK324SEL_CFS_32HZ  = 0x20,
    DSK324SEL_CFS_1HZ   = 0x30,
};
typedef enum DSK324SelectCFS DSK324SelectCFS;

enum DSK324SelectTCS {
    DSK324SEL_TCS_2HZ   = 0,
    DSK324SEL_TCS_2SEC  = 0x40,
    DSK324SEL_TCS_10SEC = 0x80,
    DSK324SEL_TCS_30SEC = 0xC0,
};
typedef enum DSK324SelectTCS DSK324SelectTCS;

enum DSK324Flag{
    DSK324FLAG_UTF       = 1,    //< Time update completion detected
    DSK324FLAG_AF        = 2,    //< Alarm time detected
    DSK324FLAG_TF        = 4,    //< Fixed-cycle timer down-counter zero detected

    DSK324FLAG_IRQ       = DSK324FLAG_UTF | DSK324FLAG_AF | DSK324FLAG_TF,

    /* The VDLF bit is the flag with the undervoltage detection or the power-ON reset signal detection .
        Voltage detection is performed intermittently in sync with the temperature compensation
        operating interval timing.
    */
    // Supply voltage is VDET2 (+1.5V max.) or lower, or power-ON reset signal detected
    DSK324FLAG_VDLF      = 0x10,    //< Voltage Detect Low Flag

    /* The VDHF bit is the flag with the temperature compensation operating voltage detection.
        Voltage detection is performed intermittently in sync with the temperature compensation
        operating interval timing.
     * */
    // Supply voltage is VDET1 (+2.0V max.) or lower
    DSK324FLAG_VDHF      = 0x20,    //< Voltage Detect High Flag

    DSK324FLAG_DUMMY0    = 0xc4,    // < dummy bits, always read as 0
};

enum DSK324CtrlFlag{
    //< TIE, AIE, UTIE bits (Timer, Alarm, Update Time Interrupt Enable)
    DSK324CTRL_UTIE     = 1,
    DSK324CTRL_AIE      = 2,
    DSK324CTRL_TIE      = 4,

    //< he TE bit enables the fixed-cycle timer down-counter
    DSK324CTRL_TE       = 8,
    DSK324CTRL_TSTOP    = 0,
    DSK324CTRL_TSART    = DSK324CTRL_TE,

    //< The FIE bit is the enable bit for 1Hz signal output of 50% duty on INTN.
    DSK324CTRL_FIE      = 0x10,
    DSK324CTRL_INT_1HZ  = DSK324CTRL_FIE,

    //< Can be used as a general-purpose RAM bit.
    DSK324CTRL_RAM      = 0x20,

    //< not use it, leave it 0 for normal operation.
    DSK324CTRL_TEST     = 0x40,

    //< 64 to 1Hz frequency divider counter reset. Clock function stops.
    DSK324CTRL_RES      = 0x80,

    DSK324CTRL_ALLIE    = DSK324CTRL_UTIE | DSK324CTRL_AIE | DSK324CTRL_TIE | DSK324CTRL_FIE ,
};



typedef uint8_t dsk324_bcd_t;

union DSK324Ctx{
    uint8_t     reg[DSK324REG_TOTAL];
    struct {
        dsk324_bcd_t    sec;
        dsk324_bcd_t    min;
        dsk324_bcd_t    hour;
        uint8_t         week;
        dsk324_bcd_t    day;
        dsk324_bcd_t    month;
        dsk324_bcd_t    year;

        struct {
            dsk324_bcd_t    min;
            dsk324_bcd_t    hour;
            union {
                uint8_t         week;
                dsk324_bcd_t    day;
            };
        }   alarm;

        uint8_t         tcnt;
        uint8_t         sel;
        uint8_t         flag;
        uint8_t         ctrl;
    };
};
typedef union DSK324Ctx DSK324Ctx;

enum {
        RTC_DSK324_YEAR0    = 2000,
        RTC_DSK324_MDAY0    = 1,
        RTC_DSK324_MON0     = 1,
};


//====================================================================================================
//                  DSK324 RTC API
//provide:i2cHandle
#include <i2c_masterx.h>
/* Use of time structure, tm */
#include <time.h>
#include <sys/pt.h>



/// !!! время становится Valid после установки времени, и маркируется сбросом flag:DSK324FLAG_VDHF
///     это может кофликтовать с провалом напряжения ниже 2.0В

extern DSK324Ctx   rtc_dsk324_ctx;

/// @brief actual time converted from rtc_dsk324_ctx
/// @note tm:isdst saves in DSK324CTRL_RAM
///       timezone saves in DSK324REG_RAM4
extern struct tm   rtc_dsk324_now;



enum DSK324CtxState{
    DSK324CTX_FAIL      = -1,
    DSK324CTX_STARTUP   = 0,
    DSK324CTX_RECONFIG  ,        //< rct reconfigure process
    DSK324CTX_CONFIGURE,
    DSK324CTX_INVALID,
    DSK324CTX_UPDATE,               //< ctx in update process
    DSK324CTX_UPDATED,              //< ctx readen, wait processing
    DSK324CTX_VALID,                //< ctx updated
};

/// @brief поазывает rtc_dsk324_ctx считан, а не в процессе обновления.
static inline
bool    rtc_dsk324_ctx_valid() {
    extern enum DSK324CtxState rtc_dsk324_valid;
    return (rtc_dsk324_valid > DSK324CTX_UPDATED);
}



enum DSK324TimeState {
    DSK324_TIME_UNCKNOWN,
    DSK324_TIME_COLD,                 //< ctx updated, but after cold powerup
    DSK324_TIME_VALID,                //< ctx updated
    DSK324_TIME_SETUP,                //< ctx have new time, writing to rtc

};

/// @brief поазывает что время часов актуальное, тоесть было установлено и не сбрасывалось питанием
///         время становится Valid после установки времени, и маркируется сбросом flag:DSK324FLAG_VDHF
///         @sa rtc_dsk324_CalendarTimeSet
static inline
bool    rtc_dsk324_time_valid() {
    extern enum DSK324TimeState rtc_dsk324_time_status;
    return (rtc_dsk324_time_status >= DSK324_TIME_VALID);
}

/// @brief поазывает что время часов ждет прописывания в RTC.
///         время становится Valid после установки времени, и маркируется сбросом flag:DSK324FLAG_VDHF
///         @sa rtc_dsk324_CalendarTimeSet
static inline
bool    rtc_dsk324_time_setup() {
    extern enum DSK324TimeState rtc_dsk324_time_status;
    return (rtc_dsk324_time_status >= DSK324_TIME_SETUP);
}

/// @brief показывает с часами произошел обмен.
///         установится после считвания времени из РТС. сбросится после неудачного чтения
///         @sa rtc_dsk324_CalendarTimeSet
static inline
bool    rtc_dsk324_online() {
    extern enum DSK324TimeState rtc_dsk324_time_status;
    return (rtc_dsk324_time_status > DSK324_TIME_UNCKNOWN);
}


/// @brief cleaup RTC context registers
void        rtc_dsk324_clear(void);

/// @brief assign context regs cfg : 1SEc IRQ, 32kHz output, fast TCS
void        rtc_dsk324_cfg_online_1sec_32k(void);

/// @brief assign context regs cfg : no IRQ and outputs, sloowest TCS
void        rtc_dsk324_cfg_offline(void);




enum RTCStatusID{
    RTC_OK        = FSP_SUCCESS ,
    RTC_BUSY      = 700 ,   //I2C_BUSY
};
typedef fsp_err_t   RTCStatus_t;


#ifndef DSK324_API_SYNC
#define DSK324_API_SYNC 1
#endif

void        rtc_dsk324_init(i2cHandle port);

#if DSK324_API_SYNC

/** sync styled API - all operations are blocking
 *
 */

enum DSK324InitMode{
    // only update regs from RTC
    DSK324_INIT_SHORT,

    // clean regs in RTC, if COLD start from power-off
    DSK324_INIT_CLEAN,
};
typedef enum DSK324InitMode DSK324InitMode;

/// @brief  check rtc power status and:
///         if it power-up - reset RTC with current context
///         if it`s ctrl:DSK324CTRL_ALLIE field not mutch provided in context - reconfigures
///         if all ok - refresh context form RTC
/// @note  use set or reconfigure - if need override current RTC contents besides it state
/// @args  rtc_dsk324_ctx - provides requerd configuration - for reconfigure, and regs contens - for cold reset
/// @param mode
RTCStatus_t rtc_dsk324_startup(DSK324InitMode mode);

/// @brief  read rtc content
RTCStatus_t rtc_dsk324_refresh(void);

/// @brief reconfigure rtc_dsk324 from ctx - set registers SEL, CTRL, clear flags.
/// @note leading to refresh.
RTCStatus_t rtc_dsk324_reconfigure(void);

/// @brief  write rtc content
RTCStatus_t rtc_dsk324_set( DSK324Ctx*  regs );


#else

/** Async styled API - all operations are non-blocking, and polls by rtc_dsk324_wait() for completion.
 */

/// @brief waiting until RTC operation completes
RTCStatus_t rtc_dsk324_wait(void);

/// @brief  starts read rtc content
RTCStatus_t rtc_dsk324_refresh(void);

/// @brief reconfigure rtc_dsk324 from ctx - set registers SEL, CTRL, clear flags.
/// @note leading to refresh.
void        rtc_dsk324_reconfigure(void);


/// @brief обработка операций dsk324. должно поллится регулярно в цикле main
/// @result результат работы некоторых процессов отражается вызовом rtc_dsk324_on_event
PT_THREAD( rtc_dsk324_process(void) );


enum DSK324EventID{
    DSK324EV_AFTER_REFRESH,
    DSK324EV_AFTER_STORE,

    //< invoke before send initiation config registers 0x0b-0x0d
    //  app could setup control mode here for write to RTC
    DSK324EV_PREPARE_RESET,

    DSK324EV_AFTER_INIT,    //< invoke after sent initiation config registers 0x0b-0x0d
    DSK324EV_ERROR_INIT,    //< invoke on fail after sent initiation config registers 0x0b-0x0d

    //< invoke on cold powerup. need init rtc context for setup.
    //  app should setup startup contents here for write to RTC
    DSK324EV_POWERUP ,

    //< DSK324 требует работы, может прилетать из прерывания i2c
    DSK324EV_BUSY ,
};
typedef enum DSK324EventID DSK324EventID;

/// @brief обработчик приложения событий от dsk324. Вызывается из rtc_dsk324_process
///         пользовательский код может сделать свою реализацию
void rtc_dsk324_on_event(i2cHandle port, DSK324EventID ev);

/// @brief обработчик приложения событий от прерывания i2c на dsk324.
///         пользовательский код может сделать свою реализацию.
///         Необходимо чтобы на события i2c вызывался rtc_dsk324_process
void dsk324sr_i2c_callback(i2cHandle h, i2c_master_event_t ev, void* ctx);

#endif

/// @brief invalidates rtc_dsk324_ctx, leading to refresh ctx content.
///         it may informed on RTC 1sec int
void        rtc_dsk324_invalidate(void);


/// @note  время становится Valid после установки времени
///         @sa rtc_dsk324_time_valid
RTCStatus_t rtc_dsk324_CalendarTimeSet(const struct tm* p_time);



#endif /* RTC_DSK324SR_DSK324SR_H_ */
