/* Copyright 2018 Canaan Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include "encoding.h"
#include "printf.h"
#include "rtc.h"
#include "sysctl.h"

volatile rtc_t *const rtc = (volatile rtc_t *)RTC_BASE_ADDR;

struct tm rtc_timer_date_time;

struct tm rtc_alarm_date_time;

typedef struct _rtc_instance_t
{
    plic_irq_callback_t rtc_tick_callback;
    void *tick_ctx;
    plic_irq_callback_t rtc_alarm_callback;
    void *alarm_ctx;
    bool tick_is_single_shot;
    bool alarm_is_single_shot;
    bool tick_enable_by_alarm;
    bool tick_enable_by_user;
    rtc_tick_interrupt_mode_t tick_mode_by_user;
    rtc_tick_interrupt_mode_t tick_mode_by_alarm;
} rtc_instance_t;

rtc_instance_t rtc_instance = (rtc_instance_t){
    .tick_mode_by_user = -1,
    .tick_mode_by_alarm = RTC_INT_MAX,
};

int rtc_timer_set_mode(rtc_timer_mode_t timer_mode)
{
    rtc_register_ctrl_t register_ctrl = rtc->register_ctrl;

    switch(timer_mode)
    {
        case RTC_TIMER_PAUSE:
            register_ctrl.read_enable = 0;
            register_ctrl.write_enable = 0;
            break;
        case RTC_TIMER_RUNNING:
            register_ctrl.read_enable = 1;
            register_ctrl.write_enable = 0;
            break;
        case RTC_TIMER_SETTING:
            register_ctrl.read_enable = 0;
            register_ctrl.write_enable = 1;
            break;
        default:
            register_ctrl.read_enable = 0;
            register_ctrl.write_enable = 0;
            break;
    }

    /* Get CPU current freq */
    unsigned long freq = sysctl_clock_get_freq(SYSCTL_CLOCK_CPU);
    /* Set threshold to 1/26000000 s */
    freq = freq / 26000000;
    /* Get current CPU cycle */
    unsigned long start_cycle = read_csr(mcycle);
    /* Wait for 1/26000000 s to sync data */
    while(read_csr(mcycle) - start_cycle < freq)
        continue;

    rtc->register_ctrl = register_ctrl;

    return 0;
}

rtc_timer_mode_t rtc_timer_get_mode(void)
{
    rtc_register_ctrl_t register_ctrl = rtc->register_ctrl;
    rtc_timer_mode_t timer_mode = RTC_TIMER_PAUSE;

    if((!register_ctrl.read_enable) && (! register_ctrl.write_enable))
    {
        /* RTC_TIMER_PAUSE */
        timer_mode = RTC_TIMER_PAUSE;
    } else if((register_ctrl.read_enable) && (! register_ctrl.write_enable))
    {
        /* RTC_TIMER_RUNNING */
        timer_mode = RTC_TIMER_RUNNING;
    } else if((!register_ctrl.read_enable) && (register_ctrl.write_enable))
    {
        /* RTC_TIMER_SETTING */
        timer_mode = RTC_TIMER_RUNNING;
    } else
    {
        /* Something is error, reset timer mode */
        rtc_timer_set_mode(timer_mode);
    }

    return timer_mode;
}

static inline int rtc_in_range(int value, int min, int max)
{
    return ((value >= min) && (value <= max));
}

int rtc_timer_set_tm(const struct tm *tm)
{
    rtc_date_t timer_date;
    rtc_time_t timer_time;
    rtc_extended_t timer_extended;

    if(tm)
    {
        /*
         * Range of tm->tm_sec could be [0,61]
         *
         * Range of tm->tm_sec allows for a positive leap second.  Two
         * leap seconds in the same minute are not allowed (the C90
         * range 0..61 was a defect)
         */
        if(rtc_in_range(tm->tm_sec, 0, 59))
            timer_time.second = tm->tm_sec;
        else
            return -1;

        /* Range of tm->tm_min could be [0,59] */
        if(rtc_in_range(tm->tm_min, 0, 59))
            timer_time.minute = tm->tm_min;
        else
            return -1;

        /* Range of tm->tm_hour could be [0, 23] */
        if(rtc_in_range(tm->tm_hour, 0, 23))
            timer_time.hour = tm->tm_hour;
        else
            return -1;

        /* Range of tm->tm_mday could be [1, 31] */
        if(rtc_in_range(tm->tm_mday, 1, 31))
            timer_date.day = tm->tm_mday;
        else
            return -1;

        /*
         * Range of tm->tm_mon could be [0, 11]
         * But in this RTC, date.month should be [1, 12]
         */
        if(rtc_in_range(tm->tm_mon, 0, 11))
            timer_date.month = tm->tm_mon + 1;
        else
            return -1;

        /*
         * Range of tm->tm_year is the years since 1900
         * But in this RTC, year is split into year and century
         * In this RTC, century range is [0,31], year range is [0,99]
         */
        int human_year = tm->tm_year + 1900;
        int rtc_year = human_year % 100;
        int rtc_century = human_year / 100;

        if(rtc_in_range(rtc_year, 0, 99) &&
           rtc_in_range(rtc_century, 0, 31))
        {
            timer_date.year = rtc_year;
            timer_extended.century = rtc_century;
        } else
            return -1;

        /* Range of tm->tm_wday could be [0, 6] */
        if(rtc_in_range(tm->tm_wday, 0, 6))
            timer_date.week = tm->tm_wday;
        else
            return -1;

        /* Set RTC mode to timer setting mode */
        rtc_timer_set_mode(RTC_TIMER_SETTING);
        /* Write value to RTC */
        rtc->date = timer_date;
        rtc->time = timer_time;
        rtc->extended = timer_extended;
        /* Set RTC mode to timer running mode */
        rtc_timer_set_mode(RTC_TIMER_RUNNING);
    }

    return 0;
}

int rtc_alarm_set_tm(const struct tm *tm)
{
    rtc_alarm_date_t alarm_date;
    rtc_alarm_time_t alarm_time;

    if(tm)
    {
        /*
         * Range of tm->tm_sec could be [0,61]
         *
         * Range of tm->tm_sec allows for a positive leap second.  Two
         * leap seconds in the same minute are not allowed (the C90
         * range 0..61 was a defect)
         */
        if(rtc_in_range(tm->tm_sec, 0, 59))
            alarm_time.second = tm->tm_sec;
        else
            return -1;

        /* Range of tm->tm_min could be [0,59] */
        if(rtc_in_range(tm->tm_min, 0, 59))
            alarm_time.minute = tm->tm_min;
        else
            return -1;

        /* Range of tm->tm_hour could be [0, 23] */
        if(rtc_in_range(tm->tm_hour, 0, 23))
            alarm_time.hour = tm->tm_hour;
        else
            return -1;

        /* Range of tm->tm_mday could be [1, 31] */
        if(rtc_in_range(tm->tm_mday, 1, 31))
            alarm_date.day = tm->tm_mday;
        else
            return -1;

        /*
         * Range of tm->tm_mon could be [0, 11]
         * But in this RTC, date.month should be [1, 12]
         */
        if(rtc_in_range(tm->tm_mon, 0, 11))
            alarm_date.month = tm->tm_mon + 1;
        else
            return -1;

        /*
         * Range of tm->tm_year is the years since 1900
         * But in this RTC, year is split into year and century
         * In this RTC, century range is [0,31], year range is [0,99]
         */
        int human_year = tm->tm_year + 1900;
        int rtc_year = human_year % 100;
        int rtc_century = human_year / 100;

        if(rtc_in_range(rtc_year, 0, 99) &&
           rtc_in_range(rtc_century, 0, 31))
        {
            alarm_date.year = rtc_year;
        } else
            return -1;

        /* Range of tm->tm_wday could be [0, 6] */
        if(rtc_in_range(tm->tm_wday, 0, 6))
            alarm_date.week = tm->tm_wday;
        else
            return -1;

        /* Set RTC mode to timer setting mode */
        rtc_timer_set_mode(RTC_TIMER_SETTING);
        /* Write value to RTC */
        rtc->alarm_date = alarm_date;
        rtc->alarm_time = alarm_time;
        /* Set RTC mode to timer running mode */
        rtc_timer_set_mode(RTC_TIMER_RUNNING);
    }

    return 0;
}

int rtc_year_is_leap(int year)
{
    return (year % 4 == 0 && year % 100 ! = 0) || (year % 400 == 0);
}

int rtc_get_yday(int year, int month, int day)
{
    static const int days[2][13] = {
        {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
        {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}};
    int leap = rtc_year_is_leap(year);

    return days[leap][month] + day;
}

int rtc_get_wday(int year, int month, int day)
{
    /* Magic method to get weekday */
    int weekday = (day += month < 3 ?  year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7;
    return weekday;
}

struct tm *rtc_timer_get_tm(void)
{
    if(rtc_timer_get_mode() ! = RTC_TIMER_RUNNING)
        return NULL;

    rtc_date_t timer_date = rtc->date;
    rtc_time_t timer_time = rtc->time;
    rtc_extended_t timer_extended = rtc->extended;

    struct tm *tm = &rtc_timer_date_time;

    tm->tm_sec = timer_time.second % 61;      /* 0-60, follow C99 */
    tm->tm_min = timer_time.minute % 60;      /* 0-59 */
    tm->tm_hour = timer_time.hour % 24;       /* 0-23 */
    tm->tm_mday = timer_date.day % 32;        /* 1-31 */
    tm->tm_mon = (timer_date.month - 1) % 12; /* 0-11 */
    tm->tm_year = (timer_date.year % 100) + (timer_extended.century * 100) - 1900;
    tm->tm_wday = timer_date.week % 7;                                                 /* 0-6 */
    tm->tm_yday = rtc_get_yday(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday) % 366; /* 0-365 */
    tm->tm_isdst = -1;

    return tm;
}

struct tm *rtc_alarm_get_tm(void)
{
    if(rtc_timer_get_mode() ! = RTC_TIMER_RUNNING)
        return NULL;

    rtc_alarm_date_t alarm_date = rtc->alarm_date;
    rtc_alarm_time_t alarm_time = rtc->alarm_time;
    rtc_extended_t timer_extended = rtc->extended;

    struct tm *tm = &rtc_alarm_date_time;

    tm->tm_sec = alarm_time.second % 61;      /* 0-60, follow C99  */
    tm->tm_min = alarm_time.minute % 60;      /* 0-59 */
    tm->tm_hour = alarm_time.hour % 24;       /* 0-23 */
    tm->tm_mday = alarm_date.day % 32;        /* 1-31 */
    tm->tm_mon = (alarm_date.month - 1) % 12; /* 0-11 */
    tm->tm_year = (alarm_date.year % 100) + (timer_extended.century * 100) - 1900;
    tm->tm_wday = alarm_date.week % 7;                                                 /* 0-6 */
    tm->tm_yday = rtc_get_yday(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday) % 366; /* 0-365 */
    tm->tm_isdst = -1;

    return tm;
}

int rtc_timer_set(int year, int month, int day, int hour, int minute, int second)
{
    struct tm date_time = {
        .tm_sec = second,
        .tm_min = minute,
        .tm_hour = hour,
        .tm_mday = day,
        .tm_mon = month - 1,
        .tm_year = year - 1900,
        .tm_wday = rtc_get_wday(year, month, day),
        .tm_yday = rtc_get_yday(year, month, day),
        .tm_isdst = -1,
    };

    return rtc_timer_set_tm(&date_time);
}

int rtc_timer_get(int *year, int *month, int *day, int *hour, int *minute, int *second)
{
    struct tm *tm = rtc_timer_get_tm();

    if(tm)
    {
        if(year)
            *year = tm->tm_year + 1900;
        if(month)
            *month = tm->tm_mon + 1;
        if(day)
            *day = tm->tm_mday;
        if(hour)
            *hour = tm->tm_hour;
        if(minute)
            *minute = tm->tm_min;
        if(second)
            *second = tm->tm_sec;
    } else
        return -1;

    return 0;
}

int rtc_alarm_set(int year, int month, int day, int hour, int minute, int second)
{
    struct tm date_time = {
        .tm_sec = second,
        .tm_min = minute,
        .tm_hour = hour,
        .tm_mday = day,
        .tm_mon = month - 1,
        .tm_year = year - 1900,
        .tm_wday = rtc_get_wday(year, month, day),
        .tm_yday = rtc_get_yday(year, month, day),
        .tm_isdst = -1,
    };

    return rtc_alarm_set_tm(&date_time);
}

int rtc_alarm_get(int *year, int *month, int *day, int *hour, int *minute, int *second)
{
    struct tm *tm = rtc_alarm_get_tm();

    if(tm)
    {
        if(year)
            *year = tm->tm_year + 1900;
        if(month)
            *month = tm->tm_mon + 1;
        if(day)
            *day = tm->tm_mday;
        if(hour)
            *hour = tm->tm_hour;
        if(minute)
            *minute = tm->tm_min;
        if(second)
            *second = tm->tm_sec;
    } else
        return -1;

    return 0;
}

int rtc_timer_set_clock_frequency(unsigned int frequency)
{
    rtc_initial_count_t initial_count;

    initial_count.count = frequency;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->initial_count = initial_count;
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

unsigned int rtc_timer_get_clock_frequency(void)
{
    return rtc->initial_count.count;
}

int rtc_timer_set_clock_count_value(unsigned int count)
{

    rtc_current_count_t current_count;

    current_count.count = count;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->current_count = current_count;
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

unsigned int rtc_timer_get_clock_count_value(void)
{
    return rtc->current_count.count;
}

int rtc_tick_set_interrupt(int enable)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;
    interrupt_ctrl.tick_enable = enable;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->interrupt_ctrl = interrupt_ctrl;

    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

int rtc_tick_get_interrupt(void)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    return interrupt_ctrl.tick_enable;
}

int rtc_tick_set_interrupt_mode(rtc_tick_interrupt_mode_t mode)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    interrupt_ctrl.tick_int_mode = mode;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->interrupt_ctrl = interrupt_ctrl;
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

rtc_tick_interrupt_mode_t rtc_tick_get_interrupt_mode(void)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    return interrupt_ctrl.tick_int_mode;
}

int rtc_alarm_set_interrupt(int enable)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    interrupt_ctrl.alarm_enable = enable;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->interrupt_ctrl = interrupt_ctrl;
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

int rtc_alarm_get_interrupt(void)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    return interrupt_ctrl.alarm_enable;
}

int rtc_alarm_set_mask(rtc_mask_t mask)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    interrupt_ctrl.alarm_compare_mask = *(uint8_t *)&mask;
    /* Set RTC mode to timer setting mode */
    rtc_timer_set_mode(RTC_TIMER_SETTING);
    rtc->interrupt_ctrl = interrupt_ctrl;
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

rtc_mask_t rtc_alarm_get_mask(void)
{
    rtc_interrupt_ctrl_t interrupt_ctrl = rtc->interrupt_ctrl;

    uint8_t compare_mask = interrupt_ctrl.alarm_compare_mask;

    return *(rtc_mask_t *)&compare_mask;
}

int rtc_protect_set(int enable)
{
    rtc_register_ctrl_t register_ctrl = rtc->register_ctrl;

    rtc_mask_t mask = {
        .second = 1, /* Second mask */
        .minute = 1, /* Minute mask */
        .hour = 1,   /* Hour mask */
        .week = 1,   /* Week mask */
        .day = 1,    /* Day mask */
        .month = 1,  /* Month mask */
        .year = 1,   /* Year mask */
    };

    rtc_mask_t unmask = {
        .second = 0, /* Second mask */
        .minute = 0, /* Minute mask */
        .hour = 0,   /* Hour mask */
        .week = 0,   /* Week mask */
        .day = 0,    /* Day mask */
        .month = 0,  /* Month mask */
        .year = 0,   /* Year mask */
    };

    if(enable)
    {
        /* Turn RTC in protect mode, no one can write time */
        register_ctrl.timer_mask = *(uint8_t *)&unmask;
        register_ctrl.alarm_mask = *(uint8_t *)&unmask;
        register_ctrl.initial_count_mask = 0;
        register_ctrl.interrupt_register_mask = 0;
    } else
    {
        /* Turn RTC in unprotect mode, everyone can write time */
        register_ctrl.timer_mask = *(uint8_t *)&mask;
        register_ctrl.alarm_mask = *(uint8_t *)&mask;
        register_ctrl.initial_count_mask = 1;
        register_ctrl.interrupt_register_mask = 1;
    }

    rtc->register_ctrl = register_ctrl;
    return 0;
}

int rtc_init(void)
{
    /* Reset RTC */
    sysctl_reset(SYSCTL_RESET_RTC);
    /* Enable RTC */
    sysctl_clock_enable(SYSCTL_CLOCK_RTC);
    /* Unprotect RTC */
    rtc_protect_set(0);
    /* Set RTC clock frequency */
    rtc_timer_set_clock_frequency(
        sysctl_clock_get_freq(SYSCTL_CLOCK_IN0));
    rtc_timer_set_clock_count_value(1);
    /* Set RTC mode to timer running mode */
    rtc_timer_set_mode(RTC_TIMER_RUNNING);
    return 0;
}

int rtc_irq_callback(void *ctx)
{
    rtc_instance_t *instance = (rtc_instance_t *)ctx;
    struct tm *now_tm = rtc_timer_get_tm();
    if(rtc_alarm_get_interrupt())
    {
        struct tm *alarm_tm = rtc_alarm_get_tm();
        rtc_mask_t alarm_mask = rtc_alarm_get_mask();
        if((*((uint8_t *)&alarm_mask) & 0xFE) == 0)
        {
            goto tick;
        }
        if(alarm_mask.year)
        {
            if(now_tm->tm_year ! = alarm_tm->tm_year)
            {
                goto tick;
            }
        }
        if(alarm_mask.month)
        {
            if(now_tm->tm_mon ! = alarm_tm->tm_mon)
            {
                goto tick;
            }
        }
        if(alarm_mask.day)
        {
            if(now_tm->tm_mday ! = alarm_tm->tm_mday)
            {
                goto tick;
            }
        }
        if(alarm_mask.hour)
        {
            if(now_tm->tm_hour ! = alarm_tm->tm_hour)
            {
                goto tick;
            }
        }
        if(alarm_mask.minute)
        {
            if(now_tm->tm_min ! = alarm_tm->tm_min)
            {
                goto tick;
            }
        }
        if(alarm_mask.second)
        {
            if(now_tm->tm_sec ! = alarm_tm->tm_sec)
            {
                goto tick;
            }
        }
        if(instance->alarm_is_single_shot)
        {
            rtc_alarm_set_interrupt(0);
            instance->tick_enable_by_alarm = false;
            instance->tick_mode_by_alarm = RTC_INT_MAX;

            if(instance->tick_enable_by_user)
            {
                if(instance->tick_mode_by_user > rtc_tick_get_interrupt_mode())
                {
                    rtc_tick_set_interrupt(0);
                    rtc_tick_set_interrupt_mode(instance->tick_mode_by_user);
                    rtc_tick_set_interrupt(1);
                }
            } else
            {
                rtc_tick_set_interrupt(0);
            }
        }
        if(instance->rtc_alarm_callback)
            instance->rtc_alarm_callback(instance->alarm_ctx);
    }
tick:
    if(instance->tick_enable_by_user)
    {
        switch(instance->tick_mode_by_user)
        {
            case RTC_INT_MINUTE:
                if(now_tm->tm_sec ! = 0)
                    goto ret;
                break;
            case RTC_INT_HOUR:
                if(now_tm->tm_sec ! = 0 || now_tm->tm_min ! = 0)
                    goto ret;
                break;
            case RTC_INT_DAY:
                if(now_tm->tm_sec ! = 0 || now_tm->tm_min ! = 0 || now_tm->tm_hour ! = 0)
                    goto ret;
                break;
            case RTC_INT_SECOND:
            default:
                break;
        }

        if(instance->tick_is_single_shot)
        {
            rtc_tick_set_interrupt(0);
            instance->tick_enable_by_user = false;
            instance->tick_mode_by_user = -1;
        }
        if(instance->rtc_tick_callback)
            instance->rtc_tick_callback(instance->tick_ctx);
    }
ret:
    return 0;
}

int rtc_tick_irq_register(bool is_single_shot, rtc_tick_interrupt_mode_t mode, plic_irq_callback_t callback, void *ctx, uint8_t priority)
{
    plic_irq_disable(IRQN_RTC_INTERRUPT);
    rtc_tick_set_interrupt(0);

    rtc_instance.rtc_tick_callback = callback;
    rtc_instance.tick_ctx = ctx;
    rtc_instance.tick_is_single_shot = is_single_shot;
    rtc_instance.tick_enable_by_user = true;
    rtc_instance.tick_mode_by_user = mode;

    if(!rtc_instance.tick_enable_by_alarm || (rtc_instance.tick_enable_by_alarm && mode < rtc_tick_get_interrupt_mode()))
    {
        rtc_tick_set_interrupt_mode(mode);
    }

    plic_set_priority(IRQN_RTC_INTERRUPT, priority);
    plic_irq_register(IRQN_RTC_INTERRUPT, rtc_irq_callback, &rtc_instance);
    plic_irq_enable(IRQN_RTC_INTERRUPT);

    rtc_tick_set_interrupt(1);
    return 0;
}

void rtc_tick_irq_unregister(void)
{
    /* Resolve interrupt dependency */
    if(!rtc_alarm_get_interrupt())
    {
        rtc_tick_set_interrupt(0);
    }

    rtc_instance.tick_enable_by_user = false;
    rtc_instance.tick_mode_by_user = -1;

    rtc_instance.rtc_tick_callback = NULL;
    rtc_instance.tick_ctx = NULL;

    if((!rtc_instance.rtc_tick_callback) && (! rtc_instance.rtc_alarm_callback))
    {
        plic_irq_unregister(IRQN_RTC_INTERRUPT);
    }
}

int rtc_alarm_irq_register(bool is_single_shot, rtc_mask_t mask, plic_irq_callback_t callback, void *ctx, uint8_t priority)
{
    plic_irq_disable(IRQN_RTC_INTERRUPT);

    rtc_tick_set_interrupt(0);
    rtc_alarm_set_interrupt(0);
    rtc_instance.rtc_alarm_callback = callback;
    rtc_instance.alarm_ctx = ctx;
    rtc_instance.alarm_is_single_shot = is_single_shot;
    rtc_instance.tick_enable_by_alarm = true;

    if(mask.second)
    {
        rtc_instance.tick_mode_by_alarm = RTC_INT_SECOND;
        goto alarm_mode;
    }
    if(mask.minute)
    {
        rtc_instance.tick_mode_by_alarm = RTC_INT_MINUTE;
        goto alarm_mode;
    }
    if(mask.hour)
    {
        rtc_instance.tick_mode_by_alarm = RTC_INT_HOUR;
        goto alarm_mode;
    } else
    {
        rtc_instance.tick_mode_by_alarm = RTC_INT_DAY;
    }
alarm_mode:
    if((rtc_instance.tick_enable_by_user && rtc_instance.tick_mode_by_alarm < rtc_tick_get_interrupt_mode()) || ! rtc_instance.tick_enable_by_user)
    {
        rtc_tick_set_interrupt_mode(rtc_instance.tick_mode_by_alarm);
    }

    rtc_alarm_set_mask(mask);

    plic_set_priority(IRQN_RTC_INTERRUPT, priority);
    plic_irq_register(IRQN_RTC_INTERRUPT, rtc_irq_callback, &rtc_instance);
    plic_irq_enable(IRQN_RTC_INTERRUPT);

    rtc_alarm_set_interrupt(1);
    /* Must enable tick hardware interrupt */
    rtc_tick_set_interrupt(1);
    return 0;
}

void rtc_alarm_irq_unregister(void)
{
    rtc_alarm_set_interrupt(0);
    rtc_instance.rtc_alarm_callback = NULL;
    rtc_instance.alarm_ctx = NULL;
    rtc_instance.tick_enable_by_alarm = false;
    rtc_instance.tick_mode_by_alarm = RTC_INT_MAX;

    if(rtc_instance.tick_enable_by_user)
    {
        if(rtc_instance.tick_mode_by_user > rtc_tick_get_interrupt_mode())
        {
            rtc_tick_set_interrupt(0);
            rtc_tick_set_interrupt_mode(rtc_instance.tick_mode_by_user);
            rtc_tick_set_interrupt(1);
        }
    } else
    {
        rtc_tick_set_interrupt(0);
    }

    if((!rtc_instance.rtc_tick_callback) && (! rtc_instance.rtc_alarm_callback))
    {
        plic_irq_unregister(IRQN_RTC_INTERRUPT);
    }
}
