diff --git a/core/arch/arm/plat-qcom/hoya/lemans/target.mk b/core/arch/arm/plat-qcom/hoya/lemans/target.mk index d82eecb50..99d92cf26 100644 --- a/core/arch/arm/plat-qcom/hoya/lemans/target.mk +++ b/core/arch/arm/plat-qcom/hoya/lemans/target.mk @@ -23,3 +23,12 @@ CFG_RESERVED_VASPACE_SIZE ?= (256 * 1024 * 1024) CFG_IN_TREE_EARLY_TAS += qcom_pas/cff7d191-7ca0-4784-af13-48223b9a4fbe endif CFG_QCOM_HWKM ?= y + +# QUPv3 serial-engine (bus) clock set-rate/DFS walker, consumed on-demand by a +# future TEE-side SPI/I2C driver. Set-rate votes CX/MX via RPMh, so pull +# cmd_db/RPMh client in whenever the walker is built. +CFG_QCOM_CLK_CFG ?= y +ifeq ($(CFG_QCOM_CLK_CFG),y) +$(call force,CFG_QCOM_CMD_DB,y) +$(call force,CFG_QCOM_RPMH_CLIENT,y) +endif diff --git a/core/drivers/clk/qcom/clock-qcom.c b/core/drivers/clk/qcom/clk.c similarity index 84% rename from core/drivers/clk/qcom/clock-qcom.c rename to core/drivers/clk/qcom/clk.c index a84964842..15454988d 100644 --- a/core/drivers/clk/qcom/clock-qcom.c +++ b/core/drivers/clk/qcom/clk.c @@ -8,6 +8,7 @@ #include #include #include +#include register_phys_mem(MEM_AREA_IO_NSEC, GCC_BASE, GCC_SIZE); @@ -50,22 +51,12 @@ register_phys_mem(MEM_AREA_IO_NSEC, GCC_BASE, GCC_SIZE); /* PLL_USER_CTL_U fields */ #define PLL_USER_CTL_U_FINE_LOCK_DET BIT(0) -static inline bool cbcr_branch_on(uint32_t val) -{ - return !(val & CBCR_BRANCH_OFF_BIT); -} - TEE_Result qcom_clock_enable_cbc(vaddr_t cbcr) { int ret = 0; io_setbits32(cbcr, CBCR_BRANCH_ENABLE_BIT); - /* - * In hardware clock-control mode (HW_CTL set) CLK_OFF is driven by HW, - * not the software CLK_ENABLE write, so skip the poll to avoid - * spinning. - */ if (io_read32(cbcr) & CBCR_HW_CTL_ENABLE_BIT) return TEE_SUCCESS; @@ -88,18 +79,15 @@ TEE_Result qcom_lucidevo_pll_enable(vaddr_t pll_base, uint32_t user_val = 0; int ret = 0; - /* Reg settings: program the static PLL trim/config registers. */ io_write32(pll_base + PLL_CONFIG_CTL, cfg->config_ctl); io_write32(pll_base + PLL_CONFIG_CTL_U, cfg->config_ctl_u); io_write32(pll_base + PLL_CONFIG_CTL_U1, cfg->config_ctl_u1); io_write32(pll_base + PLL_USER_CTL, cfg->user_ctl); io_write32(pll_base + PLL_USER_CTL_U, cfg->user_ctl_u); - /* ConfigPLL: program L value and fractional value. */ io_mask32(pll_base + PLL_L_VAL, cfg->l_val, PLL_L_VAL_L_MASK); io_write32(pll_base + PLL_ALPHA_VAL, cfg->alpha_val); - /* Select fractional format and program the pre-/post-div ratios. */ user_val = io_read32(pll_base + PLL_USER_CTL); if (cfg->frac_mode_mn) user_val |= PLL_USER_CTL_FRAC_FORMAT_SEL; @@ -115,24 +103,19 @@ TEE_Result qcom_lucidevo_pll_enable(vaddr_t pll_base, PLL_USER_CTL_PRE_DIV_MASK; io_write32(pll_base + PLL_USER_CTL, user_val); - /* Always use fine-grained lock detection. */ io_setbits32(pll_base + PLL_USER_CTL_U, PLL_USER_CTL_U_FINE_LOCK_DET); - /* SetCalConfig: program the calibration L value. */ io_mask32(pll_base + PLL_L_VAL, SHIFT_U32(cfg->cal_l_val, PLL_L_VAL_CAL_L_SHIFT), PLL_L_VAL_CAL_L_MASK); - /* Enable: select RUN opmode and take the PLL out of reset. */ io_write32(pll_base + PLL_OPMODE, PLL_OPMODE_RUN); io_setbits32(pll_base + PLL_MODE, PLL_MODE_RESET_N); - /* Wait for the PLL to lock. */ REG_POLL_TIMEOUT(pll_base + PLL_MODE, 10 * 1000, 10, &ret, pll_locked); if (ret < 0) return TEE_ERROR_TIMEOUT; - /* Enable PLL outputs and the main output. */ io_setbits32(pll_base + PLL_MODE, PLL_MODE_OUTCTRL); io_setbits32(pll_base + PLL_USER_CTL, PLL_USER_CTL_PLLOUT_MAIN_EN); @@ -165,7 +148,7 @@ TEE_Result qcom_clock_enable(enum qcom_clk_group group) case QCOM_CLKS_GPDSP1: return qcom_clock_enable_pas(group); default: - EMSG("Unsupported clock group %d\n", group); + EMSG("Unsupported clock group %d", group); return TEE_ERROR_BAD_PARAMETERS; } } diff --git a/core/drivers/clk/qcom/clk_cfg.h b/core/drivers/clk/qcom/clk_cfg.h new file mode 100644 index 000000000..4b60dcd5d --- /dev/null +++ b/core/drivers/clk/qcom/clk_cfg.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * Data contract for a platform's RCGs and branch clocks, consumed by the + * generic clock provider in clk_ops.c. + */ + +#ifndef _CLK_CFG_H_ +#define _CLK_CFG_H_ + +#include +#include +#include + +/* configs[] must be sorted ascending by freq_hz. */ +struct clk_mux_config { + uint32_t freq_hz; + uint8_t src; /* software id, matched against parent_map[].src */ + uint16_t div2x; /* 2x half-integer SRC_DIV; SRC_DIV = div2x - 1 */ + uint32_t m; + uint32_t n; /* MND divider; used only when m && m < n */ + uint8_t dfs_idx; /* DFS perf-state index, or CLK_DFS_NA */ + uint16_t corner; /* RAIL_VOLTAGE_LEVEL_* this rate needs, or 0 */ +}; + +#define CLK_DFS_NA 0xFF + +/* One MMIO block a domain's registers live in, e.g. one GCC instance. */ +struct clk_regmap { + struct io_pa_va io; + size_t size; +}; + +/* Maps one RCG's own hardware SRC_SEL encoding to a source clk. */ +struct clk_parent_map { + uint8_t src; /* index into clk_cfg.pll_votes[] */ + uint8_t mux_sel; /* this RCG's own SRC_SEL value for that source */ +}; + +/* An RCG (mux/divider/DFS block), registered as its own struct clk. */ +struct clk_rcg_desc { + const char *name; + struct clk_regmap *regmap; + paddr_t cmd_rcgr_addr; + uint16_t mnd_width; /* 0 if this RCG has no M/N/D hardware */ + uint16_t dfs_states; /* DFS perf states supported; 0 means no DFS */ + const struct clk_mux_config *configs; + uint32_t n_configs; + const struct clk_parent_map *parent_map; + uint8_t n_parents; /* number of entries in parent_map, also clk->parents[] count */ +}; + +/* A CBCR branch gate, registered as its own struct clk. */ +struct clk_branch_desc { + const char *name; + struct clk_regmap *regmap; + paddr_t cbcr_addr; + paddr_t clk_vote_addr; /* shared vote register; 0 for none */ + uint8_t vote_bit; + const struct clk_rcg_desc *rcg; /* parent RCG, or NULL if branch-only */ +}; + +/* One RCG source, registered as its own struct clk. Direct register write, not RPMh. */ +struct clk_pll_vote { + const char *name; + struct clk_regmap *regmap; + paddr_t vote_reg_addr; /* 0 means enable/disable are no-ops (e.g. XO) */ + uint8_t vote_bit; +}; + +/* Per-target config; combined branches point @rcg at a separate clk_rcg_desc. */ +struct clk_cfg { + const struct clk_branch_desc *branches; + uint32_t n_branches; + const struct clk_pll_vote *pll_votes; + uint32_t n_pll_votes; +}; + +const struct clk_cfg *clk_get_cfg(void); + +#endif /* _CLK_CFG_H_ */ diff --git a/core/drivers/clk/qcom/clk_ops.c b/core/drivers/clk/qcom/clk_ops.c new file mode 100644 index 000000000..9991a91f8 --- /dev/null +++ b/core/drivers/clk/qcom/clk_ops.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2025, Linaro Ltd + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk_cfg.h" +#include "clock_group.h" +#include "rail_vote.h" + +/* A shared vote-gated PLL source, or XO when @vote is 0 (no vote bit). */ +struct pll_vote_priv { + vaddr_t vote; + uint8_t vote_bit; +}; + +/* clk->parent is the sole record of the currently-voted parent; rcg_set_rate() updates it on every switch. */ +struct rcg_priv { + const struct clk_rcg_desc *desc; + vaddr_t cmd_rcgr; + uint16_t corner; /* corner voted for this RCG */ + uint32_t rate; /* last resolved output rate, 0 until set */ + bool dfs; /* hardware DFS already handed the RCG */ + bool configured; /* set_rate() has run at least once; enable()/disable() are no-ops until then */ +}; + +/* A CBCR branch gate, optionally parented by an rcg_priv clk. */ +struct branch_priv { + const struct clk_branch_desc *desc; + vaddr_t cbcr; + vaddr_t vote; +}; + +static struct clk **branch_clks; +static size_t branch_clk_count; + +/* Bounds-checked against @regmap as a whole, not a per-register span. */ +static TEE_Result clk_regmap_va(struct clk_regmap *regmap, paddr_t pa, + vaddr_t *va) +{ + if (pa < regmap->io.pa || pa - regmap->io.pa >= regmap->size) + return TEE_ERROR_BAD_PARAMETERS; + + *va = io_pa_or_va(®map->io, regmap->size) + (pa - regmap->io.pa); + + return TEE_SUCCESS; +} + +static void config_mux_regs(vaddr_t cgr, const struct clk_mux_config *cfg, + uint32_t mux_sel, uint16_t mnd_width, + uint32_t cfg_off, uint32_t m_off, + uint32_t n_off, uint32_t d_off) +{ + uint32_t half_div = cfg->div2x ? cfg->div2x - 1 : 0; + uint32_t val = io_read32(cgr + cfg_off); + + val &= ~(QCOM_RCG_CFG_SRC_SEL_FMSK | QCOM_RCG_CFG_SRC_DIV_FMSK | + QCOM_RCG_CFG_MODE_FMSK | QCOM_RCG_CFG_HW_CLK_CONTROL_FMSK); + + val |= SHIFT_U32(mux_sel, QCOM_RCG_CFG_SRC_SEL_SHFT) & + QCOM_RCG_CFG_SRC_SEL_FMSK; + val |= SHIFT_U32(half_div, QCOM_RCG_CFG_SRC_DIV_SHFT) & + QCOM_RCG_CFG_SRC_DIV_FMSK; + + if (mnd_width && cfg->m != 0 && cfg->m < cfg->n) { + io_write32(cgr + m_off, cfg->m); + io_write32(cgr + n_off, ~(cfg->n - cfg->m)); + io_write32(cgr + d_off, ~cfg->n); + + val |= SHIFT_U32(QCOM_RCG_CFG_DUAL_EDGE_MODE_VAL, + QCOM_RCG_CFG_MODE_SHFT) & + QCOM_RCG_CFG_MODE_FMSK; + } + + io_write32(cgr + cfg_off, val); +} + +enum freq_policy { + FLOOR, + CEIL, +}; + +/* Smallest config row >= @freq_hz; requires configs[] sorted ascending by freq_hz. */ +static const struct clk_mux_config * +find_freq(const struct clk_rcg_desc *desc, uint32_t freq_hz) +{ + uint32_t i = 0; + + for (i = 0; i < desc->n_configs; i++) + if (freq_hz <= desc->configs[i].freq_hz) + return &desc->configs[i]; + + return NULL; +} + +/* Largest config row <= @freq_hz; same ascending-order requirement as find_freq(). */ +static const struct clk_mux_config * +find_freq_floor(const struct clk_rcg_desc *desc, uint32_t freq_hz) +{ + const struct clk_mux_config *best = NULL; + uint32_t i = 0; + + for (i = 0; i < desc->n_configs; i++) { + if (freq_hz < desc->configs[i].freq_hz) + break; + best = &desc->configs[i]; + } + + return best; +} + +/* This driver only ever calls with CEIL. */ +static const struct clk_mux_config * +rcg_determine_rate(const struct clk_rcg_desc *desc, uint32_t freq_hz, + enum freq_policy policy) +{ + if (policy == CEIL) + return find_freq(desc, freq_hz); + + return find_freq_floor(desc, freq_hz); +} + +/* @src's SRC_SEL/parents[] index is per-RCG -- always via this RCG's own parent_map. */ +static bool find_src_index(const struct clk_rcg_desc *desc, uint8_t src, + size_t *idx, uint32_t *mux_sel) +{ + size_t i = 0; + + for (i = 0; i < desc->n_parents; i++) { + if (desc->parent_map[i].src != src) + continue; + + *idx = i; + *mux_sel = desc->parent_map[i].mux_sel; + return true; + } + + return false; +} + +/* Reverse of find_src_index(): live SRC_SEL to software @src. */ +static bool find_src(const struct clk_rcg_desc *desc, uint32_t mux_sel, + uint8_t *src) +{ + size_t i = 0; + + for (i = 0; i < desc->n_parents; i++) { + if (desc->parent_map[i].mux_sel != mux_sel) + continue; + + *src = desc->parent_map[i].src; + return true; + } + + return false; +} + +/* Read this RCG's live CFG SRC_SEL field straight from hardware. */ +static uint32_t rcg_read_mux_sel(vaddr_t cmd_rcgr) +{ + uint32_t val = io_read32(cmd_rcgr + QCOM_RCG_CFG_REG_OFFSET); + + return (val & QCOM_RCG_CFG_SRC_SEL_FMSK) >> QCOM_RCG_CFG_SRC_SEL_SHFT; +} + +static TEE_Result pll_vote_enable(struct clk *clk) +{ + struct pll_vote_priv *priv = clk->priv; + + if (priv->vote) + io_setbits32(priv->vote, BIT(priv->vote_bit)); + + return TEE_SUCCESS; +} + +static void pll_vote_disable(struct clk *clk) +{ + struct pll_vote_priv *priv = clk->priv; + + if (priv->vote) + io_clrbits32(priv->vote, BIT(priv->vote_bit)); +} + +static const struct clk_ops pll_vote_ops = { + .enable = pll_vote_enable, + .disable = pll_vote_disable, +}; + +/* Votes @pll via its own enabled_count directly -- clk_enable() would self-deadlock under lock_clk(). */ +static TEE_Result pll_ref(struct clk *pll) +{ + TEE_Result res = TEE_SUCCESS; + + if (refcount_inc(&pll->enabled_count)) + return TEE_SUCCESS; + + res = pll_vote_enable(pll); + if (res) + return res; + + refcount_set(&pll->enabled_count, 1); + + return TEE_SUCCESS; +} + +/* Counterpart to pll_ref(); same lock-free rationale. */ +static void pll_unref(struct clk *pll) +{ + if (refcount_dec(&pll->enabled_count)) + pll_vote_disable(pll); +} + +/* Latch a just-written CFG/M/N/D bank and wait for hardware to clear the update bit. */ +static TEE_Result rcg_commit_config(vaddr_t cmd_rcgr) +{ + uint32_t val = 0; + + io_setbits32(cmd_rcgr, QCOM_RCG_CMD_CFG_UPDATE_FMSK); + + if (IO_READ32_POLL_TIMEOUT(cmd_rcgr, val, + !(val & QCOM_RCG_CMD_CFG_UPDATE_FMSK), + 1, 10 * 1000)) + return TEE_ERROR_TIMEOUT; + + return TEE_SUCCESS; +} + +/* Always vote @new_parent up before a live switch, so CMD_RCGR's update bit is + * never written against a source we haven't just confirmed is running. + */ +static TEE_Result rcg_switch_parent_pre(struct clk *new_parent, bool switching) +{ + if (!switching) + return TEE_SUCCESS; + + return pll_ref(new_parent); +} + +/* Releases @old_parent's vote now that @new_parent's is live, then updates clk->parent. */ +static void rcg_switch_parent_post(struct clk *clk, struct clk *old_parent, + struct clk *new_parent) +{ + if (old_parent) + pll_unref(old_parent); + + clk->parent = new_parent; +} + +static TEE_Result rcg_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate __unused) +{ + struct rcg_priv *priv = clk->priv; + const struct clk_mux_config *cfg = NULL; + struct clk *old_parent = NULL; + struct clk *new_parent = NULL; + size_t new_idx = 0; + uint32_t mux_sel = 0; + uint16_t curr_corner = priv->corner; + uint16_t next_corner = curr_corner; + bool switching = false; + bool enabled = false; + bool raised = false; + TEE_Result res = TEE_SUCCESS; + + if (!priv->cmd_rcgr || rate > UINT32_MAX) + return TEE_ERROR_BAD_PARAMETERS; + + cfg = rcg_determine_rate(priv->desc, rate, CEIL); + if (!cfg) + return TEE_ERROR_ITEM_NOT_FOUND; + + if (!find_src_index(priv->desc, cfg->src, &new_idx, &mux_sel)) + return TEE_ERROR_ITEM_NOT_FOUND; + + new_parent = clk_get_parent_by_index(clk, new_idx); + if (!new_parent) + return TEE_ERROR_ITEM_NOT_FOUND; + + old_parent = clk_get_parent(clk); + switching = old_parent != new_parent; + enabled = clk_is_enabled(clk); + + next_corner = cfg->corner; + + /* Disabled: rcg_enable() commits this, incl. the rail vote, once clk->parent is actually voted. */ + if (!enabled) { + if (switching) + clk->parent = new_parent; + priv->corner = next_corner; + priv->rate = cfg->freq_hz; + priv->configured = true; + return TEE_SUCCESS; + } + + if (next_corner > curr_corner) { + res = rail_vote(curr_corner, next_corner); + if (res) + return res; + raised = true; + } + + res = rcg_switch_parent_pre(new_parent, switching); + if (res) { + if (raised) + rail_vote(next_corner, curr_corner); + return res; + } + + config_mux_regs(priv->cmd_rcgr, cfg, mux_sel, priv->desc->mnd_width, + QCOM_RCG_CFG_REG_OFFSET, QCOM_RCG_M_REG_OFFSET, + QCOM_RCG_N_REG_OFFSET, QCOM_RCG_D_REG_OFFSET); + + res = rcg_commit_config(priv->cmd_rcgr); + if (res) { + if (switching) + pll_unref(new_parent); + if (raised) + rail_vote(next_corner, curr_corner); + return res; + } + + if (switching) + rcg_switch_parent_post(clk, old_parent, new_parent); + + if (raised) + priv->corner = next_corner; + else if (next_corner < curr_corner && !rail_vote(curr_corner, next_corner)) + priv->corner = next_corner; + + priv->rate = cfg->freq_hz; + priv->configured = true; + + return TEE_SUCCESS; +} + +/* Undoes rcg_disable()'s parking -- always runs on enable, since disable always leaves live hardware pointing at XO. */ +static TEE_Result rcg_enable(struct clk *clk) +{ + struct rcg_priv *priv = clk->priv; + const struct clk_mux_config *cfg = NULL; + uint32_t mux_sel = 0; + size_t idx = 0; + TEE_Result res = TEE_SUCCESS; + + if (priv->dfs || !priv->configured) + return TEE_SUCCESS; + + res = rail_vote(0, priv->corner); + if (res) + return res; + + cfg = rcg_determine_rate(priv->desc, priv->rate, CEIL); + if (!cfg || !find_src_index(priv->desc, cfg->src, &idx, &mux_sel)) { + rail_vote(priv->corner, 0); + return TEE_ERROR_ITEM_NOT_FOUND; + } + + config_mux_regs(priv->cmd_rcgr, cfg, mux_sel, priv->desc->mnd_width, + QCOM_RCG_CFG_REG_OFFSET, QCOM_RCG_M_REG_OFFSET, + QCOM_RCG_N_REG_OFFSET, QCOM_RCG_D_REG_OFFSET); + + res = rcg_commit_config(priv->cmd_rcgr); + if (res) { + rail_vote(priv->corner, 0); + return res; + } + + return TEE_SUCCESS; +} + +/* Parks on XO (SRC_SEL 0 by convention) before releasing the rail vote. */ +static void rcg_disable(struct clk *clk) +{ + struct rcg_priv *priv = clk->priv; + + if (priv->dfs || !priv->configured) + return; + + io_write32(priv->cmd_rcgr + QCOM_RCG_CFG_REG_OFFSET, 0); + rcg_commit_config(priv->cmd_rcgr); + + rail_vote(priv->corner, 0); +} + +/* Program one DFS performance-state bank. */ +static TEE_Result rcg_configure_dfs_bank(const struct clk_rcg_desc *desc, + vaddr_t cmd_rcgr, + const struct clk_mux_config *c) +{ + uint32_t mux_sel = 0; + uint32_t perf = 0; + size_t idx = 0; + + if (!find_src_index(desc, c->src, &idx, &mux_sel)) + return TEE_ERROR_ITEM_NOT_FOUND; + + perf = 0x4 * c->dfs_idx; + config_mux_regs(cmd_rcgr, c, mux_sel, desc->mnd_width, + QCOM_RCG_PERF_DFSR_REG_OFFSET + perf, + QCOM_RCG_PERF_M_DFSR_REG_OFFSET + perf, + QCOM_RCG_PERF_N_DFSR_REG_OFFSET + perf, + QCOM_RCG_PERF_D_DFSR_REG_OFFSET + perf); + + return TEE_SUCCESS; +} + +/* No PLL/rail voting here -- hardware handles both autonomously once DFS_EN is set. */ +static TEE_Result rcg_enable_dfs(struct rcg_priv *priv) +{ + const struct clk_rcg_desc *desc = priv->desc; + uint32_t i = 0; + TEE_Result res = TEE_SUCCESS; + + if (!desc->dfs_states || !priv->cmd_rcgr) + return TEE_ERROR_BAD_PARAMETERS; + + if (priv->dfs) + return TEE_SUCCESS; + + for (i = 0; i < desc->n_configs; i++) { + const struct clk_mux_config *c = &desc->configs[i]; + + if (c->dfs_idx == CLK_DFS_NA || + c->dfs_idx >= desc->dfs_states) + continue; + + res = rcg_configure_dfs_bank(desc, priv->cmd_rcgr, c); + if (res) + return res; + } + + io_write32(priv->cmd_rcgr + QCOM_RCG_CMD_DFSR_REG_OFFSET, + QCOM_RCG_CMD_DFSR_HW_CLK_CONTROL_FMSK | + QCOM_RCG_CMD_DFSR_DFS_EN_FMSK); + + priv->dfs = true; + + return TEE_SUCCESS; +} + +/* Reads live hardware rather than trusting a cache: CMD_DFSR's SW_PERF_STATE once DFS is active, else CFG/M/N/D matched against configs[]. */ +static unsigned long rcg_get_rate(struct clk *clk, + unsigned long parent_rate __unused) +{ + struct rcg_priv *priv = clk->priv; + const struct clk_rcg_desc *desc = priv->desc; + uint32_t val = 0; + uint32_t mux_sel = 0; + uint32_t src_div = 0; + uint32_t m = 0; + uint32_t n = 0; + uint8_t src = 0; + size_t i = 0; + + if (desc->dfs_states) { + uint32_t dfsr = io_read32(priv->cmd_rcgr + + QCOM_RCG_CMD_DFSR_REG_OFFSET); + + if (dfsr & QCOM_RCG_CMD_DFSR_DFS_EN_FMSK) { + uint8_t dfs_idx = (dfsr & + QCOM_RCG_CMD_DFSR_SW_PERF_STATE_FMSK) >> + QCOM_RCG_CMD_DFSR_SW_PERF_STATE_SHFT; + + for (i = 0; i < desc->n_configs; i++) + if (desc->configs[i].dfs_idx == dfs_idx) + return desc->configs[i].freq_hz; + + return priv->rate; + } + } + + /* Disabled and not DFS-owned: live CFG reads back XO, parked there by rcg_disable(). */ + if (!priv->dfs && !clk_is_enabled(clk)) + return priv->rate; + + val = io_read32(priv->cmd_rcgr + QCOM_RCG_CFG_REG_OFFSET); + mux_sel = (val & QCOM_RCG_CFG_SRC_SEL_FMSK) >> QCOM_RCG_CFG_SRC_SEL_SHFT; + src_div = (val & QCOM_RCG_CFG_SRC_DIV_FMSK) >> QCOM_RCG_CFG_SRC_DIV_SHFT; + + if (!find_src(desc, mux_sel, &src)) + return priv->rate; + + if (desc->mnd_width) { + uint32_t mask = BIT(desc->mnd_width) - 1; + + m = io_read32(priv->cmd_rcgr + QCOM_RCG_M_REG_OFFSET) & mask; + n = (~io_read32(priv->cmd_rcgr + QCOM_RCG_N_REG_OFFSET) & + mask) + m; + } + + for (i = 0; i < desc->n_configs; i++) { + const struct clk_mux_config *c = &desc->configs[i]; + uint32_t c_div = c->div2x ? c->div2x - 1 : 0; + + if (c->src != src || c_div != src_div) + continue; + + if (desc->mnd_width && c->m != 0 && c->m < c->n && + (c->m != m || c->n != n)) + continue; + + return c->freq_hz; + } + + return priv->rate; +} + +/* Reads live SRC_SEL rather than assuming index 0, since clk_register() seeds clk->parent from this. */ +static size_t rcg_get_parent(struct clk *clk) +{ + struct rcg_priv *priv = clk->priv; + uint32_t mux_sel = rcg_read_mux_sel(priv->cmd_rcgr); + size_t i = 0; + + for (i = 0; i < priv->desc->n_parents; i++) + if (priv->desc->parent_map[i].mux_sel == mux_sel) + return i; + + return 0; +} + +/* Supported rates in ascending order, straight from desc->configs[]. */ +static TEE_Result rcg_get_rates_array(struct clk *clk, size_t start_index, + unsigned long *rates, + size_t *nb_elts) +{ + struct rcg_priv *priv = clk->priv; + const struct clk_rcg_desc *desc = priv->desc; + size_t i = 0; + + if (!rates) { + *nb_elts = desc->n_configs; + return TEE_SUCCESS; + } + + if (start_index + *nb_elts > desc->n_configs) { + EMSG("Bad parameter(s): start_index %zu, nb_elts %zu", + start_index, *nb_elts); + return TEE_ERROR_BAD_PARAMETERS; + } + + for (i = 0; i < *nb_elts; i++) + rates[i] = desc->configs[start_index + i].freq_hz; + + return TEE_SUCCESS; +} + +static const struct clk_ops rcg_ops = { + .enable = rcg_enable, + .disable = rcg_disable, + .set_rate = rcg_set_rate, + .get_rate = rcg_get_rate, + .get_parent = rcg_get_parent, + .get_rates_array = rcg_get_rates_array, +}; + +static inline bool cbcr_branch_isoff(uint32_t val) +{ + return !cbcr_branch_on(val); +} + +static TEE_Result branch_enable(struct clk *clk) +{ + struct branch_priv *priv = clk->priv; + int ret = 0; + + if (priv->vote) + io_setbits32(priv->vote, BIT(priv->desc->vote_bit)); + else + io_setbits32(priv->cbcr, CBCR_BRANCH_ENABLE_BIT); + + if (io_read32(priv->cbcr) & CBCR_HW_CTL_ENABLE_BIT) + return TEE_SUCCESS; + + REG_POLL_TIMEOUT(priv->cbcr, 10 * 1000, 10, &ret, cbcr_branch_on); + + return ret < 0 ? TEE_ERROR_TIMEOUT : TEE_SUCCESS; +} + +static void branch_disable(struct clk *clk) +{ + struct branch_priv *priv = clk->priv; + int ret = 0; + + if (priv->vote) + io_clrbits32(priv->vote, BIT(priv->desc->vote_bit)); + else + io_clrbits32(priv->cbcr, CBCR_BRANCH_ENABLE_BIT); + + if (io_read32(priv->cbcr) & CBCR_HW_CTL_ENABLE_BIT) + return; + + /* + * clk_ops.disable() has no return value, so a timeout here can't be + * reported -- and for a vote-gated branch, the CBCR may legitimately + * stay on if another voter still holds it, so we don't treat this as + * an error. + */ + REG_POLL_TIMEOUT(priv->cbcr, 10 * 1000, 10, &ret, cbcr_branch_isoff); +} + +/* Delegates to the RCG parent; branch-only clks have no rates to enumerate. */ +static TEE_Result branch_get_rates_array(struct clk *clk, size_t start_index, + unsigned long *rates, + size_t *nb_elts) +{ + struct clk *rcg = clk_get_parent(clk); + + if (!rcg || rcg->ops != &rcg_ops) + return TEE_ERROR_NOT_SUPPORTED; + + return rcg_get_rates_array(rcg, start_index, rates, nb_elts); +} + +static const struct clk_ops branch_ops = { + .enable = branch_enable, + .disable = branch_disable, + .get_rates_array = branch_get_rates_array, +}; + +static TEE_Result rcg_priv_resolve(struct rcg_priv *priv, + const struct clk_rcg_desc *desc) +{ + priv->desc = desc; + + return clk_regmap_va(desc->regmap, desc->cmd_rcgr_addr, &priv->cmd_rcgr); +} + +static TEE_Result branch_priv_resolve(struct branch_priv *priv, + const struct clk_branch_desc *desc) +{ + TEE_Result res = TEE_SUCCESS; + + priv->desc = desc; + + res = clk_regmap_va(desc->regmap, desc->cbcr_addr, &priv->cbcr); + if (res) + return res; + + if (!desc->clk_vote_addr) + return TEE_SUCCESS; + + return clk_regmap_va(desc->regmap, desc->clk_vote_addr, &priv->vote); +} + +/* Free a clk allocated via clk_alloc() along with its own priv struct. */ +static void clk_priv_free(struct clk *clk) +{ + if (!clk) + return; + + free(clk->priv); + clk_free(clk); +} + +/* Frees out[0..count-1] on a partial pll_votes_register() failure. */ +static void pll_vote_unregister(struct clk **out, size_t count) +{ + size_t i = 0; + + for (i = 0; i < count; i++) + clk_priv_free(out[i]); +} + +static TEE_Result pll_votes_register(const struct clk_cfg *cfg, + struct clk **out) +{ + size_t i = 0; + + for (i = 0; i < cfg->n_pll_votes; i++) { + const struct clk_pll_vote *pv = &cfg->pll_votes[i]; + struct pll_vote_priv *priv = NULL; + struct clk *clk = NULL; + TEE_Result res = TEE_SUCCESS; + + priv = calloc(1, sizeof(*priv)); + if (!priv) { + pll_vote_unregister(out, i); + return TEE_ERROR_OUT_OF_MEMORY; + } + + if (pv->vote_reg_addr) { + res = clk_regmap_va(pv->regmap, pv->vote_reg_addr, + &priv->vote); + if (res) { + free(priv); + pll_vote_unregister(out, i); + return res; + } + priv->vote_bit = pv->vote_bit; + } + + clk = clk_alloc(pv->name, &pll_vote_ops, NULL, 0); + if (!clk) { + free(priv); + pll_vote_unregister(out, i); + return TEE_ERROR_OUT_OF_MEMORY; + } + + clk->priv = priv; + + res = clk_register(clk); + if (res) { + clk_priv_free(clk); + pll_vote_unregister(out, i); + return res; + } + + out[i] = clk; + } + + return TEE_SUCCESS; +} + +/* Build one RCG's own clk->parents[] from its parent_map, in map order. */ +static TEE_Result rcg_parents_build(const struct clk_rcg_desc *desc, + struct clk **pll_vote_clks, + size_t n_pll_votes, + struct clk ***out_parents) +{ + struct clk **parents = NULL; + size_t i = 0; + + parents = calloc(desc->n_parents, sizeof(*parents)); + if (!parents) + return TEE_ERROR_OUT_OF_MEMORY; + + for (i = 0; i < desc->n_parents; i++) { + uint8_t src = desc->parent_map[i].src; + + if (src >= n_pll_votes) { + free(parents); + return TEE_ERROR_BAD_PARAMETERS; + } + + parents[i] = pll_vote_clks[src]; + } + + *out_parents = parents; + + return TEE_SUCCESS; +} + +/* Registers @desc parented by its own resolved PLL sources. */ +static TEE_Result rcg_register(const struct clk_rcg_desc *desc, + struct clk **pll_vote_clks, + size_t n_pll_votes, struct clk **out_rcg) +{ + struct rcg_priv *priv = NULL; + struct clk **parents = NULL; + struct clk *clk = NULL; + TEE_Result res = TEE_SUCCESS; + + priv = calloc(1, sizeof(*priv)); + if (!priv) + return TEE_ERROR_OUT_OF_MEMORY; + + res = rcg_priv_resolve(priv, desc); + if (res) { + free(priv); + return res; + } + + res = rcg_parents_build(desc, pll_vote_clks, n_pll_votes, &parents); + if (res) { + free(priv); + return res; + } + + clk = clk_alloc(desc->name, &rcg_ops, parents, desc->n_parents); + free(parents); + if (!clk) { + free(priv); + return TEE_ERROR_OUT_OF_MEMORY; + } + + clk->priv = priv; + + res = clk_register(clk); + if (res) { + clk_priv_free(clk); + return res; + } + + *out_rcg = clk; + + return TEE_SUCCESS; +} + +/* Registers @desc parented by @rcg when desc->rcg named one. */ +static TEE_Result branch_register(const struct clk_branch_desc *desc, + struct clk *rcg, struct clk **out_branch) +{ + struct branch_priv *priv = NULL; + struct clk *clk = NULL; + TEE_Result res = TEE_SUCCESS; + + priv = calloc(1, sizeof(*priv)); + if (!priv) + return TEE_ERROR_OUT_OF_MEMORY; + + res = branch_priv_resolve(priv, desc); + if (res) { + free(priv); + return res; + } + + clk = rcg ? clk_alloc(desc->name, &branch_ops, &rcg, 1) : + clk_alloc(desc->name, &branch_ops, NULL, 0); + if (!clk) { + free(priv); + return TEE_ERROR_OUT_OF_MEMORY; + } + + clk->priv = priv; + if (rcg) + clk->flags |= CLK_SET_RATE_PARENT; + + res = clk_register(clk); + if (res) { + clk_priv_free(clk); + return res; + } + + *out_branch = clk; + + return TEE_SUCCESS; +} + +/* Unwinds whatever clocks_register() already registered; each rcg is reached via clk_get_parent(). */ +static void clocks_unregister(struct clk **pll_clks, size_t n_pll_clks) +{ + size_t i = 0; + + for (i = 0; i < branch_clk_count; i++) { + struct clk *rcg = clk_get_parent(branch_clks[i]); + + clk_priv_free(branch_clks[i]); + if (rcg) + clk_priv_free(rcg); + } + + free(branch_clks); + branch_clks = NULL; + branch_clk_count = 0; + + pll_vote_unregister(pll_clks, n_pll_clks); +} + +static TEE_Result clocks_register(void) +{ + const struct clk_cfg *cfg = clk_get_cfg(); + struct clk **pll_clks = NULL; + size_t i = 0; + TEE_Result res = TEE_SUCCESS; + + if (branch_clks) + return TEE_SUCCESS; + + if (!cfg || !cfg->n_branches || !cfg->n_pll_votes) + return TEE_ERROR_BAD_STATE; + + res = rail_vote_init(); + if (res) + return res; + + pll_clks = calloc(cfg->n_pll_votes, sizeof(*pll_clks)); + if (!pll_clks) + return TEE_ERROR_OUT_OF_MEMORY; + + res = pll_votes_register(cfg, pll_clks); + if (res) { + free(pll_clks); + return res; + } + + branch_clks = calloc(cfg->n_branches, sizeof(*branch_clks)); + if (!branch_clks) { + clocks_unregister(pll_clks, cfg->n_pll_votes); + free(pll_clks); + return TEE_ERROR_OUT_OF_MEMORY; + } + + for (i = 0; i < cfg->n_branches; i++) { + const struct clk_branch_desc *desc = &cfg->branches[i]; + struct clk *rcg = NULL; + + if (desc->rcg) { + res = rcg_register(desc->rcg, pll_clks, + cfg->n_pll_votes, &rcg); + if (res) { + EMSG("%s: failed to register", + desc->rcg->name); + clocks_unregister(pll_clks, cfg->n_pll_votes); + free(pll_clks); + return res; + } + } + + res = branch_register(desc, rcg, &branch_clks[i]); + if (res) { + EMSG("%s: failed to register", desc->name); + if (rcg) + clk_priv_free(rcg); + clocks_unregister(pll_clks, cfg->n_pll_votes); + free(pll_clks); + return res; + } + + branch_clk_count = i + 1; + } + + free(pll_clks); + + return TEE_SUCCESS; +} + +TEE_Result qcom_clk_get_by_name(const char *name, struct clk **out) +{ + size_t i = 0; + + if (!name || !out) + return TEE_ERROR_BAD_PARAMETERS; + + if (!branch_clk_count) + return TEE_ERROR_BAD_STATE; + + for (i = 0; i < branch_clk_count; i++) { + if (!strcmp(branch_clks[i]->name, name)) { + *out = branch_clks[i]; + return TEE_SUCCESS; + } + } + + return TEE_ERROR_ITEM_NOT_FOUND; +} + +TEE_Result qcom_clk_enable_dfs(struct clk *clk) +{ + struct clk *rcg = NULL; + + if (!clk || clk->ops != &branch_ops) + return TEE_ERROR_BAD_PARAMETERS; + + rcg = clk_get_parent(clk); + if (!rcg || rcg->ops != &rcg_ops) + return TEE_ERROR_BAD_PARAMETERS; + + return rcg_enable_dfs(rcg->priv); +} + +/* Copies out DFS-selectable rates paired with each row's DFS index -- never hands out a pointer into this driver's own config table. */ +TEE_Result qcom_clk_get_dfs_rates_array(struct clk *clk, size_t start_index, + unsigned long *rates, + uint8_t *dfs_indices, + size_t *count) +{ + struct clk *rcg = NULL; + const struct clk_rcg_desc *desc = NULL; + size_t total = 0; + size_t seen = 0; + size_t filled = 0; + size_t i = 0; + + if (!clk || clk->ops != &branch_ops || !count) + return TEE_ERROR_BAD_PARAMETERS; + + rcg = clk_get_parent(clk); + if (!rcg || rcg->ops != &rcg_ops) + return TEE_ERROR_BAD_PARAMETERS; + + desc = ((struct rcg_priv *)rcg->priv)->desc; + + if (!desc->dfs_states) + return TEE_ERROR_BAD_PARAMETERS; + + for (i = 0; i < desc->n_configs; i++) + if (desc->configs[i].dfs_idx != CLK_DFS_NA) + total++; + + if (!rates) { + *count = total; + return TEE_SUCCESS; + } + + if (start_index + *count > total) + return TEE_ERROR_BAD_PARAMETERS; + + for (i = 0; i < desc->n_configs && filled < *count; i++) { + const struct clk_mux_config *c = &desc->configs[i]; + + if (c->dfs_idx == CLK_DFS_NA) + continue; + + if (seen++ < start_index) + continue; + + rates[filled] = c->freq_hz; + dfs_indices[filled] = c->dfs_idx; + filled++; + } + + *count = filled; + + return TEE_SUCCESS; +} + +driver_init(clocks_register); diff --git a/core/drivers/clk/qcom/platform/kodiak/clock-qcom-pas.c b/core/drivers/clk/qcom/platform/kodiak/clock-pas.c similarity index 99% rename from core/drivers/clk/qcom/platform/kodiak/clock-qcom-pas.c rename to core/drivers/clk/qcom/platform/kodiak/clock-pas.c index 2aa66ca28..98d500ceb 100644 --- a/core/drivers/clk/qcom/platform/kodiak/clock-qcom-pas.c +++ b/core/drivers/clk/qcom/platform/kodiak/clock-pas.c @@ -14,7 +14,7 @@ #include #include -#include "clock_group_qcom.h" +#include "clock_group.h" #define CBCR_BRANCH_ENABLE_BIT BIT(0) #define CBCR_HW_CTL_ENABLE_BIT BIT(1) diff --git a/core/drivers/clk/qcom/platform/kodiak/clock_group_qcom.h b/core/drivers/clk/qcom/platform/kodiak/clock_group.h similarity index 100% rename from core/drivers/clk/qcom/platform/kodiak/clock_group_qcom.h rename to core/drivers/clk/qcom/platform/kodiak/clock_group.h diff --git a/core/drivers/clk/qcom/platform/lemans/clk_cfg.c b/core/drivers/clk/qcom/platform/lemans/clk_cfg.c new file mode 100644 index 000000000..6f12cf884 --- /dev/null +++ b/core/drivers/clk/qcom/platform/lemans/clk_cfg.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include + +#include "clk_cfg.h" +#include "clock_group.h" +#include "rail_vote.h" + +/* Software source identities, indexing pll_votes[]. */ +#define SRC_BI_TCXO 0 +#define SRC_GPLL0_OUT_EVEN 1 +#define SRC_GPLL4_OUT_MAIN 2 + +#define QUP_SE_DFS_STATES 8 + +static const struct clk_mux_config qup_se_120mhz[] = { + { 7372800, SRC_GPLL0_OUT_EVEN, 2, 384, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 14745600, SRC_GPLL0_OUT_EVEN, 2, 768, 15625, 0x00, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 19200000, SRC_BI_TCXO, 2, 0, 0, 0x01, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 29491200, SRC_GPLL0_OUT_EVEN, 2, 1536, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 32000000, SRC_GPLL0_OUT_EVEN, 2, 8, 75, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 48000000, SRC_GPLL0_OUT_EVEN, 2, 4, 25, 0x02, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 51200000, SRC_GPLL0_OUT_EVEN, 2, 64, 375, 0x03, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 64000000, SRC_GPLL0_OUT_EVEN, 2, 16, 75, 0x04, RAIL_VOLTAGE_LEVEL_LOW_SVS }, + { 75000000, SRC_GPLL0_OUT_EVEN, 8, 0, 0, 0x05, RAIL_VOLTAGE_LEVEL_SVS }, + { 80000000, SRC_GPLL0_OUT_EVEN, 2, 4, 15, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 96000000, SRC_GPLL0_OUT_EVEN, 2, 8, 25, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 100000000, SRC_GPLL0_OUT_EVEN, 6, 0, 0, 0x06, RAIL_VOLTAGE_LEVEL_SVS }, + { 120000000, SRC_GPLL0_OUT_EVEN, 5, 0, 0, 0x07, RAIL_VOLTAGE_LEVEL_SVS }, +}; + +static const struct clk_mux_config qup_se_100mhz[] = { + { 7372800, SRC_GPLL0_OUT_EVEN, 2, 384, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 14745600, SRC_GPLL0_OUT_EVEN, 2, 768, 15625, 0x00, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 19200000, SRC_BI_TCXO, 2, 0, 0, 0x01, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 29491200, SRC_GPLL0_OUT_EVEN, 2, 1536, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 32000000, SRC_GPLL0_OUT_EVEN, 2, 8, 75, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 48000000, SRC_GPLL0_OUT_EVEN, 2, 4, 25, 0x02, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 51200000, SRC_GPLL0_OUT_EVEN, 2, 64, 375, 0x03, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 64000000, SRC_GPLL0_OUT_EVEN, 2, 16, 75, 0x04, RAIL_VOLTAGE_LEVEL_LOW_SVS }, + { 75000000, SRC_GPLL0_OUT_EVEN, 8, 0, 0, 0x05, RAIL_VOLTAGE_LEVEL_SVS }, + { 80000000, SRC_GPLL0_OUT_EVEN, 2, 4, 15, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 96000000, SRC_GPLL0_OUT_EVEN, 2, 8, 25, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 100000000, SRC_GPLL0_OUT_EVEN, 6, 0, 0, 0x06, RAIL_VOLTAGE_LEVEL_SVS }, +}; + +static const struct clk_mux_config qup_se_403mhz[] = { + { 7372800, SRC_GPLL0_OUT_EVEN, 2, 384, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 14745600, SRC_GPLL0_OUT_EVEN, 2, 768, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 19200000, SRC_BI_TCXO, 2, 0, 0, 0x00, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 29491200, SRC_GPLL0_OUT_EVEN, 2, 1536, 15625, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 32000000, SRC_GPLL0_OUT_EVEN, 2, 8, 75, 0x01, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 48000000, SRC_GPLL0_OUT_EVEN, 2, 4, 25, 0x02, RAIL_VOLTAGE_LEVEL_MIN_SVS }, + { 64000000, SRC_GPLL0_OUT_EVEN, 2, 16, 75, 0x03, RAIL_VOLTAGE_LEVEL_LOW_SVS }, + { 75000000, SRC_GPLL0_OUT_EVEN, 8, 0, 0, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 80000000, SRC_GPLL0_OUT_EVEN, 2, 4, 15, CLK_DFS_NA, RAIL_VOLTAGE_LEVEL_SVS }, + { 96000000, SRC_GPLL0_OUT_EVEN, 2, 8, 25, 0x04, RAIL_VOLTAGE_LEVEL_SVS }, + { 100000000, SRC_GPLL0_OUT_EVEN, 6, 0, 0, 0x05, RAIL_VOLTAGE_LEVEL_SVS }, + { 403200000, SRC_GPLL4_OUT_MAIN, 4, 0, 0, 0x06, RAIL_VOLTAGE_LEVEL_SVS }, +}; + +static struct clk_regmap gcc_regmap = { + .io.pa = GCC_BASE, + .size = GCC_SIZE, +}; + +static const struct clk_pll_vote pll_votes[] = { + { "xo", NULL, 0, 0 }, + { "gpll0_div2", &gcc_regmap, GCC_PLL_BRANCH_ENA_VOTE, + GCC_PLL_VOTE_BIT_GPLL0 }, + { "gpll4", &gcc_regmap, GCC_PLL_BRANCH_ENA_VOTE, + GCC_PLL_VOTE_BIT_GPLL4 }, +}; + +/* mux_sel values are this wrapper's own SRC_SEL encoding -- literal, not shared macros, since a differently-wired RCG would need different values for the same PLL. */ +static const struct clk_parent_map parent_map_0[] = { + { SRC_BI_TCXO, 0 }, /* BI_TCXO */ + { SRC_GPLL0_OUT_EVEN, 6 }, /* GPLL0_OUT_EVEN */ + { SRC_GPLL4_OUT_MAIN, 5 }, /* GPLL4_OUT_MAIN */ +}; + +/* RCGs -- one named instance per QUP SE index that needs rate control. */ +#define QUP_SE_MND_WIDTH 16 + +#define QUP_SE_RCG(_name, _cmd_rcgr, _plan) \ + { \ + .name = (_name), \ + .regmap = &gcc_regmap, \ + .cmd_rcgr_addr = (_cmd_rcgr), \ + .mnd_width = QUP_SE_MND_WIDTH, \ + .dfs_states = QUP_SE_DFS_STATES, \ + .configs = (_plan), \ + .n_configs = ARRAY_SIZE(_plan), \ + .parent_map = parent_map_0, \ + .n_parents = ARRAY_SIZE(parent_map_0), \ + } + +static const struct clk_rcg_desc gcc_qupv3_wrap0_s0_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s0_clk_src", + GCC_QUPV3_WRAP0_S0_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s1_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s1_clk_src", + GCC_QUPV3_WRAP0_S1_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s2_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s2_clk_src", + GCC_QUPV3_WRAP0_S2_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s3_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s3_clk_src", + GCC_QUPV3_WRAP0_S3_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s4_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s4_clk_src", + GCC_QUPV3_WRAP0_S4_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s5_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s5_clk_src", + GCC_QUPV3_WRAP0_S5_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap0_s6_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap0_s6_clk_src", + GCC_QUPV3_WRAP0_S6_CMD_RCGR, qup_se_100mhz); + +static const struct clk_rcg_desc gcc_qupv3_wrap1_s0_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s0_clk_src", + GCC_QUPV3_WRAP1_S0_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s1_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s1_clk_src", + GCC_QUPV3_WRAP1_S1_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s2_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s2_clk_src", + GCC_QUPV3_WRAP1_S2_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s3_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s3_clk_src", + GCC_QUPV3_WRAP1_S3_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s4_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s4_clk_src", + GCC_QUPV3_WRAP1_S4_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s5_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s5_clk_src", + GCC_QUPV3_WRAP1_S5_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap1_s6_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap1_s6_clk_src", + GCC_QUPV3_WRAP1_S6_CMD_RCGR, qup_se_100mhz); + +static const struct clk_rcg_desc gcc_qupv3_wrap2_s0_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s0_clk_src", + GCC_QUPV3_WRAP2_S0_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s1_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s1_clk_src", + GCC_QUPV3_WRAP2_S1_CMD_RCGR, qup_se_120mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s2_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s2_clk_src", + GCC_QUPV3_WRAP2_S2_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s3_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s3_clk_src", + GCC_QUPV3_WRAP2_S3_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s4_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s4_clk_src", + GCC_QUPV3_WRAP2_S4_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s5_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s5_clk_src", + GCC_QUPV3_WRAP2_S5_CMD_RCGR, qup_se_100mhz); +static const struct clk_rcg_desc gcc_qupv3_wrap2_s6_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap2_s6_clk_src", + GCC_QUPV3_WRAP2_S6_CMD_RCGR, qup_se_100mhz); + +static const struct clk_rcg_desc gcc_qupv3_wrap3_s0_clk_src = + QUP_SE_RCG("gcc_qupv3_wrap3_s0_clk_src", + GCC_QUPV3_WRAP3_S0_CMD_RCGR, qup_se_403mhz); + +/* Branches -- one entry per gated QUP SE clock; combined ones point @rcg at their own named RCG above. */ +#define BRANCH_CLK(_name, _cbcr, _vote_reg, _vote_bit, _rcg) \ + { \ + .name = (_name), \ + .regmap = &gcc_regmap, \ + .cbcr_addr = (_cbcr), \ + .clk_vote_addr = (_vote_reg), \ + .vote_bit = (_vote_bit), \ + .rcg = (_rcg), \ + } + +static const struct clk_branch_desc branches[] = { + BRANCH_CLK("gcc_qupv3_wrap0_s0_clk", GCC_QUPV3_WRAP0_S0_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S0_SHFT, + &gcc_qupv3_wrap0_s0_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s1_clk", GCC_QUPV3_WRAP0_S1_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S1_SHFT, + &gcc_qupv3_wrap0_s1_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s2_clk", GCC_QUPV3_WRAP0_S2_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S2_SHFT, + &gcc_qupv3_wrap0_s2_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s3_clk", GCC_QUPV3_WRAP0_S3_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S3_SHFT, + &gcc_qupv3_wrap0_s3_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s4_clk", GCC_QUPV3_WRAP0_S4_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S4_SHFT, + &gcc_qupv3_wrap0_s4_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s5_clk", GCC_QUPV3_WRAP0_S5_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S5_SHFT, + &gcc_qupv3_wrap0_s5_clk_src), + BRANCH_CLK("gcc_qupv3_wrap0_s6_clk", GCC_QUPV3_WRAP0_S6_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_S6_SHFT, + &gcc_qupv3_wrap0_s6_clk_src), + + BRANCH_CLK("gcc_qupv3_wrap1_s0_clk", GCC_QUPV3_WRAP1_S0_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S0_SHFT, + &gcc_qupv3_wrap1_s0_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s1_clk", GCC_QUPV3_WRAP1_S1_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S1_SHFT, + &gcc_qupv3_wrap1_s1_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s2_clk", GCC_QUPV3_WRAP1_S2_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S2_SHFT, + &gcc_qupv3_wrap1_s2_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s3_clk", GCC_QUPV3_WRAP1_S3_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S3_SHFT, + &gcc_qupv3_wrap1_s3_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s4_clk", GCC_QUPV3_WRAP1_S4_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S4_SHFT, + &gcc_qupv3_wrap1_s4_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s5_clk", GCC_QUPV3_WRAP1_S5_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_S5_SHFT, + &gcc_qupv3_wrap1_s5_clk_src), + BRANCH_CLK("gcc_qupv3_wrap1_s6_clk", GCC_QUPV3_WRAP1_S6_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_3, QUPV3_WRAP1_S6_SHFT, + &gcc_qupv3_wrap1_s6_clk_src), + + BRANCH_CLK("gcc_qupv3_wrap2_s0_clk", GCC_QUPV3_WRAP2_S0_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S0_SHFT, + &gcc_qupv3_wrap2_s0_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s1_clk", GCC_QUPV3_WRAP2_S1_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S1_SHFT, + &gcc_qupv3_wrap2_s1_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s2_clk", GCC_QUPV3_WRAP2_S2_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S2_SHFT, + &gcc_qupv3_wrap2_s2_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s3_clk", GCC_QUPV3_WRAP2_S3_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S3_SHFT, + &gcc_qupv3_wrap2_s3_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s4_clk", GCC_QUPV3_WRAP2_S4_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S4_SHFT, + &gcc_qupv3_wrap2_s4_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s5_clk", GCC_QUPV3_WRAP2_S5_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_S5_SHFT, + &gcc_qupv3_wrap2_s5_clk_src), + BRANCH_CLK("gcc_qupv3_wrap2_s6_clk", GCC_QUPV3_WRAP2_S6_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_3, QUPV3_WRAP2_S6_SHFT, + &gcc_qupv3_wrap2_s6_clk_src), + + BRANCH_CLK("gcc_qupv3_wrap3_s0_clk", GCC_QUPV3_WRAP3_S0_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE, QUPV3_WRAP3_S0_SHFT, + &gcc_qupv3_wrap3_s0_clk_src), + + BRANCH_CLK("gcc_qupv3_wrap0_core_2x_clk", + GCC_QUPV3_WRAP0_CORE_2X_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_CORE_2X_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap0_core_clk", + GCC_QUPV3_WRAP0_CORE_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP0_CORE_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_0_m_ahb_clk", + GCC_QUPV3_WRAP_0_M_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP_0_M_AHB_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_0_s_ahb_clk", + GCC_QUPV3_WRAP_0_S_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP_0_S_AHB_SHFT, + NULL), + + BRANCH_CLK("gcc_qupv3_wrap1_core_2x_clk", + GCC_QUPV3_WRAP1_CORE_2X_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_CORE_2X_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap1_core_clk", + GCC_QUPV3_WRAP1_CORE_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP1_CORE_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_1_m_ahb_clk", + GCC_QUPV3_WRAP_1_M_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP_1_M_AHB_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_1_s_ahb_clk", + GCC_QUPV3_WRAP_1_S_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_1, QUPV3_WRAP_1_S_AHB_SHFT, + NULL), + + BRANCH_CLK("gcc_qupv3_wrap2_core_2x_clk", + GCC_QUPV3_WRAP2_CORE_2X_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_CORE_2X_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap2_core_clk", + GCC_QUPV3_WRAP2_CORE_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP2_CORE_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_2_m_ahb_clk", + GCC_QUPV3_WRAP_2_M_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP_2_M_AHB_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_2_s_ahb_clk", + GCC_QUPV3_WRAP_2_S_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE_2, QUPV3_WRAP_2_S_AHB_SHFT, + NULL), + + BRANCH_CLK("gcc_qupv3_wrap3_core_2x_clk", + GCC_QUPV3_WRAP3_CORE_2X_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE, QUPV3_WRAP3_CORE_2X_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap3_core_clk", + GCC_QUPV3_WRAP3_CORE_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE, QUPV3_WRAP3_CORE_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_3_m_ahb_clk", + GCC_QUPV3_WRAP_3_M_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE, QUPV3_WRAP_3_M_AHB_SHFT, + NULL), + BRANCH_CLK("gcc_qupv3_wrap_3_s_ahb_clk", + GCC_QUPV3_WRAP_3_S_AHB_CBCR, + GCC_CLOCK_BRANCH_ENA_VOTE, QUPV3_WRAP_3_S_AHB_SHFT, + NULL), +}; + +static const struct clk_cfg lemans_clk_cfg = { + .branches = branches, + .n_branches = ARRAY_SIZE(branches), + .pll_votes = pll_votes, + .n_pll_votes = ARRAY_SIZE(pll_votes), +}; + +const struct clk_cfg *clk_get_cfg(void) +{ + return &lemans_clk_cfg; +} diff --git a/core/drivers/clk/qcom/platform/lemans/clock-qcom-pas.c b/core/drivers/clk/qcom/platform/lemans/clock-pas.c similarity index 99% rename from core/drivers/clk/qcom/platform/lemans/clock-qcom-pas.c rename to core/drivers/clk/qcom/platform/lemans/clock-pas.c index 244750925..296875935 100644 --- a/core/drivers/clk/qcom/platform/lemans/clock-qcom-pas.c +++ b/core/drivers/clk/qcom/platform/lemans/clock-pas.c @@ -13,7 +13,7 @@ #include #include -#include "clock_group_qcom.h" +#include "clock_group.h" #define CBCR_BRANCH_ENABLE_BIT BIT(0) #define CBCR_HW_CTL_ENABLE_BIT BIT(1) diff --git a/core/drivers/clk/qcom/platform/lemans/clock_group_qcom.h b/core/drivers/clk/qcom/platform/lemans/clock_group.h similarity index 54% rename from core/drivers/clk/qcom/platform/lemans/clock_group_qcom.h rename to core/drivers/clk/qcom/platform/lemans/clock_group.h index 8dd0bcd14..139e41d3b 100644 --- a/core/drivers/clk/qcom/platform/lemans/clock_group_qcom.h +++ b/core/drivers/clk/qcom/platform/lemans/clock_group.h @@ -5,6 +5,8 @@ #ifndef _CLOCK_GROUP_QCOM_H_ #define _CLOCK_GROUP_QCOM_H_ +#include + #define GCC_SEC_CTRL_CFG_RCGR 0x39038 #define GCC_SEC_CTRL_CMD_RCGR 0x39034 #define QFPROM_CLOCK_DIVIDE 0x7 @@ -224,4 +226,165 @@ #define GPDSP_Q6_PLL_USER_CTL 0x00000000 #define GPDSP_Q6_PLL_USER_CTL_U 0x00400805 +#ifdef CFG_QCOM_CLK_CFG +/* Full physical addresses; CBCR-to-CMD_RCGR distance is not architectural, don't derive one from the other. */ +#define GCC_QUPV3_WRAP0_S0_CBCR (GCC_BASE + 0x1314c) +#define GCC_QUPV3_WRAP0_S0_CMD_RCGR (GCC_BASE + 0x13154) +#define GCC_QUPV3_WRAP0_S1_CBCR (GCC_BASE + 0x13280) +#define GCC_QUPV3_WRAP0_S1_CMD_RCGR (GCC_BASE + 0x13288) +#define GCC_QUPV3_WRAP0_S2_CBCR (GCC_BASE + 0x133b4) +#define GCC_QUPV3_WRAP0_S2_CMD_RCGR (GCC_BASE + 0x133bc) +#define GCC_QUPV3_WRAP0_S3_CBCR (GCC_BASE + 0x134e8) +#define GCC_QUPV3_WRAP0_S3_CMD_RCGR (GCC_BASE + 0x134f0) +#define GCC_QUPV3_WRAP0_S4_CBCR (GCC_BASE + 0x1361c) +#define GCC_QUPV3_WRAP0_S4_CMD_RCGR (GCC_BASE + 0x13624) +#define GCC_QUPV3_WRAP0_S5_CBCR (GCC_BASE + 0x13750) +#define GCC_QUPV3_WRAP0_S5_CMD_RCGR (GCC_BASE + 0x13758) +#define GCC_QUPV3_WRAP0_S6_CBCR (GCC_BASE + 0x13884) +#define GCC_QUPV3_WRAP0_S6_CMD_RCGR (GCC_BASE + 0x1388c) + +#define GCC_QUPV3_WRAP1_S0_CBCR (GCC_BASE + 0x1414c) +#define GCC_QUPV3_WRAP1_S0_CMD_RCGR (GCC_BASE + 0x14154) +#define GCC_QUPV3_WRAP1_S1_CBCR (GCC_BASE + 0x14280) +#define GCC_QUPV3_WRAP1_S1_CMD_RCGR (GCC_BASE + 0x14288) +#define GCC_QUPV3_WRAP1_S2_CBCR (GCC_BASE + 0x143b4) +#define GCC_QUPV3_WRAP1_S2_CMD_RCGR (GCC_BASE + 0x143bc) +#define GCC_QUPV3_WRAP1_S3_CBCR (GCC_BASE + 0x144e8) +#define GCC_QUPV3_WRAP1_S3_CMD_RCGR (GCC_BASE + 0x144f0) +#define GCC_QUPV3_WRAP1_S4_CBCR (GCC_BASE + 0x1461c) +#define GCC_QUPV3_WRAP1_S4_CMD_RCGR (GCC_BASE + 0x14624) +#define GCC_QUPV3_WRAP1_S5_CBCR (GCC_BASE + 0x14750) +#define GCC_QUPV3_WRAP1_S5_CMD_RCGR (GCC_BASE + 0x14758) +#define GCC_QUPV3_WRAP1_S6_CBCR (GCC_BASE + 0x14884) +#define GCC_QUPV3_WRAP1_S6_CMD_RCGR (GCC_BASE + 0x1488c) + +#define GCC_QUPV3_WRAP2_S0_CBCR (GCC_BASE + 0x1a14c) +#define GCC_QUPV3_WRAP2_S0_CMD_RCGR (GCC_BASE + 0x1a154) +#define GCC_QUPV3_WRAP2_S1_CBCR (GCC_BASE + 0x1a280) +#define GCC_QUPV3_WRAP2_S1_CMD_RCGR (GCC_BASE + 0x1a288) +#define GCC_QUPV3_WRAP2_S2_CBCR (GCC_BASE + 0x1a3b4) +#define GCC_QUPV3_WRAP2_S2_CMD_RCGR (GCC_BASE + 0x1a3bc) +#define GCC_QUPV3_WRAP2_S3_CBCR (GCC_BASE + 0x1a4e8) +#define GCC_QUPV3_WRAP2_S3_CMD_RCGR (GCC_BASE + 0x1a4f0) +#define GCC_QUPV3_WRAP2_S4_CBCR (GCC_BASE + 0x1a61c) +#define GCC_QUPV3_WRAP2_S4_CMD_RCGR (GCC_BASE + 0x1a624) +#define GCC_QUPV3_WRAP2_S5_CBCR (GCC_BASE + 0x1a750) +#define GCC_QUPV3_WRAP2_S5_CMD_RCGR (GCC_BASE + 0x1a758) +#define GCC_QUPV3_WRAP2_S6_CBCR (GCC_BASE + 0x1a884) +#define GCC_QUPV3_WRAP2_S6_CMD_RCGR (GCC_BASE + 0x1a88c) + +#define GCC_QUPV3_WRAP3_S0_CBCR (GCC_BASE + 0xb414c) +#define GCC_QUPV3_WRAP3_S0_CMD_RCGR (GCC_BASE + 0xb4154) + +/* Wrapper-level gating clocks (core/core_2x/m_ahb/s_ahb): no RCG backs these. */ +#define GCC_QUPV3_WRAP_0_M_AHB_CBCR (GCC_BASE + 0x13004) +#define GCC_QUPV3_WRAP_0_S_AHB_CBCR (GCC_BASE + 0x13008) +#define GCC_QUPV3_WRAP0_CORE_CBCR (GCC_BASE + 0x1300c) +#define GCC_QUPV3_WRAP0_CORE_2X_CBCR (GCC_BASE + 0x13018) + +#define GCC_QUPV3_WRAP_1_M_AHB_CBCR (GCC_BASE + 0x14004) +#define GCC_QUPV3_WRAP_1_S_AHB_CBCR (GCC_BASE + 0x14008) +#define GCC_QUPV3_WRAP1_CORE_CBCR (GCC_BASE + 0x1400c) +#define GCC_QUPV3_WRAP1_CORE_2X_CBCR (GCC_BASE + 0x14018) + +#define GCC_QUPV3_WRAP_2_M_AHB_CBCR (GCC_BASE + 0x1a004) +#define GCC_QUPV3_WRAP_2_S_AHB_CBCR (GCC_BASE + 0x1a008) +#define GCC_QUPV3_WRAP2_CORE_CBCR (GCC_BASE + 0x1a00c) +#define GCC_QUPV3_WRAP2_CORE_2X_CBCR (GCC_BASE + 0x1a018) + +#define GCC_QUPV3_WRAP_3_M_AHB_CBCR (GCC_BASE + 0xb4004) +#define GCC_QUPV3_WRAP_3_S_AHB_CBCR (GCC_BASE + 0xb4008) +#define GCC_QUPV3_WRAP3_CORE_CBCR (GCC_BASE + 0xb400c) +#define GCC_QUPV3_WRAP3_CORE_2X_CBCR (GCC_BASE + 0xb4018) + +/* Shared branch-enable vote registers; branches gate through one of these, not their own CBCR CLK_ENABLE bit. */ +#define GCC_CLOCK_BRANCH_ENA_VOTE (GCC_BASE + 0x3e040) +#define GCC_CLOCK_BRANCH_ENA_VOTE_1 (GCC_BASE + 0x3e048) +#define GCC_CLOCK_BRANCH_ENA_VOTE_2 (GCC_BASE + 0x3e050) +#define GCC_CLOCK_BRANCH_ENA_VOTE_3 (GCC_BASE + 0x3e058) + +/* Vote-bit positions, named after their HWIO_..._CLK_ENA_SHFT counterparts. */ + +/* Bits within GCC_CLOCK_BRANCH_ENA_VOTE (base). */ +#define QUPV3_WRAP3_S0_SHFT 25 +#define QUPV3_WRAP3_CORE_SHFT 23 +#define QUPV3_WRAP3_CORE_2X_SHFT 24 +#define QUPV3_WRAP_3_M_AHB_SHFT 27 +#define QUPV3_WRAP_3_S_AHB_SHFT 20 + +/* Bits within GCC_CLOCK_BRANCH_ENA_VOTE_1. */ +#define QUPV3_WRAP0_S0_SHFT 10 +#define QUPV3_WRAP0_S1_SHFT 11 +#define QUPV3_WRAP0_S2_SHFT 12 +#define QUPV3_WRAP0_S3_SHFT 13 +#define QUPV3_WRAP0_S4_SHFT 14 +#define QUPV3_WRAP0_S5_SHFT 15 +#define QUPV3_WRAP0_S6_SHFT 16 +#define QUPV3_WRAP0_CORE_SHFT 8 +#define QUPV3_WRAP0_CORE_2X_SHFT 9 +#define QUPV3_WRAP_0_M_AHB_SHFT 6 +#define QUPV3_WRAP_0_S_AHB_SHFT 7 +#define QUPV3_WRAP1_S0_SHFT 22 +#define QUPV3_WRAP1_S1_SHFT 23 +#define QUPV3_WRAP1_S2_SHFT 24 +#define QUPV3_WRAP1_S3_SHFT 25 +#define QUPV3_WRAP1_S4_SHFT 26 +#define QUPV3_WRAP1_S5_SHFT 27 +#define QUPV3_WRAP1_CORE_SHFT 19 +#define QUPV3_WRAP1_CORE_2X_SHFT 18 +#define QUPV3_WRAP_1_M_AHB_SHFT 20 +#define QUPV3_WRAP_1_S_AHB_SHFT 21 + +/* Bits within GCC_CLOCK_BRANCH_ENA_VOTE_2. */ +#define QUPV3_WRAP2_S0_SHFT 4 +#define QUPV3_WRAP2_S1_SHFT 5 +#define QUPV3_WRAP2_S2_SHFT 6 +#define QUPV3_WRAP2_S3_SHFT 7 +#define QUPV3_WRAP2_S4_SHFT 8 +#define QUPV3_WRAP2_S5_SHFT 9 +#define QUPV3_WRAP2_CORE_SHFT 0 +#define QUPV3_WRAP2_CORE_2X_SHFT 3 +#define QUPV3_WRAP_2_M_AHB_SHFT 2 +#define QUPV3_WRAP_2_S_AHB_SHFT 1 + +/* Bits within GCC_CLOCK_BRANCH_ENA_VOTE_3. */ +#define QUPV3_WRAP1_S6_SHFT 27 +#define QUPV3_WRAP2_S6_SHFT 29 + +/* PLL branch-enable vote register and bits for the PLLs QUP SE RCGs source from; direct GCC write, not RPMh. */ +#define GCC_PLL_BRANCH_ENA_VOTE (GCC_BASE + 0x3e068) +#define GCC_PLL_VOTE_BIT_GPLL0 0 +#define GCC_PLL_VOTE_BIT_GPLL4 4 + +/* RCG register layout; offsets relative to clk_rcg_desc.cmd_rcgr_addr. */ +#define QCOM_RCG_CFG_REG_OFFSET 0x4 +#define QCOM_RCG_M_REG_OFFSET 0x8 +#define QCOM_RCG_N_REG_OFFSET 0xC +#define QCOM_RCG_D_REG_OFFSET 0x10 +#define QCOM_RCG_CMD_DFSR_REG_OFFSET 0x14 +#define QCOM_RCG_PERF_DFSR_REG_OFFSET 0x1C +#define QCOM_RCG_PERF_M_DFSR_REG_OFFSET 0x5C +#define QCOM_RCG_PERF_N_DFSR_REG_OFFSET 0x9C +#define QCOM_RCG_PERF_D_DFSR_REG_OFFSET 0xDC + +/* CMD_RCGR fields. */ +#define QCOM_RCG_CMD_CFG_UPDATE_FMSK 0x00000001 + +/* CFG_RCGR fields. */ +#define QCOM_RCG_CFG_HW_CLK_CONTROL_FMSK 0x00100000 +#define QCOM_RCG_CFG_MODE_FMSK 0x00003000 +#define QCOM_RCG_CFG_MODE_SHFT 0xC +#define QCOM_RCG_CFG_SRC_SEL_FMSK 0x00000700 +#define QCOM_RCG_CFG_SRC_SEL_SHFT 0x8 +#define QCOM_RCG_CFG_SRC_DIV_FMSK 0x0000001F +#define QCOM_RCG_CFG_SRC_DIV_SHFT 0 +#define QCOM_RCG_CFG_DUAL_EDGE_MODE_VAL 0x2 + +/* CMD_DFSR fields. */ +#define QCOM_RCG_CMD_DFSR_HW_CLK_CONTROL_FMSK 0x00000020 +#define QCOM_RCG_CMD_DFSR_DFS_EN_FMSK 0x00000001 +#define QCOM_RCG_CMD_DFSR_SW_PERF_STATE_FMSK 0x00007800 +#define QCOM_RCG_CMD_DFSR_SW_PERF_STATE_SHFT 11 +#endif /* CFG_QCOM_CLK_CFG */ + #endif /* _CLOCK_GROUP_QCOM_H_ */ diff --git a/core/drivers/clk/qcom/rail_rpmh.c b/core/drivers/clk/qcom/rail_rpmh.c new file mode 100644 index 000000000..513ec2f27 --- /dev/null +++ b/core/drivers/clk/qcom/rail_rpmh.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include "rail_vote.h" + +#define RPMH_RAIL_HLVL_MAX 32 + +#define RPMH_RAIL_HLVL_INVALID 0xFFFFFFFF + +/* A rail's own hlvl table and last-applied hlvl -- cx and mx never share a mapping. */ +struct rpmh_rail_hlvl { + uint32_t addr; + uint16_t corners[RPMH_RAIL_HLVL_MAX]; /* ascending */ + size_t num_hlvls; + uint32_t voted_hlvl; /* hlvl applied here; INVALID if unknown/partial */ +}; + +struct rpmh_rail { + struct mutex lock; /* serialises the vote + tracking state */ + struct rpmh_client *rpmh; + bool ready; + struct rpmh_rail_hlvl cx; + struct rpmh_rail_hlvl mx; /* .addr stays 0 if this chip has none */ + /* Refcount per corner requested, indexed via cx's table (always present, unlike mx). Demand tracking only -- both rails are still voted independently in rpmh_rail_apply(). */ + uint16_t votes[RPMH_RAIL_HLVL_MAX]; +}; + +static struct rpmh_rail rpmh_rail = { + .lock = MUTEX_INITIALIZER, +}; + +/* Loads one rail's own hlvl table; @hlvl->addr stays 0 on any failure. */ +static TEE_Result rpmh_hlvl_load(const char *res_id, + struct rpmh_rail_hlvl *hlvl) +{ + size_t len = sizeof(hlvl->corners); + TEE_Result res = TEE_SUCCESS; + + hlvl->addr = 0; + + res = cmd_db_get_addr(res_id, &hlvl->addr); + if (res) + return res; + + res = cmd_db_get_aux(res_id, (uint8_t *)hlvl->corners, &len); + if (res) { + hlvl->addr = 0; + return res; + } + + hlvl->num_hlvls = len / sizeof(hlvl->corners[0]); + + while (hlvl->num_hlvls > 1 && hlvl->corners[hlvl->num_hlvls - 1] == 0) + hlvl->num_hlvls--; + + if (!hlvl->num_hlvls) { + hlvl->addr = 0; + return TEE_ERROR_BAD_STATE; + } + + hlvl->voted_hlvl = RPMH_RAIL_HLVL_INVALID; + + return TEE_SUCCESS; +} + +/* Called once from clocks_register(); early_init() has already brought up cmd_db/rpmh by driver_init time. */ +TEE_Result rail_vote_init(void) +{ + struct rpmh_rail *r = &rpmh_rail; + TEE_Result res = TEE_SUCCESS; + + if (r->ready) + return TEE_SUCCESS; + + if (!r->rpmh) { + r->rpmh = rpmh_create_handle(RSC_DRV_SECURE, "rail"); + if (!r->rpmh) + return TEE_ERROR_GENERIC; + } + + res = rpmh_hlvl_load("cx.lvl", &r->cx); + if (res) + return res; + + /* MX is optional; some chips have none. */ + rpmh_hlvl_load("mx.lvl", &r->mx); + + r->ready = true; + + return TEE_SUCCESS; +} + +/* Ceiling match against @hlvl's own table; requires corners[] ascending. */ +static TEE_Result rpmh_vlvl_to_hlvl(struct rpmh_rail_hlvl *hlvl, + uint16_t corner, uint32_t *out) +{ + size_t i = 0; + + for (i = 0; i < hlvl->num_hlvls; i++) { + if (hlvl->corners[i] >= corner) { + *out = i; + return TEE_SUCCESS; + } + } + + return TEE_ERROR_BAD_PARAMETERS; +} + +/* Resolves @corner against this rail's own table and applies it if changed. */ +static TEE_Result rpmh_hlvl_apply(struct rpmh_client *rpmh, + struct rpmh_rail_hlvl *hlvl, + uint16_t corner) +{ + uint32_t new_hlvl = 0; + uint32_t req_id = 0; + TEE_Result res = TEE_SUCCESS; + + if (!hlvl->addr) + return TEE_SUCCESS; + + res = rpmh_vlvl_to_hlvl(hlvl, corner, &new_hlvl); + if (res) + return res; + + if (new_hlvl == hlvl->voted_hlvl) + return TEE_SUCCESS; + + hlvl->voted_hlvl = RPMH_RAIL_HLVL_INVALID; + + res = rpmh_send_command(rpmh, RPMH_SET_ACTIVE, true, hlvl->addr, + new_hlvl, &req_id); + if (res) + return res; + rpmh_barrier_single(rpmh, req_id); + + hlvl->voted_hlvl = new_hlvl; + + return TEE_SUCCESS; +} + +static TEE_Result rpmh_rail_apply(uint16_t corner) +{ + struct rpmh_rail *r = &rpmh_rail; + TEE_Result res = TEE_SUCCESS; + + res = rpmh_hlvl_apply(r->rpmh, &r->cx, corner); + if (res) + return res; + + return rpmh_hlvl_apply(r->rpmh, &r->mx, corner); +} + +/* Highest corner (from cx's own table) any voter currently needs. */ +static uint16_t rpmh_rail_peak(void) +{ + struct rpmh_rail *r = &rpmh_rail; + size_t peak = 0; + size_t i = 0; + + for (i = 0; i < r->cx.num_hlvls; i++) + if (r->votes[i]) + peak = i; + + return r->cx.corners[peak]; +} + +static TEE_Result rpmh_rail_move(uint16_t old_corner, uint16_t new_corner) +{ + struct rpmh_rail *r = &rpmh_rail; + uint32_t old_hlvl = 0; + uint32_t new_hlvl = 0; + TEE_Result res = TEE_SUCCESS; + + if (new_corner) { + res = rpmh_vlvl_to_hlvl(&r->cx, new_corner, &new_hlvl); + if (res) + return res; + } + + if (old_corner) { + res = rpmh_vlvl_to_hlvl(&r->cx, old_corner, &old_hlvl); + if (res) + return res; + + if (!r->votes[old_hlvl]) + return TEE_ERROR_BAD_STATE; + } + + if (new_corner) + r->votes[new_hlvl]++; + if (old_corner) + r->votes[old_hlvl]--; + + res = rpmh_rail_apply(rpmh_rail_peak()); + if (res) { + if (new_corner) + r->votes[new_hlvl]--; + if (old_corner) + r->votes[old_hlvl]++; + } + + return res; +} + +TEE_Result rail_vote(uint16_t old_corner, uint16_t new_corner) +{ + struct rpmh_rail *r = &rpmh_rail; + TEE_Result res = TEE_SUCCESS; + + mutex_lock(&r->lock); + if (r->ready) + res = rpmh_rail_move(old_corner, new_corner); + else + res = TEE_ERROR_BAD_STATE; + mutex_unlock(&r->lock); + + return res; +} diff --git a/core/drivers/clk/qcom/rail_vote.h b/core/drivers/clk/qcom/rail_vote.h new file mode 100644 index 000000000..153db4f08 --- /dev/null +++ b/core/drivers/clk/qcom/rail_vote.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _RAIL_VOTE_H_ +#define _RAIL_VOTE_H_ + +#include +#include + +/* RPMh corner levels; not tied to any one rail. */ +#define RAIL_VOLTAGE_LEVEL_MIN_SVS 0x30 +#define RAIL_VOLTAGE_LEVEL_LOW_SVS 0x40 +#define RAIL_VOLTAGE_LEVEL_SVS 0x80 + +TEE_Result rail_vote_init(void); +TEE_Result rail_vote(uint16_t old_corner, uint16_t new_corner); + +#endif /* _RAIL_VOTE_H_ */ diff --git a/core/drivers/clk/qcom/sub.mk b/core/drivers/clk/qcom/sub.mk index f47622b46..ec4ba1283 100644 --- a/core/drivers/clk/qcom/sub.mk +++ b/core/drivers/clk/qcom/sub.mk @@ -1,8 +1,15 @@ global-incdirs-y += . global-incdirs-y += platform/$(PLATFORM_FLAVOR) -srcs-y += clock-qcom.c -srcs-$(CFG_QCOM_PAS_PTA) += platform/$(PLATFORM_FLAVOR)/clock-qcom-pas.c +srcs-y += clk.c +srcs-$(CFG_QCOM_PAS_PTA) += platform/$(PLATFORM_FLAVOR)/clock-pas.c + +ifeq ($(CFG_QCOM_CLK_CFG),y) +srcs-y += platform/$(PLATFORM_FLAVOR)/clk_cfg.c +srcs-y += clk_ops.c +srcs-$(CFG_QCOM_RPMH_CLIENT) += rail_rpmh.c +endif incdirs-y += . incdirs-$(CFG_QCOM_PAS_PTA) += platform/$(PLATFORM_FLAVOR) +incdirs-$(CFG_QCOM_CLK_CFG) += platform/$(PLATFORM_FLAVOR) diff --git a/core/drivers/qcom/cmd_db/cmd_db.c b/core/drivers/qcom/cmd_db/cmd_db.c index 17a51d73d..58839a5b3 100644 --- a/core/drivers/qcom/cmd_db/cmd_db.c +++ b/core/drivers/qcom/cmd_db/cmd_db.c @@ -247,13 +247,22 @@ static TEE_Result copy_aux_data(uint16_t slv_idx, struct entry_header *entry, uint8_t *data) { struct slv_id_info *slv_info = &query_db.data->slv_id_info[slv_idx]; - uint32_t len = MIN(result->len, entry->len); + uint32_t len = entry->len; size_t offset = 0; size_t bounds = 0; if (!data) return TEE_ERROR_BAD_PARAMETERS; + /* + * Report the size the caller needs rather than copying a partial blob; + * a truncated aux list is indistinguishable from a complete one. + */ + if (len > result->len) { + result->len = len; + return TEE_ERROR_SHORT_BUFFER; + } + if (ADD_OVERFLOW(slv_info->data_offset, entry->offset, &offset) || ADD_OVERFLOW(sizeof(struct db_header), offset, &bounds) || ADD_OVERFLOW(bounds, len, &bounds) || @@ -297,8 +306,10 @@ cmd_db_get_entry_by_res_id(const char *res_id, result->priority[1] = entry.priority[1]; result->version = query_db.data->slv_id_info[slv_idx].version; - if (entry.len == 0) + if (entry.len == 0) { + result->len = 0; return TEE_SUCCESS; + } if (result->len == 0) { result->len = entry.len; @@ -372,6 +383,32 @@ TEE_Result cmd_db_get_addr(const char *res_id, uint32_t *addr) return res; } +TEE_Result cmd_db_get_aux(const char *res_id, uint8_t *buf, size_t *len) +{ + struct cmd_db_query_result_type result = { }; + TEE_Result res = TEE_SUCCESS; + + if (!buf || !len || !*len || *len > UINT32_MAX || + !is_valid_res_id(res_id)) + return TEE_ERROR_BAD_PARAMETERS; + + result.len = *len; + + mutex_lock(&query_db.lock); + + if (!query_db.data) { + mutex_unlock(&query_db.lock); + return TEE_ERROR_BAD_STATE; + } + + res = cmd_db_get_entry_by_res_id(res_id, &result, buf); + if (res == TEE_SUCCESS || res == TEE_ERROR_SHORT_BUFFER) + *len = result.len; + + mutex_unlock(&query_db.lock); + return res; +} + static uint32_t extract_priority(struct cmd_db_query_result_type *result, uint8_t drv_id) { diff --git a/core/drivers/qcom/qfprom/kodiak/qfprom_target.h b/core/drivers/qcom/qfprom/kodiak/qfprom_target.h index 48f02520a..a9bc44b11 100644 --- a/core/drivers/qcom/qfprom/kodiak/qfprom_target.h +++ b/core/drivers/qcom/qfprom/kodiak/qfprom_target.h @@ -6,7 +6,7 @@ #ifndef __QFPROM_TARGET_H__ #define __QFPROM_TARGET_H__ -#include +#include #include #include #include diff --git a/core/drivers/qcom/qfprom/lemans/qfprom_target.h b/core/drivers/qcom/qfprom/lemans/qfprom_target.h index 8207330b4..a90503e1c 100644 --- a/core/drivers/qcom/qfprom/lemans/qfprom_target.h +++ b/core/drivers/qcom/qfprom/lemans/qfprom_target.h @@ -6,7 +6,7 @@ #ifndef __QFPROM_TARGET_H__ #define __QFPROM_TARGET_H__ -#include +#include #include #include #include diff --git a/core/include/drivers/clk_qcom.h b/core/include/drivers/clk_qcom.h index b4c5d3579..3221930ee 100644 --- a/core/include/drivers/clk_qcom.h +++ b/core/include/drivers/clk_qcom.h @@ -7,10 +7,12 @@ #ifndef _CLK_QCOM_H_ #define _CLK_QCOM_H_ +#include #include #include #include #include +#include #define REG_POLL_TIMEOUT(_addr, _timeout_us, _delay_us, _retp, _match) \ do { \ @@ -38,6 +40,11 @@ enum qcom_clk_group { #define CBCR_BRANCH_OFF_BIT BIT(31) #define CMD_RCGR_UPDATE_BIT BIT(0) +static inline bool cbcr_branch_on(uint32_t val) +{ + return !(val & CBCR_BRANCH_OFF_BIT); +} + /* Register configuration for a Lucid-EVO PLL. */ struct qcom_lucidevo_pll_config { uint32_t l_val; @@ -62,6 +69,41 @@ TEE_Result qcom_clock_set_rate(vaddr_t cfg_rcgr, vaddr_t cmd_rcgr, */ TEE_Result qcom_lucidevo_pll_enable(vaddr_t pll_base, const struct qcom_lucidevo_pll_config *cfg); + +#if defined(CFG_QCOM_CLK_CFG) +/* qcom has no secure DT, so a consumer acquires the clk by name instead of clk_dt_get_by_name(). */ +TEE_Result qcom_clk_get_by_name(const char *name, struct clk **out); + +/* Enable hardware DFS on a registered clk (no clk_ops equivalent). */ +TEE_Result qcom_clk_enable_dfs(struct clk *clk); + +/* Copies out DFS-selectable rates for @clk paired with each row's DFS index -- never hands out a pointer into this driver's own config table. */ +TEE_Result qcom_clk_get_dfs_rates_array(struct clk *clk, size_t start_index, + unsigned long *rates, + uint8_t *dfs_indices, size_t *count); +#else +static inline TEE_Result qcom_clk_get_by_name(const char *name __unused, + struct clk **out __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline TEE_Result qcom_clk_enable_dfs(struct clk *clk __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline TEE_Result +qcom_clk_get_dfs_rates_array(struct clk *clk __unused, + size_t start_index __unused, + unsigned long *rates __unused, + uint8_t *dfs_indices __unused, + size_t *count __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} +#endif /* CFG_QCOM_CLK_CFG */ + #ifdef CFG_QCOM_PAS_PTA TEE_Result qcom_clock_enable_pas(enum qcom_clk_group group); TEE_Result qcom_clock_enable_pas_processor(enum qcom_clk_group group); diff --git a/core/include/drivers/qcom/cmd_db/cmd_db.h b/core/include/drivers/qcom/cmd_db/cmd_db.h index 443adf239..d894280ad 100644 --- a/core/include/drivers/qcom/cmd_db/cmd_db.h +++ b/core/include/drivers/qcom/cmd_db/cmd_db.h @@ -19,4 +19,14 @@ TEE_Result cmd_db_get_addr(const char *res_id, uint32_t *addr); TEE_Result cmd_db_get_priority(uint32_t addr, uint8_t drv_id, uint32_t *priority); +/* + * Copy a resource's auxiliary data blob by resource ID. For ARC resources + * (e.g. "cx.lvl") this is the little-endian uint16 list of vlvls the rail + * supports; RPMh commands take an index into that list. *len is the @buf + * capacity on entry and the copied byte count on success. Returns + * TEE_ERROR_SHORT_BUFFER with *len set to the required size if @buf is too + * small; nothing is copied in that case. + */ +TEE_Result cmd_db_get_aux(const char *res_id, uint8_t *buf, size_t *len); + #endif /* __CMD_DB_H__ */