1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <ddk/binding.h>
6 #include <ddk/debug.h>
7 #include <ddk/device.h>
8 #include <ddk/driver.h>
9 #include <hw/inout.h>
10 #include <librtc.h>
11 
12 #include <fuchsia/hardware/rtc/c/fidl.h>
13 #include <zircon/syscalls.h>
14 #include <zircon/types.h>
15 
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <threads.h>
20 
21 #define RTC_IO_BASE 0x70
22 #define RTC_NUM_IO_REGISTERS 8
23 
24 #define RTC_IDX_REG 0x70
25 #define RTC_DATA_REG 0x71
26 
27 #define RTC_HOUR_PM_BIT 0x80
28 
29 static mtx_t lock = MTX_INIT;
30 
31 enum intel_rtc_registers {
32     REG_SECONDS,
33     REG_SECONDS_ALARM,
34     REG_MINUTES,
35     REG_MINUTES_ALARM,
36     REG_HOURS,
37     REG_HOURS_ALARM,
38     REG_DAY_OF_WEEK,
39     REG_DAY_OF_MONTH,
40     REG_MONTH,
41     REG_YEAR,
42     REG_A,
43     REG_B,
44     REG_C,
45     REG_D,
46 };
47 
48 enum intel_rtc_register_a {
49     REG_A_UPDATE_IN_PROGRESS_BIT = 1 << 7,
50 };
51 
52 enum intel_rtc_register_b {
53     REG_B_DAYLIGHT_SAVINGS_ENABLE_BIT = 1 << 0,
54     REG_B_HOUR_FORMAT_BIT = 1 << 1,
55     REG_B_DATA_MODE_BIT = 1 << 2,
56     REG_B_SQUARE_WAVE_ENABLE_BIT = 1 << 3,
57     REG_B_UPDATE_ENDED_INTERRUPT_ENABLE_BIT = 1 << 4,
58     REB_B_ALARM_INTERRUPT_ENABLE_BIT = 1 << 5,
59     REG_B_PERIODIC_INTERRUPT_ENABLE_BIT = 1 << 6,
60     REG_B_UPDATE_CYCLE_INHIBIT_BIT = 1 << 7,
61 };
62 
read_reg_raw(enum intel_rtc_registers reg)63 static uint8_t read_reg_raw(enum intel_rtc_registers reg) {
64     outp(RTC_IDX_REG, reg);
65     return inp(RTC_DATA_REG);
66 }
67 
write_reg_raw(enum intel_rtc_registers reg,uint8_t val)68 static void write_reg_raw(enum intel_rtc_registers reg, uint8_t val) {
69     outp(RTC_IDX_REG, reg);
70     outp(RTC_DATA_REG, val);
71 }
72 
read_reg(enum intel_rtc_registers reg,bool reg_is_binary)73 static uint8_t read_reg(enum intel_rtc_registers reg, bool reg_is_binary) {
74     uint8_t data = read_reg_raw(reg);
75     return reg_is_binary ? data : from_bcd(data);
76 }
77 
write_reg(enum intel_rtc_registers reg,uint8_t val,bool reg_is_binary)78 static void write_reg(enum intel_rtc_registers reg, uint8_t val, bool reg_is_binary) {
79     write_reg_raw(reg, reg_is_binary ? val : to_bcd(val));
80 }
81 
82 // The high bit (RTC_HOUR_PM_BIT) is special for hours when not using
83 // the 24 hour time encoding. In that case, it is set for PM and unset
84 // for AM. This is true for both BCD and binary encodings of the
85 // value, so it has to be masked out first.
86 
read_reg_hour(bool reg_is_binary,bool reg_is_24_hour)87 static uint8_t read_reg_hour(bool reg_is_binary, bool reg_is_24_hour) {
88     uint8_t data = read_reg_raw(REG_HOURS);
89 
90     bool pm = data & RTC_HOUR_PM_BIT;
91     data &= ~RTC_HOUR_PM_BIT;
92 
93     uint8_t hour = reg_is_binary ? data : from_bcd(data);
94 
95     if (reg_is_24_hour) {
96         return hour;
97     }
98 
99     if (pm) {
100         hour += 12;
101     }
102 
103     // Adjust noon and midnight.
104     switch (hour) {
105     case 24: // 12 PM
106         return 12;
107     case 12: // 12 AM
108         return 0;
109     default:
110         return hour;
111     }
112 }
113 
write_reg_hour(uint8_t hour,bool reg_is_binary,bool reg_is_24_hour)114 static void write_reg_hour(uint8_t hour, bool reg_is_binary, bool reg_is_24_hour) {
115     bool pm = hour > 11;
116 
117     if (!reg_is_24_hour) {
118         if (pm) {
119             hour -= 12;
120         }
121         if (hour == 0) {
122             hour = 12;
123         }
124     }
125 
126     uint8_t data = reg_is_binary ? hour : to_bcd(hour);
127 
128     if (pm && !reg_is_24_hour) {
129         data |= RTC_HOUR_PM_BIT;
130     }
131 
132     write_reg_raw(REG_HOURS, data);
133 }
134 
set_utc_offset(const fuchsia_hardware_rtc_Time * rtc)135 static zx_status_t set_utc_offset(const fuchsia_hardware_rtc_Time* rtc) {
136     uint64_t rtc_nanoseconds = seconds_since_epoch(rtc) * 1000000000;;
137     int64_t offset = rtc_nanoseconds - zx_clock_get_monotonic();
138     return zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
139 }
140 
141 // Retrieve the hour format and data mode bits. Note that on some
142 // platforms (including the acer) these bits can not be reliably
143 // written. So we must instead parse and provide the data in whatever
144 // format is given to us.
rtc_mode(bool * reg_is_24_hour,bool * reg_is_binary)145 static void rtc_mode(bool* reg_is_24_hour, bool* reg_is_binary) {
146     uint8_t reg_b = read_reg_raw(REG_B);
147     *reg_is_24_hour = reg_b & REG_B_HOUR_FORMAT_BIT;
148     *reg_is_binary = reg_b & REG_B_DATA_MODE_BIT;
149 }
150 
read_time(fuchsia_hardware_rtc_Time * rtc)151 static void read_time(fuchsia_hardware_rtc_Time* rtc) {
152     mtx_lock(&lock);
153     bool reg_is_24_hour;
154     bool reg_is_binary;
155     rtc_mode(&reg_is_24_hour, &reg_is_binary);
156 
157     rtc->seconds = read_reg(REG_SECONDS, reg_is_binary);
158     rtc->minutes = read_reg(REG_MINUTES, reg_is_binary);
159     rtc->hours = read_reg_hour(reg_is_binary, reg_is_24_hour);
160 
161     rtc->day = read_reg(REG_DAY_OF_MONTH, reg_is_binary);
162     rtc->month = read_reg(REG_MONTH, reg_is_binary);
163     rtc->year = read_reg(REG_YEAR, reg_is_binary) + 2000;
164 
165     mtx_unlock(&lock);
166 }
167 
write_time(const fuchsia_hardware_rtc_Time * rtc)168 static void write_time(const fuchsia_hardware_rtc_Time* rtc) {
169     mtx_lock(&lock);
170     bool reg_is_24_hour;
171     bool reg_is_binary;
172     rtc_mode(&reg_is_24_hour, &reg_is_binary);
173 
174     write_reg_raw(REG_B, read_reg_raw(REG_B) | REG_B_UPDATE_CYCLE_INHIBIT_BIT);
175 
176     write_reg(REG_SECONDS, rtc->seconds, reg_is_binary);
177     write_reg(REG_MINUTES, rtc->minutes, reg_is_binary);
178     write_reg_hour(rtc->hours, reg_is_binary, reg_is_24_hour);
179 
180     write_reg(REG_DAY_OF_MONTH, rtc->day, reg_is_binary);
181     write_reg(REG_MONTH, rtc->month, reg_is_binary);
182     write_reg(REG_YEAR, rtc->year - 2000, reg_is_binary);
183 
184     write_reg_raw(REG_B, read_reg_raw(REG_B) & ~REG_B_UPDATE_CYCLE_INHIBIT_BIT);
185 
186     mtx_unlock(&lock);
187 }
188 
intel_rtc_get(void * ctx,fuchsia_hardware_rtc_Time * rtc)189 static zx_status_t intel_rtc_get(void* ctx, fuchsia_hardware_rtc_Time* rtc) {
190     // Ensure we have a consistent time.
191     fuchsia_hardware_rtc_Time prev;
192     do {
193         // Using memcpy, as we use memcmp to compare.
194         memcpy(&prev, rtc, sizeof prev);
195         read_time(rtc);
196     } while (memcmp(rtc, &prev, sizeof prev));
197     return ZX_OK;
198 }
199 
intel_rtc_set(void * ctx,const fuchsia_hardware_rtc_Time * rtc)200 static zx_status_t intel_rtc_set(void* ctx, const fuchsia_hardware_rtc_Time* rtc) {
201     // An invalid time was supplied.
202     if (rtc_is_invalid(rtc)) {
203         return ZX_ERR_OUT_OF_RANGE;
204     }
205 
206     write_time(rtc);
207     // TODO(kulakowski) This isn't the place for this long term.
208     zx_status_t status = set_utc_offset(rtc);
209     if (status != ZX_OK) {
210         zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
211     }
212     return ZX_OK;
213 }
214 
fidl_Get(void * ctx,fidl_txn_t * txn)215 static zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn) {
216     fuchsia_hardware_rtc_Time rtc;
217     intel_rtc_get(ctx, &rtc);
218     return fuchsia_hardware_rtc_DeviceGet_reply(txn, &rtc);
219 }
220 
fidl_Set(void * ctx,const fuchsia_hardware_rtc_Time * rtc,fidl_txn_t * txn)221 static zx_status_t fidl_Set(void* ctx, const fuchsia_hardware_rtc_Time* rtc, fidl_txn_t* txn) {
222     zx_status_t status = intel_rtc_set(ctx, rtc);
223     return fuchsia_hardware_rtc_DeviceSet_reply(txn, status);
224 }
225 
226 static fuchsia_hardware_rtc_Device_ops_t fidl_ops = {
227     .Get = fidl_Get,
228     .Set = fidl_Set,
229 };
230 
intel_rtc_message(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn)231 static zx_status_t intel_rtc_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
232     return fuchsia_hardware_rtc_Device_dispatch(ctx, txn, msg, &fidl_ops);
233 }
234 
235 static zx_protocol_device_t intel_rtc_device_proto __UNUSED = {
236     .version = DEVICE_OPS_VERSION,
237     .message = intel_rtc_message
238 };
239 
240 //TODO: bind against hw, not misc
intel_rtc_bind(void * ctx,zx_device_t * parent)241 static zx_status_t intel_rtc_bind(void* ctx, zx_device_t* parent) {
242 #if defined(__x86_64__) || defined(__i386__)
243     // TODO(teisenbe): This should be probed via the ACPI pseudo bus whenever it
244     // exists.
245 
246     zx_status_t status = zx_ioports_request(get_root_resource(), RTC_IO_BASE, RTC_NUM_IO_REGISTERS);
247     if (status != ZX_OK) {
248         return status;
249     }
250 
251     device_add_args_t args = {
252         .version = DEVICE_ADD_ARGS_VERSION,
253         .name = "rtc",
254         .ops = &intel_rtc_device_proto,
255         .proto_id = ZX_PROTOCOL_RTC
256     };
257 
258     zx_device_t* dev;
259     status = device_add(parent, &args, &dev);
260     if (status != ZX_OK) {
261         return status;
262     }
263 
264     fuchsia_hardware_rtc_Time rtc;
265     sanitize_rtc(NULL, &rtc, intel_rtc_get, intel_rtc_set);
266     status = set_utc_offset(&rtc);
267     if (status != ZX_OK) {
268         zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
269     }
270 
271     return ZX_OK;
272 #else
273     return ZX_ERR_NOT_SUPPORTED;
274 #endif
275 }
276 
277 static zx_driver_ops_t intel_rtc_driver_ops = {
278     .version = DRIVER_OPS_VERSION,
279     .bind = intel_rtc_bind,
280 };
281 
282 ZIRCON_DRIVER_BEGIN(intel_rtc, intel_rtc_driver_ops, "zircon", "0.1", 6)
283     BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI),
284     BI_GOTO_IF(NE, BIND_ACPI_HID_0_3, 0x504e5030, 0), // PNP0B00\0
285     BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x42303000),
286     BI_LABEL(0),
287     BI_ABORT_IF(NE, BIND_ACPI_CID_0_3, 0x504e5030), // PNP0B00\0
288     BI_MATCH_IF(EQ, BIND_ACPI_CID_4_7, 0x42303000),
289 ZIRCON_DRIVER_END(intel_rtc)
290