/** * @file rtc.c * @author Lunaixsky * @brief RTC & CMOS abstraction. Reference: MC146818A & Intel Series 500 PCH * datasheet * @version 0.1 * @date 2022-03-07 * * @copyright Copyright (c) 2022 * */ #include #include #include #include #include #define RTC_INDEX_PORT 0x70 #define RTC_TARGET_PORT 0x71 #define WITH_NMI_DISABLED 0x80 #define RTC_CURRENT_CENTRY 20 #define RTC_REG_YRS 0x9 #define RTC_REG_MTH 0x8 #define RTC_REG_DAY 0x7 #define RTC_REG_WDY 0x6 #define RTC_REG_HRS 0x4 #define RTC_REG_MIN 0x2 #define RTC_REG_SEC 0x0 #define RTC_REG_A 0xA #define RTC_REG_B 0xB #define RTC_REG_C 0xC #define RTC_REG_D 0xD #define RTC_BIN_ENCODED(reg) (reg & 0x04) #define RTC_24HRS_ENCODED(reg) (reg & 0x02) #define RTC_TIMER_BASE_FREQUENCY 1024 #define RTC_TIMER_ON 0x40 #define RTC_FREQUENCY_1024HZ 0b110 #define RTC_DIVIDER_33KHZ (0b010 << 4) struct mc146818 { struct hwrtc* rtc_context; u32_t rtc_iv; int ticking; void (*on_tick_cb)(const struct hwrtc*); }; #define rtc_state(data) ((struct mc146818*)(data)) static u8_t rtc_read_reg(u8_t reg_selector) { port_wrbyte(RTC_INDEX_PORT, reg_selector); return port_rdbyte(RTC_TARGET_PORT); } static void rtc_write_reg(u8_t reg_selector, u8_t val) { port_wrbyte(RTC_INDEX_PORT, reg_selector); port_wrbyte(RTC_TARGET_PORT, val); } static u8_t bcd2dec(u8_t bcd) { return ((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3) + (bcd & 0xf); } static void rtc_enable_timer() { u8_t regB = rtc_read_reg(RTC_REG_B | WITH_NMI_DISABLED); rtc_write_reg(RTC_REG_B | WITH_NMI_DISABLED, regB | RTC_TIMER_ON); } static void rtc_disable_timer() { u8_t regB = rtc_read_reg(RTC_REG_B | WITH_NMI_DISABLED); rtc_write_reg(RTC_REG_B | WITH_NMI_DISABLED, regB & ~RTC_TIMER_ON); } static void clock_walltime(struct hwrtc* rtc, datetime_t* datetime) { datetime_t current; do { while (rtc_read_reg(RTC_REG_A) & 0x80) ; memcpy(¤t, datetime, sizeof(datetime_t)); datetime->year = rtc_read_reg(RTC_REG_YRS); datetime->month = rtc_read_reg(RTC_REG_MTH); datetime->day = rtc_read_reg(RTC_REG_DAY); datetime->weekday = rtc_read_reg(RTC_REG_WDY); datetime->hour = rtc_read_reg(RTC_REG_HRS); datetime->minute = rtc_read_reg(RTC_REG_MIN); datetime->second = rtc_read_reg(RTC_REG_SEC); } while (!datatime_eq(datetime, ¤t)); u8_t regbv = rtc_read_reg(RTC_REG_B); // Convert from bcd to binary when needed if (!RTC_BIN_ENCODED(regbv)) { datetime->year = bcd2dec(datetime->year); datetime->month = bcd2dec(datetime->month); datetime->day = bcd2dec(datetime->day); datetime->hour = bcd2dec(datetime->hour); datetime->minute = bcd2dec(datetime->minute); datetime->second = bcd2dec(datetime->second); } // To 24 hour format if (!RTC_24HRS_ENCODED(regbv) && (datetime->hour >> 7)) { datetime->hour = 12 + (datetime->hour & 0x80); } datetime->year += RTC_CURRENT_CENTRY * 100; } static int mc146818_check_support(struct hwrtc* rtc) { #if __ARCH__ == i386 return 1; #else return 0; #endif } static void rtc_init(struct hwrtc* rtc) { u8_t regA = rtc_read_reg(RTC_REG_A | WITH_NMI_DISABLED); regA = (regA & ~0x7f) | RTC_FREQUENCY_1024HZ | RTC_DIVIDER_33KHZ; rtc_write_reg(RTC_REG_A | WITH_NMI_DISABLED, regA); rtc_state(rtc->data)->rtc_context = rtc; // Make sure the rtc timer is disabled by default rtc_disable_timer(); } static struct mc146818 rtc_state = { .ticking = 0 }; static void __rtc_tick(const isr_param* param) { rtc_state.on_tick_cb(rtc_state.rtc_context); (void)rtc_read_reg(RTC_REG_C); } static void rtc_do_ticking(struct hwrtc* rtc, void (*on_tick)()) { if (!on_tick || rtc_state(rtc->data)->ticking) { return; } struct mc146818* state = rtc_state(rtc->data); state->ticking = 1; state->on_tick_cb = on_tick; /* We realise that using rtc to tick something has an extremely rare use * case (e.g., calibrating some timer). Therefore, we will release this * allocated IV when rtc ticking is no longer required to save IV * resources. */ state->rtc_iv = isrm_bindirq(PC_AT_IRQ_RTC, __rtc_tick); rtc_enable_timer(); } static void rtc_end_ticking(struct hwrtc* rtc) { if (!rtc_state(rtc->data)->ticking) { return; } rtc_disable_timer(); // do some delay, ensure there is no more interrupt from rtc before we // release isr port_delay(1000); isrm_ivfree(rtc_state(rtc->data)->rtc_iv); rtc_state(rtc->data)->ticking = 0; } static struct hwrtc hwrtc_mc146818a = { .name = "mc146818a", .data = &rtc_state, .init = rtc_init, .base_freq = RTC_TIMER_BASE_FREQUENCY, .supported = mc146818_check_support, .get_walltime = clock_walltime, .do_ticking = rtc_do_ticking, .end_ticking = rtc_end_ticking }; struct hwrtc* mc146818a_rtc_context() { return &hwrtc_mc146818a; }