blob: fe8c5229f787c22b1833f78b509d1a960600f14a [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/devices/rtc/drivers/intel-rtc/intel-rtc.h"
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/hw/inout.h>
#include <librtc.h>
#include <librtc_llcpp.h>
#include <zircon/errors.h>
#include <ddktl/device.h>
#include <safemath/checked_math.h>
#include "src/devices/lib/acpi/client.h"
// The Intel RTC is documented in "7th and 8th Generation IntelĀ® Processor Family I/O for U/Y
// Platforms and 10th Generation IntelĀ® Processor Family I/O for Y Platforms", vol 1 section 27 and
// vol 2 section 33.
namespace intel_rtc {
constexpr uint32_t kPortCount = 2;
constexpr uint16_t RtcIndex(uint16_t bank) { return bank * 2; }
constexpr uint16_t RtcData(uint16_t bank) { return (bank * 2) + 1; }
#ifdef FOR_TEST
void TestOutp(uint16_t port, uint8_t value);
uint8_t TestInp(uint16_t port);
#define outp TestOutp
#define inp TestInp
#endif
uint8_t RtcDevice::ReadRegRaw(Registers reg) {
outp(port_base_ + RtcIndex(0), reg);
return inp(port_base_ + RtcData(0));
}
void RtcDevice::WriteRegRaw(Registers reg, uint8_t val) {
outp(port_base_ + RtcIndex(0), reg);
outp(port_base_ + RtcData(0), val);
}
uint8_t RtcDevice::ReadReg(Registers reg) {
uint8_t val = ReadRegRaw(reg);
return is_bcd_ ? from_bcd(val) : val;
}
void RtcDevice::WriteReg(Registers reg, uint8_t val) {
WriteRegRaw(reg, is_bcd_ ? to_bcd(val) : val);
}
uint8_t RtcDevice::ReadHour() {
uint8_t data = ReadRegRaw(kRegHours);
// The high bit is set for PM and unset for AM in when not in 24-hour mode.
bool pm = data & kHourPmBit;
data &= ~kHourPmBit;
uint8_t hour = is_bcd_ ? from_bcd(data) : data;
if (is_24_hour_) {
return hour;
}
if (pm) {
hour += 12;
}
switch (hour) {
case 24: // Fix up 12 pm.
return 12;
case 12: // Fix up 12 am.
return 0;
default:
return hour;
}
}
void RtcDevice::WriteHour(uint8_t hour) {
bool pm = hour > 11;
uint8_t data = 0;
if (!is_24_hour_) {
if (pm) {
data |= kHourPmBit;
hour -= 12;
}
if (hour == 0) {
hour = 12;
}
}
data |= is_bcd_ ? to_bcd(hour) : hour;
WriteRegRaw(kRegHours, data);
}
// Retrieve the hour format and data mode bits. Note that on some
// platforms (including the acer) these bits cannot be reliably
// written. So we must instead parse and provide the data in whatever
// format is given to us.
void RtcDevice::CheckRtcMode() {
uint8_t reg_b = ReadRegRaw(kRegB);
// if HOUR_FORMAT_BIT is set, then the RTC is in 24-hour mode.
is_24_hour_ = (reg_b & kRegBHourFormatBit) == kRegBHourFormatBit;
// if DATA_MODE_BIT is set, then the RTC uses binary values.
is_bcd_ = !(reg_b & kRegBDataFormatBit);
}
FidlRtc::wire::Time RtcDevice::ReadTime() {
std::scoped_lock lock(time_lock_);
FidlRtc::wire::Time result;
CheckRtcMode();
while (ReadRegRaw(kRegA) & kRegAUpdateInProgressBit) {
// The datasheet says "the entire cycle does not take more than 1984 uS to complete".
// This should be plenty of time for the RTC to update itself.
zx::nanosleep(zx::deadline_after(zx::usec(2000)));
}
result.seconds = ReadReg(kRegSeconds);
result.minutes = ReadReg(kRegMinutes);
result.hours = ReadHour();
result.day = ReadReg(kRegDayOfMonth);
result.month = ReadReg(kRegMonth);
result.year = ReadReg(kRegYear) + 2000;
return result;
}
void RtcDevice::WriteTime(FidlRtc::wire::Time time) {
std::scoped_lock lock(time_lock_);
CheckRtcMode();
WriteRegRaw(kRegB, ReadRegRaw(kRegB) | kRegBUpdateCycleInhibitBit);
WriteReg(kRegSeconds, time.seconds);
WriteReg(kRegMinutes, time.minutes);
WriteHour(time.hours);
WriteReg(kRegDayOfMonth, time.day);
WriteReg(kRegMonth, time.month);
// If present, we should use the "century" register described by the FADT.
if (unlikely(time.year >= 2100)) {
zxlogf(
WARNING,
"The Intel RTC driver does not support the year 2100. Please return to the 21st century.");
}
WriteReg(kRegYear, static_cast<uint8_t>(time.year - 2000));
WriteRegRaw(kRegB, ReadRegRaw(kRegB) & ~kRegBUpdateCycleInhibitBit);
}
void RtcDevice::Get(GetCompleter::Sync& completer) {
// TODO(https://fxbug.dev/42074113): Reply with error if RTC time is known to be invalid.
completer.ReplySuccess(ReadTime());
}
void RtcDevice::Set(SetRequestView request, SetCompleter::Sync& completer) {
WriteTime(request->rtc);
completer.Reply(ZX_OK);
}
zx_status_t Bind(void* ctx, zx_device_t* parent) {
auto client = acpi::Client::Create(parent);
if (client.is_error()) {
return client.error_value();
}
auto acpi = std::move(client.value());
auto pio = acpi.borrow()->GetPio(0);
if (!pio.ok() || pio->is_error()) {
zxlogf(ERROR, "Failed to get port I/O resource");
return pio.ok() ? pio->error_value() : pio.status();
}
zx::resource io_port = std::move(pio->value()->pio);
zx_info_resource_t resource_info;
zx_status_t status =
io_port.get_info(ZX_INFO_RESOURCE, &resource_info, sizeof(resource_info), nullptr, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "io_port.get_info failed: %d", status);
return status;
}
if (resource_info.base > UINT16_MAX) {
zxlogf(ERROR, "UART port base is too high.");
return ZX_ERR_BAD_STATE;
}
if (resource_info.size < kPortCount) {
zxlogf(ERROR, "Not enough I/O ports: wanted %u, got %lu", kPortCount, resource_info.size);
return ZX_ERR_BAD_STATE;
}
status = zx_ioports_request(io_port.get(), resource_info.base, resource_info.size);
if (status != ZX_OK) {
zxlogf(ERROR, "zx_ioports_request_failed: %d", status);
return status;
}
std::unique_ptr<RtcDevice> rtc = std::make_unique<RtcDevice>(parent, resource_info.base);
auto time = rtc->ReadTime();
auto new_time = rtc::SanitizeRtc(parent, time);
rtc->WriteTime(new_time);
status = rtc->DdkAdd(ddk::DeviceAddArgs("rtc").set_proto_id(ZX_PROTOCOL_RTC));
if (status == ZX_OK) {
[[maybe_unused]] auto unused = rtc.release();
}
return status;
}
static zx_driver_ops_t driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = Bind,
};
} // namespace intel_rtc
ZIRCON_DRIVER(intel_rtc, intel_rtc::driver_ops, "zircon", "0.1");