| /* |
| * 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" }, |
| {}, |
| }; |
| |
| const struct fdt_driver fdt_cppc_rpmi = { |
| .match_table = rpmi_cppc_match, |
| .init = rpmi_cppc_cold_init, |
| }; |