blob: 18063b7ed98bf3cd0edfb5e3cc6b5eebec42f2ba [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024, James Raphael Tiovalen <jamestiotio@gmail.com>
*/
#include <libcflat.h>
#include <devicetree.h>
#include <limits.h>
#include <asm/csr.h>
#include <asm/delay.h>
#include <asm/isa.h>
#include <asm/sbi.h>
#include <asm/setup.h>
#include <asm/smp.h>
#include <asm/timer.h>
void timer_get_frequency(void)
{
const struct fdt_property *prop;
u32 *data;
int cpus, len;
assert_msg(dt_available(), "ACPI not yet supported");
const void *fdt = dt_fdt();
cpus = fdt_path_offset(fdt, "/cpus");
assert(cpus >= 0);
prop = fdt_get_property(fdt, cpus, "timebase-frequency", &len);
assert(prop != NULL && len == 4);
data = (u32 *)prop->data;
timebase_frequency = fdt32_to_cpu(*data);
}
void timer_start(unsigned long duration_us)
{
uint64_t next = timer_get_cycles() + usec_to_cycles((uint64_t)duration_us);
if (cpu_has_extension(smp_processor_id(), ISA_SSTC)) {
csr_write(CSR_STIMECMP, (unsigned long)next);
if (__riscv_xlen == 32)
csr_write(CSR_STIMECMPH, (unsigned long)(next >> 32));
} else if (sbi_probe(SBI_EXT_TIME)) {
struct sbiret ret = sbi_set_timer(next);
assert(ret.error == SBI_SUCCESS);
assert(!(next >> 32));
} else {
assert_msg(false, "No timer to start!");
}
}
void timer_stop(void)
{
if (cpu_has_extension(smp_processor_id(), ISA_SSTC)) {
/*
* Subtract one from ULONG_MAX to workaround QEMU using that
* exact number to decide *not* to update the timer. IOW, if
* we used ULONG_MAX, then we wouldn't stop the timer at all,
* but one less is still a big number ("infinity") and it gets
* QEMU to do what we want.
*/
csr_write(CSR_STIMECMP, ULONG_MAX - 1);
if (__riscv_xlen == 32)
csr_write(CSR_STIMECMPH, ULONG_MAX - 1);
} else if (sbi_probe(SBI_EXT_TIME)) {
struct sbiret ret = sbi_set_timer(ULONG_MAX);
assert(ret.error == SBI_SUCCESS);
} else {
assert_msg(false, "No timer to stop!");
}
}