Files
opensbi/lib/utils/cppc/fdt_cppc_rpmi.c
Samuel Holland 434add551c lib: utils: Initialize miscellaneous drivers in one pass
For driver subsystems that are not tightly integrated into the OpenSBI
init sequence, it is not important that the drivers are initialized in
any particular order. By putting all of these drivers in one array, they
can all be initialized with a single pass through the devicetree. This
saves about 10 ms of boot time on HiFive Unmatched.

Signed-off-by: Samuel Holland <samuel.holland@sifive.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
2025-02-12 21:39:25 +05:30

375 lines
9.9 KiB
C

/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2024 Ventana Micro Systems Inc.
*
* Authors:
* Subrahmanya Lingappa <slingappa@ventanamicro.com>
*/
#include <libfdt.h>
#include <sbi/riscv_io.h>
#include <sbi/sbi_cppc.h>
#include <sbi/sbi_ecall_interface.h>
#include <sbi/sbi_scratch.h>
#include <sbi_utils/fdt/fdt_driver.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/mailbox/fdt_mailbox.h>
#include <sbi_utils/mailbox/rpmi_mailbox.h>
/**
* Per hart RPMI CPPC fast channel size (bytes)
* PASSIVE MODE:
* 0x0: DESIRED_PERFORMANCE (4-byte)
* 0x4: __RESERVED (4-byte)
* ACTIVE MODE: (not supported yet)
* 0x0: MINIMUM PERFORMANCE (4-byte)
* 0x4: MAXIMUM PERFORMANCE (4-byte)
*/
#define RPMI_CPPC_HART_FASTCHAN_SIZE 0x8
struct rpmi_cppc {
struct mbox_chan *chan;
bool fc_supported;
bool fc_db_supported;
enum rpmi_cppc_fast_channel_db_width fc_db_width;
enum rpmi_cppc_fast_channel_cppc_mode mode;
ulong fc_perf_request_addr;
ulong fc_perf_feedback_addr;
ulong fc_db_addr;
u64 fc_db_setmask;
u64 fc_db_preservemask;
};
static unsigned long rpmi_cppc_offset;
static struct rpmi_cppc *rpmi_cppc_get_pointer(u32 hartid)
{
struct sbi_scratch *scratch;
scratch = sbi_hartid_to_scratch(hartid);
if (!scratch || !rpmi_cppc_offset)
return NULL;
return sbi_scratch_offset_ptr(scratch, rpmi_cppc_offset);
}
static void rpmi_cppc_fc_db_trigger(struct rpmi_cppc *cppc)
{
u8 db_val_u8 = 0;
u16 db_val_u16 = 0;
u32 db_val_u32 = 0;
u64 db_val_u64 = 0;
switch (cppc->fc_db_width) {
case RPMI_CPPC_FAST_CHANNEL_DB_WIDTH_8:
db_val_u8 = readb((void *)cppc->fc_db_addr);
db_val_u8 = (u8)cppc->fc_db_setmask |
(db_val_u8 & (u8)cppc->fc_db_preservemask);
writeb(db_val_u8, (void *)cppc->fc_db_addr);
break;
case RPMI_CPPC_FAST_CHANNEL_DB_WIDTH_16:
db_val_u16 = readw((void *)cppc->fc_db_addr);
db_val_u16 = (u16)cppc->fc_db_setmask |
(db_val_u16 & (u16)cppc->fc_db_preservemask);
writew(db_val_u16, (void *)cppc->fc_db_addr);
break;
case RPMI_CPPC_FAST_CHANNEL_DB_WIDTH_32:
db_val_u32 = readl((void *)cppc->fc_db_addr);
db_val_u32 = (u32)cppc->fc_db_setmask |
(db_val_u32 & (u32)cppc->fc_db_preservemask);
writel(db_val_u32, (void *)cppc->fc_db_addr);
break;
case RPMI_CPPC_FAST_CHANNEL_DB_WIDTH_64:
#if __riscv_xlen != 32
db_val_u64 = readq((void *)cppc->fc_db_addr);
db_val_u64 = cppc->fc_db_setmask |
(db_val_u64 & cppc->fc_db_preservemask);
writeq(db_val_u64, (void *)cppc->fc_db_addr);
#else
db_val_u64 = readl((void *)(cppc->fc_db_addr + 4));
db_val_u64 <<= 32;
db_val_u64 |= readl((void *)cppc->fc_db_addr);
db_val_u64 = cppc->fc_db_setmask |
(db_val_u64 & cppc->fc_db_preservemask);
writel(db_val_u64, (void *)cppc->fc_db_addr);
writel(db_val_u64 >> 32, (void *)(cppc->fc_db_addr + 4));
#endif
break;
default:
break;
}
}
static int rpmi_cppc_read(unsigned long reg, u64 *val)
{
int rc = SBI_SUCCESS;
struct rpmi_cppc_read_reg_req req;
struct rpmi_cppc_read_reg_resp resp;
struct rpmi_cppc *cppc;
req.hart_id = current_hartid();
req.reg_id = reg;
cppc = rpmi_cppc_get_pointer(req.hart_id);
rc = rpmi_normal_request_with_status(
cppc->chan, RPMI_CPPC_SRV_READ_REG,
&req, rpmi_u32_count(req), rpmi_u32_count(req),
&resp, rpmi_u32_count(resp), rpmi_u32_count(resp));
if (rc)
return rc;
#if __riscv_xlen == 32
*val = resp.data_lo;
#else
*val = (u64)resp.data_hi << 32 | resp.data_lo;
#endif
return rc;
}
static int rpmi_cppc_write(unsigned long reg, u64 val)
{
int rc = SBI_SUCCESS;
u32 hart_id = current_hartid();
struct rpmi_cppc_write_reg_req req;
struct rpmi_cppc_write_reg_resp resp;
struct rpmi_cppc *cppc = rpmi_cppc_get_pointer(hart_id);
if (reg != SBI_CPPC_DESIRED_PERF || !cppc->fc_supported) {
req.hart_id = hart_id;
req.reg_id = reg;
req.data_lo = val & 0xFFFFFFFF;
req.data_hi = val >> 32;
rc = rpmi_normal_request_with_status(
cppc->chan, RPMI_CPPC_SRV_WRITE_REG,
&req, rpmi_u32_count(req), rpmi_u32_count(req),
&resp, rpmi_u32_count(resp), rpmi_u32_count(resp));
} else {
/* use fast path writes for desired_perf in passive mode */
writel((u32)val, (void *)cppc->fc_perf_request_addr);
if (cppc->fc_db_supported)
rpmi_cppc_fc_db_trigger(cppc);
}
return rc;
}
static int rpmi_cppc_probe(unsigned long reg)
{
int rc;
struct rpmi_cppc *cppc;
struct rpmi_cppc_probe_resp resp;
struct rpmi_cppc_probe_req req;
req.hart_id = current_hartid();
req.reg_id = reg;
cppc = rpmi_cppc_get_pointer(req.hart_id);
if (!cppc)
return SBI_ENOSYS;
rc = rpmi_normal_request_with_status(
cppc->chan, RPMI_CPPC_SRV_PROBE_REG,
&req, rpmi_u32_count(req), rpmi_u32_count(req),
&resp, rpmi_u32_count(resp), rpmi_u32_count(resp));
if (rc)
return rc;
return resp.reg_len;
}
static struct sbi_cppc_device sbi_rpmi_cppc = {
.name = "rpmi-cppc",
.cppc_read = rpmi_cppc_read,
.cppc_write = rpmi_cppc_write,
.cppc_probe = rpmi_cppc_probe,
};
static int rpmi_cppc_update_hart_scratch(struct mbox_chan *chan)
{
int rc, i;
bool fc_supported = false;
bool fc_db_supported = false;
struct rpmi_cppc_hart_list_req req;
struct rpmi_cppc_hart_list_resp resp;
struct rpmi_cppc_get_fastchan_offset_req hfreq;
struct rpmi_cppc_get_fastchan_offset_resp hfresp;
struct rpmi_cppc_get_fastchan_region_resp fresp;
enum rpmi_cppc_fast_channel_db_width fc_db_width = 0;
enum rpmi_cppc_fast_channel_cppc_mode cppc_mode = 0;
struct rpmi_cppc *cppc;
unsigned long fc_region_addr = 0;
unsigned long fc_region_size = 0;
unsigned long fc_db_addr = 0;
u64 fc_db_setmask = 0;
u64 fc_db_preservemask = 0;
rc = rpmi_normal_request_with_status(
chan, RPMI_CPPC_SRV_GET_FAST_CHANNEL_REGION,
NULL, 0, 0,
&fresp, rpmi_u32_count(fresp), rpmi_u32_count(fresp));
if (rc && rc != SBI_ENOTSUPP)
return rc;
/* At this point fast channel availability is confirmed */
fc_supported = (rc != SBI_ENOTSUPP)? true : false;
/* If fast channel is supported, add the fast channel
* region in root domain as MMIO RW. And, get the doorbell
* information from the response */
if (fc_supported) {
#if __riscv_xlen == 32
fc_region_addr = fresp.region_addr_lo;
fc_region_size = fresp.region_size_lo;
fc_db_addr = fresp.db_addr_lo;
#else
fc_region_addr = (ulong)fresp.region_addr_hi << 32 |
fresp.region_addr_lo;
fc_region_size = (ulong)fresp.region_size_hi << 32 |
fresp.region_size_lo;
fc_db_addr = (ulong)fresp.db_addr_hi << 32 | fresp.db_addr_lo;
#endif
rc = sbi_domain_root_add_memrange(fc_region_addr,
fc_region_size,
RPMI_CPPC_HART_FASTCHAN_SIZE,
(SBI_DOMAIN_MEMREGION_MMIO |
SBI_DOMAIN_MEMREGION_M_READABLE |
SBI_DOMAIN_MEMREGION_M_WRITABLE));
if (rc)
return rc;
cppc_mode = (fresp.flags & RPMI_CPPC_FAST_CHANNEL_CPPC_MODE_MASK) >>
RPMI_CPPC_FAST_CHANNEL_CPPC_MODE_POS;
fc_db_supported = fresp.flags &
RPMI_CPPC_FAST_CHANNEL_FLAGS_DB_SUPPORTED;
fc_db_width = (fresp.flags &
RPMI_CPPC_FAST_CHANNEL_FLAGS_DB_WIDTH_MASK) >>
RPMI_CPPC_FAST_CHANNEL_FLAGS_DB_WIDTH_POS;
fc_db_setmask = (u64)fresp.db_setmask_hi << 32 |
fresp.db_setmask_lo;
fc_db_preservemask = (u64)fresp.db_preservemask_hi << 32 |
fresp.db_preservemask_lo;
}
/* Get the hart list and depending on the fast channel support
* initialize the per hart cppc structure */
req.start_index = 0;
do {
rc = rpmi_normal_request_with_status(
chan, RPMI_CPPC_SRV_GET_HART_LIST,
&req, rpmi_u32_count(req), rpmi_u32_count(req),
&resp, rpmi_u32_count(resp), rpmi_u32_count(resp));
if (rc)
return rc;
/* For each returned harts, get their fastchan offset and
* complete the initialization of per hart cppc structure */
for (i = 0; i < resp.returned; i++) {
cppc = rpmi_cppc_get_pointer(resp.hartid[i]);
if (!cppc)
return SBI_ENOSYS;
cppc->chan = chan;
cppc->mode = cppc_mode;
cppc->fc_supported = fc_supported;
if (fc_supported) {
hfreq.hart_id = resp.hartid[i];
rc = rpmi_normal_request_with_status(
chan,
RPMI_CPPC_SRV_GET_FAST_CHANNEL_OFFSET,
&hfreq,
rpmi_u32_count(hfreq),
rpmi_u32_count(hfreq),
&hfresp,
rpmi_u32_count(hfresp),
rpmi_u32_count(hfresp));
if (rc)
continue;
#if __riscv_xlen == 32
cppc->fc_perf_request_addr = fc_region_addr +
hfresp.fc_perf_request_offset_lo;
cppc->fc_perf_feedback_addr = fc_region_addr +
hfresp.fc_perf_feedback_offset_lo;
#else
cppc->fc_perf_request_addr = fc_region_addr +
((ulong)hfresp.fc_perf_request_offset_hi << 32 |
hfresp.fc_perf_request_offset_lo);
cppc->fc_perf_feedback_addr = fc_region_addr +
((ulong)hfresp.fc_perf_feedback_offset_hi << 32 |
hfresp.fc_perf_feedback_offset_lo);
#endif
cppc->fc_db_supported = fc_db_supported;
cppc->fc_db_addr = fc_db_addr;
cppc->fc_db_width = fc_db_width;
cppc->fc_db_setmask = fc_db_setmask;
cppc->fc_db_preservemask = fc_db_preservemask;
}
else {
cppc->fc_perf_request_addr = 0;
cppc->fc_perf_feedback_addr = 0;
cppc->fc_db_supported = 0;
cppc->fc_db_addr = 0;
cppc->fc_db_width = 0;
cppc->fc_db_setmask = 0;
cppc->fc_db_preservemask = 0;
}
}
req.start_index += resp.returned;
} while (resp.remaining);
return 0;
}
static int rpmi_cppc_cold_init(const void *fdt, int nodeoff,
const struct fdt_match *match)
{
int rc;
struct mbox_chan *chan;
if (!rpmi_cppc_offset) {
rpmi_cppc_offset =
sbi_scratch_alloc_type_offset(struct rpmi_cppc);
if (!rpmi_cppc_offset)
return SBI_ENOMEM;
}
/*
* If channel request failed then other end does not support
* CPPC service group so do nothing.
*/
rc = fdt_mailbox_request_chan(fdt, nodeoff, 0, &chan);
if (rc)
return SBI_ENODEV;
/* Update per-HART scratch space */
rc = rpmi_cppc_update_hart_scratch(chan);
if (rc)
return rc;
sbi_cppc_set_device(&sbi_rpmi_cppc);
return 0;
}
static const struct fdt_match rpmi_cppc_match[] = {
{ .compatible = "riscv,rpmi-cppc" },
{},
};
struct fdt_driver fdt_cppc_rpmi = {
.match_table = rpmi_cppc_match,
.init = rpmi_cppc_cold_init,
.experimental = true,
};