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