/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Western Digital Corporation or its affiliates. * * Authors: * Anup Patel */ #include #include #include #include #include #include #include #include #include #include static struct aclint_mtimer_data *mtimer_hartid2data[SBI_HARTMASK_MAX_BITS]; #if __riscv_xlen != 32 static u64 mtimer_time_rd64(volatile u64 *addr) { return readq_relaxed(addr); } static void mtimer_time_wr64(bool timecmp, u64 value, volatile u64 *addr) { writeq_relaxed(value, addr); } #endif static u64 mtimer_time_rd32(volatile u64 *addr) { u32 lo, hi; do { hi = readl_relaxed((u32 *)addr + 1); lo = readl_relaxed((u32 *)addr); } while (hi != readl_relaxed((u32 *)addr + 1)); return ((u64)hi << 32) | (u64)lo; } static void mtimer_time_wr32(bool timecmp, u64 value, volatile u64 *addr) { writel_relaxed((timecmp) ? -1U : 0U, (void *)(addr)); writel_relaxed((u32)(value >> 32), (char *)(addr) + 0x04); writel_relaxed((u32)value, (void *)(addr)); } static u64 mtimer_value(void) { struct aclint_mtimer_data *mt = mtimer_hartid2data[current_hartid()]; u64 *time_val = (void *)mt->mtime_addr; /* Read MTIMER Time Value */ return mt->time_rd(time_val); } static void mtimer_event_stop(void) { u32 target_hart = current_hartid(); struct aclint_mtimer_data *mt = mtimer_hartid2data[target_hart]; u64 *time_cmp = (void *)mt->mtimecmp_addr; /* Clear MTIMER Time Compare */ mt->time_wr(true, -1ULL, &time_cmp[target_hart - mt->first_hartid]); } static void mtimer_event_start(u64 next_event) { u32 target_hart = current_hartid(); struct aclint_mtimer_data *mt = mtimer_hartid2data[target_hart]; u64 *time_cmp = (void *)mt->mtimecmp_addr; /* Program MTIMER Time Compare */ mt->time_wr(true, next_event, &time_cmp[target_hart - mt->first_hartid]); } static struct sbi_timer_device mtimer = { .name = "aclint-mtimer", .timer_value = mtimer_value, .timer_event_start = mtimer_event_start, .timer_event_stop = mtimer_event_stop }; void aclint_mtimer_sync(struct aclint_mtimer_data *mt) { u64 v1, v2, mv, delta; u64 *mt_time_val, *ref_time_val; struct aclint_mtimer_data *reference; /* Sync-up non-shared MTIME if reference is available */ if (mt->has_shared_mtime || !mt->time_delta_reference) return; reference = mt->time_delta_reference; mt_time_val = (void *)mt->mtime_addr; ref_time_val = (void *)reference->mtime_addr; if (!atomic_raw_xchg_ulong(&mt->time_delta_computed, 1)) { v1 = mt->time_rd(mt_time_val); mv = reference->time_rd(ref_time_val); v2 = mt->time_rd(mt_time_val); delta = mv - ((v1 / 2) + (v2 / 2)); mt->time_wr(false, mt->time_rd(mt_time_val) + delta, mt_time_val); } } void aclint_mtimer_set_reference(struct aclint_mtimer_data *mt, struct aclint_mtimer_data *ref) { if (!mt || !ref || mt == ref) return; mt->time_delta_reference = ref; mt->time_delta_computed = 0; } int aclint_mtimer_warm_init(void) { u64 *mt_time_cmp; u32 target_hart = current_hartid(); struct aclint_mtimer_data *mt = mtimer_hartid2data[target_hart]; if (!mt) return SBI_ENODEV; /* Sync-up MTIME register */ aclint_mtimer_sync(mt); /* Clear Time Compare */ mt_time_cmp = (void *)mt->mtimecmp_addr; mt->time_wr(true, -1ULL, &mt_time_cmp[target_hart - mt->first_hartid]); return 0; } static int aclint_mtimer_add_regions(unsigned long addr, unsigned long size) { #define MTIMER_ADD_REGION_ALIGN 0x1000 int rc; unsigned long pos, end, rsize; struct sbi_domain_memregion reg; pos = addr; end = addr + size; while (pos < end) { rsize = pos & (MTIMER_ADD_REGION_ALIGN - 1); if (rsize) rsize = 1UL << sbi_ffs(pos); else rsize = ((end - pos) < MTIMER_ADD_REGION_ALIGN) ? (end - pos) : MTIMER_ADD_REGION_ALIGN; sbi_domain_memregion_init(pos, rsize, SBI_DOMAIN_MEMREGION_MMIO, ®); rc = sbi_domain_root_add_memregion(®); if (rc) return rc; pos += rsize; } return 0; } int aclint_mtimer_cold_init(struct aclint_mtimer_data *mt, struct aclint_mtimer_data *reference) { u32 i; int rc; /* Sanity checks */ if (!mt || !mt->mtime_size || (mt->hart_count && !mt->mtimecmp_size) || (mt->mtime_addr & (ACLINT_MTIMER_ALIGN - 1)) || (mt->mtime_size & (ACLINT_MTIMER_ALIGN - 1)) || (mt->mtimecmp_addr & (ACLINT_MTIMER_ALIGN - 1)) || (mt->mtimecmp_size & (ACLINT_MTIMER_ALIGN - 1)) || (mt->first_hartid >= SBI_HARTMASK_MAX_BITS) || (mt->hart_count > ACLINT_MTIMER_MAX_HARTS)) return SBI_EINVAL; if (reference && mt->mtime_freq != reference->mtime_freq) return SBI_EINVAL; /* Initialize private data */ aclint_mtimer_set_reference(mt, reference); mt->time_rd = mtimer_time_rd32; mt->time_wr = mtimer_time_wr32; /* Override read/write accessors for 64bit MMIO */ #if __riscv_xlen != 32 if (mt->has_64bit_mmio) { mt->time_rd = mtimer_time_rd64; mt->time_wr = mtimer_time_wr64; } #endif /* Update MTIMER hartid table */ for (i = 0; i < mt->hart_count; i++) mtimer_hartid2data[mt->first_hartid + i] = mt; /* Add MTIMER regions to the root domain */ if (mt->mtime_addr == (mt->mtimecmp_addr + mt->mtimecmp_size)) { rc = aclint_mtimer_add_regions(mt->mtimecmp_addr, mt->mtime_size + mt->mtimecmp_size); if (rc) return rc; } else if (mt->mtimecmp_addr == (mt->mtime_addr + mt->mtime_size)) { rc = aclint_mtimer_add_regions(mt->mtime_addr, mt->mtime_size + mt->mtimecmp_size); if (rc) return rc; } else { rc = aclint_mtimer_add_regions(mt->mtime_addr, mt->mtime_size); if (rc) return rc; rc = aclint_mtimer_add_regions(mt->mtimecmp_addr, mt->mtimecmp_size); if (rc) return rc; } mtimer.timer_freq = mt->mtime_freq; sbi_timer_set_device(&mtimer); return 0; }