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