refactor: one more step towards arch-agnostic design
[lunaix-os.git] / lunaix-os / hal / rtc / mc146818a.c
1 /**
2  * @file rtc.c
3  * @author Lunaixsky
4  * @brief RTC & CMOS abstraction. Reference: MC146818A & Intel Series 500 PCH
5  * datasheet
6  * @version 0.1
7  * @date 2022-03-07
8  *
9  * @copyright Copyright (c) 2022
10  *
11  */
12
13 #include <lunaix/isrm.h>
14
15 #include <hal/rtc/mc146818a.h>
16
17 #include <klibc/string.h>
18
19 #include <sys/interrupts.h>
20 #include <sys/port_io.h>
21
22 #define RTC_INDEX_PORT 0x70
23 #define RTC_TARGET_PORT 0x71
24
25 #define WITH_NMI_DISABLED 0x80
26
27 #define RTC_CURRENT_CENTRY 20
28
29 #define RTC_REG_YRS 0x9
30 #define RTC_REG_MTH 0x8
31 #define RTC_REG_DAY 0x7
32 #define RTC_REG_WDY 0x6
33 #define RTC_REG_HRS 0x4
34 #define RTC_REG_MIN 0x2
35 #define RTC_REG_SEC 0x0
36
37 #define RTC_REG_A 0xA
38 #define RTC_REG_B 0xB
39 #define RTC_REG_C 0xC
40 #define RTC_REG_D 0xD
41
42 #define RTC_BIN_ENCODED(reg) (reg & 0x04)
43 #define RTC_24HRS_ENCODED(reg) (reg & 0x02)
44
45 #define RTC_TIMER_BASE_FREQUENCY 1024
46 #define RTC_TIMER_ON 0x40
47
48 #define RTC_FREQUENCY_1024HZ 0b110
49 #define RTC_DIVIDER_33KHZ (0b010 << 4)
50
51 struct mc146818
52 {
53     struct hwrtc* rtc_context;
54     u32_t rtc_iv;
55     int ticking;
56     void (*on_tick_cb)(const struct hwrtc*);
57 };
58
59 #define rtc_state(data) ((struct mc146818*)(data))
60
61 static u8_t
62 rtc_read_reg(u8_t reg_selector)
63 {
64     port_wrbyte(RTC_INDEX_PORT, reg_selector);
65     return port_rdbyte(RTC_TARGET_PORT);
66 }
67
68 static void
69 rtc_write_reg(u8_t reg_selector, u8_t val)
70 {
71     port_wrbyte(RTC_INDEX_PORT, reg_selector);
72     port_wrbyte(RTC_TARGET_PORT, val);
73 }
74
75 static u8_t
76 bcd2dec(u8_t bcd)
77 {
78     return ((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3) + (bcd & 0xf);
79 }
80
81 static void
82 rtc_enable_timer()
83 {
84     u8_t regB = rtc_read_reg(RTC_REG_B | WITH_NMI_DISABLED);
85     rtc_write_reg(RTC_REG_B | WITH_NMI_DISABLED, regB | RTC_TIMER_ON);
86 }
87
88 static void
89 rtc_disable_timer()
90 {
91     u8_t regB = rtc_read_reg(RTC_REG_B | WITH_NMI_DISABLED);
92     rtc_write_reg(RTC_REG_B | WITH_NMI_DISABLED, regB & ~RTC_TIMER_ON);
93 }
94
95 static void
96 clock_walltime(struct hwrtc* rtc, datetime_t* datetime)
97 {
98     datetime_t current;
99
100     do {
101         while (rtc_read_reg(RTC_REG_A) & 0x80)
102             ;
103         memcpy(&current, datetime, sizeof(datetime_t));
104
105         datetime->year = rtc_read_reg(RTC_REG_YRS);
106         datetime->month = rtc_read_reg(RTC_REG_MTH);
107         datetime->day = rtc_read_reg(RTC_REG_DAY);
108         datetime->weekday = rtc_read_reg(RTC_REG_WDY);
109         datetime->hour = rtc_read_reg(RTC_REG_HRS);
110         datetime->minute = rtc_read_reg(RTC_REG_MIN);
111         datetime->second = rtc_read_reg(RTC_REG_SEC);
112     } while (!datatime_eq(datetime, &current));
113
114     u8_t regbv = rtc_read_reg(RTC_REG_B);
115
116     // Convert from bcd to binary when needed
117     if (!RTC_BIN_ENCODED(regbv)) {
118         datetime->year = bcd2dec(datetime->year);
119         datetime->month = bcd2dec(datetime->month);
120         datetime->day = bcd2dec(datetime->day);
121         datetime->hour = bcd2dec(datetime->hour);
122         datetime->minute = bcd2dec(datetime->minute);
123         datetime->second = bcd2dec(datetime->second);
124     }
125
126     // To 24 hour format
127     if (!RTC_24HRS_ENCODED(regbv) && (datetime->hour >> 7)) {
128         datetime->hour = 12 + (datetime->hour & 0x80);
129     }
130
131     datetime->year += RTC_CURRENT_CENTRY * 100;
132 }
133
134 static int
135 mc146818_check_support(struct hwrtc* rtc)
136 {
137 #if __ARCH__ == i386
138     return 1;
139 #else
140     return 0;
141 #endif
142 }
143
144 static void
145 rtc_init(struct hwrtc* rtc)
146 {
147     u8_t regA = rtc_read_reg(RTC_REG_A | WITH_NMI_DISABLED);
148     regA = (regA & ~0x7f) | RTC_FREQUENCY_1024HZ | RTC_DIVIDER_33KHZ;
149     rtc_write_reg(RTC_REG_A | WITH_NMI_DISABLED, regA);
150
151     rtc_state(rtc->data)->rtc_context = rtc;
152
153     // Make sure the rtc timer is disabled by default
154     rtc_disable_timer();
155 }
156
157 static struct mc146818 rtc_state = { .ticking = 0 };
158
159 static void
160 __rtc_tick(const isr_param* param)
161 {
162     rtc_state.on_tick_cb(rtc_state.rtc_context);
163
164     (void)rtc_read_reg(RTC_REG_C);
165 }
166
167 static void
168 rtc_do_ticking(struct hwrtc* rtc, void (*on_tick)())
169 {
170     if (!on_tick || rtc_state(rtc->data)->ticking) {
171         return;
172     }
173
174     struct mc146818* state = rtc_state(rtc->data);
175     state->ticking = 1;
176     state->on_tick_cb = on_tick;
177
178     /* We realise that using rtc to tick something has an extremely rare use
179      * case (e.g., calibrating some timer). Therefore, we will release this
180      * allocated IV when rtc ticking is no longer required to save IV
181      * resources.
182      */
183     state->rtc_iv = isrm_bindirq(PC_AT_IRQ_RTC, __rtc_tick);
184
185     rtc_enable_timer();
186 }
187
188 static void
189 rtc_end_ticking(struct hwrtc* rtc)
190 {
191     if (!rtc_state(rtc->data)->ticking) {
192         return;
193     }
194
195     rtc_disable_timer();
196
197     // do some delay, ensure there is no more interrupt from rtc before we
198     // release isr
199     port_delay(1000);
200
201     isrm_ivfree(rtc_state(rtc->data)->rtc_iv);
202
203     rtc_state(rtc->data)->ticking = 0;
204 }
205
206 static struct hwrtc hwrtc_mc146818a = { .name = "mc146818a",
207                                         .data = &rtc_state,
208                                         .init = rtc_init,
209                                         .base_freq = RTC_TIMER_BASE_FREQUENCY,
210                                         .supported = mc146818_check_support,
211                                         .get_walltime = clock_walltime,
212                                         .do_ticking = rtc_do_ticking,
213                                         .end_ticking = rtc_end_ticking };
214
215 struct hwrtc*
216 mc146818a_rtc_context()
217 {
218     return &hwrtc_mc146818a;
219 }