mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2025-08-24 23:41:23 +01:00

Currently, the platform's timer device is tracked in two places: the core SBI implementation has `timer_dev`, and the FDT timer layer has `current_driver`. The latter is used for warm initialization of the timer device. However, this warm init is not specific to FDT-based platforms; other platforms call exactly the same functions from the same point in the boot sequence. The code is simplified and made common across platforms by treating warm init and exit as properties of the driver, not the platform. Then the platform's only role is to select and prepare a driver during cold boot. For now, only add a .warm_init hook, since none of the existing drivers need an .exit hook. It could be added in the future if needed. Signed-off-by: Samuel Holland <samuel.holland@sifive.com> Reviewed-by: Anup Patel <anup@brainfault.org>
171 lines
4.4 KiB
C
171 lines
4.4 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2021 Western Digital Corporation or its affiliates.
|
|
*
|
|
* Authors:
|
|
* Anup Patel <anup.patel@wdc.com>
|
|
*/
|
|
|
|
#include <libfdt.h>
|
|
#include <sbi/sbi_error.h>
|
|
#include <sbi/sbi_heap.h>
|
|
#include <sbi/sbi_list.h>
|
|
#include <sbi_utils/fdt/fdt_helper.h>
|
|
#include <sbi_utils/timer/fdt_timer.h>
|
|
#include <sbi_utils/timer/aclint_mtimer.h>
|
|
|
|
struct timer_mtimer_quirks {
|
|
bool is_clint;
|
|
unsigned int clint_mtime_offset;
|
|
bool clint_without_mtime;
|
|
bool has_64bit_mmio;
|
|
};
|
|
|
|
struct timer_mtimer_node {
|
|
struct sbi_dlist head;
|
|
struct aclint_mtimer_data data;
|
|
};
|
|
static SBI_LIST_HEAD(mtn_list);
|
|
|
|
static struct aclint_mtimer_data *mt_reference = NULL;
|
|
|
|
static int timer_mtimer_cold_init(const void *fdt, int nodeoff,
|
|
const struct fdt_match *match)
|
|
{
|
|
int rc;
|
|
unsigned long addr[2], size[2];
|
|
struct timer_mtimer_node *mtn, *n;
|
|
struct aclint_mtimer_data *mt;
|
|
const struct timer_mtimer_quirks *quirks = match->data;
|
|
bool is_clint = quirks && quirks->is_clint;
|
|
|
|
mtn = sbi_zalloc(sizeof(*mtn));
|
|
if (!mtn)
|
|
return SBI_ENOMEM;
|
|
mt = &mtn->data;
|
|
|
|
rc = fdt_parse_aclint_node(fdt, nodeoff, true, !is_clint,
|
|
&addr[0], &size[0], &addr[1], &size[1],
|
|
&mt->first_hartid, &mt->hart_count);
|
|
if (rc) {
|
|
sbi_free(mtn);
|
|
return rc;
|
|
}
|
|
mt->has_64bit_mmio = true;
|
|
mt->has_shared_mtime = false;
|
|
|
|
rc = fdt_parse_timebase_frequency(fdt, &mt->mtime_freq);
|
|
if (rc) {
|
|
sbi_free(mtn);
|
|
return rc;
|
|
}
|
|
|
|
if (is_clint) { /* SiFive CLINT */
|
|
/* Set CLINT addresses */
|
|
mt->mtimecmp_addr = addr[0] + ACLINT_DEFAULT_MTIMECMP_OFFSET;
|
|
mt->mtimecmp_size = ACLINT_DEFAULT_MTIMECMP_SIZE;
|
|
if (!quirks->clint_without_mtime) {
|
|
mt->mtime_addr = addr[0] + ACLINT_DEFAULT_MTIME_OFFSET;
|
|
mt->mtime_size = size[0] - mt->mtimecmp_size;
|
|
/* Adjust MTIMER address and size for CLINT device */
|
|
mt->mtime_addr += quirks->clint_mtime_offset;
|
|
mt->mtime_size -= quirks->clint_mtime_offset;
|
|
} else {
|
|
mt->mtime_addr = mt->mtime_size = 0;
|
|
}
|
|
mt->mtimecmp_addr += quirks->clint_mtime_offset;
|
|
} else { /* RISC-V ACLINT MTIMER */
|
|
/* Set ACLINT MTIMER addresses */
|
|
mt->mtime_addr = addr[0];
|
|
mt->mtime_size = size[0];
|
|
mt->mtimecmp_addr = addr[1];
|
|
mt->mtimecmp_size = size[1];
|
|
}
|
|
|
|
/* Apply additional quirks */
|
|
if (quirks) {
|
|
mt->has_64bit_mmio = quirks->has_64bit_mmio;
|
|
}
|
|
|
|
/* Check if MTIMER device has shared MTIME address */
|
|
if (mt->mtime_size) {
|
|
mt->has_shared_mtime = false;
|
|
sbi_list_for_each_entry(n, &mtn_list, head) {
|
|
if (n->data.mtime_addr == mt->mtime_addr) {
|
|
mt->has_shared_mtime = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Assume shared time CSR */
|
|
mt->has_shared_mtime = true;
|
|
}
|
|
|
|
/* Initialize the MTIMER device */
|
|
rc = aclint_mtimer_cold_init(mt, mt_reference);
|
|
if (rc) {
|
|
sbi_free(mtn);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Select first MTIMER device with no associated HARTs as our
|
|
* reference MTIMER device. This is only a temporary strategy
|
|
* of selecting reference MTIMER device. In future, we might
|
|
* define an optional DT property or some other mechanism to
|
|
* help us select the reference MTIMER device.
|
|
*/
|
|
if (!mt->hart_count && !mt_reference) {
|
|
mt_reference = mt;
|
|
/*
|
|
* Set reference for already propbed MTIMER devices
|
|
* with non-shared MTIME
|
|
*/
|
|
sbi_list_for_each_entry(n, &mtn_list, head) {
|
|
if (!n->data.has_shared_mtime)
|
|
aclint_mtimer_set_reference(&n->data, mt);
|
|
}
|
|
}
|
|
|
|
/* Explicitly sync-up MTIMER devices not associated with any HARTs */
|
|
if (!mt->hart_count)
|
|
aclint_mtimer_sync(mt);
|
|
|
|
sbi_list_add_tail(&mtn->head, &mtn_list);
|
|
return 0;
|
|
}
|
|
|
|
static const struct timer_mtimer_quirks sifive_clint_quirks = {
|
|
.is_clint = true,
|
|
.clint_mtime_offset = CLINT_MTIMER_OFFSET,
|
|
.has_64bit_mmio = true,
|
|
};
|
|
|
|
static const struct timer_mtimer_quirks thead_clint_quirks = {
|
|
.is_clint = true,
|
|
.clint_mtime_offset = CLINT_MTIMER_OFFSET,
|
|
.clint_without_mtime = true,
|
|
};
|
|
|
|
static const struct timer_mtimer_quirks thead_aclint_quirks = {
|
|
.has_64bit_mmio = false,
|
|
};
|
|
|
|
static const struct fdt_match timer_mtimer_match[] = {
|
|
{ .compatible = "riscv,clint0", .data = &sifive_clint_quirks },
|
|
{ .compatible = "sifive,clint0", .data = &sifive_clint_quirks },
|
|
{ .compatible = "thead,c900-clint", .data = &thead_clint_quirks },
|
|
{ .compatible = "thead,c900-aclint-mtimer",
|
|
.data = &thead_aclint_quirks },
|
|
{ .compatible = "riscv,aclint-mtimer" },
|
|
{ },
|
|
};
|
|
|
|
struct fdt_timer fdt_timer_mtimer = {
|
|
.match_table = timer_mtimer_match,
|
|
.cold_init = timer_mtimer_cold_init,
|
|
.warm_init = NULL,
|
|
.exit = NULL,
|
|
};
|