blob: f95364adbbea104f5f13f594960e87a4db43a770 [file] [log] [blame]
// Copyright 2020 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/pl031-rtc/pl031-rtc.h"
#include <lib/device-protocol/pdev.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/fidl.h>
#include "src/devices/rtc/drivers/pl031-rtc/pl031_rtc_bind.h"
#include "src/devices/rtc/lib/rtc/include/librtc_llcpp.h"
namespace rtc {
zx_status_t Pl031::Bind(void* /*unused*/, zx_device_t* dev) {
ddk::PDev pdev(dev);
if (!pdev.is_valid()) {
return ZX_ERR_NO_RESOURCES;
}
// Carve out some address space for this device.
std::optional<ddk::MmioBuffer> mmio;
zx_status_t status = pdev.MapMmio(0, &mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to map mmio: %s", __func__, zx_status_get_string(status));
return status;
}
auto pl031_device = std::make_unique<Pl031>(dev, *std::move(mmio));
status = pl031_device->DdkAdd(ddk::DeviceAddArgs("rtc").set_proto_id(ZX_PROTOCOL_RTC));
if (status != ZX_OK) {
zxlogf(ERROR, "%s error adding device: %s", __func__, zx_status_get_string(status));
return status;
}
// Retrieve and sanitize the RTC value. Set the RTC to the value.
FidlRtc::Time rtc = SecondsToRtc(MmioRead32(&pl031_device->regs_->dr));
rtc = SanitizeRtc(rtc);
status = pl031_device->SetRtc(rtc);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to set rtc: %s", __func__, zx_status_get_string(status));
}
// The object is owned by the DDK, now that it has been added. It will be deleted
// when the device is released.
__UNUSED auto ptr = pl031_device.release();
return status;
}
Pl031::Pl031(zx_device_t* parent, ddk::MmioBuffer mmio)
: RtcDeviceType(parent),
mmio_(std::move(mmio)),
regs_(reinterpret_cast<MMIO_PTR Pl031Regs*>(mmio_.get())) {}
void Pl031::Get(GetCompleter::Sync& completer) {
FidlRtc::Time rtc = SecondsToRtc(MmioRead32(&regs_->dr));
completer.Reply(rtc);
}
void Pl031::Set(FidlRtc::Time rtc, SetCompleter::Sync& completer) { completer.Reply(SetRtc(rtc)); }
zx_status_t Pl031::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
FidlRtc::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void Pl031::DdkRelease() { delete this; }
zx_status_t Pl031::SetRtc(FidlRtc::Time rtc) {
if (!IsRtcValid(rtc)) {
return ZX_ERR_OUT_OF_RANGE;
}
MmioWrite32(static_cast<uint32_t>(SecondsSinceEpoch(rtc)), &regs_->lr);
// Set the UTC offset.
const zx::time time_since_epoch = zx::time(SecondsSinceEpoch(rtc) * 1'000'000'000);
const zx::duration utc_offset = time_since_epoch - zx::clock::get_monotonic();
// TODO(fxb/31358): Replace get_root_resource().
const zx_status_t status = zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, utc_offset.get());
if (status != ZX_OK) {
zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!");
}
return ZX_OK;
}
zx_driver_ops_t pl031_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = Pl031::Bind,
};
} // namespace rtc
ZIRCON_DRIVER(pl031, rtc::pl031_driver_ops, "zircon", "0.1");