From dc162f84bec26b0022b91a744ca065881820fdd4 Mon Sep 17 00:00:00 2001 From: Swathi Tamilselvan Date: Wed, 10 Dec 2025 16:31:35 +0530 Subject: [PATCH] soc/qualcomm/common: Add RPMh driver support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-on: https://review.coreboot.org/c/coreboot/+/90466 Tested-by: build bot (Jenkins) Reviewed-by: Kapil Porwal --- src/soc/qualcomm/common/include/soc/rpmh.h | 25 + .../qualcomm/common/include/soc/rpmh_bcm.h | 17 + .../common/include/soc/rpmh_internal.h | 220 ++++++++ .../common/include/soc/rpmh_regulator.h | 220 ++++++++ .../qualcomm/common/include/soc/rpmh_rsc.h | 87 +++ src/soc/qualcomm/common/include/soc/tcs.h | 72 +++ src/soc/qualcomm/common/rpmh.c | 408 ++++++++++++++ src/soc/qualcomm/common/rpmh_bcm.c | 59 ++ src/soc/qualcomm/common/rpmh_regulator.c | 458 ++++++++++++++++ src/soc/qualcomm/common/rpmh_rsc.c | 511 ++++++++++++++++++ src/soc/qualcomm/x1p42100/Makefile.mk | 1 + 11 files changed, 2078 insertions(+) create mode 100644 src/soc/qualcomm/common/include/soc/rpmh.h create mode 100644 src/soc/qualcomm/common/include/soc/rpmh_bcm.h create mode 100644 src/soc/qualcomm/common/include/soc/rpmh_internal.h create mode 100644 src/soc/qualcomm/common/include/soc/rpmh_regulator.h create mode 100644 src/soc/qualcomm/common/include/soc/rpmh_rsc.h create mode 100644 src/soc/qualcomm/common/include/soc/tcs.h create mode 100644 src/soc/qualcomm/common/rpmh.c create mode 100644 src/soc/qualcomm/common/rpmh_bcm.c create mode 100644 src/soc/qualcomm/common/rpmh_regulator.c create mode 100644 src/soc/qualcomm/common/rpmh_rsc.c diff --git a/src/soc/qualcomm/common/include/soc/rpmh.h b/src/soc/qualcomm/common/include/soc/rpmh.h new file mode 100644 index 0000000000..5b7d76de0b --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/rpmh.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_RPMH_H__ +#define __SOC_QUALCOMM_RPMH_H__ + +#include +#include + +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__ */ diff --git a/src/soc/qualcomm/common/include/soc/rpmh_bcm.h b/src/soc/qualcomm/common/include/soc/rpmh_bcm.h new file mode 100644 index 0000000000..e359a26fcb --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/rpmh_bcm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_RPMH_BCM_H__ +#define __SOC_QUALCOMM_RPMH_BCM_H__ + +#include + +/** + * 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__ */ diff --git a/src/soc/qualcomm/common/include/soc/rpmh_internal.h b/src/soc/qualcomm/common/include/soc/rpmh_internal.h new file mode 100644 index 0000000000..23cc3a7456 --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/rpmh_internal.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_RPMH_INTERNAL_H__ +#define __SOC_QUALCOMM_RPMH_INTERNAL_H__ + +#include +#include + +#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__ */ diff --git a/src/soc/qualcomm/common/include/soc/rpmh_regulator.h b/src/soc/qualcomm/common/include/soc/rpmh_regulator.h new file mode 100644 index 0000000000..310cd4c7ef --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/rpmh_regulator.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_RPMH_REGULATOR_H__ +#define __SOC_QUALCOMM_RPMH_REGULATOR_H__ + +#include +#include + +#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__ */ diff --git a/src/soc/qualcomm/common/include/soc/rpmh_rsc.h b/src/soc/qualcomm/common/include/soc/rpmh_rsc.h new file mode 100644 index 0000000000..cfe7d601ae --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/rpmh_rsc.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_RPMH_RSC_H__ +#define __SOC_QUALCOMM_RPMH_RSC_H__ + +#include +#include + +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__ */ diff --git a/src/soc/qualcomm/common/include/soc/tcs.h b/src/soc/qualcomm/common/include/soc/tcs.h new file mode 100644 index 0000000000..260e010862 --- /dev/null +++ b/src/soc/qualcomm/common/include/soc/tcs.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_QUALCOMM_TCS_H__ +#define __SOC_QUALCOMM_TCS_H__ + +#include + +#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__ */ diff --git a/src/soc/qualcomm/common/rpmh.c b/src/soc/qualcomm/common/rpmh.c new file mode 100644 index 0000000000..bd348a07a9 --- /dev/null +++ b/src/soc/qualcomm/common/rpmh.c @@ -0,0 +1,408 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/soc/qualcomm/common/rpmh_bcm.c b/src/soc/qualcomm/common/rpmh_bcm.c new file mode 100644 index 0000000000..01de7ffd88 --- /dev/null +++ b/src/soc/qualcomm/common/rpmh_bcm.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include +#include +#include +#include +#include +#include + +/** + * 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; +} diff --git a/src/soc/qualcomm/common/rpmh_regulator.c b/src/soc/qualcomm/common/rpmh_regulator.c new file mode 100644 index 0000000000..c273333349 --- /dev/null +++ b/src/soc/qualcomm/common/rpmh_regulator.c @@ -0,0 +1,458 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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; +} diff --git a/src/soc/qualcomm/common/rpmh_rsc.c b/src/soc/qualcomm/common/rpmh_rsc.c new file mode 100644 index 0000000000..e93d4454eb --- /dev/null +++ b/src/soc/qualcomm/common/rpmh_rsc.c @@ -0,0 +1,511 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/soc/qualcomm/x1p42100/Makefile.mk b/src/soc/qualcomm/x1p42100/Makefile.mk index abf5971e20..46965a5375 100644 --- a/src/soc/qualcomm/x1p42100/Makefile.mk +++ b/src/soc/qualcomm/x1p42100/Makefile.mk @@ -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 ################################################################################