| // Copyright 2022 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/aml-rtc/aml-rtc.h" |
| |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/driver/platform-device/cpp/pdev.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/time.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <ddktl/fidl.h> |
| |
| #include "src/devices/rtc/lib/rtc/include/librtc_llcpp.h" |
| |
| namespace rtc { |
| |
| zx_status_t AmlRtc::Bind(void* ctx, zx_device_t* device) { |
| uint32_t reg_val; |
| |
| zx::result pdev_client_end = |
| ddk::Device<void>::DdkConnectFidlProtocol<fuchsia_hardware_platform_device::Service::Device>( |
| device); |
| if (pdev_client_end.is_error()) { |
| zxlogf(ERROR, "Failed to connect to platform device: %s", pdev_client_end.status_string()); |
| return pdev_client_end.status_value(); |
| } |
| fdf::PDev pdev{std::move(pdev_client_end.value())}; |
| |
| zx::result mmio = pdev.MapMmio(0); |
| if (mmio.is_error()) { |
| zxlogf(ERROR, "Failed to map mmio: %s", mmio.status_string()); |
| return mmio.status_value(); |
| } |
| |
| mmio->SetBit<uint32_t>(RTC_OSC_SEL_BIT, RTC_CTRL); |
| |
| /* Set RTC osillator to freq_out to freq_in/((N0*M0+N1*M1)/(M0+M1)) */ |
| reg_val = mmio->Read32(RTC_OSCIN_CTRL0); |
| reg_val &= (~(0x3 << FREQ_OUT_SELECT)); |
| reg_val |= (0x1 << FREQ_OUT_SELECT); |
| /* Enable clock_in gate of osillator 24MHz */ |
| reg_val |= (1 << CLK_IN_GATE_EN); |
| /* N0 is set to 733, N1 is set to 732 by default */ |
| mmio->Write32(reg_val, RTC_OSCIN_CTRL0); |
| /* Set M0 to 2, M1 to 3, so freq_out = 32768 Hz */ |
| reg_val = mmio->Read32(RTC_OSCIN_CTRL1); |
| reg_val &= ~(0xfff); |
| reg_val |= (0x1 << CLK_DIV_M0); |
| reg_val &= ~(0xfff << CLK_DIV_M1); |
| reg_val |= (0x2 << CLK_DIV_M1); |
| mmio->Write32(reg_val, RTC_OSCIN_CTRL1); |
| |
| /* Enable RTC, which Requires a delay to take effect */ |
| /* Referring to the RTC code in Linux, the delay range is 100us~200us */ |
| /* Tested in fuchsia, A minimum 5us delay is required for the RTC to work correctly */ |
| mmio->SetBit<uint32_t>(RTC_ENABLE_BIT, RTC_CTRL); |
| usleep(5); |
| |
| auto amlrtc_device = std::make_unique<AmlRtc>(device, *std::move(mmio)); |
| |
| // Retrieve and sanitize the RTC value. Set the RTC to the value. |
| FidlRtc::wire::Time rtc = SecondsToRtc(MmioRead32(&amlrtc_device->regs_->real_time)); |
| rtc = SanitizeRtc(device, rtc); |
| zx_status_t status = amlrtc_device->SetRtc(rtc); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to set rtc: %s", zx_status_get_string(status)); |
| } |
| |
| status = amlrtc_device->DdkAdd(ddk::DeviceAddArgs("aml-rtc").set_proto_id(ZX_PROTOCOL_RTC)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "error adding device: %s", zx_status_get_string(status)); |
| return status; |
| } |
| // The object is owned by the DDK, now that it has been added. It will be deleted |
| // when the device is released. |
| [[maybe_unused]] auto ptr = amlrtc_device.release(); |
| |
| return status; |
| } |
| |
| AmlRtc::AmlRtc(zx_device_t* parent, fdf::MmioBuffer mmio) |
| : RtcDeviceType(parent), |
| mmio_(std::move(mmio)), |
| regs_(reinterpret_cast<MMIO_PTR AmlRtcRegs*>(mmio_.get())) {} |
| |
| void AmlRtc::Get(GetCompleter::Sync& completer) { |
| FidlRtc::wire::Time rtc = SecondsToRtc(MmioRead32(®s_->real_time)); |
| // TODO(https://fxbug.dev/42074113): Reply with error if RTC time is known to be invalid. |
| completer.ReplySuccess(rtc); |
| } |
| |
| void AmlRtc::Set2(Set2RequestView request, Set2Completer::Sync& completer) { |
| zx_status_t status{SetRtc(request->rtc)}; |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| } else { |
| completer.ReplySuccess(); |
| } |
| } |
| |
| void AmlRtc::DdkRelease() { delete this; } |
| |
| zx_status_t AmlRtc::SetRtc(FidlRtc::wire::Time rtc) { |
| if (!IsRtcValid(rtc)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| MmioWrite32(static_cast<uint32_t>(SecondsSinceEpoch(rtc)), ®s_->counter); |
| |
| return ZX_OK; |
| } |
| |
| zx_driver_ops_t amlrtc_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = AmlRtc::Bind, |
| }; |
| |
| } // namespace rtc |
| |
| ZIRCON_DRIVER(amlrtc, rtc::amlrtc_driver_ops, "zircon", "0.1"); |