--- /dev/null
+/* Copyright 2019 SiFive, Inc */
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include <metal/machine/platform.h>
+
+#ifdef METAL_SIFIVE_RTC0
+
+#include <metal/drivers/sifive_rtc0.h>
+#include <metal/machine.h>
+
+#include <limits.h>
+
+/* RTCCFG */
+#define METAL_RTCCFG_RTCSCALE_MASK 0xF
+#define METAL_RTCCFG_ENALWAYS (1 << 12)
+#define METAL_RTCCFG_IP0 (1 << 28)
+
+/* RTCCMP0 */
+#define METAL_RTCCMP0_MAX UINT32_MAX
+
+#define RTC_REG(base, offset) (((unsigned long)base + offset))
+#define RTC_REGW(base, offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)RTC_REG(base, offset)))
+
+uint64_t __metal_driver_sifive_rtc0_get_rate(const struct metal_rtc *const rtc) {
+ const struct metal_clock *const clock = __metal_driver_sifive_rtc0_clock(rtc);
+ return metal_clock_get_rate_hz(clock);
+}
+
+uint64_t __metal_driver_sifive_rtc0_set_rate(const struct metal_rtc *const rtc, const uint64_t rate) {
+ const struct metal_clock *const clock = __metal_driver_sifive_rtc0_clock(rtc);
+ return metal_clock_get_rate_hz(clock);
+}
+
+uint64_t __metal_driver_sifive_rtc0_get_compare(const struct metal_rtc *const rtc) {
+ const uint64_t base = __metal_driver_sifive_rtc0_control_base(rtc);
+
+ const uint32_t shift = RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCFG) & METAL_RTCCFG_RTCSCALE_MASK;
+
+ return ((uint64_t)RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCMP0) << shift);
+}
+
+uint64_t __metal_driver_sifive_rtc0_set_compare(const struct metal_rtc *const rtc, const uint64_t compare) {
+ const uint64_t base = __metal_driver_sifive_rtc0_control_base(rtc);
+
+ /* Determine the bit shift and shifted value to store in rtccmp0/rtccfg.scale */
+ uint32_t shift = 0;
+ uint64_t comp_shifted = compare;
+ while (comp_shifted > METAL_RTCCMP0_MAX) {
+ shift += 1;
+ comp_shifted = comp_shifted >> shift;
+ }
+
+ /* Set the value of rtccfg.scale */
+ uint32_t cfg = RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCFG);
+ cfg &= ~(METAL_RTCCFG_RTCSCALE_MASK);
+ cfg |= (METAL_RTCCFG_RTCSCALE_MASK & shift);
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCFG) = cfg;
+
+ /* Set the value of rtccmp0 */
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCMP0) = (uint32_t) comp_shifted;
+
+ return __metal_driver_sifive_rtc0_get_compare(rtc);
+}
+
+uint64_t __metal_driver_sifive_rtc0_get_count(const struct metal_rtc *const rtc) {
+ const uint64_t base = __metal_driver_sifive_rtc0_control_base(rtc);
+
+ uint64_t count = RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCOUNTHI);
+ count <<= 32;
+ count |= RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCOUNTLO);
+
+ return count;
+}
+
+uint64_t __metal_driver_sifive_rtc0_set_count(const struct metal_rtc *const rtc, const uint64_t count) {
+ const uint64_t base = __metal_driver_sifive_rtc0_control_base(rtc);
+
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCOUNTHI) = (UINT_MAX & (count >> 32));
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCOUNTLO) = (UINT_MAX & count);
+
+ return __metal_driver_sifive_rtc0_get_count(rtc);
+}
+
+int __metal_driver_sifive_rtc0_run(const struct metal_rtc *const rtc, const enum metal_rtc_run_option option) {
+ const uint64_t base = __metal_driver_sifive_rtc0_control_base(rtc);
+
+ switch (option) {
+ default:
+ case METAL_RTC_STOP:
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCFG) &= ~(METAL_RTCCFG_ENALWAYS);
+ break;
+ case METAL_RTC_RUN:
+ RTC_REGW(base, METAL_SIFIVE_RTC0_RTCCFG) |= METAL_RTCCFG_ENALWAYS;
+ break;
+ }
+
+ return 0;
+}
+
+struct metal_interrupt *__metal_driver_sifive_rtc0_get_interrupt(const struct metal_rtc *const rtc) {
+ return __metal_driver_sifive_rtc0_interrupt_parent(rtc);
+}
+
+int __metal_driver_sifive_rtc0_get_interrupt_id(const struct metal_rtc *const rtc) {
+ return __metal_driver_sifive_rtc0_interrupt_line(rtc);
+}
+
+__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_rtc0) = {
+ .rtc.get_rate = __metal_driver_sifive_rtc0_get_rate,
+ .rtc.set_rate = __metal_driver_sifive_rtc0_set_rate,
+ .rtc.get_compare = __metal_driver_sifive_rtc0_get_compare,
+ .rtc.set_compare = __metal_driver_sifive_rtc0_set_compare,
+ .rtc.get_count = __metal_driver_sifive_rtc0_get_count,
+ .rtc.set_count = __metal_driver_sifive_rtc0_set_count,
+ .rtc.run = __metal_driver_sifive_rtc0_run,
+ .rtc.get_interrupt = __metal_driver_sifive_rtc0_get_interrupt,
+ .rtc.get_interrupt_id = __metal_driver_sifive_rtc0_get_interrupt_id,
+};
+
+#endif
+