| // 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!"); | |
| } | |
| } |