rework external irq system, introduce hierarchical irq
[lunaix-os.git] / lunaix-os / arch / x86 / hal / 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/mm/valloc.h>
14 #include <lunaix/status.h>
15 #include <lunaix/hart_state.h>
16
17 #include <hal/hwrtc.h>
18 #include <hal/irq.h>
19
20 #include <klibc/string.h>
21
22 #include <asm/x86_isrm.h>
23 #include <asm/x86_pmio.h>
24
25 #define RTC_INDEX_PORT 0x70
26 #define RTC_TARGET_PORT 0x71
27
28 #define RTC_CURRENT_CENTRY 20
29
30 #define RTC_REG_YRS 0x9
31 #define RTC_REG_MTH 0x8
32 #define RTC_REG_DAY 0x7
33 #define RTC_REG_WDY 0x6
34 #define RTC_REG_HRS 0x4
35 #define RTC_REG_MIN 0x2
36 #define RTC_REG_SEC 0x0
37
38 #define RTC_REG_A 0xA
39 #define RTC_REG_B 0xB
40 #define RTC_REG_C 0xC
41 #define RTC_REG_D 0xD
42
43 #define RTC_SET 0x80
44 #define RTC_BIN (1 << 2)
45 #define RTC_HOURFORM24 (1 << 1)
46
47 #define RTC_BIN_ENCODED(reg) (reg & 0x04)
48 #define RTC_24HRS_ENCODED(reg) (reg & 0x02)
49
50 #define RTC_TIMER_BASE_FREQUENCY 1024
51 #define RTC_TIMER_ON 0x40
52
53 #define RTC_FREQUENCY_1024HZ 0b110
54 #define RTC_DIVIDER_33KHZ (0b010 << 4)
55
56 #define PC_AT_IRQ_RTC                   8
57
58 struct mc146818
59 {
60     struct hwrtc_potens* rtc_context;
61     irq_t irq;
62 };
63
64 #define rtc_state(data) ((struct mc146818*)(data))
65
66 static inline u8_t
67 rtc_read_reg(u8_t reg_selector)
68 {
69     port_wrbyte(RTC_INDEX_PORT, reg_selector);
70     return port_rdbyte(RTC_TARGET_PORT);
71 }
72
73 static inline void
74 rtc_write_reg(u8_t reg_selector, u8_t val)
75 {
76     port_wrbyte(RTC_INDEX_PORT, reg_selector);
77     port_wrbyte(RTC_TARGET_PORT, val);
78 }
79
80 static inline void
81 rtc_enable_timer()
82 {
83     u8_t regB = rtc_read_reg(RTC_REG_B);
84     rtc_write_reg(RTC_REG_B, regB | RTC_TIMER_ON);
85 }
86
87 static inline void
88 rtc_disable_timer()
89 {
90     u8_t regB = rtc_read_reg(RTC_REG_B);
91     rtc_write_reg(RTC_REG_B, regB & ~RTC_TIMER_ON);
92 }
93
94 static void
95 rtc_getwalltime(struct hwrtc_potens* rtc, datetime_t* datetime)
96 {
97     datetime_t current;
98
99     do {
100         while (rtc_read_reg(RTC_REG_A) & 0x80)
101             ;
102         memcpy(&current, datetime, sizeof(datetime_t));
103
104         datetime->year = rtc_read_reg(RTC_REG_YRS);
105         datetime->month = rtc_read_reg(RTC_REG_MTH);
106         datetime->day = rtc_read_reg(RTC_REG_DAY);
107         datetime->weekday = rtc_read_reg(RTC_REG_WDY);
108         datetime->hour = rtc_read_reg(RTC_REG_HRS);
109         datetime->minute = rtc_read_reg(RTC_REG_MIN);
110         datetime->second = rtc_read_reg(RTC_REG_SEC);
111     } while (!datatime_eq(datetime, &current));
112
113     datetime->year += RTC_CURRENT_CENTRY * 100;
114 }
115
116 static void
117 rtc_setwalltime(struct hwrtc_potens* rtc, datetime_t* datetime)
118 {
119     u8_t reg = rtc_read_reg(RTC_REG_B);
120     reg = reg & ~RTC_SET;
121
122     rtc_write_reg(RTC_REG_B, reg | RTC_SET);
123
124     rtc_write_reg(RTC_REG_YRS, datetime->year - RTC_CURRENT_CENTRY * 100);
125     rtc_write_reg(RTC_REG_MTH, datetime->month);
126     rtc_write_reg(RTC_REG_DAY, datetime->day);
127     rtc_write_reg(RTC_REG_WDY, datetime->weekday);
128     rtc_write_reg(RTC_REG_HRS, datetime->hour);
129     rtc_write_reg(RTC_REG_MIN, datetime->minute);
130     rtc_write_reg(RTC_REG_SEC, datetime->second);
131
132     rtc_write_reg(RTC_REG_B, reg & ~RTC_SET);
133 }
134
135 static void
136 __rtc_tick(irq_t irq, const struct hart_state* hstate)
137 {
138     struct mc146818* state;
139
140     state = irq_payload(irq, struct mc146818);
141     state->rtc_context->live++;
142
143     (void)rtc_read_reg(RTC_REG_C);
144 }
145
146 static void
147 rtc_set_proactive(struct hwrtc_potens* pot, bool proactive)
148 {
149     if (proactive) {
150         pot->state = 0;
151         rtc_enable_timer();
152     }
153     else {
154         pot->state = RTC_STATE_MASKED;
155         rtc_disable_timer();
156     }
157 }
158
159 static int
160 rtc_chfreq(struct hwrtc_potens* rtc, int freq)
161 {
162     return ENOTSUP;
163 }
164
165 static int
166 __rtc_calibrate(struct hwrtc_potens* pot)
167 {
168     struct mc146818* state;
169     struct device* rtc_dev;
170     u8_t reg;
171
172     rtc_dev = potens_dev(pot);
173
174     reg = rtc_read_reg(RTC_REG_A);
175     reg = (reg & ~0x7f) | RTC_FREQUENCY_1024HZ | RTC_DIVIDER_33KHZ;
176     rtc_write_reg(RTC_REG_A, reg);
177
178     reg = RTC_BIN | RTC_HOURFORM24;
179     rtc_write_reg(RTC_REG_B, reg);
180
181     // Make sure the rtc timer is disabled by default
182     rtc_disable_timer();
183
184     pot->base_freq = RTC_TIMER_BASE_FREQUENCY;
185
186     state = (struct mc146818*)rtc_dev->underlay;
187
188     state->irq = irq_declare_line(__rtc_tick, PC_AT_IRQ_RTC, NULL);
189     irq_set_payload(state->irq, state);
190
191     irq_assign(irq_owning_domain(rtc_dev), state->irq);
192
193     return 0;
194 }
195
196 static struct hwrtc_potens_ops ops = {
197     .get_walltime  = rtc_getwalltime,
198     .set_walltime  = rtc_setwalltime,
199     .set_proactive = rtc_set_proactive,
200     .chfreq = rtc_chfreq,
201     .calibrate = __rtc_calibrate
202 };
203
204 static int
205 rtc_load(struct device_def* devdef)
206 {
207     struct device* dev;
208     struct mc146818* state;
209     struct hwrtc_potens* pot;
210  
211     state = valloc(sizeof(struct mc146818));
212     dev = device_allocsys(NULL, state);
213
214     pot = hwrtc_attach_potens(dev, &ops);
215     if (!pot) {
216         return 1;
217     }
218
219     pot->state = RTC_STATE_MASKED;    
220     state->rtc_context = pot;
221
222     register_device(dev, &devdef->class, "mc146818");
223
224     return 0;
225 }
226
227 static struct device_def devrtc_mc146818 = {
228     def_device_class(INTEL, TIME, RTC),
229     def_device_name("x86 legacy RTC"),
230     def_on_load(rtc_load)
231 };
232 EXPORT_DEVICE(mc146818, &devrtc_mc146818, load_sysconf);