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 <zircon/device/rtc.h>
10#include <hw/inout.h>
11#include <librtc.h>
12
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
29static mtx_t lock = MTX_INIT;
30
31enum 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
48enum intel_rtc_register_a {
49    REG_A_UPDATE_IN_PROGRESS_BIT = 1 << 7,
50};
51
52enum 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
63static uint8_t read_reg_raw(enum intel_rtc_registers reg) {
64    outp(RTC_IDX_REG, reg);
65    return inp(RTC_DATA_REG);
66}
67
68static 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
73static 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
78static 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
87static 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
114static 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
135static zx_status_t set_utc_offset(const rtc_t* 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.
145static 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
151static void read_time(rtc_t* 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
168static void write_time(const rtc_t* 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
189static ssize_t intel_rtc_get(void* buf, size_t count) {
190    if (count < sizeof(rtc_t)) {
191        return ZX_ERR_BUFFER_TOO_SMALL;
192    }
193
194    // Ensure we have a consistent time.
195    rtc_t rtc, prev;
196    do {
197        // Using memcpy, as we use memcmp to compare.
198        memcpy(&prev, &rtc, sizeof(rtc_t));
199        read_time(&rtc);
200    } while (memcmp(&rtc, &prev, sizeof(rtc_t)));
201
202    memcpy(buf, &rtc, sizeof(rtc_t));
203    return sizeof(rtc_t);
204}
205
206static ssize_t intel_rtc_set(const void* buf, size_t count) {
207    if (count < sizeof(rtc_t)) {
208        return ZX_ERR_BUFFER_TOO_SMALL;
209    }
210    rtc_t rtc;
211    memcpy(&rtc, buf, sizeof(rtc_t));
212
213    // An invalid time was supplied.
214    if (rtc_is_invalid(&rtc)) {
215        return ZX_ERR_OUT_OF_RANGE;
216    }
217
218    write_time(&rtc);
219    // TODO(kulakowski) This isn't the place for this long term.
220    zx_status_t status = set_utc_offset(&rtc);
221    if (status != ZX_OK) {
222        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
223    }
224    return sizeof(rtc_t);
225}
226
227// Implement ioctl protocol.
228static zx_status_t intel_rtc_ioctl(void* ctx, uint32_t op,
229                                   const void* in_buf, size_t in_len,
230                                   void* out_buf, size_t out_len, size_t* out_actual) {
231    switch (op) {
232    case IOCTL_RTC_GET: {
233        ssize_t ret = intel_rtc_get(out_buf, out_len);
234        if (ret < 0) {
235            return ret;
236        }
237        *out_actual = ret;
238        return ZX_OK;
239    }
240    case IOCTL_RTC_SET:
241        return intel_rtc_set(in_buf, in_len);
242    }
243    return ZX_ERR_NOT_SUPPORTED;
244}
245
246static zx_protocol_device_t intel_rtc_device_proto __UNUSED = {
247    .version = DEVICE_OPS_VERSION,
248    .ioctl = intel_rtc_ioctl,
249};
250
251//TODO: bind against hw, not misc
252static zx_status_t intel_rtc_bind(void* ctx, zx_device_t* parent) {
253#if defined(__x86_64__) || defined(__i386__)
254    // TODO(teisenbe): This should be probed via the ACPI pseudo bus whenever it
255    // exists.
256
257    zx_status_t status = zx_ioports_request(get_root_resource(), RTC_IO_BASE, RTC_NUM_IO_REGISTERS);
258    if (status != ZX_OK) {
259        return status;
260    }
261
262    device_add_args_t args = {
263        .version = DEVICE_ADD_ARGS_VERSION,
264        .name = "rtc",
265        .ops = &intel_rtc_device_proto,
266        .proto_id = ZX_PROTOCOL_RTC
267    };
268
269    zx_device_t* dev;
270    status = device_add(parent, &args, &dev);
271    if (status != ZX_OK) {
272        return status;
273    }
274
275    rtc_t rtc;
276    sanitize_rtc(NULL, &intel_rtc_device_proto, &rtc);
277    status = set_utc_offset(&rtc);
278    if (status != ZX_OK) {
279        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
280    }
281
282    return ZX_OK;
283#else
284    return ZX_ERR_NOT_SUPPORTED;
285#endif
286}
287
288static zx_driver_ops_t intel_rtc_driver_ops = {
289    .version = DRIVER_OPS_VERSION,
290    .bind = intel_rtc_bind,
291};
292
293ZIRCON_DRIVER_BEGIN(intel_rtc, intel_rtc_driver_ops, "zircon", "0.1", 6)
294    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI),
295    BI_GOTO_IF(NE, BIND_ACPI_HID_0_3, 0x504e5030, 0), // PNP0B00\0
296    BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x42303000),
297    BI_LABEL(0),
298    BI_ABORT_IF(NE, BIND_ACPI_CID_0_3, 0x504e5030), // PNP0B00\0
299    BI_MATCH_IF(EQ, BIND_ACPI_CID_4_7, 0x42303000),
300ZIRCON_DRIVER_END(intel_rtc)
301