diff options
| author | Harrison Mutai <harrison.mutai@arm.com> | 2026-07-31 08:36:38 +0000 |
|---|---|---|
| committer | TrustedFirmware Code Review <review@review.trustedfirmware.org> | 2026-07-31 08:36:38 +0000 |
| commit | 92d930f35917c39042b8cf43423c695cc2e2589a (patch) | |
| tree | 32f1a9a2b6315d51076ad1abfd1db98a3d6d4805 /drivers | |
| parent | 994af9e0606a3f5f225bd8c75d5bc9264668fd0c (diff) | |
| parent | 456e76541e31d908afef6eda64191654fd546c1c (diff) | |
| download | arm-trusted-firmware-92d930f35917c39042b8cf43423c695cc2e2589a.tar.gz arm-trusted-firmware-92d930f35917c39042b8cf43423c695cc2e2589a.zip | |
Merge changes Ib7bbee3a,Iad97a8e6,I559b1a76,Id06acacc,I73c48adb into integration
* changes:
feat(lemans_evk): enable RPMh driver
feat(qti): add RPMh command service driver
feat(lemans_evk): enable power utils driver
feat(qti): add power utils (vlvl <-> hlvl) driver
feat(qti): add aux data APIs to CMD DB driver
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/qti/cmd_db/cmd_db.c | 60 | ||||
| -rw-r--r-- | drivers/qti/pwr_utils/pwr_utils.c | 158 | ||||
| -rw-r--r-- | drivers/qti/pwr_utils/pwr_utils.mk | 16 | ||||
| -rw-r--r-- | drivers/qti/rpmh/lemans/rsc_regs.h | 91 | ||||
| -rw-r--r-- | drivers/qti/rpmh/rpmh.mk | 18 | ||||
| -rw-r--r-- | drivers/qti/rpmh/rpmh_client.c | 228 |
6 files changed, 566 insertions, 5 deletions
diff --git a/drivers/qti/cmd_db/cmd_db.c b/drivers/qti/cmd_db/cmd_db.c index 3c01e1976..fc44f8657 100644 --- a/drivers/qti/cmd_db/cmd_db.c +++ b/drivers/qti/cmd_db/cmd_db.c @@ -85,7 +85,14 @@ static uint64_t res_id_to_u64(const char *res_id) return val; } -uint32_t cmd_db_query_addr(const char *res_id) +/* + * Look up a resource entry by its identifier string. On success returns a + * pointer to the matching entry and, if @info_out is non-NULL, the owning + * slave ID info block. Returns NULL when the resource is not found. + */ +static const struct cmd_db_entry *cmd_db_find_entry( + const char *res_id, + const struct cmd_db_slv_id_info **info_out) { uint64_t key; unsigned int slv; @@ -94,12 +101,12 @@ uint32_t cmd_db_query_addr(const char *res_id) const struct cmd_db_entry *entry; if (res_id == NULL) { - return 0U; + return NULL; } if (g_cmd_db == NULL) { if (cmd_db_init() != 0) { - return 0U; + return NULL; } } @@ -119,10 +126,53 @@ uint32_t cmd_db_query_addr(const char *res_id) idx * sizeof(struct cmd_db_entry)); if (entry->res_id == key) { - return entry->addr; + if (info_out != NULL) { + *info_out = info; + } + return entry; } } } - return 0U; + return NULL; +} + +uint32_t cmd_db_query_addr(const char *res_id) +{ + const struct cmd_db_entry *entry = cmd_db_find_entry(res_id, NULL); + + return (entry != NULL) ? entry->addr : 0U; +} + +uint32_t cmd_db_query_len(const char *res_id) +{ + const struct cmd_db_entry *entry = cmd_db_find_entry(res_id, NULL); + + return (entry != NULL) ? (uint32_t)entry->len : 0U; +} + +int cmd_db_query_aux_data(const char *res_id, uint8_t *len, uint8_t *data) +{ + const struct cmd_db_slv_id_info *info = NULL; + const struct cmd_db_entry *entry; + const uint8_t *src; + uint32_t copy_len; + + if ((len == NULL) || (data == NULL)) { + return -1; + } + + entry = cmd_db_find_entry(res_id, &info); + if ((entry == NULL) || (entry->len == 0U)) { + return -1; + } + + /* Copy back at most what the caller's buffer can hold */ + copy_len = (*len < entry->len) ? (uint32_t)*len : (uint32_t)entry->len; + + src = g_cmd_db->data + info->data_offset + entry->offset; + memcpy(data, src, copy_len); + + *len = (uint8_t)copy_len; + return 0; } diff --git a/drivers/qti/pwr_utils/pwr_utils.c b/drivers/qti/pwr_utils/pwr_utils.c new file mode 100644 index 000000000..cd7d1790d --- /dev/null +++ b/drivers/qti/pwr_utils/pwr_utils.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * pwr_utils.c + * + * Implements the APIs to convert SW corners/levels (vlvl) to HW corners/levels + * (hlvl) and vice versa, using the per-resource aux data stored in the RPMh + * command DB. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include <drivers/qti/cmd_db/cmd_db.h> +#include <drivers/qti/pwr_utils/pwr_utils.h> +#include <lib/utils_def.h> + +/* Max aux data length (in bytes) supported per resource. */ +#define MAX_AUX_DATA_LEN 32U + +/* Describes the supported vlvl<->hlvl mapping for a single rail resource. */ +struct pwr_utils_lvl_res { + const char *name; /* resource name, e.g. "cx.lvl", "mx.lvl" */ + uint16_t *vlvls; /* supported vlvls, indexed by hlvl */ + size_t count; /* number of valid entries in vlvls */ +}; + +/* + * Target specific rail resources. + * + * This information would normally live in a dedicated target file or devcfg, + * but the power utils are meant to be light on code footprint, so it is kept + * here. Targets that share the same set of rail resources can share this list. + */ +static struct pwr_utils_lvl_res resource_list[] = { + {"cx.lvl", NULL, 0}, + {"mx.lvl", NULL, 0}, + {"ebi.lvl", NULL, 0}, + {"lcx.lvl", NULL, 0}, + {"lmx.lvl", NULL, 0}, + {"gfx.lvl", NULL, 0}, + {"mss.lvl", NULL, 0}, + {"ddr.lvl", NULL, 0}, + {"xo.lvl", NULL, 0}, + {"mmcx.lvl", NULL, 0}, +}; + +/* Backing store for the per-resource vlvl tables (no malloc available). */ +static uint16_t lvl_buf[512]; + +static bool valid_resource_idx(int resource_idx) +{ + return (resource_idx >= 0) && + ((size_t)resource_idx < ARRAY_SIZE(resource_list)); +} + +void qti_pwr_utils_init(void) +{ + size_t used = 0U; + size_t i; + + for (i = 0U; i < ARRAY_SIZE(resource_list); i++) { + struct pwr_utils_lvl_res *res = &resource_list[i]; + uint32_t data_len = cmd_db_query_len(res->name); + uint8_t aux_len = (uint8_t)data_len; + size_t avail, j; + + /* No aux data: leave vlvls NULL and count 0. */ + if (data_len == 0U) { + continue; + } + + assert(data_len <= MAX_AUX_DATA_LEN); + + /* Carve the resource's table out of the static buffer. */ + assert((used + (data_len / sizeof(uint16_t))) <= + ARRAY_SIZE(lvl_buf)); + res->vlvls = &lvl_buf[used]; + + cmd_db_query_aux_data(res->name, &aux_len, + (uint8_t *)res->vlvls); + assert(data_len == (uint32_t)aux_len); + + /* + * Count the valid levels. A zero entry past index 0 marks the + * end of the table. + */ + avail = data_len / sizeof(uint16_t); + for (j = 0U; j < avail; j++) { + if ((j != 0U) && (res->vlvls[j] == 0U)) { + break; + } + } + + res->count = j; + used += avail; + } +} + +int pwr_utils_lvl_resource_idx(const char *res_name) +{ + size_t i; + + if (res_name == NULL) { + return -1; + } + + for (i = 0U; i < ARRAY_SIZE(resource_list); i++) { + if (strcmp(resource_list[i].name, res_name) == 0) { + return (int)i; + } + } + + return -1; /* Resource not found */ +} + +static int pwr_utils_hlvl(int resource_idx, int vlvl, int *mapped_vlvl) +{ + const struct pwr_utils_lvl_res *res; + int discard; + size_t i; + + if (mapped_vlvl == NULL) { + mapped_vlvl = &discard; + } + + if (!valid_resource_idx(resource_idx) || (vlvl < 0)) { + *mapped_vlvl = RAIL_VOLTAGE_LEVEL_INVALID; + return -1; + } + + res = &resource_list[resource_idx]; + + /* Return the first hlvl whose vlvl meets or exceeds the request. */ + for (i = 0U; i < res->count; i++) { + if (res->vlvls[i] >= vlvl) { + *mapped_vlvl = res->vlvls[i]; + return (int)i; + } + } + + /* Requested vlvl exceeds the max supported vlvl for this resource. */ + *mapped_vlvl = RAIL_VOLTAGE_LEVEL_OVERLIMIT; + return -1; +} + +int pwr_utils_hlvl_named_resource(const char *resource, int vlvl, + int *mapped_vlvl) +{ + /* pwr_utils_hlvl() validates the index, so no check is needed here. */ + return pwr_utils_hlvl(pwr_utils_lvl_resource_idx(resource), vlvl, + mapped_vlvl); +} diff --git a/drivers/qti/pwr_utils/pwr_utils.mk b/drivers/qti/pwr_utils/pwr_utils.mk new file mode 100644 index 000000000..dd5033716 --- /dev/null +++ b/drivers/qti/pwr_utils/pwr_utils.mk @@ -0,0 +1,16 @@ +# +# Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Power utils (vlvl <-> hlvl) driver +# + +$(eval $(call add_define,QTI_PWR_UTILS_ENABLED)) + +PWR_UTILS_DRV_PATH := drivers/qti/pwr_utils +CMD_DB_DRV_PATH := drivers/qti/cmd_db + +BL31_SOURCES += \ + $(PWR_UTILS_DRV_PATH)/pwr_utils.c \ + $(CMD_DB_DRV_PATH)/cmd_db.c diff --git a/drivers/qti/rpmh/lemans/rsc_regs.h b/drivers/qti/rpmh/lemans/rsc_regs.h new file mode 100644 index 000000000..5933a3c5b --- /dev/null +++ b/drivers/qti/rpmh/lemans/rsc_regs.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * RSC (Resource State Coordinator) register definitions for the lemans + * (qcs9075) APSS RPMh controller, DRV0 (TZ) view. + */ + +#ifndef RSC_REGS_H +#define RSC_REGS_H + +#include <lib/mmio.h> +#include <lib/utils_def.h> + +#include <platform_def.h> + +/* + * APSS RSC register base. Derived from the HW description: + * APSS_RSC_RSCC_RSCC_RSC_REG_BASE = QTI_APSS_HM_BASE + 0x00a00000 + */ +#define RSC_REG_BASE (QTI_APSS_HM_BASE + 0x00a00000U) + +/* Per-DRV AMC-mode IRQ registers (DRV0). */ +#define RSC_AMC_IRQ_ENABLE_OFF 0xd00U +#define RSC_AMC_IRQ_STATUS_OFF 0xd04U +#define RSC_AMC_IRQ_CLEAR_OFF 0xd08U + +/* Per-TCS register block within DRV0. */ +#define RSC_TCS_STRIDE 0x2a0U +#define RSC_TCS_BASE_OFF 0xd10U + +#define RSC_TCS_CMD_WAIT_FOR_CMPL_OFF 0x00U +#define RSC_TCS_CONTROL_OFF 0x04U +#define RSC_TCS_STATUS_OFF 0x08U +#define RSC_TCS_CMD_ENABLE_OFF 0x0cU + +/* CONTROL register fields. */ +#define RSC_TCS_CONTROL_AMC_MODE_TRIGGER BIT(24) +#define RSC_TCS_CONTROL_AMC_MODE_EN BIT(16) + +/* STATUS register fields. */ +#define RSC_TCS_STATUS_CONTROLLER_IDLE BIT(0) + +/* Per-command register block within a TCS. */ +#define RSC_CMD_STRIDE 0x14U +#define RSC_CMD_BASE_OFF 0xd30U + +#define RSC_CMD_MSGID_OFF 0x00U +#define RSC_CMD_ADDR_OFF 0x04U +#define RSC_CMD_DATA_OFF 0x08U +#define RSC_CMD_STATUS_OFF 0x0cU +#define RSC_CMD_RESP_DATA_OFF 0x10U + +/* MSGID register fields. */ +#define RSC_CMD_MSGID_READ_OR_WRITE BIT(16) +#define RSC_CMD_MSGID_RES_REQ BIT(8) +#define RSC_CMD_MSGID_MSG_LENGTH_SHFT 0U +#define RSC_CMD_MSGID_MSG_LENGTH_MASK 0xfU + +/* ADDR register fields. */ +#define RSC_CMD_ADDR_SLV_ID_SHFT 16U +#define RSC_CMD_ADDR_SLV_ID_MASK 0x7U +#define RSC_CMD_ADDR_OFFSET_SHFT 0U +#define RSC_CMD_ADDR_OFFSET_MASK 0xffffU + +/* CMD STATUS register fields. */ +#define RSC_CMD_STATUS_COMPLETED BIT(16) +#define RSC_CMD_STATUS_ISSUED BIT(8) +#define RSC_CMD_STATUS_TRIGGERED BIT(0) + +/* Each RPMh command transfers a single 32-bit word (encoded as 8 bytes). */ +#define RSC_CMD_DATA_LEN 8U + +/* Split a {slave-id, offset} resource address. */ +#define RSC_SLAVE_ID(addr) (((addr) >> 16) & RSC_CMD_ADDR_SLV_ID_MASK) +#define RSC_OFFSET(addr) ((addr) & RSC_CMD_ADDR_OFFSET_MASK) + +/* Address helpers (DRV0). */ +#define RSC_DRV0_REG(off) \ + ((uintptr_t)RSC_REG_BASE + (uint32_t)(off)) + +#define RSC_TCS_REG(tcs, off) \ + RSC_DRV0_REG(RSC_TCS_BASE_OFF + (RSC_TCS_STRIDE * (uint32_t)(tcs)) + \ + (uint32_t)(off)) + +#define RSC_CMD_REG(tcs, cmd, off) \ + RSC_DRV0_REG(RSC_CMD_BASE_OFF + (RSC_TCS_STRIDE * (uint32_t)(tcs)) + \ + (RSC_CMD_STRIDE * (uint32_t)(cmd)) + (uint32_t)(off)) + +#endif /* RSC_REGS_H */ diff --git a/drivers/qti/rpmh/rpmh.mk b/drivers/qti/rpmh/rpmh.mk new file mode 100644 index 000000000..52fcd593f --- /dev/null +++ b/drivers/qti/rpmh/rpmh.mk @@ -0,0 +1,18 @@ +# +# Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# RPMh (Resource Power Manager hardened) command service driver +# + +$(eval $(call add_define,QTI_RPMH_ENABLED)) + +RPMH_DRV_PATH := drivers/qti/rpmh + +PLAT_INCLUDES += \ + -I$(RPMH_DRV_PATH) \ + -I$(RPMH_DRV_PATH)/$(CHIPSET) + +BL31_SOURCES += \ + $(RPMH_DRV_PATH)/rpmh_client.c diff --git a/drivers/qti/rpmh/rpmh_client.c b/drivers/qti/rpmh/rpmh_client.c new file mode 100644 index 000000000..d4225c661 --- /dev/null +++ b/drivers/qti/rpmh/rpmh_client.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * RPMh command service for the QTI APSS RSC, TZ DRV. + * + * The TZ DRV issues all of its commands as ACTIVE-set AMCs (Active Mode + * Commands). Unlike the full multi-client RPMh driver, BL31 runs single + * threaded and issues commands synchronously: a command set is programmed + * into a TCS, the AMC is triggered, and the issuing call polls the AMC + * finished interrupt status before returning. Sleep/wake sets are not + * supported on the TZ DRV. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include <common/debug.h> +#include <drivers/qti/rpmh/rpmh_client.h> +#include <lib/spinlock.h> + +#include "rsc_regs.h" + +/* + * TZ DRV TCS configuration (matches the RPMh DRV config): + * - 4 TCSs total, the first two are AMC TCSs. + * We use the first AMC TCS for synchronous ACTIVE requests. + */ +#define RPMH_TZ_NUM_TCS 4U +#define RPMH_TZ_AMC_TCS 0U + +/* AMC completion poll bound; the AMC normally completes in microseconds. */ +#define RPMH_AMC_POLL_COUNT 1000000U + +struct rpmh_client { + uint32_t drv_id; + const char *name; + uint32_t next_req_id; + bool in_use; +}; + +static struct rpmh_client rpmh_clients[RSC_DRV_TZ + 1U]; +static spinlock_t rpmh_lock; +static bool rpmh_initialized; + +void rpmh_client_init(void) +{ + rpmh_initialized = true; +} + +struct rpmh_client *rpmh_create_handle(uint32_t drv_id, + const char *client_name) +{ + struct rpmh_client *client; + + assert(rpmh_initialized); + + /* Only the TZ DRV is driven from TF-A. */ + if (drv_id != RSC_DRV_TZ) { + return NULL; + } + + client = &rpmh_clients[drv_id]; + client->drv_id = drv_id; + client->name = client_name; + client->next_req_id = 1U; + client->in_use = true; + + return client; +} + +static bool rpmh_tcs_is_idle(uint32_t tcs) +{ + uint32_t status = mmio_read_32(RSC_TCS_REG(tcs, RSC_TCS_STATUS_OFF)); + + return (status & RSC_TCS_STATUS_CONTROLLER_IDLE) != 0U; +} + +static void rpmh_setup_cmd(uint32_t tcs, uint32_t cmd, + const struct rpmh_command *info) +{ + uint32_t msgid; + uint32_t addr; + + /* Write request: always set the read/write bit for a write. */ + msgid = RSC_CMD_MSGID_READ_OR_WRITE; + if (info->completion) { + msgid |= RSC_CMD_MSGID_RES_REQ; + } + msgid |= (RSC_CMD_DATA_LEN & RSC_CMD_MSGID_MSG_LENGTH_MASK) << + RSC_CMD_MSGID_MSG_LENGTH_SHFT; + + addr = (RSC_SLAVE_ID(info->address) << RSC_CMD_ADDR_SLV_ID_SHFT) | + (RSC_OFFSET(info->address) << RSC_CMD_ADDR_OFFSET_SHFT); + + mmio_write_32(RSC_CMD_REG(tcs, cmd, RSC_CMD_MSGID_OFF), msgid); + mmio_write_32(RSC_CMD_REG(tcs, cmd, RSC_CMD_ADDR_OFF), addr); + mmio_write_32(RSC_CMD_REG(tcs, cmd, RSC_CMD_DATA_OFF), info->data); +} + +/* + * Program the AMC TCS with the given command set, trigger it and poll until + * the AMC has finished. Returns the request id assigned to the command set. + */ +static uint32_t rpmh_send_amc(struct rpmh_client *client, + const struct rpmh_command_set *cmd_set) +{ + uint32_t tcs = RPMH_TZ_AMC_TCS; + uint32_t cmd_en_mask = 0U; + uint32_t wait_mask = 0U; + uint32_t req_id; + uint32_t poll; + uint32_t i; + + assert(cmd_set->num_commands > 0U); + assert(cmd_set->num_commands <= IMAGE_TCS_SIZE); + + /* TZ DRV only supports active requests. */ + assert(cmd_set->set == RPMH_SET_ACTIVE); + + spin_lock(&rpmh_lock); + + /* The TCS must be idle before reprogramming it. */ + for (poll = 0U; poll < RPMH_AMC_POLL_COUNT; poll++) { + if (rpmh_tcs_is_idle(tcs)) { + break; + } + } + if (poll == RPMH_AMC_POLL_COUNT) { + ERROR("RPMh: TCS %u not idle\n", tcs); + panic(); + } + + for (i = 0U; i < cmd_set->num_commands; i++) { + rpmh_setup_cmd(tcs, i, &cmd_set->commands[i]); + cmd_en_mask |= BIT(i); + if (cmd_set->commands[i].completion) { + wait_mask |= BIT(i); + } + } + + /* Program completion dependencies and enabled commands. */ + mmio_write_32(RSC_TCS_REG(tcs, RSC_TCS_CMD_WAIT_FOR_CMPL_OFF), + wait_mask); + mmio_write_32(RSC_TCS_REG(tcs, RSC_TCS_CMD_ENABLE_OFF), cmd_en_mask); + + /* Clear and enable the AMC finished interrupt for this TCS. */ + mmio_write_32(RSC_DRV0_REG(RSC_AMC_IRQ_CLEAR_OFF), BIT(tcs)); + mmio_setbits_32(RSC_DRV0_REG(RSC_AMC_IRQ_ENABLE_OFF), BIT(tcs)); + + /* Trigger the AMC: set AMC mode then pulse the trigger bit. */ + mmio_clrsetbits_32(RSC_TCS_REG(tcs, RSC_TCS_CONTROL_OFF), + RSC_TCS_CONTROL_AMC_MODE_TRIGGER, + RSC_TCS_CONTROL_AMC_MODE_EN); + mmio_setbits_32(RSC_TCS_REG(tcs, RSC_TCS_CONTROL_OFF), + RSC_TCS_CONTROL_AMC_MODE_TRIGGER); + + /* Poll for AMC completion. */ + for (poll = 0U; poll < RPMH_AMC_POLL_COUNT; poll++) { + uint32_t irq = mmio_read_32(RSC_DRV0_REG(RSC_AMC_IRQ_STATUS_OFF)); + + if ((irq & BIT(tcs)) != 0U) { + break; + } + } + if (poll == RPMH_AMC_POLL_COUNT) { + ERROR("RPMh: AMC on TCS %u did not complete\n", tcs); + panic(); + } + + /* Clear the AMC finished interrupt and disable AMC mode. */ + mmio_write_32(RSC_DRV0_REG(RSC_AMC_IRQ_CLEAR_OFF), BIT(tcs)); + mmio_clrbits_32(RSC_TCS_REG(tcs, RSC_TCS_CONTROL_OFF), + RSC_TCS_CONTROL_AMC_MODE_EN); + + req_id = client->next_req_id++; + + spin_unlock(&rpmh_lock); + + return req_id; +} + +uint32_t rpmh_issue_command_set(struct rpmh_client *handle, + struct rpmh_command_set *command_set) +{ + assert(handle != NULL); + assert(command_set != NULL); + + return rpmh_send_amc(handle, command_set); +} + +uint32_t rpmh_issue_command(struct rpmh_client *handle, enum rpmh_set set, + bool completion, uint32_t address, uint32_t data) +{ + struct rpmh_command_set cmd_set = { + .set = set, + .num_commands = 1U, + .commands[0] = { + .address = address, + .data = data, + .completion = completion, + }, + }; + + assert(handle != NULL); + + return rpmh_send_amc(handle, &cmd_set); +} + +/* + * Commands are issued synchronously, so by the time a request id has been + * returned the corresponding AMC has already finished. The barrier APIs are + * therefore no-ops kept for interface compatibility. + */ +void rpmh_barrier_single(struct rpmh_client *handle, uint32_t req_id) +{ + assert(handle != NULL); + (void)req_id; +} + +void rpmh_barrier_all(struct rpmh_client *handle, uint32_t req_id) +{ + assert(handle != NULL); + (void)req_id; +} |
