162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Qualcomm Ramp Controller driver 462306a36Sopenharmony_ci * Copyright (c) 2022, AngeloGioacchino Del Regno 562306a36Sopenharmony_ci * <angelogioacchino.delregno@collabora.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/bitfield.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/of.h> 1262306a36Sopenharmony_ci#include <linux/of_platform.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/regmap.h> 1562306a36Sopenharmony_ci#include <linux/types.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define RC_UPDATE_EN BIT(0) 1862306a36Sopenharmony_ci#define RC_ROOT_EN BIT(1) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define RC_REG_CFG_UPDATE 0x60 2162306a36Sopenharmony_ci#define RC_CFG_UPDATE_EN BIT(8) 2262306a36Sopenharmony_ci#define RC_CFG_ACK GENMASK(31, 16) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define RC_DCVS_CFG_SID 2 2562306a36Sopenharmony_ci#define RC_LINK_SID 3 2662306a36Sopenharmony_ci#define RC_LMH_SID 6 2762306a36Sopenharmony_ci#define RC_DFS_SID 14 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define RC_UPDATE_TIMEOUT_US 500 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/** 3262306a36Sopenharmony_ci * struct qcom_ramp_controller_desc - SoC specific parameters 3362306a36Sopenharmony_ci * @cfg_dfs_sid: Dynamic Frequency Scaling SID configuration 3462306a36Sopenharmony_ci * @cfg_link_sid: Link SID configuration 3562306a36Sopenharmony_ci * @cfg_lmh_sid: Limits Management hardware SID configuration 3662306a36Sopenharmony_ci * @cfg_ramp_en: Ramp Controller enable sequence 3762306a36Sopenharmony_ci * @cfg_ramp_dis: Ramp Controller disable sequence 3862306a36Sopenharmony_ci * @cmd_reg: Command register offset 3962306a36Sopenharmony_ci * @num_dfs_sids: Number of DFS SIDs (max 8) 4062306a36Sopenharmony_ci * @num_link_sids: Number of Link SIDs (max 3) 4162306a36Sopenharmony_ci * @num_lmh_sids: Number of LMh SIDs (max 8) 4262306a36Sopenharmony_ci * @num_ramp_en: Number of entries in enable sequence 4362306a36Sopenharmony_ci * @num_ramp_dis: Number of entries in disable sequence 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_cistruct qcom_ramp_controller_desc { 4662306a36Sopenharmony_ci const struct reg_sequence *cfg_dfs_sid; 4762306a36Sopenharmony_ci const struct reg_sequence *cfg_link_sid; 4862306a36Sopenharmony_ci const struct reg_sequence *cfg_lmh_sid; 4962306a36Sopenharmony_ci const struct reg_sequence *cfg_ramp_en; 5062306a36Sopenharmony_ci const struct reg_sequence *cfg_ramp_dis; 5162306a36Sopenharmony_ci u8 cmd_reg; 5262306a36Sopenharmony_ci u8 num_dfs_sids; 5362306a36Sopenharmony_ci u8 num_link_sids; 5462306a36Sopenharmony_ci u8 num_lmh_sids; 5562306a36Sopenharmony_ci u8 num_ramp_en; 5662306a36Sopenharmony_ci u8 num_ramp_dis; 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/** 6062306a36Sopenharmony_ci * struct qcom_ramp_controller - Main driver structure 6162306a36Sopenharmony_ci * @regmap: Regmap handle 6262306a36Sopenharmony_ci * @desc: SoC specific parameters 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_cistruct qcom_ramp_controller { 6562306a36Sopenharmony_ci struct regmap *regmap; 6662306a36Sopenharmony_ci const struct qcom_ramp_controller_desc *desc; 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/** 7062306a36Sopenharmony_ci * rc_wait_for_update() - Wait for Ramp Controller root update 7162306a36Sopenharmony_ci * @qrc: Main driver structure 7262306a36Sopenharmony_ci * 7362306a36Sopenharmony_ci * Return: Zero for success or negative number for failure 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_cistatic int rc_wait_for_update(struct qcom_ramp_controller *qrc) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci const struct qcom_ramp_controller_desc *d = qrc->desc; 7862306a36Sopenharmony_ci struct regmap *r = qrc->regmap; 7962306a36Sopenharmony_ci u32 val; 8062306a36Sopenharmony_ci int ret; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci ret = regmap_set_bits(r, d->cmd_reg, RC_ROOT_EN); 8362306a36Sopenharmony_ci if (ret) 8462306a36Sopenharmony_ci return ret; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return regmap_read_poll_timeout(r, d->cmd_reg, val, !(val & RC_UPDATE_EN), 8762306a36Sopenharmony_ci 1, RC_UPDATE_TIMEOUT_US); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci/** 9162306a36Sopenharmony_ci * rc_set_cfg_update() - Ramp Controller configuration update 9262306a36Sopenharmony_ci * @qrc: Main driver structure 9362306a36Sopenharmony_ci * @ce: Configuration entry to update 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * Return: Zero for success or negative number for failure 9662306a36Sopenharmony_ci */ 9762306a36Sopenharmony_cistatic int rc_set_cfg_update(struct qcom_ramp_controller *qrc, u8 ce) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci const struct qcom_ramp_controller_desc *d = qrc->desc; 10062306a36Sopenharmony_ci struct regmap *r = qrc->regmap; 10162306a36Sopenharmony_ci u32 ack, val; 10262306a36Sopenharmony_ci int ret; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* The ack bit is between bits 16-31 of RC_REG_CFG_UPDATE */ 10562306a36Sopenharmony_ci ack = FIELD_PREP(RC_CFG_ACK, BIT(ce)); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* Write the configuration type first... */ 10862306a36Sopenharmony_ci ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, ce); 10962306a36Sopenharmony_ci if (ret) 11062306a36Sopenharmony_ci return ret; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* ...and after that, enable the update bit to sync the changes */ 11362306a36Sopenharmony_ci ret = regmap_set_bits(r, d->cmd_reg + RC_REG_CFG_UPDATE, RC_CFG_UPDATE_EN); 11462306a36Sopenharmony_ci if (ret) 11562306a36Sopenharmony_ci return ret; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* Wait for the changes to go through */ 11862306a36Sopenharmony_ci ret = regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, val, 11962306a36Sopenharmony_ci val & ack, 1, RC_UPDATE_TIMEOUT_US); 12062306a36Sopenharmony_ci if (ret) 12162306a36Sopenharmony_ci return ret; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* 12462306a36Sopenharmony_ci * Configuration update success! The CFG_UPDATE register will not be 12562306a36Sopenharmony_ci * cleared automatically upon applying the configuration, so we have 12662306a36Sopenharmony_ci * to do that manually in order to leave the ramp controller in a 12762306a36Sopenharmony_ci * predictable and clean state. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_ci ret = regmap_write(r, d->cmd_reg + RC_REG_CFG_UPDATE, 0); 13062306a36Sopenharmony_ci if (ret) 13162306a36Sopenharmony_ci return ret; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* Wait for the update bit cleared ack */ 13462306a36Sopenharmony_ci return regmap_read_poll_timeout(r, d->cmd_reg + RC_REG_CFG_UPDATE, 13562306a36Sopenharmony_ci val, !(val & RC_CFG_ACK), 1, 13662306a36Sopenharmony_ci RC_UPDATE_TIMEOUT_US); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/** 14062306a36Sopenharmony_ci * rc_write_cfg - Send configuration sequence 14162306a36Sopenharmony_ci * @qrc: Main driver structure 14262306a36Sopenharmony_ci * @seq: Register sequence to send before asking for update 14362306a36Sopenharmony_ci * @ce: Configuration SID 14462306a36Sopenharmony_ci * @nsids: Total number of SIDs 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * Returns: Zero for success or negative number for error 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_cistatic int rc_write_cfg(struct qcom_ramp_controller *qrc, 14962306a36Sopenharmony_ci const struct reg_sequence *seq, 15062306a36Sopenharmony_ci u16 ce, u8 nsids) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci int ret; 15362306a36Sopenharmony_ci u8 i; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* Check if, and wait until the ramp controller is ready */ 15662306a36Sopenharmony_ci ret = rc_wait_for_update(qrc); 15762306a36Sopenharmony_ci if (ret) 15862306a36Sopenharmony_ci return ret; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* Write the sequence */ 16162306a36Sopenharmony_ci ret = regmap_multi_reg_write(qrc->regmap, seq, nsids); 16262306a36Sopenharmony_ci if (ret) 16362306a36Sopenharmony_ci return ret; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Pull the trigger: do config update starting from the last sid */ 16662306a36Sopenharmony_ci for (i = 0; i < nsids; i++) { 16762306a36Sopenharmony_ci ret = rc_set_cfg_update(qrc, (u8)ce - i); 16862306a36Sopenharmony_ci if (ret) 16962306a36Sopenharmony_ci return ret; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci/** 17662306a36Sopenharmony_ci * rc_ramp_ctrl_enable() - Enable Ramp up/down Control 17762306a36Sopenharmony_ci * @qrc: Main driver structure 17862306a36Sopenharmony_ci * 17962306a36Sopenharmony_ci * Return: Zero for success or negative number for error 18062306a36Sopenharmony_ci */ 18162306a36Sopenharmony_cistatic int rc_ramp_ctrl_enable(struct qcom_ramp_controller *qrc) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci const struct qcom_ramp_controller_desc *d = qrc->desc; 18462306a36Sopenharmony_ci int i, ret; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci for (i = 0; i < d->num_ramp_en; i++) { 18762306a36Sopenharmony_ci ret = rc_write_cfg(qrc, &d->cfg_ramp_en[i], RC_DCVS_CFG_SID, 1); 18862306a36Sopenharmony_ci if (ret) 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci return 0; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/** 19662306a36Sopenharmony_ci * qcom_ramp_controller_start() - Initialize and start the ramp controller 19762306a36Sopenharmony_ci * @qrc: Main driver structure 19862306a36Sopenharmony_ci * 19962306a36Sopenharmony_ci * The Ramp Controller needs to be initialized by programming the relevant 20062306a36Sopenharmony_ci * registers with SoC-specific configuration: once programming is done, 20162306a36Sopenharmony_ci * the hardware will take care of the rest (no further handling required). 20262306a36Sopenharmony_ci * 20362306a36Sopenharmony_ci * Return: Zero for success or negative number for error 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_cistatic int qcom_ramp_controller_start(struct qcom_ramp_controller *qrc) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci const struct qcom_ramp_controller_desc *d = qrc->desc; 20862306a36Sopenharmony_ci int ret; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* Program LMH, DFS, Link SIDs */ 21162306a36Sopenharmony_ci ret = rc_write_cfg(qrc, d->cfg_lmh_sid, RC_LMH_SID, d->num_lmh_sids); 21262306a36Sopenharmony_ci if (ret) 21362306a36Sopenharmony_ci return ret; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci ret = rc_write_cfg(qrc, d->cfg_dfs_sid, RC_DFS_SID, d->num_dfs_sids); 21662306a36Sopenharmony_ci if (ret) 21762306a36Sopenharmony_ci return ret; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci ret = rc_write_cfg(qrc, d->cfg_link_sid, RC_LINK_SID, d->num_link_sids); 22062306a36Sopenharmony_ci if (ret) 22162306a36Sopenharmony_ci return ret; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* Everything is ready! Enable the ramp up/down control */ 22462306a36Sopenharmony_ci return rc_ramp_ctrl_enable(qrc); 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic const struct regmap_config qrc_regmap_config = { 22862306a36Sopenharmony_ci .reg_bits = 32, 22962306a36Sopenharmony_ci .reg_stride = 4, 23062306a36Sopenharmony_ci .val_bits = 32, 23162306a36Sopenharmony_ci .max_register = 0x68, 23262306a36Sopenharmony_ci .fast_io = true, 23362306a36Sopenharmony_ci}; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic const struct reg_sequence msm8976_cfg_dfs_sid[] = { 23662306a36Sopenharmony_ci { 0x10, 0xfefebff7 }, 23762306a36Sopenharmony_ci { 0x14, 0xfdff7fef }, 23862306a36Sopenharmony_ci { 0x18, 0xfbffdefb }, 23962306a36Sopenharmony_ci { 0x1c, 0xb69b5555 }, 24062306a36Sopenharmony_ci { 0x20, 0x24929249 }, 24162306a36Sopenharmony_ci { 0x24, 0x49241112 }, 24262306a36Sopenharmony_ci { 0x28, 0x11112111 }, 24362306a36Sopenharmony_ci { 0x2c, 0x8102 } 24462306a36Sopenharmony_ci}; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic const struct reg_sequence msm8976_cfg_link_sid[] = { 24762306a36Sopenharmony_ci { 0x40, 0xfc987 } 24862306a36Sopenharmony_ci}; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic const struct reg_sequence msm8976_cfg_lmh_sid[] = { 25162306a36Sopenharmony_ci { 0x30, 0x77706db }, 25262306a36Sopenharmony_ci { 0x34, 0x5550249 }, 25362306a36Sopenharmony_ci { 0x38, 0x111 } 25462306a36Sopenharmony_ci}; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic const struct reg_sequence msm8976_cfg_ramp_en[] = { 25762306a36Sopenharmony_ci { 0x50, 0x800 }, /* pre_en */ 25862306a36Sopenharmony_ci { 0x50, 0xc00 }, /* en */ 25962306a36Sopenharmony_ci { 0x50, 0x400 } /* post_en */ 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic const struct reg_sequence msm8976_cfg_ramp_dis[] = { 26362306a36Sopenharmony_ci { 0x50, 0x0 } 26462306a36Sopenharmony_ci}; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic const struct qcom_ramp_controller_desc msm8976_rc_cfg = { 26762306a36Sopenharmony_ci .cfg_dfs_sid = msm8976_cfg_dfs_sid, 26862306a36Sopenharmony_ci .num_dfs_sids = ARRAY_SIZE(msm8976_cfg_dfs_sid), 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci .cfg_link_sid = msm8976_cfg_link_sid, 27162306a36Sopenharmony_ci .num_link_sids = ARRAY_SIZE(msm8976_cfg_link_sid), 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci .cfg_lmh_sid = msm8976_cfg_lmh_sid, 27462306a36Sopenharmony_ci .num_lmh_sids = ARRAY_SIZE(msm8976_cfg_lmh_sid), 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci .cfg_ramp_en = msm8976_cfg_ramp_en, 27762306a36Sopenharmony_ci .num_ramp_en = ARRAY_SIZE(msm8976_cfg_ramp_en), 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci .cfg_ramp_dis = msm8976_cfg_ramp_dis, 28062306a36Sopenharmony_ci .num_ramp_dis = ARRAY_SIZE(msm8976_cfg_ramp_dis), 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci .cmd_reg = 0x0, 28362306a36Sopenharmony_ci}; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int qcom_ramp_controller_probe(struct platform_device *pdev) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci struct qcom_ramp_controller *qrc; 28862306a36Sopenharmony_ci void __iomem *base; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 0); 29162306a36Sopenharmony_ci if (IS_ERR(base)) 29262306a36Sopenharmony_ci return PTR_ERR(base); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci qrc = devm_kmalloc(&pdev->dev, sizeof(*qrc), GFP_KERNEL); 29562306a36Sopenharmony_ci if (!qrc) 29662306a36Sopenharmony_ci return -ENOMEM; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci qrc->desc = device_get_match_data(&pdev->dev); 29962306a36Sopenharmony_ci if (!qrc->desc) 30062306a36Sopenharmony_ci return -EINVAL; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci qrc->regmap = devm_regmap_init_mmio(&pdev->dev, base, &qrc_regmap_config); 30362306a36Sopenharmony_ci if (IS_ERR(qrc->regmap)) 30462306a36Sopenharmony_ci return PTR_ERR(qrc->regmap); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci platform_set_drvdata(pdev, qrc); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci return qcom_ramp_controller_start(qrc); 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic void qcom_ramp_controller_remove(struct platform_device *pdev) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci struct qcom_ramp_controller *qrc = platform_get_drvdata(pdev); 31462306a36Sopenharmony_ci int ret; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci ret = rc_write_cfg(qrc, qrc->desc->cfg_ramp_dis, 31762306a36Sopenharmony_ci RC_DCVS_CFG_SID, qrc->desc->num_ramp_dis); 31862306a36Sopenharmony_ci if (ret) 31962306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to send disable sequence\n"); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic const struct of_device_id qcom_ramp_controller_match_table[] = { 32362306a36Sopenharmony_ci { .compatible = "qcom,msm8976-ramp-controller", .data = &msm8976_rc_cfg }, 32462306a36Sopenharmony_ci { /* sentinel */ } 32562306a36Sopenharmony_ci}; 32662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_ramp_controller_match_table); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic struct platform_driver qcom_ramp_controller_driver = { 32962306a36Sopenharmony_ci .driver = { 33062306a36Sopenharmony_ci .name = "qcom-ramp-controller", 33162306a36Sopenharmony_ci .of_match_table = qcom_ramp_controller_match_table, 33262306a36Sopenharmony_ci .suppress_bind_attrs = true, 33362306a36Sopenharmony_ci }, 33462306a36Sopenharmony_ci .probe = qcom_ramp_controller_probe, 33562306a36Sopenharmony_ci .remove_new = qcom_ramp_controller_remove, 33662306a36Sopenharmony_ci}; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cistatic int __init qcom_ramp_controller_init(void) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci return platform_driver_register(&qcom_ramp_controller_driver); 34162306a36Sopenharmony_ci} 34262306a36Sopenharmony_ciarch_initcall(qcom_ramp_controller_init); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ciMODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); 34562306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Ramp Controller driver"); 34662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 347