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