xref: /openbmc/linux/drivers/thermal/qcom/lmh.c (revision 24f68eb5bf14a74027946970a18bc902e19d986a)
153bca371SThara Gopinath // SPDX-License-Identifier: GPL-2.0-only
253bca371SThara Gopinath 
353bca371SThara Gopinath /*
453bca371SThara Gopinath  * Copyright (C) 2021, Linaro Limited. All rights reserved.
553bca371SThara Gopinath  */
653bca371SThara Gopinath #include <linux/module.h>
753bca371SThara Gopinath #include <linux/interrupt.h>
853bca371SThara Gopinath #include <linux/irqdomain.h>
953bca371SThara Gopinath #include <linux/err.h>
1053bca371SThara Gopinath #include <linux/platform_device.h>
1153bca371SThara Gopinath #include <linux/of_platform.h>
1253bca371SThara Gopinath #include <linux/slab.h>
133bf90ecaSElliot Berman #include <linux/firmware/qcom/qcom_scm.h>
1453bca371SThara Gopinath 
1553bca371SThara Gopinath #define LMH_NODE_DCVS			0x44435653
1653bca371SThara Gopinath #define LMH_CLUSTER0_NODE_ID		0x6370302D
1753bca371SThara Gopinath #define LMH_CLUSTER1_NODE_ID		0x6370312D
1853bca371SThara Gopinath 
1953bca371SThara Gopinath #define LMH_SUB_FN_THERMAL		0x54484D4C
2053bca371SThara Gopinath #define LMH_SUB_FN_CRNT			0x43524E54
2153bca371SThara Gopinath #define LMH_SUB_FN_REL			0x52454C00
2253bca371SThara Gopinath #define LMH_SUB_FN_BCL			0x42434C00
2353bca371SThara Gopinath 
2453bca371SThara Gopinath #define LMH_ALGO_MODE_ENABLE		0x454E424C
2553bca371SThara Gopinath #define LMH_TH_HI_THRESHOLD		0x48494748
2653bca371SThara Gopinath #define LMH_TH_LOW_THRESHOLD		0x4C4F5700
2753bca371SThara Gopinath #define LMH_TH_ARM_THRESHOLD		0x41524D00
2853bca371SThara Gopinath 
2953bca371SThara Gopinath #define LMH_REG_DCVS_INTR_CLR		0x8
3053bca371SThara Gopinath 
31cf0c54dbSThara Gopinath #define LMH_ENABLE_ALGOS		1
32cf0c54dbSThara Gopinath 
3353bca371SThara Gopinath struct lmh_hw_data {
3453bca371SThara Gopinath 	void __iomem *base;
3553bca371SThara Gopinath 	struct irq_domain *domain;
3653bca371SThara Gopinath 	int irq;
3753bca371SThara Gopinath };
3853bca371SThara Gopinath 
lmh_handle_irq(int hw_irq,void * data)3953bca371SThara Gopinath static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
4053bca371SThara Gopinath {
4153bca371SThara Gopinath 	struct lmh_hw_data *lmh_data = data;
4253bca371SThara Gopinath 	int irq = irq_find_mapping(lmh_data->domain, 0);
4353bca371SThara Gopinath 
4453bca371SThara Gopinath 	/* Call the cpufreq driver to handle the interrupt */
4553bca371SThara Gopinath 	if (irq)
4653bca371SThara Gopinath 		generic_handle_irq(irq);
4753bca371SThara Gopinath 
4846a891e4SBjorn Andersson 	return IRQ_HANDLED;
4953bca371SThara Gopinath }
5053bca371SThara Gopinath 
lmh_enable_interrupt(struct irq_data * d)5153bca371SThara Gopinath static void lmh_enable_interrupt(struct irq_data *d)
5253bca371SThara Gopinath {
5353bca371SThara Gopinath 	struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
5453bca371SThara Gopinath 
5553bca371SThara Gopinath 	/* Clear the existing interrupt */
5653bca371SThara Gopinath 	writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
5753bca371SThara Gopinath 	enable_irq(lmh_data->irq);
5853bca371SThara Gopinath }
5953bca371SThara Gopinath 
lmh_disable_interrupt(struct irq_data * d)6053bca371SThara Gopinath static void lmh_disable_interrupt(struct irq_data *d)
6153bca371SThara Gopinath {
6253bca371SThara Gopinath 	struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
6353bca371SThara Gopinath 
6453bca371SThara Gopinath 	disable_irq_nosync(lmh_data->irq);
6553bca371SThara Gopinath }
6653bca371SThara Gopinath 
6753bca371SThara Gopinath static struct irq_chip lmh_irq_chip = {
6853bca371SThara Gopinath 	.name           = "lmh",
6953bca371SThara Gopinath 	.irq_enable	= lmh_enable_interrupt,
7053bca371SThara Gopinath 	.irq_disable	= lmh_disable_interrupt
7153bca371SThara Gopinath };
7253bca371SThara Gopinath 
lmh_irq_map(struct irq_domain * d,unsigned int irq,irq_hw_number_t hw)7353bca371SThara Gopinath static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
7453bca371SThara Gopinath {
7553bca371SThara Gopinath 	struct lmh_hw_data *lmh_data = d->host_data;
76*1df8231fSDmitry Baryshkov 	static struct lock_class_key lmh_lock_key;
77*1df8231fSDmitry Baryshkov 	static struct lock_class_key lmh_request_key;
7853bca371SThara Gopinath 
79*1df8231fSDmitry Baryshkov 	/*
80*1df8231fSDmitry Baryshkov 	 * This lock class tells lockdep that GPIO irqs are in a different
81*1df8231fSDmitry Baryshkov 	 * category than their parents, so it won't report false recursion.
82*1df8231fSDmitry Baryshkov 	 */
83*1df8231fSDmitry Baryshkov 	irq_set_lockdep_class(irq, &lmh_lock_key, &lmh_request_key);
8453bca371SThara Gopinath 	irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
8553bca371SThara Gopinath 	irq_set_chip_data(irq, lmh_data);
8653bca371SThara Gopinath 
8753bca371SThara Gopinath 	return 0;
8853bca371SThara Gopinath }
8953bca371SThara Gopinath 
9053bca371SThara Gopinath static const struct irq_domain_ops lmh_irq_ops = {
9153bca371SThara Gopinath 	.map = lmh_irq_map,
9253bca371SThara Gopinath 	.xlate = irq_domain_xlate_onecell,
9353bca371SThara Gopinath };
9453bca371SThara Gopinath 
lmh_probe(struct platform_device * pdev)9553bca371SThara Gopinath static int lmh_probe(struct platform_device *pdev)
9653bca371SThara Gopinath {
9753bca371SThara Gopinath 	struct device *dev = &pdev->dev;
9853bca371SThara Gopinath 	struct device_node *np = dev->of_node;
9953bca371SThara Gopinath 	struct device_node *cpu_node;
10053bca371SThara Gopinath 	struct lmh_hw_data *lmh_data;
10153bca371SThara Gopinath 	int temp_low, temp_high, temp_arm, cpu_id, ret;
102cf0c54dbSThara Gopinath 	unsigned int enable_alg;
10353bca371SThara Gopinath 	u32 node_id;
10453bca371SThara Gopinath 
1050a47ba94SKonrad Dybcio 	if (!qcom_scm_is_available())
1060a47ba94SKonrad Dybcio 		return -EPROBE_DEFER;
1070a47ba94SKonrad Dybcio 
10853bca371SThara Gopinath 	lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
10953bca371SThara Gopinath 	if (!lmh_data)
11053bca371SThara Gopinath 		return -ENOMEM;
11153bca371SThara Gopinath 
11253bca371SThara Gopinath 	lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
11353bca371SThara Gopinath 	if (IS_ERR(lmh_data->base))
11453bca371SThara Gopinath 		return PTR_ERR(lmh_data->base);
11553bca371SThara Gopinath 
11653bca371SThara Gopinath 	cpu_node = of_parse_phandle(np, "cpus", 0);
11753bca371SThara Gopinath 	if (!cpu_node)
11853bca371SThara Gopinath 		return -EINVAL;
11953bca371SThara Gopinath 	cpu_id = of_cpu_node_to_id(cpu_node);
12053bca371SThara Gopinath 	of_node_put(cpu_node);
12153bca371SThara Gopinath 
12253bca371SThara Gopinath 	ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
12353bca371SThara Gopinath 	if (ret) {
12453bca371SThara Gopinath 		dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
12553bca371SThara Gopinath 		return ret;
12653bca371SThara Gopinath 	}
12753bca371SThara Gopinath 
12853bca371SThara Gopinath 	ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
12953bca371SThara Gopinath 	if (ret) {
13053bca371SThara Gopinath 		dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
13153bca371SThara Gopinath 		return ret;
13253bca371SThara Gopinath 	}
13353bca371SThara Gopinath 
13453bca371SThara Gopinath 	ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
13553bca371SThara Gopinath 	if (ret) {
13653bca371SThara Gopinath 		dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
13753bca371SThara Gopinath 		return ret;
13853bca371SThara Gopinath 	}
13953bca371SThara Gopinath 
14053bca371SThara Gopinath 	/*
14153bca371SThara Gopinath 	 * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
14253bca371SThara Gopinath 	 * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
14353bca371SThara Gopinath 	 * of a dt match table.
14453bca371SThara Gopinath 	 */
14553bca371SThara Gopinath 	if (cpu_id == 0) {
14653bca371SThara Gopinath 		node_id = LMH_CLUSTER0_NODE_ID;
14753bca371SThara Gopinath 	} else if (cpu_id == 4) {
14853bca371SThara Gopinath 		node_id = LMH_CLUSTER1_NODE_ID;
14953bca371SThara Gopinath 	} else {
15053bca371SThara Gopinath 		dev_err(dev, "Wrong CPU id associated with LMh node\n");
15153bca371SThara Gopinath 		return -EINVAL;
15253bca371SThara Gopinath 	}
15353bca371SThara Gopinath 
15453bca371SThara Gopinath 	if (!qcom_scm_lmh_dcvsh_available())
15553bca371SThara Gopinath 		return -EINVAL;
15653bca371SThara Gopinath 
157cf0c54dbSThara Gopinath 	enable_alg = (uintptr_t)of_device_get_match_data(dev);
158cf0c54dbSThara Gopinath 
159cf0c54dbSThara Gopinath 	if (enable_alg) {
16053bca371SThara Gopinath 		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
16153bca371SThara Gopinath 					 LMH_NODE_DCVS, node_id, 0);
16253bca371SThara Gopinath 		if (ret)
16353bca371SThara Gopinath 			dev_err(dev, "Error %d enabling current subfunction\n", ret);
16453bca371SThara Gopinath 
16553bca371SThara Gopinath 		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
16653bca371SThara Gopinath 					 LMH_NODE_DCVS, node_id, 0);
16753bca371SThara Gopinath 		if (ret)
16853bca371SThara Gopinath 			dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
16953bca371SThara Gopinath 
17053bca371SThara Gopinath 		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
17153bca371SThara Gopinath 					 LMH_NODE_DCVS, node_id, 0);
17253bca371SThara Gopinath 		if (ret)
17353bca371SThara Gopinath 			dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
17453bca371SThara Gopinath 
17553bca371SThara Gopinath 		ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
17653bca371SThara Gopinath 					 LMH_NODE_DCVS, node_id, 0);
17753bca371SThara Gopinath 		if (ret) {
17853bca371SThara Gopinath 			dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
17953bca371SThara Gopinath 			return ret;
18053bca371SThara Gopinath 		}
18153bca371SThara Gopinath 
18253bca371SThara Gopinath 		ret = qcom_scm_lmh_profile_change(0x1);
18353bca371SThara Gopinath 		if (ret) {
18453bca371SThara Gopinath 			dev_err(dev, "Error %d changing profile\n", ret);
18553bca371SThara Gopinath 			return ret;
18653bca371SThara Gopinath 		}
187cf0c54dbSThara Gopinath 	}
18853bca371SThara Gopinath 
18953bca371SThara Gopinath 	/* Set default thermal trips */
19053bca371SThara Gopinath 	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
19153bca371SThara Gopinath 				 LMH_NODE_DCVS, node_id, 0);
19253bca371SThara Gopinath 	if (ret) {
19353bca371SThara Gopinath 		dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
19453bca371SThara Gopinath 		return ret;
19553bca371SThara Gopinath 	}
19653bca371SThara Gopinath 
19753bca371SThara Gopinath 	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
19853bca371SThara Gopinath 				 LMH_NODE_DCVS, node_id, 0);
19953bca371SThara Gopinath 	if (ret) {
20053bca371SThara Gopinath 		dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
20153bca371SThara Gopinath 		return ret;
20253bca371SThara Gopinath 	}
20353bca371SThara Gopinath 
20453bca371SThara Gopinath 	ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
20553bca371SThara Gopinath 				 LMH_NODE_DCVS, node_id, 0);
20653bca371SThara Gopinath 	if (ret) {
20753bca371SThara Gopinath 		dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
20853bca371SThara Gopinath 		return ret;
20953bca371SThara Gopinath 	}
21053bca371SThara Gopinath 
21153bca371SThara Gopinath 	lmh_data->irq = platform_get_irq(pdev, 0);
21253bca371SThara Gopinath 	lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data);
21353bca371SThara Gopinath 	if (!lmh_data->domain) {
21453bca371SThara Gopinath 		dev_err(dev, "Error adding irq_domain\n");
21553bca371SThara Gopinath 		return -EINVAL;
21653bca371SThara Gopinath 	}
21753bca371SThara Gopinath 
21853bca371SThara Gopinath 	/* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
21953bca371SThara Gopinath 	irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
22053bca371SThara Gopinath 	ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
22153bca371SThara Gopinath 			       IRQF_ONESHOT | IRQF_NO_SUSPEND,
22253bca371SThara Gopinath 			       "lmh-irq", lmh_data);
22353bca371SThara Gopinath 	if (ret) {
22453bca371SThara Gopinath 		dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq);
22553bca371SThara Gopinath 		irq_domain_remove(lmh_data->domain);
22653bca371SThara Gopinath 		return ret;
22753bca371SThara Gopinath 	}
22853bca371SThara Gopinath 
22953bca371SThara Gopinath 	return 0;
23053bca371SThara Gopinath }
23153bca371SThara Gopinath 
23253bca371SThara Gopinath static const struct of_device_id lmh_table[] = {
233ef6673e8SBjorn Andersson 	{ .compatible = "qcom,sc8180x-lmh", },
234cf0c54dbSThara Gopinath 	{ .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
235cf0c54dbSThara Gopinath 	{ .compatible = "qcom,sm8150-lmh", },
23653bca371SThara Gopinath 	{}
23753bca371SThara Gopinath };
23853bca371SThara Gopinath MODULE_DEVICE_TABLE(of, lmh_table);
23953bca371SThara Gopinath 
24053bca371SThara Gopinath static struct platform_driver lmh_driver = {
24153bca371SThara Gopinath 	.probe = lmh_probe,
24253bca371SThara Gopinath 	.driver = {
24353bca371SThara Gopinath 		.name = "qcom-lmh",
24453bca371SThara Gopinath 		.of_match_table = lmh_table,
24553bca371SThara Gopinath 		.suppress_bind_attrs = true,
24653bca371SThara Gopinath 	},
24753bca371SThara Gopinath };
24853bca371SThara Gopinath module_platform_driver(lmh_driver);
24953bca371SThara Gopinath 
25053bca371SThara Gopinath MODULE_LICENSE("GPL v2");
25153bca371SThara Gopinath MODULE_DESCRIPTION("QCOM LMh driver");
252