1 // SPDX-License-Identifier: GPL-2.0-only
2
3 /*
4 * Copyright (C) 2021, Linaro Limited. All rights reserved.
5 */
6 #include <linux/module.h>
7 #include <linux/interrupt.h>
8 #include <linux/irq.h>
9 #include <linux/irqdesc.h>
10 #include <linux/irqdomain.h>
11 #include <linux/err.h>
12 #include <linux/platform_device.h>
13 #include <linux/of_platform.h>
14 #include <linux/slab.h>
15 #include <linux/firmware/qcom/qcom_scm.h>
16
17 #define LMH_NODE_DCVS 0x44435653
18 #define LMH_CLUSTER0_NODE_ID 0x6370302D
19 #define LMH_CLUSTER1_NODE_ID 0x6370312D
20
21 #define LMH_SUB_FN_THERMAL 0x54484D4C
22 #define LMH_SUB_FN_CRNT 0x43524E54
23 #define LMH_SUB_FN_REL 0x52454C00
24 #define LMH_SUB_FN_BCL 0x42434C00
25
26 #define LMH_ALGO_MODE_ENABLE 0x454E424C
27 #define LMH_TH_HI_THRESHOLD 0x48494748
28 #define LMH_TH_LOW_THRESHOLD 0x4C4F5700
29 #define LMH_TH_ARM_THRESHOLD 0x41524D00
30
31 #define LMH_REG_DCVS_INTR_CLR 0x8
32
33 #define LMH_ENABLE_ALGOS 1
34
35 struct lmh_hw_data {
36 void __iomem *base;
37 struct irq_domain *domain;
38 int irq;
39 };
40
lmh_handle_irq(int hw_irq,void * data)41 static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
42 {
43 struct lmh_hw_data *lmh_data = data;
44 int irq = irq_find_mapping(lmh_data->domain, 0);
45
46 /* Call the cpufreq driver to handle the interrupt */
47 if (irq)
48 generic_handle_irq(irq);
49
50 return IRQ_HANDLED;
51 }
52
lmh_enable_interrupt(struct irq_data * d)53 static void lmh_enable_interrupt(struct irq_data *d)
54 {
55 struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
56
57 /* Clear the existing interrupt */
58 writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
59 enable_irq(lmh_data->irq);
60 }
61
lmh_disable_interrupt(struct irq_data * d)62 static void lmh_disable_interrupt(struct irq_data *d)
63 {
64 struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
65
66 disable_irq_nosync(lmh_data->irq);
67 }
68
69 static struct irq_chip lmh_irq_chip = {
70 .name = "lmh",
71 .irq_enable = lmh_enable_interrupt,
72 .irq_disable = lmh_disable_interrupt
73 };
74
lmh_irq_map(struct irq_domain * d,unsigned int irq,irq_hw_number_t hw)75 static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
76 {
77 struct lmh_hw_data *lmh_data = d->host_data;
78 static struct lock_class_key lmh_lock_key;
79 static struct lock_class_key lmh_request_key;
80
81 /*
82 * This lock class tells lockdep that GPIO irqs are in a different
83 * category than their parents, so it won't report false recursion.
84 */
85 irq_set_lockdep_class(irq, &lmh_lock_key, &lmh_request_key);
86 irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
87 irq_set_chip_data(irq, lmh_data);
88
89 return 0;
90 }
91
92 static const struct irq_domain_ops lmh_irq_ops = {
93 .map = lmh_irq_map,
94 .xlate = irq_domain_xlate_onecell,
95 };
96
lmh_probe(struct platform_device * pdev)97 static int lmh_probe(struct platform_device *pdev)
98 {
99 struct device *dev = &pdev->dev;
100 struct device_node *np = dev->of_node;
101 struct device_node *cpu_node;
102 struct lmh_hw_data *lmh_data;
103 int temp_low, temp_high, temp_arm, cpu_id, ret;
104 unsigned int enable_alg;
105 u32 node_id;
106
107 if (!qcom_scm_is_available())
108 return -EPROBE_DEFER;
109
110 lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
111 if (!lmh_data)
112 return -ENOMEM;
113
114 lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
115 if (IS_ERR(lmh_data->base))
116 return PTR_ERR(lmh_data->base);
117
118 cpu_node = of_parse_phandle(np, "cpus", 0);
119 if (!cpu_node)
120 return -EINVAL;
121 cpu_id = of_cpu_node_to_id(cpu_node);
122 of_node_put(cpu_node);
123
124 ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
125 if (ret) {
126 dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
127 return ret;
128 }
129
130 ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
131 if (ret) {
132 dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
133 return ret;
134 }
135
136 ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
137 if (ret) {
138 dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
139 return ret;
140 }
141
142 /*
143 * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
144 * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
145 * of a dt match table.
146 */
147 if (cpu_id == 0) {
148 node_id = LMH_CLUSTER0_NODE_ID;
149 } else if (cpu_id == 4) {
150 node_id = LMH_CLUSTER1_NODE_ID;
151 } else {
152 dev_err(dev, "Wrong CPU id associated with LMh node\n");
153 return -EINVAL;
154 }
155
156 if (!qcom_scm_lmh_dcvsh_available())
157 return -EINVAL;
158
159 enable_alg = (uintptr_t)of_device_get_match_data(dev);
160
161 if (enable_alg) {
162 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
163 LMH_NODE_DCVS, node_id, 0);
164 if (ret)
165 dev_err(dev, "Error %d enabling current subfunction\n", ret);
166
167 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
168 LMH_NODE_DCVS, node_id, 0);
169 if (ret)
170 dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
171
172 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
173 LMH_NODE_DCVS, node_id, 0);
174 if (ret)
175 dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
176
177 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
178 LMH_NODE_DCVS, node_id, 0);
179 if (ret) {
180 dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
181 return ret;
182 }
183
184 ret = qcom_scm_lmh_profile_change(0x1);
185 if (ret) {
186 dev_err(dev, "Error %d changing profile\n", ret);
187 return ret;
188 }
189 }
190
191 /* Set default thermal trips */
192 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
193 LMH_NODE_DCVS, node_id, 0);
194 if (ret) {
195 dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
196 return ret;
197 }
198
199 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
200 LMH_NODE_DCVS, node_id, 0);
201 if (ret) {
202 dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
203 return ret;
204 }
205
206 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
207 LMH_NODE_DCVS, node_id, 0);
208 if (ret) {
209 dev_err(dev, "Error setting thermal LOW threshold%d\n", ret);
210 return ret;
211 }
212
213 lmh_data->irq = platform_get_irq(pdev, 0);
214 lmh_data->domain = irq_domain_create_linear(dev_fwnode(dev), 1, &lmh_irq_ops, lmh_data);
215 if (!lmh_data->domain) {
216 dev_err(dev, "Error adding irq_domain\n");
217 return -EINVAL;
218 }
219
220 /* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
221 irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
222 ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
223 IRQF_NO_THREAD | IRQF_NO_SUSPEND,
224 "lmh-irq", lmh_data);
225 if (ret) {
226 irq_domain_remove(lmh_data->domain);
227 return ret;
228 }
229
230 return 0;
231 }
232
233 static const struct of_device_id lmh_table[] = {
234 { .compatible = "qcom,sc8180x-lmh", },
235 { .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
236 { .compatible = "qcom,sm8150-lmh", },
237 {}
238 };
239 MODULE_DEVICE_TABLE(of, lmh_table);
240
241 static struct platform_driver lmh_driver = {
242 .probe = lmh_probe,
243 .driver = {
244 .name = "qcom-lmh",
245 .of_match_table = lmh_table,
246 .suppress_bind_attrs = true,
247 },
248 };
249 module_platform_driver(lmh_driver);
250
251 MODULE_LICENSE("GPL v2");
252 MODULE_DESCRIPTION("QCOM LMh driver");
253