soc/qualcomm/common: Add RPMh driver support

Add RPMh driver support, introducing the core driver that provides
an interface to the RPMh protocol for managing ARC/VRM/BCM type
resource requests. This includes basic TCS (Trigger Command Sets)
handling and helper functions for sending RPMh requests.

RPMh (Resource Power Manager – hardware) is a protocol that enables
processors (e.g., APSS, LPASS) to send power-related commands to the
RPMh hardware block. Dynamic management of power and clocks for shared
resources is handled either directly by hardware or by RPM.

Key features include:
- Core infrastructure for submitting TCS (Trigger Command Sets)
commands to the RPMh.
- Regulator driver using RPMh for LDOs and SMPS control.
- BCM (Bus Clock Manager) voting for clock resources.

Test=Create an image.serial.bin and ensure it boots on X1P42100.

Change-Id: I1f85459c68d0256e15765b0716856dc928080df9
Signed-off-by: Swathi Tamilselvan <tswathi@qualcomm.corp-partner.google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/90466
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Kapil Porwal <kapilporwal@google.com>
This commit is contained in:
Swathi Tamilselvan 2025-12-10 16:31:35 +05:30 committed by Matt DeVillier
commit dc162f84be
11 changed files with 2078 additions and 0 deletions

View file

@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_RPMH_H__
#define __SOC_QUALCOMM_RPMH_H__
#include <device/device.h>
#include <soc/tcs.h>
int rpmh_write(enum rpmh_state state, const struct tcs_cmd *cmd, u32 n);
int rpmh_write_async(enum rpmh_state state, const struct tcs_cmd *cmd, u32 n);
int rpmh_write_batch(enum rpmh_state state, const struct tcs_cmd *cmd, u32 *n);
void rpmh_invalidate(void);
int rpmh_write_sleep_and_wake(void);
int rpmh_mode_solver_set(bool enable);
int rpmh_init_fast_path(struct tcs_cmd *cmd, int n);
int rpmh_update_fast_path(struct tcs_cmd *cmd, int n, u32 update_mask);
#endif /* __SOC_QUALCOMM_RPMH_H__ */

View file

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_RPMH_BCM_H__
#define __SOC_QUALCOMM_RPMH_BCM_H__
#include <types.h>
/**
* rpmh_bcm_vote() - Send BCM (Bus Clock Manager) vote to RPMh
* @resource_name: BCM resource name
* @vote_value: Vote value to send
*
* Return: 0 on success, negative error code on failure
*/
int rpmh_bcm_vote(const char *resource_name, u32 vote_value);
#endif /* __SOC_QUALCOMM_RPMH_BCM_H__ */

View file

@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_RPMH_INTERNAL_H__
#define __SOC_QUALCOMM_RPMH_INTERNAL_H__
#include <types.h>
#include <soc/tcs.h>
#define MAX_NAME_LENGTH 20
#define USECS_1S 1000000
#define USECS_100MS 100000
#define USECS_10MS 10000
#define USECS_100US 100
#define USECS_5US 5
#define LOOP_DELAY_US 1
#define LOOP_DELAY_10US 10
/* DRV Channel IDs */
enum {
CH0,
CH1,
MAX_CHANNEL,
};
#define TCS_TYPE_NR 5
#define MAX_CMDS_PER_TCS 16
#define MAX_TCS_PER_TYPE 3
#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR)
#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE)
#define SOLVER_PRESENT 1
#define HW_CHANNEL_PRESENT 2
#define CMD_DB_MAX_RESOURCES 250
/* TCS types */
enum {
ACTIVE_TCS,
SLEEP_TCS,
WAKE_TCS,
CONTROL_TCS,
FAST_PATH_TCS,
};
/* RSC register offsets */
enum {
RSC_DRV_TCS_OFFSET,
RSC_DRV_CMD_OFFSET,
DRV_SOLVER_CONFIG,
DRV_PRNT_CHLD_CONFIG,
RSC_DRV_IRQ_ENABLE,
RSC_DRV_IRQ_STATUS,
RSC_DRV_IRQ_CLEAR,
RSC_DRV_CMD_WAIT_FOR_CMPL,
RSC_DRV_CONTROL,
RSC_DRV_STATUS,
RSC_DRV_CMD_ENABLE,
RSC_DRV_CMD_MSGID,
RSC_DRV_CMD_ADDR,
RSC_DRV_CMD_DATA,
RSC_DRV_CMD_STATUS,
RSC_DRV_CMD_RESP_DATA,
RSC_DRV_CHN_TCS_TRIGGER,
RSC_DRV_CHN_TCS_COMPLETE,
RSC_DRV_CHN_SEQ_BUSY,
RSC_DRV_CHN_SEQ_PC,
RSC_DRV_CHN_UPDATE,
RSC_DRV_CHN_BUSY,
RSC_DRV_CHN_EN,
};
/* DRV TCS Configuration Information Register */
#define DRV_NUM_TCS_MASK 0x3F
#define DRV_NUM_TCS_SHIFT 6
#define DRV_NCPT_MASK 0x1F
#define DRV_NCPT_SHIFT 27
struct rsc_drv;
/**
* struct cache_req: the request object for caching
*
* @addr: the address of the resource
* @sleep_val: the sleep vote
* @wake_val: the wake vote
*/
struct cache_req {
u32 addr;
u32 sleep_val;
u32 wake_val;
};
/**
* struct tcs_group: group of Trigger Command Sets (TCS) to send state requests
* to the controller
*
* @drv: The controller.
* @type: Type of the TCS in this group - active, sleep, wake.
* @mask: Mask of the TCSes relative to all the TCSes in the RSC.
* @offset: Start of the TCS group relative to the TCSes in the RSC.
* @num_tcs: Number of TCSes in this type.
* @ncpt: Number of commands in each TCS.
* @req: Requests that are sent from the TCS.
* @slots: Bitmap indicating which slots are occupied.
*/
struct tcs_group {
struct rsc_drv *drv;
int type;
u32 mask;
u32 offset;
int num_tcs;
int ncpt;
const struct tcs_request *req[MAX_TCS_PER_TYPE];
u32 slots[MAX_TCS_SLOTS / 32];
};
/**
* struct rpmh_request: the message to be sent to rpmh-rsc
*
* @msg: the request
* @cmd: the payload that will be part of the @msg
*/
struct rpmh_request {
struct tcs_request msg;
struct tcs_cmd cmd[MAX_RPMH_PAYLOAD];
};
/**
* struct rpmh_ctrlr: our representation of the controller
*
* @dirty: was the cache updated since flush
* @in_solver_mode: Controller is busy in solver mode
* @flags: Controller specific flags
* @batch_cache: Cache sleep and wake requests sent as batch
* @non_batch_cache_idx: Index for non-batch cache
* @non_batch_cache: Cache for non-batch requests
*/
struct rpmh_ctrlr {
bool dirty;
bool in_solver_mode;
u32 flags;
struct rpmh_request batch_cache[RPMH_ACTIVE_ONLY_STATE];
u32 non_batch_cache_idx;
struct cache_req *non_batch_cache;
};
/**
* struct drv_channel: our representation of the drv channels
*
* @tcs: TCS groups
* @drv: DRV containing the channel
* @initialized: Whether channel is initialized
*/
struct drv_channel {
struct tcs_group tcs[TCS_TYPE_NR];
struct rsc_drv *drv;
bool initialized;
};
/**
* struct rsc_drv: the Direct Resource Voter (DRV) of the
* Resource State Coordinator controller (RSC)
*
* @name: Controller identifier
* @base: Start address of the DRV registers in this controller
* @tcs_base: Start address of the TCS registers in this controller
* @tcs_distance: Distance between two TCSes
* @id: Instance id in the controller (Direct Resource Voter)
* @num_tcs: Number of TCSes in this DRV
* @num_channels: Number of channels in this DRV
* @in_solver_mode: Controller is busy in solver mode
* @initialized: Whether DRV is initialized
* @tcs: TCS groups
* @ch: DRV channels
* @tcs_in_use: S/W state of the TCS bitmap
* @client: Handle to the DRV's client
* @dev: RSC device
* @regs: Register offsets for RSC controller
*/
struct rsc_drv {
char name[MAX_NAME_LENGTH];
void *base;
void *tcs_base;
u32 tcs_distance;
unsigned int id;
int num_tcs;
int num_channels;
bool in_solver_mode;
bool initialized;
struct tcs_group tcs[TCS_TYPE_NR];
struct drv_channel ch[MAX_CHANNEL];
u32 tcs_in_use[(MAX_TCS_NR + 31) / 32];
struct rpmh_ctrlr client;
struct device *dev;
u32 *regs;
};
extern bool rpmh_standalone;
int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg, int ch);
int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv,
const struct tcs_request *msg,
int ch);
void rpmh_rsc_invalidate(struct rsc_drv *drv, int ch);
int rpmh_rsc_mode_solver_set(struct rsc_drv *drv, bool enable);
int rpmh_rsc_get_channel(struct rsc_drv *drv);
int rpmh_rsc_switch_channel(struct rsc_drv *drv, int ch);
int rpmh_rsc_drv_enable(struct rsc_drv *drv, bool enable);
int rpmh_flush(struct rpmh_ctrlr *ctrlr, int ch);
int rpmh_rsc_init_fast_path(struct rsc_drv *drv, const struct tcs_request *msg, int ch);
int rpmh_rsc_update_fast_path(struct rsc_drv *drv,
const struct tcs_request *msg,
u32 update_mask, int ch);
void rpmh_set_rsc_drv(struct rsc_drv *drv);
#endif /* __SOC_QUALCOMM_RPMH_INTERNAL_H__ */

View file

@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_RPMH_REGULATOR_H__
#define __SOC_QUALCOMM_RPMH_REGULATOR_H__
#include <types.h>
#include <device/device.h>
#define RPMH_ARC_LEVEL_SIZE 2
#define RPMH_ARC_MAX_LEVELS 32
#define RPMH_REGULATOR_LEVEL_OFF 0
/* Min and max limits of VRM resource request parameters */
#define RPMH_VRM_MIN_UV 0
#define RPMH_VRM_MAX_UV 8191000
#define RPMH_VRM_STEP_UV 1000
#define RPMH_VRM_HEADROOM_MIN_UV 0
#define RPMH_VRM_HEADROOM_MAX_UV 511000
#define RPMH_VRM_MODE_MIN 0
#define RPMH_VRM_MODE_MAX 7
/* XOB and PBS voting registers are found in the VRM hardware module */
#define CMD_DB_HW_XOB CMD_DB_HW_VRM
#define CMD_DB_HW_PBS CMD_DB_HW_VRM
/* Voltage unknown placeholder */
#define VOLTAGE_UNKNOWN 1
/**
* enum rpmh_regulator_type - supported RPMh accelerator types
* @RPMH_REGULATOR_TYPE_VRM: RPMh VRM accelerator which supports voting on
* enable, voltage, mode, and headroom voltage of
* LDO, SMPS, VS, and BOB type PMIC regulators.
* @RPMH_REGULATOR_TYPE_ARC: RPMh ARC accelerator which supports voting on
* the CPR managed voltage level of LDO and SMPS
* type PMIC regulators.
* @RPMH_REGULATOR_TYPE_XOB: RPMh XOB accelerator which supports voting on
* the enable state of PMIC regulators.
* @RPMH_REGULATOR_TYPE_PBS: RPMh PBS accelerator which supports voting on
* the enable state of PBS resources.
* @RPMH_REGULATOR_TYPE_BCM: RPMh BCM (Bus Clock Manager) accelerator which
* supports voting on bandwidth and clock frequency
* requirements for interconnect and bus resources.
*/
enum rpmh_regulator_type {
RPMH_REGULATOR_TYPE_VRM,
RPMH_REGULATOR_TYPE_ARC,
RPMH_REGULATOR_TYPE_XOB,
RPMH_REGULATOR_TYPE_PBS,
RPMH_REGULATOR_TYPE_BCM,
};
/**
* enum rpmh_regulator_reg_index - RPMh accelerator register indices
* @RPMH_REGULATOR_REG_VRM_VOLTAGE: VRM voltage voting register index
* @RPMH_REGULATOR_REG_ARC_LEVEL: ARC voltage level voting register index
* @RPMH_REGULATOR_REG_PBS_ENABLE: PBS enable voting register index
* @RPMH_REGULATOR_REG_VRM_ENABLE: VRM enable voltage voting register index
* @RPMH_REGULATOR_REG_ARC_PSEUDO_ENABLE: Place-holder for enable aggregation
* @RPMH_REGULATOR_REG_XOB_ENABLE: XOB enable voting register index
* @RPMH_REGULATOR_REG_ENABLE: Common enable index
* @RPMH_REGULATOR_REG_VRM_MODE: VRM regulator mode voting register index
* @RPMH_REGULATOR_REG_VRM_HEADROOM: VRM headroom voltage voting register index
* @RPMH_REGULATOR_REG_MAX: Maximum number of registers
*/
enum rpmh_regulator_reg_index {
RPMH_REGULATOR_REG_VRM_VOLTAGE = 0,
RPMH_REGULATOR_REG_ARC_LEVEL = 0,
RPMH_REGULATOR_REG_PBS_ENABLE = 0,
RPMH_REGULATOR_REG_VRM_ENABLE = 1,
RPMH_REGULATOR_REG_ARC_PSEUDO_ENABLE = 1,
RPMH_REGULATOR_REG_XOB_ENABLE = 1,
RPMH_REGULATOR_REG_ENABLE = 1,
RPMH_REGULATOR_REG_VRM_MODE = 2,
RPMH_REGULATOR_REG_VRM_HEADROOM = 3,
RPMH_REGULATOR_REG_MAX = 4,
};
/**
* struct rpmh_regulator_request - internal request structure
*/
struct rpmh_regulator_request {
u32 reg[RPMH_REGULATOR_REG_MAX];
u32 valid;
};
/**
* struct rpmh_vreg - individual rpmh regulator data structure
* @resource_name: Name of rpmh regulator resource
* @addr: Base address of the regulator resource within an RPMh accelerator
* @type: RPMh accelerator type for this regulator resource
* @level: Mapping from ARC resource specific voltage levels to consumer levels
* @level_count: The number of valid entries in the level array
* @req_active: Active set RPMh accelerator register request
* @req_sleep: Sleep set RPMh accelerator register request
*/
struct rpmh_vreg {
const char *resource_name;
u32 addr;
enum rpmh_regulator_type type;
u32 level[RPMH_ARC_MAX_LEVELS];
int level_count;
struct rpmh_regulator_request req_active;
struct rpmh_regulator_request req_sleep;
};
/**
* rpmh_regulator_init() - initialize an rpmh regulator
* @vreg: Pointer to the rpmh regulator structure to initialize
* @dev: Device pointer for RPMh communication
* @resource_name: Name of the RPMh resource (e.g., "ldoa1")
* @type: Type of regulator (VRM, ARC, XOB, PBS)
*
* This function initializes an RPMh regulator by looking up its address
* in the command DB and loading any necessary configuration data.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_init(struct rpmh_vreg *vreg, const char *resource_name,
enum rpmh_regulator_type type);
/**
* rpmh_regulator_vrm_set_voltage() - set the voltage of a VRM regulator
* @vreg: Pointer to the rpmh regulator
* @min_uv: Minimum voltage in microvolts
* @max_uv: Maximum voltage in microvolts
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* This function sets the voltage for a VRM type regulator. The actual
* voltage set will be the minimum value that satisfies min_uv.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_vrm_set_voltage(struct rpmh_vreg *vreg, int min_uv, int max_uv,
bool set_active, bool set_sleep);
/**
* rpmh_regulator_vrm_set_mode() - set the mode of a VRM regulator
* @vreg: Pointer to the rpmh regulator
* @mode: PMIC mode value (use RPMH_REGULATOR_MODE_* defines)
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* This function sets the operating mode for a VRM type regulator.
* The mode value should be one of the RPMH_REGULATOR_MODE_PMIC*_* defines
* appropriate for the regulator type.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_vrm_set_mode(struct rpmh_vreg *vreg, u32 mode,
bool set_active, bool set_sleep);
/**
* rpmh_regulator_enable() - enable a regulator
* @vreg: Pointer to the rpmh regulator
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* This function enables the regulator. Works for VRM, ARC, XOB, and PBS types.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_enable(struct rpmh_vreg *vreg, bool set_active, bool set_sleep);
/**
* rpmh_regulator_disable() - disable a regulator
* @vreg: Pointer to the rpmh regulator
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* This function disables the regulator. Works for VRM, ARC, XOB, and PBS types.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_disable(struct rpmh_vreg *vreg, bool set_active, bool set_sleep);
/**
* rpmh_regulator_arc_set_level() - set the voltage level of an ARC regulator
* @vreg: Pointer to the rpmh regulator
* @level: ARC voltage level (0 to level_count-1)
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* This function sets the voltage level for an ARC type regulator.
* The level is an index into the voltage level table loaded from command DB.
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_arc_set_level(struct rpmh_vreg *vreg, u32 level,
bool set_active, bool set_sleep);
/**
* rpmh_regulator_arc_get_level() - get the current voltage level of an ARC regulator
* @vreg: Pointer to the rpmh regulator
*
* This function returns the cached ARC voltage level that was last set.
* Note: This returns the requested level, not necessarily the actual hardware level,
* as there's no way to read back from RPMh hardware.
*
* Return: ARC voltage level (0 to level_count-1) on success, -1 on failure
*/
int rpmh_regulator_arc_get_level(struct rpmh_vreg *vreg);
/**
* rpmh_regulator_arc_get_voltage_level() - get the voltage level value for an ARC level
* @vreg: Pointer to the rpmh regulator
* @level: ARC level index
*
* This function returns the actual voltage level value (vlvl) that corresponds
* to the given ARC hardware level (hlvl).
*
* Return: voltage level value on success, 0 on failure
*/
u32 rpmh_regulator_arc_get_voltage_level(struct rpmh_vreg *vreg, u32 level);
#endif /* __SOC_QUALCOMM_RPMH_REGULATOR_H__ */

View file

@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_RPMH_RSC_H__
#define __SOC_QUALCOMM_RPMH_RSC_H__
#include <types.h>
#include <soc/rpmh_internal.h>
u32 rpmh_rsc_reg_offset_ver_2_7[] = {
[RSC_DRV_TCS_OFFSET] = 672,
[RSC_DRV_CMD_OFFSET] = 20,
[DRV_SOLVER_CONFIG] = 0x04,
[DRV_PRNT_CHLD_CONFIG] = 0x0C,
[RSC_DRV_IRQ_ENABLE] = 0x00,
[RSC_DRV_IRQ_STATUS] = 0x04,
[RSC_DRV_IRQ_CLEAR] = 0x08,
[RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x10,
[RSC_DRV_CONTROL] = 0x14,
[RSC_DRV_STATUS] = 0x18,
[RSC_DRV_CMD_ENABLE] = 0x1C,
[RSC_DRV_CMD_MSGID] = 0x30,
[RSC_DRV_CMD_ADDR] = 0x34,
[RSC_DRV_CMD_DATA] = 0x38,
[RSC_DRV_CMD_STATUS] = 0x3C,
[RSC_DRV_CMD_RESP_DATA] = 0x40,
[RSC_DRV_CHN_SEQ_BUSY] = 0x0,
[RSC_DRV_CHN_SEQ_PC] = 0x0,
[RSC_DRV_CHN_TCS_TRIGGER] = 0x0,
[RSC_DRV_CHN_TCS_COMPLETE] = 0x0,
[RSC_DRV_CHN_UPDATE] = 0x0,
[RSC_DRV_CHN_BUSY] = 0x0,
[RSC_DRV_CHN_EN] = 0x0,
};
u32 rpmh_rsc_reg_offset_ver_3_0[] = {
[RSC_DRV_TCS_OFFSET] = 672,
[RSC_DRV_CMD_OFFSET] = 24,
[DRV_SOLVER_CONFIG] = 0x04,
[DRV_PRNT_CHLD_CONFIG] = 0x0C,
[RSC_DRV_IRQ_ENABLE] = 0x00,
[RSC_DRV_IRQ_STATUS] = 0x04,
[RSC_DRV_IRQ_CLEAR] = 0x08,
[RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x20,
[RSC_DRV_CONTROL] = 0x24,
[RSC_DRV_STATUS] = 0x28,
[RSC_DRV_CMD_ENABLE] = 0x2C,
[RSC_DRV_CMD_MSGID] = 0x34,
[RSC_DRV_CMD_ADDR] = 0x38,
[RSC_DRV_CMD_DATA] = 0x3C,
[RSC_DRV_CMD_STATUS] = 0x40,
[RSC_DRV_CMD_RESP_DATA] = 0x44,
[RSC_DRV_CHN_SEQ_BUSY] = 0x464,
[RSC_DRV_CHN_SEQ_PC] = 0x468,
[RSC_DRV_CHN_TCS_TRIGGER] = 0x490,
[RSC_DRV_CHN_TCS_COMPLETE] = 0x494,
[RSC_DRV_CHN_UPDATE] = 0x498,
[RSC_DRV_CHN_BUSY] = 0x49C,
[RSC_DRV_CHN_EN] = 0x4A0,
};
u32 rpmh_rsc_reg_offset_ver_3_0_hw_channel[] = {
[RSC_DRV_TCS_OFFSET] = 336,
[RSC_DRV_CMD_OFFSET] = 24,
[DRV_SOLVER_CONFIG] = 0x04,
[DRV_PRNT_CHLD_CONFIG] = 0x0C,
[RSC_DRV_IRQ_ENABLE] = 0x00,
[RSC_DRV_IRQ_STATUS] = 0x04,
[RSC_DRV_IRQ_CLEAR] = 0x08,
[RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x20,
[RSC_DRV_CONTROL] = 0x24,
[RSC_DRV_STATUS] = 0x28,
[RSC_DRV_CMD_ENABLE] = 0x2C,
[RSC_DRV_CMD_MSGID] = 0x34,
[RSC_DRV_CMD_ADDR] = 0x38,
[RSC_DRV_CMD_DATA] = 0x3C,
[RSC_DRV_CMD_STATUS] = 0x40,
[RSC_DRV_CMD_RESP_DATA] = 0x44,
[RSC_DRV_CHN_SEQ_BUSY] = 0x464,
[RSC_DRV_CHN_SEQ_PC] = 0x468,
[RSC_DRV_CHN_TCS_TRIGGER] = 0x490,
[RSC_DRV_CHN_TCS_COMPLETE] = 0x494,
[RSC_DRV_CHN_UPDATE] = 0x498,
[RSC_DRV_CHN_BUSY] = 0x49C,
[RSC_DRV_CHN_EN] = 0x4A0,
};
#endif /* __SOC_QUALCOMM_RPMH_RSC_H__ */

View file

@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_QUALCOMM_TCS_H__
#define __SOC_QUALCOMM_TCS_H__
#include <types.h>
#define MAX_RPMH_PAYLOAD 16
/**
* rpmh_state: state for the request
*
* RPMH_SLEEP_STATE: State of the resource when the processor subsystem
* is powered down. There is no client using the
* resource actively.
* RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously
* requested before the processor was powered down.
* RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state
* is aggregated immediately.
*/
enum rpmh_state {
RPMH_SLEEP_STATE,
RPMH_WAKE_ONLY_STATE,
RPMH_ACTIVE_ONLY_STATE,
};
/**
* struct tcs_cmd: an individual request to RPMH.
*
* @addr: the address of the resource slv_id:18:16 | offset:0:15
* @data: the resource state request
* @wait: ensure that this command is complete before returning.
*/
struct tcs_cmd {
u32 addr;
u32 data;
u32 wait;
};
/**
* struct tcs_request: A set of tcs_cmds sent together in a TCS
*
* @state: state for the request.
* @wait_for_compl: wait until we get a response from the h/w accelerator
* @num_cmds: the number of @cmds in this request
* @cmds: an array of tcs_cmds
*/
struct tcs_request {
enum rpmh_state state;
u32 wait_for_compl;
u32 num_cmds;
struct tcs_cmd *cmds;
};
#define BCM_TCS_CMD_COMMIT_SHFT 30
#define BCM_TCS_CMD_COMMIT_MASK 0x40000000
#define BCM_TCS_CMD_VALID_SHFT 29
#define BCM_TCS_CMD_VALID_MASK 0x20000000
#define BCM_TCS_CMD_VOTE_X_SHFT 14
#define BCM_TCS_CMD_VOTE_MASK 0x3fff
#define BCM_TCS_CMD_VOTE_Y_SHFT 0
#define BCM_TCS_CMD_VOTE_Y_MASK 0xfffc000
/* Construct a Bus Clock Manager (BCM) specific TCS command */
#define BCM_TCS_CMD(commit, valid, vote_x, vote_y) \
cpu_to_le32( \
(((uint32_t)(commit) & 0x1) << BCM_TCS_CMD_COMMIT_SHFT) | \
(((uint32_t)(valid) & 0x1) << BCM_TCS_CMD_VALID_SHFT) | \
(((uint32_t)(vote_x) & BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_X_SHFT) | \
(((uint32_t)(vote_y) & BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_Y_SHFT) \
)
#endif /* __SOC_QUALCOMM_TCS_H__ */

View file

@ -0,0 +1,408 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <console/console.h>
#include <delay.h>
#include <device/device.h>
#include <string.h>
#include <soc/rpmh.h>
#include <soc/cmd_db.h>
#include <soc/rpmh_internal.h>
#define RPMH_TIMEOUT_MS 10000
#define DEFINE_RPMH_MSG_ONSTACK(s, name) \
struct rpmh_request name = { \
.msg = { \
.state = s, \
.cmds = name.cmd, \
.num_cmds = 0, \
.wait_for_compl = 1, \
}, \
.cmd = { { 0 } }, \
}
#define ctrlr_to_drv(ctrlr) \
((struct rsc_drv *)((char *)(ctrlr) - offsetof(struct rsc_drv, client)))
bool rpmh_standalone;
static struct rsc_drv *g_rsc_drv = NULL;
static struct rpmh_ctrlr *get_rpmh_ctrlr(void)
{
return g_rsc_drv ? &g_rsc_drv->client : NULL;
}
void rpmh_set_rsc_drv(struct rsc_drv *drv)
{
g_rsc_drv = drv;
}
static int check_ctrlr_state(struct rpmh_ctrlr *ctrlr, enum rpmh_state state)
{
if (state != RPMH_ACTIVE_ONLY_STATE)
return 0;
if (!(ctrlr->flags & SOLVER_PRESENT))
return 0;
if (ctrlr->in_solver_mode)
return -1;
return 0;
}
static struct cache_req *get_non_batch_cache_req(struct rpmh_ctrlr *ctrlr, u32 addr)
{
struct cache_req *req = ctrlr->non_batch_cache;
unsigned int i;
if (ctrlr->non_batch_cache_idx >= CMD_DB_MAX_RESOURCES)
return NULL;
for (i = 0; i < ctrlr->non_batch_cache_idx; i++) {
req = &ctrlr->non_batch_cache[i];
if (req->addr == addr)
return req;
}
req = &ctrlr->non_batch_cache[ctrlr->non_batch_cache_idx];
req->sleep_val = req->wake_val = UINT_MAX;
ctrlr->non_batch_cache_idx++;
return req;
}
static struct cache_req *cache_rpm_request(struct rpmh_ctrlr *ctrlr,
enum rpmh_state state,
struct tcs_cmd *cmd)
{
struct cache_req *req;
u32 old_sleep_val, old_wake_val;
req = get_non_batch_cache_req(ctrlr, cmd->addr);
if (!req)
return NULL;
req->addr = cmd->addr;
old_sleep_val = req->sleep_val;
old_wake_val = req->wake_val;
switch (state) {
case RPMH_ACTIVE_ONLY_STATE:
case RPMH_WAKE_ONLY_STATE:
req->wake_val = cmd->data;
break;
case RPMH_SLEEP_STATE:
req->sleep_val = cmd->data;
break;
}
ctrlr->dirty |= (req->sleep_val != old_sleep_val ||
req->wake_val != old_wake_val) &&
req->sleep_val != UINT_MAX &&
req->wake_val != UINT_MAX;
return req;
}
static int __rpmh_write(enum rpmh_state state, struct rpmh_request *rpm_msg)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ret = -1;
struct cache_req *req;
int i, ch;
if (rpmh_standalone)
return 0;
if (!ctrlr)
return -1;
for (i = 0; i < rpm_msg->msg.num_cmds; i++) {
req = cache_rpm_request(ctrlr, state, &rpm_msg->msg.cmds[i]);
if (!req)
return -1;
}
if (state == RPMH_ACTIVE_ONLY_STATE) {
ch = rpmh_rsc_get_channel(ctrlr_to_drv(ctrlr));
if (ch < 0)
return ch;
ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg, ch);
} else {
ret = 0;
}
return ret;
}
static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 num_cmds)
{
if (!cmd || !num_cmds || num_cmds > MAX_RPMH_PAYLOAD)
return -1;
memcpy(req->cmd, cmd, num_cmds * sizeof(*cmd));
req->msg.state = state;
req->msg.cmds = req->cmd;
req->msg.num_cmds = num_cmds;
return 0;
}
int rpmh_write_async(enum rpmh_state state, const struct tcs_cmd *cmd, u32 n)
{
DEFINE_RPMH_MSG_ONSTACK(state, rpm_msg);
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ret;
if (rpmh_standalone)
return 0;
if (!ctrlr)
return -1;
rpm_msg.msg.wait_for_compl = 0;
ret = check_ctrlr_state(ctrlr, state);
if (ret)
return ret;
ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n);
if (ret)
return ret;
return __rpmh_write(state, &rpm_msg);
}
int rpmh_write(enum rpmh_state state, const struct tcs_cmd *cmd, u32 n)
{
DEFINE_RPMH_MSG_ONSTACK(state, rpm_msg);
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ret;
if (rpmh_standalone)
return 0;
if (!ctrlr)
return -1;
ret = check_ctrlr_state(ctrlr, state);
if (ret)
return ret;
ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n);
if (ret)
return ret;
ret = __rpmh_write(state, &rpm_msg);
return ret;
}
static int flush_batch(struct rpmh_ctrlr *ctrlr, int ch)
{
int ret;
/* Send Sleep/Wake requests to the controller */
ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr),
&ctrlr->batch_cache[RPMH_SLEEP_STATE].msg, ch);
if (ret)
return ret;
return rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr),
&ctrlr->batch_cache[RPMH_WAKE_ONLY_STATE].msg, ch);
}
int rpmh_write_batch(enum rpmh_state state, const struct tcs_cmd *cmd, u32 *n)
{
DEFINE_RPMH_MSG_ONSTACK(state, rpm_msg);
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ret, ch;
if (rpmh_standalone)
return 0;
if (!ctrlr)
return -1;
ret = check_ctrlr_state(ctrlr, state);
if (ret)
return ret;
if (state == RPMH_ACTIVE_ONLY_STATE) {
ret = __fill_rpmh_msg(&rpm_msg, state, cmd, *n);
if (ret)
return ret;
} else {
memset(&ctrlr->batch_cache[state], 0, sizeof(struct rpmh_request));
ret = __fill_rpmh_msg(&ctrlr->batch_cache[state], state, cmd, *n);
ctrlr->dirty = 1;
return ret;
}
ch = rpmh_rsc_get_channel(ctrlr_to_drv(ctrlr));
if (ch < 0)
return ch;
ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg.msg, ch);
if (ret) {
printk(BIOS_ERR, "RPMH: Error sending message addr=%#x\n",
rpm_msg.msg.cmds[0].addr);
return ret;
}
return 0;
}
static int is_req_valid(struct cache_req *req)
{
return (req->sleep_val != UINT_MAX &&
req->wake_val != UINT_MAX &&
req->sleep_val != req->wake_val);
}
static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
u32 addr, u32 data, int ch)
{
DEFINE_RPMH_MSG_ONSTACK(state, rpm_msg);
/* Wake sets are always complete and sleep sets are not */
rpm_msg.msg.wait_for_compl = (state == RPMH_WAKE_ONLY_STATE);
rpm_msg.cmd[0].addr = addr;
rpm_msg.cmd[0].data = data;
rpm_msg.msg.num_cmds = 1;
return rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), &rpm_msg.msg, ch);
}
int rpmh_flush(struct rpmh_ctrlr *ctrlr, int ch)
{
struct cache_req *p;
int ret = 0, i;
if (!ctrlr->dirty) {
printk(BIOS_DEBUG, "RPMH: Skipping flush, TCS has latest data\n");
return 0;
}
/* Invalidate the TCSes first to avoid stale data */
rpmh_rsc_invalidate(ctrlr_to_drv(ctrlr), ch);
/* First flush the cached batch requests */
ret = flush_batch(ctrlr, ch);
if (ret)
return ret;
for (i = 0; i < ctrlr->non_batch_cache_idx; i++) {
p = &ctrlr->non_batch_cache[i];
if (!is_req_valid(p)) {
printk(BIOS_DEBUG, "RPMH: skipping req: a:%#x s:%#x w:%#x\n",
p->addr, p->sleep_val, p->wake_val);
continue;
}
ret = send_single(ctrlr, RPMH_SLEEP_STATE, p->addr,
p->sleep_val, ch);
if (ret)
return ret;
ret = send_single(ctrlr, RPMH_WAKE_ONLY_STATE, p->addr,
p->wake_val, ch);
if (ret)
return ret;
}
ctrlr->dirty = 0;
return ret;
}
int rpmh_write_sleep_and_wake(void)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ch, ret;
if (!ctrlr)
return -1;
ch = rpmh_rsc_get_channel(ctrlr_to_drv(ctrlr));
if (ch < 0)
return ch;
ret = rpmh_flush(ctrlr, ch);
if (ret || !(ctrlr->flags & HW_CHANNEL_PRESENT))
return ret;
return rpmh_rsc_switch_channel(ctrlr_to_drv(ctrlr), ch);
}
void rpmh_invalidate(void)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
if (rpmh_standalone || !ctrlr)
return;
memset(&ctrlr->batch_cache[RPMH_SLEEP_STATE], 0, sizeof(struct rpmh_request));
memset(&ctrlr->batch_cache[RPMH_WAKE_ONLY_STATE], 0, sizeof(struct rpmh_request));
ctrlr->dirty = 1;
}
int rpmh_mode_solver_set(bool enable)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
int ret;
if (rpmh_standalone || !ctrlr)
return 0;
if (!(ctrlr->flags & SOLVER_PRESENT))
return -1;
ret = rpmh_rsc_mode_solver_set(ctrlr_to_drv(ctrlr), enable);
if (!ret)
ctrlr->in_solver_mode = enable;
return ret;
}
int rpmh_init_fast_path(struct tcs_cmd *cmd, int n)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
struct tcs_request req;
int ch;
if (rpmh_standalone || !ctrlr)
return 0;
ch = rpmh_rsc_get_channel(ctrlr_to_drv(ctrlr));
if (ch < 0)
return ch;
req.cmds = cmd;
req.num_cmds = n;
req.wait_for_compl = 0;
return rpmh_rsc_init_fast_path(ctrlr_to_drv(ctrlr), &req, ch);
}
int rpmh_update_fast_path(struct tcs_cmd *cmd, int n, u32 update_mask)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr();
struct tcs_request req;
int ch;
if (rpmh_standalone || !ctrlr)
return 0;
ch = rpmh_rsc_get_channel(ctrlr_to_drv(ctrlr));
if (ch < 0)
return ch;
req.cmds = cmd;
req.num_cmds = n;
req.wait_for_compl = 0;
return rpmh_rsc_update_fast_path(ctrlr_to_drv(ctrlr), &req,
update_mask, ch);
}

View file

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <device/device.h>
#include <console/console.h>
#include <soc/cmd_db.h>
#include <soc/rpmh.h>
#include <soc/rpmh_bcm.h>
#include <soc/tcs.h>
/**
* rpmh_bcm_vote() - Send BCM (Bus Clock Manager) vote to RPMh
* @dev: Device pointer for RPMh communication
* @resource_name: BCM resource name (e.g., "MM0", "SH0")
* @vote_value: Vote value to send
*
* This function sends a BCM vote to RPMh to enable bandwidth/clock requirements.
* It looks up the resource address from Command DB and sends the vote via TCS command.
*
* Return: 0 on success, negative error code on failure
*/
int rpmh_bcm_vote(const char *resource_name, u32 vote_value)
{
u32 bcm_addr;
struct tcs_cmd cmd;
int rc;
if (!resource_name) {
printk(BIOS_ERR, "BCM: Invalid parameters\n");
return -1;
}
/* Get the RPMh address for the BCM resource from Command DB */
bcm_addr = cmd_db_read_addr(resource_name);
if (!bcm_addr) {
printk(BIOS_ERR, "BCM: Could not find RPMh address for resource %s\n",
resource_name);
return -1;
}
printk(BIOS_DEBUG, "BCM: Found address 0x%08X for resource %s\n",
bcm_addr, resource_name);
/* Prepare TCS command */
cmd.addr = bcm_addr;
cmd.data = vote_value;
cmd.wait = true;
/* Send BCM vote via RPMh in active-only state */
rc = rpmh_write(RPMH_ACTIVE_ONLY_STATE, &cmd, 1);
if (rc) {
printk(BIOS_ERR, "BCM: Failed to send vote for %s (addr=0x%08X, val=0x%08X): %d\n",
resource_name, bcm_addr, vote_value, rc);
return rc;
}
printk(BIOS_INFO, "BCM: Successfully voted for %s (addr=0x%08X, val=0x%08X)\n",
resource_name, bcm_addr, vote_value);
return 0;
}

View file

@ -0,0 +1,458 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <console/console.h>
#include <delay.h>
#include <device/device.h>
#include <string.h>
#include <types.h>
#include <soc/rpmh.h>
#include <soc/cmd_db.h>
#include <soc/rpmh_regulator.h>
/**
* rpmh_regulator_load_arc_level_mapping() - load the RPMh ARC resource's
* voltage level mapping from command db
* @vreg: Pointer to the rpmh regulator
*
* Return: 0 on success, -1 on failure
*/
static int rpmh_regulator_load_arc_level_mapping(struct rpmh_vreg *vreg)
{
size_t len = 0;
int i, j;
const u8 *buf;
buf = cmd_db_read_aux_data(vreg->resource_name, &len);
if (!buf) {
printk(BIOS_ERR, "RPMH_REG: Could not retrieve ARC aux data for %s\n",
vreg->resource_name);
return -1;
}
if (len == 0) {
printk(BIOS_ERR, "RPMH_REG: ARC level mapping data missing for %s\n",
vreg->resource_name);
return -1;
}
if (len > RPMH_ARC_MAX_LEVELS * RPMH_ARC_LEVEL_SIZE) {
printk(BIOS_ERR, "RPMH_REG: Too many ARC levels defined for %s: %zu > %d\n",
vreg->resource_name, len, RPMH_ARC_MAX_LEVELS * RPMH_ARC_LEVEL_SIZE);
return -1;
}
if (len % RPMH_ARC_LEVEL_SIZE) {
printk(BIOS_ERR, "RPMH_REG: Invalid ARC aux data size for %s: %zu\n",
vreg->resource_name, len);
return -1;
}
vreg->level_count = len / RPMH_ARC_LEVEL_SIZE;
for (i = 0; i < vreg->level_count; i++) {
vreg->level[i] = 0;
for (j = 0; j < RPMH_ARC_LEVEL_SIZE; j++)
vreg->level[i] |= buf[i * RPMH_ARC_LEVEL_SIZE + j] << (8 * j);
/* The AUX data may be zero padded - ignore 0 valued entries at the end */
if (i > 0 && vreg->level[i] == 0) {
vreg->level_count = i;
break;
}
printk(BIOS_DEBUG, "RPMH_REG: %s ARC hlvl=%2d --> vlvl=%4u\n",
vreg->resource_name, i, vreg->level[i]);
}
return 0;
}
/**
* rpmh_regulator_send_request() - send the RPMh request
* @vreg: Pointer to the rpmh regulator
* @req: Request to send
* @state: RPMh state (active/sleep)
* @wait: Whether to wait for completion
*
* Return: 0 on success, -1 on failure
*/
static int rpmh_regulator_send_request(struct rpmh_vreg *vreg,
struct rpmh_regulator_request *req,
enum rpmh_state state, bool wait)
{
struct tcs_cmd cmd[RPMH_REGULATOR_REG_MAX];
int i, j = 0;
int rc;
/* Build command array from valid registers */
for (i = 0; i < RPMH_REGULATOR_REG_MAX; i++) {
if (req->valid & BIT(i)) {
cmd[j].addr = vreg->addr + i * 4;
cmd[j].data = req->reg[i];
cmd[j].wait = true;
j++;
}
}
if (j == 0)
return 0;
/* Send the request */
if (wait)
rc = rpmh_write(state, cmd, j);
else
rc = rpmh_write_async(state, cmd, j);
if (rc) {
printk(BIOS_ERR, "RPMH_REG: Failed to send %s request for %s, rc=%d\n",
state == RPMH_ACTIVE_ONLY_STATE ? "active" : "sleep",
vreg->resource_name, rc);
return rc;
}
printk(BIOS_DEBUG, "RPMH_REG: Sent %s request for %s\n",
state == RPMH_ACTIVE_ONLY_STATE ? "active" : "sleep",
vreg->resource_name);
return 0;
}
/**
* rpmh_regulator_vrm_set_voltage() - set the voltage of a VRM regulator
* @vreg: Pointer to the rpmh regulator
* @min_uv: Minimum voltage in microvolts
* @max_uv: Maximum voltage in microvolts
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_vrm_set_voltage(struct rpmh_vreg *vreg, int min_uv, int max_uv,
bool set_active, bool set_sleep)
{
int mv;
int rc = 0;
if (!vreg || vreg->type != RPMH_REGULATOR_TYPE_VRM)
return -1;
if (min_uv < RPMH_VRM_MIN_UV || max_uv > RPMH_VRM_MAX_UV) {
printk(BIOS_ERR, "RPMH_REG: Voltage range %d-%d uV out of bounds for %s\n",
min_uv, max_uv, vreg->resource_name);
return -1;
}
mv = DIV_ROUND_UP(min_uv, 1000);
if (mv * 1000 > max_uv) {
printk(BIOS_ERR, "RPMH_REG: No set points available in range %d-%d uV for %s\n",
min_uv, max_uv, vreg->resource_name);
return -1;
}
if (set_active) {
vreg->req_active.reg[RPMH_REGULATOR_REG_VRM_VOLTAGE] = mv;
vreg->req_active.valid |= BIT(RPMH_REGULATOR_REG_VRM_VOLTAGE);
rc = rpmh_regulator_send_request(vreg, &vreg->req_active,
RPMH_ACTIVE_ONLY_STATE, true);
if (rc)
return rc;
}
if (set_sleep) {
vreg->req_sleep.reg[RPMH_REGULATOR_REG_VRM_VOLTAGE] = mv;
vreg->req_sleep.valid |= BIT(RPMH_REGULATOR_REG_VRM_VOLTAGE);
rc = rpmh_regulator_send_request(vreg, &vreg->req_sleep,
RPMH_SLEEP_STATE, false);
}
return rc;
}
/**
* rpmh_regulator_vrm_set_mode() - set the mode of a VRM regulator
* @vreg: Pointer to the rpmh regulator
* @mode: PMIC mode value
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_vrm_set_mode(struct rpmh_vreg *vreg, u32 mode,
bool set_active, bool set_sleep)
{
int rc = 0;
if (!vreg || vreg->type != RPMH_REGULATOR_TYPE_VRM)
return -1;
if (mode < RPMH_VRM_MODE_MIN || mode > RPMH_VRM_MODE_MAX) {
printk(BIOS_ERR, "RPMH_REG: Mode %u out of range for %s\n",
mode, vreg->resource_name);
return -1;
}
if (set_active) {
vreg->req_active.reg[RPMH_REGULATOR_REG_VRM_MODE] = mode;
vreg->req_active.valid |= BIT(RPMH_REGULATOR_REG_VRM_MODE);
rc = rpmh_regulator_send_request(vreg, &vreg->req_active,
RPMH_ACTIVE_ONLY_STATE, true);
if (rc)
return rc;
}
if (set_sleep) {
vreg->req_sleep.reg[RPMH_REGULATOR_REG_VRM_MODE] = mode;
vreg->req_sleep.valid |= BIT(RPMH_REGULATOR_REG_VRM_MODE);
rc = rpmh_regulator_send_request(vreg, &vreg->req_sleep,
RPMH_SLEEP_STATE, false);
}
return rc;
}
/**
* rpmh_regulator_enable() - enable a regulator
* @vreg: Pointer to the rpmh regulator
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_enable(struct rpmh_vreg *vreg, bool set_active, bool set_sleep)
{
int rc = 0;
int reg_index;
if (!vreg)
return -1;
/* Determine the correct enable register based on type */
switch (vreg->type) {
case RPMH_REGULATOR_TYPE_VRM:
case RPMH_REGULATOR_TYPE_ARC:
case RPMH_REGULATOR_TYPE_XOB:
reg_index = RPMH_REGULATOR_REG_ENABLE;
break;
case RPMH_REGULATOR_TYPE_PBS:
reg_index = RPMH_REGULATOR_REG_PBS_ENABLE;
break;
default:
return -1;
}
if (set_active) {
vreg->req_active.reg[reg_index] = 1;
vreg->req_active.valid |= BIT(reg_index);
rc = rpmh_regulator_send_request(vreg, &vreg->req_active,
RPMH_ACTIVE_ONLY_STATE, true);
if (rc)
return rc;
}
if (set_sleep) {
vreg->req_sleep.reg[reg_index] = 1;
vreg->req_sleep.valid |= BIT(reg_index);
rc = rpmh_regulator_send_request(vreg, &vreg->req_sleep,
RPMH_SLEEP_STATE, false);
}
return rc;
}
/**
* rpmh_regulator_disable() - disable a regulator
* @vreg: Pointer to the rpmh regulator
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_disable(struct rpmh_vreg *vreg, bool set_active, bool set_sleep)
{
int rc = 0;
int reg_index;
if (!vreg)
return -1;
/* Determine the correct enable register based on type */
switch (vreg->type) {
case RPMH_REGULATOR_TYPE_VRM:
case RPMH_REGULATOR_TYPE_ARC:
case RPMH_REGULATOR_TYPE_XOB:
reg_index = RPMH_REGULATOR_REG_ENABLE;
break;
case RPMH_REGULATOR_TYPE_PBS:
reg_index = RPMH_REGULATOR_REG_PBS_ENABLE;
break;
default:
return -1;
}
if (set_active) {
vreg->req_active.reg[reg_index] = 0;
vreg->req_active.valid |= BIT(reg_index);
rc = rpmh_regulator_send_request(vreg, &vreg->req_active,
RPMH_ACTIVE_ONLY_STATE, true);
if (rc)
return rc;
}
if (set_sleep) {
vreg->req_sleep.reg[reg_index] = 0;
vreg->req_sleep.valid |= BIT(reg_index);
rc = rpmh_regulator_send_request(vreg, &vreg->req_sleep,
RPMH_SLEEP_STATE, false);
}
return rc;
}
/**
* rpmh_regulator_arc_set_level() - set the voltage level of an ARC regulator
* @vreg: Pointer to the rpmh regulator
* @level: ARC voltage level (0 to level_count-1)
* @set_active: Set in active state
* @set_sleep: Set in sleep state
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_arc_set_level(struct rpmh_vreg *vreg, u32 level,
bool set_active, bool set_sleep)
{
int rc = 0;
if (!vreg || vreg->type != RPMH_REGULATOR_TYPE_ARC)
return -1;
if (level >= vreg->level_count) {
printk(BIOS_ERR, "RPMH_REG: Level %u out of range for %s (max %d)\n",
level, vreg->resource_name, vreg->level_count - 1);
return -1;
}
if (set_active) {
vreg->req_active.reg[RPMH_REGULATOR_REG_ARC_LEVEL] = level;
vreg->req_active.valid |= BIT(RPMH_REGULATOR_REG_ARC_LEVEL);
rc = rpmh_regulator_send_request(vreg, &vreg->req_active,
RPMH_ACTIVE_ONLY_STATE, true);
if (rc)
return rc;
}
if (set_sleep) {
vreg->req_sleep.reg[RPMH_REGULATOR_REG_ARC_LEVEL] = level;
vreg->req_sleep.valid |= BIT(RPMH_REGULATOR_REG_ARC_LEVEL);
rc = rpmh_regulator_send_request(vreg, &vreg->req_sleep,
RPMH_SLEEP_STATE, false);
}
return rc;
}
/**
* rpmh_regulator_arc_get_level() - get the current voltage level of an ARC regulator
* @vreg: Pointer to the rpmh regulator
*
* This function returns the cached ARC voltage level that was last set.
* Note: This returns the requested level, not necessarily the actual hardware level,
* as there's no way to read back from RPMh hardware.
*
* Return: ARC voltage level (0 to level_count-1) on success, -1 on failure
*/
int rpmh_regulator_arc_get_level(struct rpmh_vreg *vreg)
{
if (!vreg || vreg->type != RPMH_REGULATOR_TYPE_ARC)
return -1;
/* Return the active set level if valid, otherwise return 0 */
if (vreg->req_active.valid & BIT(RPMH_REGULATOR_REG_ARC_LEVEL))
return vreg->req_active.reg[RPMH_REGULATOR_REG_ARC_LEVEL];
return 0;
}
/**
* rpmh_regulator_arc_get_voltage_level() - get the voltage level value for an ARC level
* @vreg: Pointer to the rpmh regulator
* @level: ARC level index
*
* This function returns the actual voltage level value (vlvl) that corresponds
* to the given ARC hardware level (hlvl).
*
* Return: voltage level value on success, 0 on failure
*/
u32 rpmh_regulator_arc_get_voltage_level(struct rpmh_vreg *vreg, u32 level)
{
if (!vreg || vreg->type != RPMH_REGULATOR_TYPE_ARC)
return 0;
if (level >= vreg->level_count)
return 0;
return vreg->level[level];
}
/**
* rpmh_regulator_init() - initialize an rpmh regulator
* @vreg: Pointer to the rpmh regulator structure to initialize
* @dev: Device pointer for RPMh communication
* @resource_name: Name of the RPMh resource
* @type: Type of regulator (VRM, ARC, XOB, PBS)
*
* Return: 0 on success, -1 on failure
*/
int rpmh_regulator_init(struct rpmh_vreg *vreg, const char *resource_name,
enum rpmh_regulator_type type)
{
enum cmd_db_hw_type hw_type;
int rc;
if (!vreg || !resource_name)
return -1;
memset(vreg, 0, sizeof(*vreg));
vreg->resource_name = resource_name;
vreg->type = type;
/* Get the RPMh address for this resource */
vreg->addr = cmd_db_read_addr(resource_name);
if (!vreg->addr) {
printk(BIOS_ERR, "RPMH_REG: Could not find RPMh address for %s\n",
resource_name);
return -1;
}
/* Verify the slave ID matches the expected type */
hw_type = cmd_db_read_slave_id(resource_name);
if (hw_type == CMD_DB_HW_INVALID) {
printk(BIOS_ERR, "RPMH_REG: Could not find slave ID for %s\n",
resource_name);
return -1;
}
/* Validate hardware type matches regulator type */
if ((type == RPMH_REGULATOR_TYPE_ARC && hw_type != CMD_DB_HW_ARC) ||
(type == RPMH_REGULATOR_TYPE_VRM && hw_type != CMD_DB_HW_VRM) ||
(type == RPMH_REGULATOR_TYPE_XOB && hw_type != CMD_DB_HW_XOB) ||
(type == RPMH_REGULATOR_TYPE_BCM && hw_type != CMD_DB_HW_BCM) ||
(type == RPMH_REGULATOR_TYPE_PBS && hw_type != CMD_DB_HW_PBS)) {
printk(BIOS_ERR, "RPMH_REG: Hardware type mismatch for %s\n",
resource_name);
return -1;
}
/* Load ARC level mapping if this is an ARC regulator */
if (type == RPMH_REGULATOR_TYPE_ARC) {
rc = rpmh_regulator_load_arc_level_mapping(vreg);
if (rc) {
printk(BIOS_ERR, "RPMH_REG: Failed to load ARC level mapping for %s\n",
resource_name);
return rc;
}
}
printk(BIOS_INFO, "RPMH_REG: Initialized %s at addr=0x%05X\n",
resource_name, vreg->addr);
return 0;
}

View file

@ -0,0 +1,511 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <console/console.h>
#include <delay.h>
#include <device/mmio.h>
#include <string.h>
#include <timer.h>
#include <soc/cmd_db.h>
#include <soc/tcs.h>
#include <soc/rpmh_internal.h>
#define TCS_AMC_MODE_ENABLE BIT(16)
#define TCS_AMC_MODE_TRIGGER BIT(24)
/* TCS CMD register bit mask */
#define CMD_MSGID_LEN 8
#define CMD_MSGID_RESP_REQ BIT(8)
#define CMD_MSGID_WRITE BIT(16)
#define CMD_STATUS_ISSUED BIT(8)
#define CMD_STATUS_COMPL BIT(16)
/* Offsets for DRV channel status register */
#define CH0_CHN_BUSY BIT(0)
#define CH1_CHN_BUSY BIT(1)
#define CH0_WAKE_TCS_STATUS BIT(0)
#define CH0_SLEEP_TCS_STATUS BIT(1)
#define CH1_WAKE_TCS_STATUS BIT(2)
#define CH1_SLEEP_TCS_STATUS BIT(3)
#define CH_CLEAR_STATUS BIT(31)
static inline void *tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id)
{
if (!drv->tcs_distance)
return drv->tcs_base + drv->regs[RSC_DRV_TCS_OFFSET] * tcs_id + reg;
return drv->tcs_base + drv->tcs_distance * tcs_id + reg;
}
static inline void *tcs_cmd_addr(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
{
return tcs_reg_addr(drv, reg, tcs_id) + drv->regs[RSC_DRV_CMD_OFFSET] * cmd_id;
}
static u32 read_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
{
return read32(tcs_cmd_addr(drv, reg, tcs_id, cmd_id));
}
static u32 read_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id)
{
return read32(tcs_reg_addr(drv, reg, tcs_id));
}
static void write_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id,
int cmd_id, u32 data)
{
write32(tcs_cmd_addr(drv, reg, tcs_id, cmd_id), data);
}
static void write_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id, u32 data)
{
write32(tcs_reg_addr(drv, reg, tcs_id), data);
}
static void write_tcs_reg_sync(const struct rsc_drv *drv, int reg, int tcs_id, u32 data)
{
struct stopwatch sw;
write32(tcs_reg_addr(drv, reg, tcs_id), data);
/* Wait until we read back the same value */
stopwatch_init_usecs_expire(&sw, USECS_1S);
while (read32(tcs_reg_addr(drv, reg, tcs_id)) != data) {
if (stopwatch_expired(&sw)) {
printk(BIOS_ERR, "%s: error writing %#x to %d:%#x\n", drv->name,
data, tcs_id, reg);
break;
}
udelay(LOOP_DELAY_US);
}
}
static void tcs_invalidate(struct rsc_drv *drv, int type, int ch)
{
int m;
struct tcs_group *tcs = &drv->ch[ch].tcs[type];
/* Check if already empty */
if (tcs->slots[0] == 0)
return;
for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) {
write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], m, 0);
write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_WAIT_FOR_CMPL], m, 0);
}
memset(tcs->slots, 0, sizeof(tcs->slots));
}
int rpmh_rsc_get_channel(struct rsc_drv *drv)
{
int chn_update, chn_busy;
if (drv->num_channels == 1)
return CH0;
/* Select Unused channel */
do {
chn_update = read32(drv->base + drv->regs[RSC_DRV_CHN_UPDATE]);
chn_busy = read32(drv->base + drv->regs[RSC_DRV_CHN_BUSY]);
} while (chn_busy != chn_update);
if (chn_busy & CH0_CHN_BUSY)
return CH1;
else if (chn_busy & CH1_CHN_BUSY)
return CH0;
else
return -1;
}
void rpmh_rsc_invalidate(struct rsc_drv *drv, int ch)
{
tcs_invalidate(drv, SLEEP_TCS, ch);
tcs_invalidate(drv, WAKE_TCS, ch);
}
static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv,
enum rpmh_state state,
int ch)
{
int type;
struct tcs_group *tcs;
switch (state) {
case RPMH_ACTIVE_ONLY_STATE:
type = ACTIVE_TCS;
break;
case RPMH_WAKE_ONLY_STATE:
type = WAKE_TCS;
break;
case RPMH_SLEEP_STATE:
type = SLEEP_TCS;
break;
default:
return NULL;
}
tcs = &drv->ch[ch].tcs[type];
if (state == RPMH_ACTIVE_ONLY_STATE && !tcs->num_tcs)
tcs = &drv->ch[ch].tcs[WAKE_TCS];
return tcs;
}
static void __tcs_set_trigger(struct rsc_drv *drv, int tcs_id, bool trigger)
{
u32 enable;
u32 reg = drv->regs[RSC_DRV_CONTROL];
enable = read_tcs_reg(drv, reg, tcs_id);
enable &= ~TCS_AMC_MODE_TRIGGER;
write_tcs_reg_sync(drv, reg, tcs_id, enable);
enable &= ~TCS_AMC_MODE_ENABLE;
write_tcs_reg_sync(drv, reg, tcs_id, enable);
if (trigger) {
enable = TCS_AMC_MODE_ENABLE;
write_tcs_reg_sync(drv, reg, tcs_id, enable);
enable |= TCS_AMC_MODE_TRIGGER;
write_tcs_reg(drv, reg, tcs_id, enable);
}
}
static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id,
const struct tcs_request *msg)
{
u32 msgid;
u32 cmd_msgid = CMD_MSGID_LEN | CMD_MSGID_WRITE;
u32 cmd_enable = 0;
u32 cmd_complete;
struct tcs_cmd *cmd;
int i, j;
cmd_msgid |= msg->wait_for_compl ? CMD_MSGID_RESP_REQ : 0;
cmd_complete = read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_WAIT_FOR_CMPL], tcs_id);
for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) {
cmd = &msg->cmds[i];
cmd_enable |= BIT(j);
cmd_complete |= cmd->wait << j;
msgid = cmd_msgid;
msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0;
write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, j, msgid);
write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, j, cmd->addr);
write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data);
}
write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_WAIT_FOR_CMPL], tcs_id, cmd_complete);
cmd_enable |= read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id);
write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, cmd_enable);
}
static int check_for_req_inflight(struct rsc_drv *drv, struct tcs_group *tcs,
const struct tcs_request *msg)
{
u32 curr_enabled;
u32 addr;
int j, k;
int i = tcs->offset;
for (; i < tcs->offset + tcs->num_tcs; i++) {
if (!(drv->tcs_in_use[i / 32] & BIT(i % 32)))
continue;
curr_enabled = read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], i);
for (j = 0; j < tcs->ncpt; j++) {
if (!(curr_enabled & BIT(j)))
continue;
addr = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], i, j);
for (k = 0; k < msg->num_cmds; k++) {
if (cmd_db_match_resource_addr(msg->cmds[k].addr, addr))
return -1;
}
}
}
return 0;
}
static int find_free_tcs(struct tcs_group *tcs)
{
const struct rsc_drv *drv = tcs->drv;
struct stopwatch sw;
int i;
u32 sts;
for (i = tcs->offset; i < tcs->offset + tcs->num_tcs; i++) {
if (drv->tcs_in_use[i / 32] & BIT(i % 32))
continue;
stopwatch_init_usecs_expire(&sw, USECS_100US);
while (1) {
sts = read_tcs_reg(drv, drv->regs[RSC_DRV_STATUS], i);
if (sts)
return i;
if (stopwatch_expired(&sw))
return -1;
udelay(LOOP_DELAY_US);
}
}
return -1;
}
static int claim_tcs_for_req(struct rsc_drv *drv, struct tcs_group *tcs,
const struct tcs_request *msg)
{
int ret;
ret = check_for_req_inflight(drv, tcs, msg);
if (ret)
return ret;
return find_free_tcs(tcs);
}
int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg, int ch)
{
struct tcs_group *tcs;
struct stopwatch sw;
int tcs_id;
u32 status;
tcs = get_tcs_for_msg(drv, msg->state, ch);
if (!tcs)
return -1;
/* Controller is busy in 'solver' mode */
if (drv->in_solver_mode) {
printk(BIOS_ERR, "RPMH RSC: Cannot send ACTIVE request in solver mode\n");
return -1;
}
/* Wait for a free tcs with hardware status verification */
stopwatch_init_usecs_expire(&sw, USECS_100MS);
while (1) {
tcs_id = claim_tcs_for_req(drv, tcs, msg);
if (tcs_id >= 0) {
/* Double-check hardware status register */
status = read_tcs_reg(drv, drv->regs[RSC_DRV_STATUS], tcs_id);
if (status) { /* 1 = IDLE, 0 = BUSY */
break;
}
/* TCS claimed but hardware still busy, retry */
printk(BIOS_DEBUG, "RPMH RSC: TCS %d claimed but HW busy, retrying\n", tcs_id);
}
if (stopwatch_expired(&sw)) {
printk(BIOS_ERR, "RPMH RSC: No free TCS available (timeout after 100ms)\n");
return -1;
}
udelay(LOOP_DELAY_10US);
}
tcs->req[tcs_id - tcs->offset] = msg;
drv->tcs_in_use[tcs_id / 32] |= BIT(tcs_id % 32);
write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, 0);
write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_WAIT_FOR_CMPL], tcs_id, 0);
__tcs_buffer_write(drv, tcs_id, 0, msg);
__tcs_set_trigger(drv, tcs_id, 1);
/* Poll for completion if wait_for_compl is set */
if (msg->wait_for_compl) {
stopwatch_init_usecs_expire(&sw, USECS_10MS);
while (1) {
u32 sts = read_tcs_reg(drv, drv->regs[RSC_DRV_STATUS], tcs_id);
if (sts)
break;
if (stopwatch_expired(&sw)) {
printk(BIOS_ERR, "RPMH RSC: TCS %d timeout\n", tcs_id);
return -1;
}
udelay(LOOP_DELAY_US);
}
}
drv->tcs_in_use[tcs_id / 32] &= ~BIT(tcs_id % 32);
return 0;
}
static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg,
int *tcs_id, int *cmd_id)
{
int slot, offset;
int i = 0;
int slots_needed = msg->num_cmds;
/* Find contiguous slots */
do {
slot = 0;
for (i = 0; i < tcs->ncpt * tcs->num_tcs; i++) {
if (tcs->slots[i / 32] & BIT(i % 32)) {
slot = 0;
} else {
slot++;
if (slot >= slots_needed)
break;
}
}
if (slot < slots_needed)
return -1;
i = i - slot + 1;
} while (i + slots_needed - 1 >= ((i / tcs->ncpt) + 1) * tcs->ncpt);
/* Mark slots as used */
for (slot = i; slot < i + slots_needed; slot++)
tcs->slots[slot / 32] |= BIT(slot % 32);
offset = i / tcs->ncpt;
*tcs_id = offset + tcs->offset;
*cmd_id = i % tcs->ncpt;
return 0;
}
int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg, int ch)
{
struct tcs_group *tcs;
int tcs_id = 0, cmd_id = 0;
int ret;
if (!msg->num_cmds)
return 0;
tcs = get_tcs_for_msg(drv, msg->state, ch);
if (!tcs)
return -1;
ret = find_slots(tcs, msg, &tcs_id, &cmd_id);
if (!ret)
__tcs_buffer_write(drv, tcs_id, cmd_id, msg);
return ret;
}
int rpmh_rsc_mode_solver_set(struct rsc_drv *drv, bool enable)
{
drv->in_solver_mode = enable;
return 0;
}
int rpmh_rsc_switch_channel(struct rsc_drv *drv, int ch)
{
struct stopwatch sw;
u32 sts;
u32 mask = (ch == 0) ? CH0_WAKE_TCS_STATUS : CH1_WAKE_TCS_STATUS;
write32(drv->base + drv->regs[RSC_DRV_CHN_UPDATE], BIT(ch));
stopwatch_init_usecs_expire(&sw, USECS_100US);
while (1) {
sts = read32(drv->base + drv->regs[RSC_DRV_CHN_TCS_COMPLETE]);
if (sts & mask)
break;
if (stopwatch_expired(&sw))
return -1;
udelay(LOOP_DELAY_US);
}
write32(drv->base + drv->regs[RSC_DRV_CHN_TCS_COMPLETE], CH_CLEAR_STATUS);
return 0;
}
int rpmh_rsc_drv_enable(struct rsc_drv *drv, bool enable)
{
int ret = 0, ch;
u32 chn_en;
chn_en = read32(drv->base + drv->regs[RSC_DRV_CHN_EN]);
if (chn_en == enable)
return -1;
if (enable) {
ch = 0;
ret = rpmh_flush(&drv->client, ch);
if (ret)
return ret;
write32(drv->base + drv->regs[RSC_DRV_CHN_EN], enable);
ret = rpmh_rsc_switch_channel(drv, ch);
} else {
ch = rpmh_rsc_get_channel(drv);
if (ch < 0)
return ch;
ret = rpmh_flush(&drv->client, ch);
if (ret)
return ret;
ret = rpmh_rsc_switch_channel(drv, ch);
if (ret)
return ret;
write32(drv->base + drv->regs[RSC_DRV_CHN_UPDATE], 0);
write32(drv->base + drv->regs[RSC_DRV_CHN_EN], enable);
}
return ret;
}
int rpmh_rsc_init_fast_path(struct rsc_drv *drv, const struct tcs_request *msg, int ch)
{
int tcs_id;
if (!drv->ch[ch].tcs[FAST_PATH_TCS].num_tcs)
return -1;
tcs_id = drv->ch[ch].tcs[FAST_PATH_TCS].offset;
__tcs_buffer_write(drv, tcs_id, 0, msg);
return 0;
}
int rpmh_rsc_update_fast_path(struct rsc_drv *drv,
const struct tcs_request *msg,
u32 mask, int ch)
{
struct stopwatch sw;
int i;
u32 sts;
int tcs_id;
struct tcs_cmd *cmd;
if (!drv->ch[ch].tcs[FAST_PATH_TCS].num_tcs)
return -1;
tcs_id = drv->ch[ch].tcs[FAST_PATH_TCS].offset;
/* Ensure the TCS is free before writing */
stopwatch_init_usecs_expire(&sw, USECS_5US);
while (1) {
sts = read_tcs_reg(drv, drv->regs[RSC_DRV_STATUS], tcs_id);
if (sts)
break;
if (stopwatch_expired(&sw)) {
printk(BIOS_ERR, "Fast-path TCS is too busy\n");
return -1;
}
udelay(LOOP_DELAY_US);
}
/* Update only the data fields */
for (i = 0; i < msg->num_cmds; i++) {
if (!(mask & BIT(i)))
continue;
cmd = &msg->cmds[i];
write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, i, cmd->data);
}
/* Trigger the TCS */
__tcs_set_trigger(drv, tcs_id, 1);
return 0;
}

View file

@ -53,6 +53,7 @@ ramstage-y += ../common/spmi.c
ramstage-$(CONFIG_PCI) += pcie.c
ramstage-y += cpucp_load_reset.c
ramstage-y += ../common/cmd_db.c
ramstage-y += ../common/rpmh.c ../common/rpmh_bcm.c ../common/rpmh_regulator.c ../common/rpmh_rsc.c
################################################################################