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