diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c index 6e4cb2871fca8..d71187c30b390 100644 --- a/drivers/regulator/qcom-rpmh-regulator.c +++ b/drivers/regulator/qcom-rpmh-regulator.c @@ -61,8 +61,13 @@ static const struct resource_name_formats vreg_rsc_name_lookup[NUM_REGULATOR_TYP }; #define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0 +#define RPMH_REGULATOR_VOLTAGE_MASK 0x1FFF + #define RPMH_REGULATOR_REG_ENABLE 0x4 +#define RPMH_REGULATOR_ENABLE_MASK 0x1 + #define RPMH_REGULATOR_REG_VRM_MODE 0x8 +#define RPMH_REGULATOR_MODE_MASK 0x7 #define PMIC4_LDO_MODE_RETENTION 4 #define PMIC4_LDO_MODE_LPM 5 @@ -104,13 +109,16 @@ static const struct resource_name_formats vreg_rsc_name_lookup[NUM_REGULATOR_TYP * regulator * @ops: Pointer to regulator ops callback structure * @voltage_ranges: The possible ranges of voltages supported by this - * PMIC regulator type + * PMIC regulator type * @n_linear_ranges: Number of entries in voltage_ranges * @n_voltages: The number of unique voltage set points defined * by voltage_ranges * @hpm_min_load_uA: Minimum load current in microamps that requires * high power mode (HPM) operation. This is used * for LDO hardware type regulators only. + * @pmic_bypass_mode: The PMIC bypass mode value. This is only + * used if bypass_supported == true. + * @bypass_supported: Indicates if bypass mode is supported * @pmic_mode_map: Array indexed by regulator framework mode * containing PMIC hardware modes. Must be large * enough to index all framework modes supported @@ -125,6 +133,8 @@ struct rpmh_vreg_hw_data { int n_linear_ranges; int n_voltages; int hpm_min_load_uA; + int pmic_bypass_mode; + bool bypass_supported; const int *pmic_mode_map; unsigned int (*of_map_mode)(unsigned int mode); }; @@ -164,6 +174,7 @@ struct rpmh_vreg { bool bypassed; int voltage_selector; unsigned int mode; + unsigned int status; }; /** @@ -208,6 +219,36 @@ static int rpmh_regulator_send_request(struct rpmh_vreg *vreg, return ret; } +static int rpmh_regulator_read_data(struct rpmh_vreg *vreg, struct tcs_cmd *cmd) +{ + return rpmh_read(vreg->dev, cmd); +} + +static int _rpmh_regulator_vrm_get_voltage(struct regulator_dev *rdev, int *uV) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE, + }; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int ret, _uV = 0; + + ret = rpmh_regulator_read_data(vreg, &cmd); + if (!ret) + _uV = (cmd.data & RPMH_REGULATOR_VOLTAGE_MASK) * 1000; + else + dev_err(vreg->dev, "failed to read VOLTAGE ret = %d\n", ret); + + if (!_uV || (_uV >= min_uV && _uV <= max_uV)) + *uV = _uV; + else + dev_dbg(vreg->dev, "read voltage %d is out-of-range[%d:%d]\n", + _uV, min_uV, max_uV); + + return ret; +} + static int _rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector, bool wait_for_ack) { @@ -249,10 +290,36 @@ static int rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev, static int rpmh_regulator_vrm_get_voltage_sel(struct regulator_dev *rdev) { struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + int ret, uV = 0; + + if (vreg->voltage_selector < 0) { + ret = _rpmh_regulator_vrm_get_voltage(rdev, &uV); + if (!ret && uV != 0) + vreg->voltage_selector = regulator_map_voltage_linear_range(rdev, + uV, INT_MAX); + } return vreg->voltage_selector; } +static enum regulator_status convert_mode_to_status(int mode) +{ + switch (mode) { + case REGULATOR_MODE_FAST: + return REGULATOR_STATUS_FAST; + case REGULATOR_MODE_NORMAL: + return REGULATOR_STATUS_NORMAL; + case REGULATOR_MODE_IDLE: + return REGULATOR_STATUS_IDLE; + case REGULATOR_MODE_STANDBY: + return REGULATOR_STATUS_STANDBY; + case REGULATOR_MODE_INVALID: + return REGULATOR_STATUS_ERROR; + default: + return REGULATOR_STATUS_UNDEFINED; + }; +} + static int rpmh_regulator_is_enabled(struct regulator_dev *rdev) { struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); @@ -282,6 +349,15 @@ static int rpmh_regulator_set_enable_state(struct regulator_dev *rdev, if (!ret) vreg->enabled = enable; + if (vreg->enabled) { + if (vreg->bypassed) + vreg->status = REGULATOR_STATUS_BYPASS; + else + vreg->status = convert_mode_to_status(vreg->mode); + } else { + vreg->status = REGULATOR_STATUS_OFF; + } + return ret; } @@ -310,10 +386,22 @@ static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg, if (pmic_mode < 0) return pmic_mode; - if (bypassed) - cmd.data = PMIC4_BOB_MODE_PASS; - else + if (bypassed) { + if (!vreg->hw_data->bypass_supported) + return -EINVAL; + cmd.data = vreg->hw_data->pmic_bypass_mode; + } else { cmd.data = pmic_mode; + } + + if (vreg->enabled) { + if (bypassed) + vreg->status = REGULATOR_STATUS_BYPASS; + else + vreg->status = convert_mode_to_status(mode); + } else { + vreg->status = REGULATOR_STATUS_OFF; + } return rpmh_regulator_send_request(vreg, &cmd, true); } @@ -334,6 +422,22 @@ static int rpmh_regulator_vrm_set_mode(struct regulator_dev *rdev, return ret; } +static int rpmh_regulator_vrm_get_pmic_mode(struct rpmh_vreg *vreg, int *pmic_mode) +{ + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE, + }; + int ret; + + ret = rpmh_regulator_read_data(vreg, &cmd); + if (!ret) + *pmic_mode = cmd.data & RPMH_REGULATOR_MODE_MASK; + else + return -EINVAL; + + return 0; +} + static unsigned int rpmh_regulator_vrm_get_mode(struct regulator_dev *rdev) { struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); @@ -341,6 +445,13 @@ static unsigned int rpmh_regulator_vrm_get_mode(struct regulator_dev *rdev) return vreg->mode; } +static int rpmh_regulator_vrm_get_status(struct regulator_dev *rdev) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->status; +} + /** * rpmh_regulator_vrm_get_optimum_mode() - get the mode based on the load * @rdev: Regulator device pointer for the rpmh-regulator @@ -399,6 +510,7 @@ static const struct regulator_ops rpmh_regulator_vrm_ops = { .list_voltage = regulator_list_voltage_linear_range, .set_mode = rpmh_regulator_vrm_set_mode, .get_mode = rpmh_regulator_vrm_get_mode, + .get_status = rpmh_regulator_vrm_get_status, }; static const struct regulator_ops rpmh_regulator_vrm_drms_ops = { @@ -410,6 +522,7 @@ static const struct regulator_ops rpmh_regulator_vrm_drms_ops = { .list_voltage = regulator_list_voltage_linear_range, .set_mode = rpmh_regulator_vrm_set_mode, .get_mode = rpmh_regulator_vrm_get_mode, + .get_status = rpmh_regulator_vrm_get_status, .get_optimum_mode = rpmh_regulator_vrm_get_optimum_mode, }; @@ -422,6 +535,7 @@ static const struct regulator_ops rpmh_regulator_vrm_bypass_ops = { .list_voltage = regulator_list_voltage_linear_range, .set_mode = rpmh_regulator_vrm_set_mode, .get_mode = rpmh_regulator_vrm_get_mode, + .get_status = rpmh_regulator_vrm_get_status, .set_bypass = rpmh_regulator_vrm_set_bypass, .get_bypass = rpmh_regulator_vrm_get_bypass, }; @@ -430,6 +544,7 @@ static const struct regulator_ops rpmh_regulator_xob_ops = { .enable = rpmh_regulator_enable, .disable = rpmh_regulator_disable, .is_enabled = rpmh_regulator_is_enabled, + .get_status = rpmh_regulator_vrm_get_status, }; /** @@ -538,6 +653,58 @@ static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg, struct device *dev, return 0; } +static int rpmh_regulator_determine_initial_status(struct rpmh_vreg *vreg) +{ + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE, + }; + int ret, pmic_mode, mode; + int sts = 0; + + ret = rpmh_regulator_read_data(vreg, &cmd); + if (ret) { + dev_dbg(vreg->dev, "failed to read ENABLE status ret = %d\n", ret); + vreg->status = REGULATOR_STATUS_UNDEFINED; + return ret; + } + + sts = cmd.data & RPMH_REGULATOR_ENABLE_MASK; + if (!sts) { + vreg->status = REGULATOR_STATUS_OFF; + return 0; + } + + if (vreg->hw_data->regulator_type == XOB) { + vreg->status = sts ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF; + return 0; + } + + ret = rpmh_regulator_vrm_get_pmic_mode(vreg, &pmic_mode); + if (ret < 0) { + dev_dbg(vreg->dev, "failed to read pmic_mode ret = %d\n", ret); + vreg->mode = REGULATOR_MODE_INVALID; + vreg->status = REGULATOR_STATUS_UNDEFINED; + return ret; + } + + if (vreg->hw_data->bypass_supported && + vreg->hw_data->pmic_bypass_mode == pmic_mode) { + vreg->bypassed = true; + vreg->status = REGULATOR_STATUS_BYPASS; + return 0; + } + + for (mode = 0; mode <= REGULATOR_MODE_STANDBY; mode++) { + if (pmic_mode == vreg->hw_data->pmic_mode_map[mode]) { + vreg->mode = mode; + break; + } + } + + vreg->status = convert_mode_to_status(vreg->mode); + return 0; +} + static const int pmic_mode_map_pmic4_ldo[REGULATOR_MODE_STANDBY + 1] = { [REGULATOR_MODE_INVALID] = -EINVAL, [REGULATOR_MODE_STANDBY] = PMIC4_LDO_MODE_RETENTION, @@ -767,6 +934,8 @@ static const struct rpmh_vreg_hw_data pmic4_bob = { }, .n_linear_ranges = 1, .n_voltages = 84, + .bypass_supported = true, + .pmic_bypass_mode = PMIC4_BOB_MODE_PASS, .pmic_mode_map = pmic_mode_map_pmic4_bob, .of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode, }; @@ -975,6 +1144,8 @@ static const struct rpmh_vreg_hw_data pmic5_bob = { }, .n_linear_ranges = 1, .n_voltages = 32, + .bypass_supported = true, + .pmic_bypass_mode = PMIC5_BOB_MODE_PASS, .pmic_mode_map = pmic_mode_map_pmic5_bob, .of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode, }; @@ -1819,6 +1990,11 @@ static int rpmh_regulator_probe(struct platform_device *pdev) vreg_data); if (ret < 0) return ret; + + ret = rpmh_regulator_determine_initial_status(vreg); + if (ret < 0) + dev_err(dev, "failed to read initial status for %s\n", + vreg->rdesc.name); } return 0; diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index c6f7d5c9c493d..ec85c457ea452 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -443,6 +443,7 @@ static irqreturn_t tcs_tx_done(int irq, void *p) int i; unsigned long irq_status; const struct tcs_request *req; + u32 reg; irq_status = readl_relaxed(drv->tcs_base + drv->regs[RSC_DRV_IRQ_STATUS]); @@ -453,6 +454,11 @@ static irqreturn_t tcs_tx_done(int irq, void *p) trace_rpmh_tx_done(drv, i, req); + if (req->is_read) { + reg = drv->regs[RSC_DRV_CMD_RESP_DATA]; + req->cmds[0].data = read_tcs_reg(drv, reg, i); + } + /* Clear AMC trigger & enable modes and * disable interrupt for this TCS */ @@ -493,13 +499,15 @@ 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_msgid = CMD_MSGID_LEN; u32 cmd_enable = 0; struct tcs_cmd *cmd; int i, j; /* Convert all commands to RR when the request has wait_for_compl set */ cmd_msgid |= msg->wait_for_compl ? CMD_MSGID_RESP_REQ : 0; + if (!msg->is_read) + cmd_msgid |= CMD_MSGID_WRITE; for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) { cmd = &msg->cmds[i]; @@ -513,7 +521,8 @@ static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, 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); + if (!msg->is_read) + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data); trace_rpmh_send_msg(drv, tcs_id, msg->state, j, msgid, cmd); } diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 8903ed956312d..4a611dac437ef 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -175,6 +175,9 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, struct cache_req *req; int i; + if (rpm_msg->msg.is_read) + goto send_data; + /* Cache the request in our store and link the payload */ for (i = 0; i < rpm_msg->msg.num_cmds; i++) { req = cache_rpm_request(ctrlr, state, &rpm_msg->msg.cmds[i]); @@ -182,6 +185,7 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, return PTR_ERR(req); } +send_data: if (state == RPMH_ACTIVE_ONLY_STATE) { ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); } else { @@ -194,7 +198,7 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, } static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state, - const struct tcs_cmd *cmd, u32 n) + const struct tcs_cmd *cmd, u32 n, bool is_read) { if (!cmd || !n || n > MAX_RPMH_PAYLOAD) return -EINVAL; @@ -204,10 +208,45 @@ static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state, req->msg.state = state; req->msg.cmds = req->cmd; req->msg.num_cmds = n; + req->msg.is_read = is_read; return 0; } +/** + * rpmh_read: Read a resource value + * + * @dev: The device making the request + * @cmd: The payload having address of resource to read + * + * Reads the value for the resource address given in tcs_cmd->addr + * and returns the tcs_cmd->data filled with same. + * + * May sleep. Do not call from atomic contexts. + * + * Return: 0 on success, negative errno on failure + */ +int rpmh_read(const struct device *dev, struct tcs_cmd *cmd) +{ + DECLARE_COMPLETION_ONSTACK(compl); + DEFINE_RPMH_MSG_ONSTACK(dev, RPMH_ACTIVE_ONLY_STATE, &compl, rpm_msg); + int ret; + + ret = __fill_rpmh_msg(&rpm_msg, RPMH_ACTIVE_ONLY_STATE, cmd, 1, true); + if (ret) + return ret; + + ret = __rpmh_write(dev, RPMH_ACTIVE_ONLY_STATE, &rpm_msg); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS); + cmd[0].data = rpm_msg.cmd[0].data; + + return (ret > 0) ? 0 : -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(rpmh_read); + /** * rpmh_write_async: Write a set of RPMH commands * @@ -230,7 +269,7 @@ int rpmh_write_async(const struct device *dev, enum rpmh_state state, return -ENOMEM; rpm_msg->needs_free = true; - ret = __fill_rpmh_msg(rpm_msg, state, cmd, n); + ret = __fill_rpmh_msg(rpm_msg, state, cmd, n, false); if (ret) { kfree(rpm_msg); return ret; @@ -257,7 +296,7 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, DEFINE_RPMH_MSG_ONSTACK(dev, state, &compl, rpm_msg); int ret; - ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n); + ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n, false); if (ret) return ret; @@ -352,7 +391,7 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, rpm_msgs = req->rpm_msgs; for (i = 0; i < count; i++) { - __fill_rpmh_msg(rpm_msgs + i, state, cmd, n[i]); + __fill_rpmh_msg(rpm_msgs + i, state, cmd, n[i], false); cmd += n[i]; } diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index bdbee1a97d368..14ecbf242b6bd 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -11,6 +11,8 @@ #if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_read(const struct device *dev, struct tcs_cmd *cmd); + int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); @@ -24,6 +26,9 @@ void rpmh_invalidate(const struct device *dev); #else +static inline int rpmh_read(const struct device *dev, struct tcs_cmd *cmd) +{ return -ENODEV; } + static inline int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h index cff67ce25488e..45b8513be2f9b 100644 --- a/include/soc/qcom/tcs.h +++ b/include/soc/qcom/tcs.h @@ -51,6 +51,7 @@ struct tcs_cmd { * struct tcs_request: A set of tcs_cmds sent together in a TCS * * @state: state for the request. + * @is_read: set for read only requests * @wait_for_compl: wait until we get a response from the h/w accelerator * (same as setting cmd->wait for all commands in the request) * @num_cmds: the number of @cmds in this request @@ -58,6 +59,7 @@ struct tcs_cmd { */ struct tcs_request { enum rpmh_state state; + bool is_read; u32 wait_for_compl; u32 num_cmds; struct tcs_cmd *cmds;