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(®_is_24_hour, ®_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(®_is_24_hour, ®_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