162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci/*
462306a36Sopenharmony_ci * Copyright (C) 2021, Linaro Limited. All rights reserved.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <linux/interrupt.h>
862306a36Sopenharmony_ci#include <linux/irqdomain.h>
962306a36Sopenharmony_ci#include <linux/err.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/of_platform.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/firmware/qcom/qcom_scm.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define LMH_NODE_DCVS			0x44435653
1662306a36Sopenharmony_ci#define LMH_CLUSTER0_NODE_ID		0x6370302D
1762306a36Sopenharmony_ci#define LMH_CLUSTER1_NODE_ID		0x6370312D
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define LMH_SUB_FN_THERMAL		0x54484D4C
2062306a36Sopenharmony_ci#define LMH_SUB_FN_CRNT			0x43524E54
2162306a36Sopenharmony_ci#define LMH_SUB_FN_REL			0x52454C00
2262306a36Sopenharmony_ci#define LMH_SUB_FN_BCL			0x42434C00
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define LMH_ALGO_MODE_ENABLE		0x454E424C
2562306a36Sopenharmony_ci#define LMH_TH_HI_THRESHOLD		0x48494748
2662306a36Sopenharmony_ci#define LMH_TH_LOW_THRESHOLD		0x4C4F5700
2762306a36Sopenharmony_ci#define LMH_TH_ARM_THRESHOLD		0x41524D00
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define LMH_REG_DCVS_INTR_CLR		0x8
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define LMH_ENABLE_ALGOS		1
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct lmh_hw_data {
3462306a36Sopenharmony_ci	void __iomem *base;
3562306a36Sopenharmony_ci	struct irq_domain *domain;
3662306a36Sopenharmony_ci	int irq;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic irqreturn_t lmh_handle_irq(int hw_irq, void *data)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct lmh_hw_data *lmh_data = data;
4262306a36Sopenharmony_ci	int irq = irq_find_mapping(lmh_data->domain, 0);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	/* Call the cpufreq driver to handle the interrupt */
4562306a36Sopenharmony_ci	if (irq)
4662306a36Sopenharmony_ci		generic_handle_irq(irq);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return IRQ_HANDLED;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic void lmh_enable_interrupt(struct irq_data *d)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* Clear the existing interrupt */
5662306a36Sopenharmony_ci	writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
5762306a36Sopenharmony_ci	enable_irq(lmh_data->irq);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void lmh_disable_interrupt(struct irq_data *d)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	disable_irq_nosync(lmh_data->irq);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic struct irq_chip lmh_irq_chip = {
6862306a36Sopenharmony_ci	.name           = "lmh",
6962306a36Sopenharmony_ci	.irq_enable	= lmh_enable_interrupt,
7062306a36Sopenharmony_ci	.irq_disable	= lmh_disable_interrupt
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct lmh_hw_data *lmh_data = d->host_data;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
7862306a36Sopenharmony_ci	irq_set_chip_data(irq, lmh_data);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return 0;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic const struct irq_domain_ops lmh_irq_ops = {
8462306a36Sopenharmony_ci	.map = lmh_irq_map,
8562306a36Sopenharmony_ci	.xlate = irq_domain_xlate_onecell,
8662306a36Sopenharmony_ci};
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int lmh_probe(struct platform_device *pdev)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
9162306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
9262306a36Sopenharmony_ci	struct device_node *cpu_node;
9362306a36Sopenharmony_ci	struct lmh_hw_data *lmh_data;
9462306a36Sopenharmony_ci	int temp_low, temp_high, temp_arm, cpu_id, ret;
9562306a36Sopenharmony_ci	unsigned int enable_alg;
9662306a36Sopenharmony_ci	u32 node_id;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
9962306a36Sopenharmony_ci	if (!lmh_data)
10062306a36Sopenharmony_ci		return -ENOMEM;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
10362306a36Sopenharmony_ci	if (IS_ERR(lmh_data->base))
10462306a36Sopenharmony_ci		return PTR_ERR(lmh_data->base);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	cpu_node = of_parse_phandle(np, "cpus", 0);
10762306a36Sopenharmony_ci	if (!cpu_node)
10862306a36Sopenharmony_ci		return -EINVAL;
10962306a36Sopenharmony_ci	cpu_id = of_cpu_node_to_id(cpu_node);
11062306a36Sopenharmony_ci	of_node_put(cpu_node);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
11362306a36Sopenharmony_ci	if (ret) {
11462306a36Sopenharmony_ci		dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
11562306a36Sopenharmony_ci		return ret;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
11962306a36Sopenharmony_ci	if (ret) {
12062306a36Sopenharmony_ci		dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
12162306a36Sopenharmony_ci		return ret;
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
12562306a36Sopenharmony_ci	if (ret) {
12662306a36Sopenharmony_ci		dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
12762306a36Sopenharmony_ci		return ret;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/*
13162306a36Sopenharmony_ci	 * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
13262306a36Sopenharmony_ci	 * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
13362306a36Sopenharmony_ci	 * of a dt match table.
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	if (cpu_id == 0) {
13662306a36Sopenharmony_ci		node_id = LMH_CLUSTER0_NODE_ID;
13762306a36Sopenharmony_ci	} else if (cpu_id == 4) {
13862306a36Sopenharmony_ci		node_id = LMH_CLUSTER1_NODE_ID;
13962306a36Sopenharmony_ci	} else {
14062306a36Sopenharmony_ci		dev_err(dev, "Wrong CPU id associated with LMh node\n");
14162306a36Sopenharmony_ci		return -EINVAL;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (!qcom_scm_lmh_dcvsh_available())
14562306a36Sopenharmony_ci		return -EINVAL;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	enable_alg = (uintptr_t)of_device_get_match_data(dev);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (enable_alg) {
15062306a36Sopenharmony_ci		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
15162306a36Sopenharmony_ci					 LMH_NODE_DCVS, node_id, 0);
15262306a36Sopenharmony_ci		if (ret)
15362306a36Sopenharmony_ci			dev_err(dev, "Error %d enabling current subfunction\n", ret);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
15662306a36Sopenharmony_ci					 LMH_NODE_DCVS, node_id, 0);
15762306a36Sopenharmony_ci		if (ret)
15862306a36Sopenharmony_ci			dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
16162306a36Sopenharmony_ci					 LMH_NODE_DCVS, node_id, 0);
16262306a36Sopenharmony_ci		if (ret)
16362306a36Sopenharmony_ci			dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
16662306a36Sopenharmony_ci					 LMH_NODE_DCVS, node_id, 0);
16762306a36Sopenharmony_ci		if (ret) {
16862306a36Sopenharmony_ci			dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
16962306a36Sopenharmony_ci			return ret;
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		ret = qcom_scm_lmh_profile_change(0x1);
17362306a36Sopenharmony_ci		if (ret) {
17462306a36Sopenharmony_ci			dev_err(dev, "Error %d changing profile\n", ret);
17562306a36Sopenharmony_ci			return ret;
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	/* Set default thermal trips */
18062306a36Sopenharmony_ci	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
18162306a36Sopenharmony_ci				 LMH_NODE_DCVS, node_id, 0);
18262306a36Sopenharmony_ci	if (ret) {
18362306a36Sopenharmony_ci		dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
18462306a36Sopenharmony_ci		return ret;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
18862306a36Sopenharmony_ci				 LMH_NODE_DCVS, node_id, 0);
18962306a36Sopenharmony_ci	if (ret) {
19062306a36Sopenharmony_ci		dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
19162306a36Sopenharmony_ci		return ret;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
19562306a36Sopenharmony_ci				 LMH_NODE_DCVS, node_id, 0);
19662306a36Sopenharmony_ci	if (ret) {
19762306a36Sopenharmony_ci		dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
19862306a36Sopenharmony_ci		return ret;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	lmh_data->irq = platform_get_irq(pdev, 0);
20262306a36Sopenharmony_ci	lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data);
20362306a36Sopenharmony_ci	if (!lmh_data->domain) {
20462306a36Sopenharmony_ci		dev_err(dev, "Error adding irq_domain\n");
20562306a36Sopenharmony_ci		return -EINVAL;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	/* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
20962306a36Sopenharmony_ci	irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
21062306a36Sopenharmony_ci	ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
21162306a36Sopenharmony_ci			       IRQF_ONESHOT | IRQF_NO_SUSPEND,
21262306a36Sopenharmony_ci			       "lmh-irq", lmh_data);
21362306a36Sopenharmony_ci	if (ret) {
21462306a36Sopenharmony_ci		dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq);
21562306a36Sopenharmony_ci		irq_domain_remove(lmh_data->domain);
21662306a36Sopenharmony_ci		return ret;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	return 0;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic const struct of_device_id lmh_table[] = {
22362306a36Sopenharmony_ci	{ .compatible = "qcom,sc8180x-lmh", },
22462306a36Sopenharmony_ci	{ .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
22562306a36Sopenharmony_ci	{ .compatible = "qcom,sm8150-lmh", },
22662306a36Sopenharmony_ci	{}
22762306a36Sopenharmony_ci};
22862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, lmh_table);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic struct platform_driver lmh_driver = {
23162306a36Sopenharmony_ci	.probe = lmh_probe,
23262306a36Sopenharmony_ci	.driver = {
23362306a36Sopenharmony_ci		.name = "qcom-lmh",
23462306a36Sopenharmony_ci		.of_match_table = lmh_table,
23562306a36Sopenharmony_ci		.suppress_bind_attrs = true,
23662306a36Sopenharmony_ci	},
23762306a36Sopenharmony_ci};
23862306a36Sopenharmony_cimodule_platform_driver(lmh_driver);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
24162306a36Sopenharmony_ciMODULE_DESCRIPTION("QCOM LMh driver");
242