1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Intel Broxton PMIC thermal driver |
4 | * |
5 | * Copyright (C) 2016 Intel Corporation. All rights reserved. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/device.h> |
14 | #include <linux/thermal.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/sched.h> |
17 | #include <linux/mfd/intel_soc_pmic.h> |
18 | |
19 | #define BXTWC_THRM0IRQ 0x4E04 |
20 | #define BXTWC_THRM1IRQ 0x4E05 |
21 | #define BXTWC_THRM2IRQ 0x4E06 |
22 | #define BXTWC_MTHRM0IRQ 0x4E12 |
23 | #define BXTWC_MTHRM1IRQ 0x4E13 |
24 | #define BXTWC_MTHRM2IRQ 0x4E14 |
25 | #define BXTWC_STHRM0IRQ 0x4F19 |
26 | #define BXTWC_STHRM1IRQ 0x4F1A |
27 | #define BXTWC_STHRM2IRQ 0x4F1B |
28 | |
29 | struct trip_config_map { |
30 | u16 irq_reg; |
31 | u16 irq_en; |
32 | u16 evt_stat; |
33 | u8 irq_mask; |
34 | u8 irq_en_mask; |
35 | u8 evt_mask; |
36 | u8 trip_num; |
37 | }; |
38 | |
39 | struct thermal_irq_map { |
40 | char handle[20]; |
41 | int num_trips; |
42 | const struct trip_config_map *trip_config; |
43 | }; |
44 | |
45 | struct pmic_thermal_data { |
46 | const struct thermal_irq_map *maps; |
47 | int num_maps; |
48 | }; |
49 | |
50 | static const struct trip_config_map bxtwc_str0_trip_config[] = { |
51 | { |
52 | .irq_reg = BXTWC_THRM0IRQ, |
53 | .irq_mask = 0x01, |
54 | .irq_en = BXTWC_MTHRM0IRQ, |
55 | .irq_en_mask = 0x01, |
56 | .evt_stat = BXTWC_STHRM0IRQ, |
57 | .evt_mask = 0x01, |
58 | .trip_num = 0 |
59 | }, |
60 | { |
61 | .irq_reg = BXTWC_THRM0IRQ, |
62 | .irq_mask = 0x10, |
63 | .irq_en = BXTWC_MTHRM0IRQ, |
64 | .irq_en_mask = 0x10, |
65 | .evt_stat = BXTWC_STHRM0IRQ, |
66 | .evt_mask = 0x10, |
67 | .trip_num = 1 |
68 | } |
69 | }; |
70 | |
71 | static const struct trip_config_map bxtwc_str1_trip_config[] = { |
72 | { |
73 | .irq_reg = BXTWC_THRM0IRQ, |
74 | .irq_mask = 0x02, |
75 | .irq_en = BXTWC_MTHRM0IRQ, |
76 | .irq_en_mask = 0x02, |
77 | .evt_stat = BXTWC_STHRM0IRQ, |
78 | .evt_mask = 0x02, |
79 | .trip_num = 0 |
80 | }, |
81 | { |
82 | .irq_reg = BXTWC_THRM0IRQ, |
83 | .irq_mask = 0x20, |
84 | .irq_en = BXTWC_MTHRM0IRQ, |
85 | .irq_en_mask = 0x20, |
86 | .evt_stat = BXTWC_STHRM0IRQ, |
87 | .evt_mask = 0x20, |
88 | .trip_num = 1 |
89 | }, |
90 | }; |
91 | |
92 | static const struct trip_config_map bxtwc_str2_trip_config[] = { |
93 | { |
94 | .irq_reg = BXTWC_THRM0IRQ, |
95 | .irq_mask = 0x04, |
96 | .irq_en = BXTWC_MTHRM0IRQ, |
97 | .irq_en_mask = 0x04, |
98 | .evt_stat = BXTWC_STHRM0IRQ, |
99 | .evt_mask = 0x04, |
100 | .trip_num = 0 |
101 | }, |
102 | { |
103 | .irq_reg = BXTWC_THRM0IRQ, |
104 | .irq_mask = 0x40, |
105 | .irq_en = BXTWC_MTHRM0IRQ, |
106 | .irq_en_mask = 0x40, |
107 | .evt_stat = BXTWC_STHRM0IRQ, |
108 | .evt_mask = 0x40, |
109 | .trip_num = 1 |
110 | }, |
111 | }; |
112 | |
113 | static const struct trip_config_map bxtwc_str3_trip_config[] = { |
114 | { |
115 | .irq_reg = BXTWC_THRM2IRQ, |
116 | .irq_mask = 0x10, |
117 | .irq_en = BXTWC_MTHRM2IRQ, |
118 | .irq_en_mask = 0x10, |
119 | .evt_stat = BXTWC_STHRM2IRQ, |
120 | .evt_mask = 0x10, |
121 | .trip_num = 0 |
122 | }, |
123 | }; |
124 | |
125 | static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { |
126 | { |
127 | .handle = "STR0" , |
128 | .trip_config = bxtwc_str0_trip_config, |
129 | .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), |
130 | }, |
131 | { |
132 | .handle = "STR1" , |
133 | .trip_config = bxtwc_str1_trip_config, |
134 | .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), |
135 | }, |
136 | { |
137 | .handle = "STR2" , |
138 | .trip_config = bxtwc_str2_trip_config, |
139 | .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), |
140 | }, |
141 | { |
142 | .handle = "STR3" , |
143 | .trip_config = bxtwc_str3_trip_config, |
144 | .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), |
145 | }, |
146 | }; |
147 | |
148 | static const struct pmic_thermal_data bxtwc_thermal_data = { |
149 | .maps = bxtwc_thermal_irq_map, |
150 | .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), |
151 | }; |
152 | |
153 | static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) |
154 | { |
155 | struct platform_device *pdev = data; |
156 | struct thermal_zone_device *tzd; |
157 | struct pmic_thermal_data *td; |
158 | struct intel_soc_pmic *pmic; |
159 | struct regmap *regmap; |
160 | u8 reg_val, mask, irq_stat; |
161 | u16 reg, evt_stat_reg; |
162 | int i, j, ret; |
163 | |
164 | pmic = dev_get_drvdata(dev: pdev->dev.parent); |
165 | regmap = pmic->regmap; |
166 | td = (struct pmic_thermal_data *) |
167 | platform_get_device_id(pdev)->driver_data; |
168 | |
169 | /* Resolve thermal irqs */ |
170 | for (i = 0; i < td->num_maps; i++) { |
171 | for (j = 0; j < td->maps[i].num_trips; j++) { |
172 | reg = td->maps[i].trip_config[j].irq_reg; |
173 | mask = td->maps[i].trip_config[j].irq_mask; |
174 | /* |
175 | * Read the irq register to resolve whether the |
176 | * interrupt was triggered for this sensor |
177 | */ |
178 | if (regmap_read(map: regmap, reg, val: &ret)) |
179 | return IRQ_HANDLED; |
180 | |
181 | reg_val = (u8)ret; |
182 | irq_stat = ((u8)ret & mask); |
183 | |
184 | if (!irq_stat) |
185 | continue; |
186 | |
187 | /* |
188 | * Read the status register to find out what |
189 | * event occurred i.e a high or a low |
190 | */ |
191 | evt_stat_reg = td->maps[i].trip_config[j].evt_stat; |
192 | if (regmap_read(map: regmap, reg: evt_stat_reg, val: &ret)) |
193 | return IRQ_HANDLED; |
194 | |
195 | tzd = thermal_zone_get_zone_by_name(name: td->maps[i].handle); |
196 | if (!IS_ERR(ptr: tzd)) |
197 | thermal_zone_device_update(tzd, |
198 | THERMAL_EVENT_UNSPECIFIED); |
199 | |
200 | /* Clear the appropriate irq */ |
201 | regmap_write(map: regmap, reg, val: reg_val & mask); |
202 | } |
203 | } |
204 | |
205 | return IRQ_HANDLED; |
206 | } |
207 | |
208 | static int pmic_thermal_probe(struct platform_device *pdev) |
209 | { |
210 | struct regmap_irq_chip_data *regmap_irq_chip; |
211 | struct pmic_thermal_data *thermal_data; |
212 | int ret, irq, virq, i, j, pmic_irq_count; |
213 | struct intel_soc_pmic *pmic; |
214 | struct regmap *regmap; |
215 | struct device *dev; |
216 | u16 reg; |
217 | u8 mask; |
218 | |
219 | dev = &pdev->dev; |
220 | pmic = dev_get_drvdata(dev: pdev->dev.parent); |
221 | if (!pmic) { |
222 | dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n" ); |
223 | return -ENODEV; |
224 | } |
225 | |
226 | thermal_data = (struct pmic_thermal_data *) |
227 | platform_get_device_id(pdev)->driver_data; |
228 | if (!thermal_data) { |
229 | dev_err(dev, "No thermal data initialized!!\n" ); |
230 | return -ENODEV; |
231 | } |
232 | |
233 | regmap = pmic->regmap; |
234 | regmap_irq_chip = pmic->irq_chip_data; |
235 | |
236 | pmic_irq_count = 0; |
237 | while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { |
238 | virq = regmap_irq_get_virq(data: regmap_irq_chip, irq); |
239 | if (virq < 0) { |
240 | dev_err(dev, "failed to get virq by irq %d\n" , irq); |
241 | return virq; |
242 | } |
243 | |
244 | ret = devm_request_threaded_irq(dev: &pdev->dev, irq: virq, |
245 | NULL, thread_fn: pmic_thermal_irq_handler, |
246 | IRQF_ONESHOT, devname: "pmic_thermal" , dev_id: pdev); |
247 | |
248 | if (ret) { |
249 | dev_err(dev, "request irq(%d) failed: %d\n" , virq, ret); |
250 | return ret; |
251 | } |
252 | pmic_irq_count++; |
253 | } |
254 | |
255 | /* Enable thermal interrupts */ |
256 | for (i = 0; i < thermal_data->num_maps; i++) { |
257 | for (j = 0; j < thermal_data->maps[i].num_trips; j++) { |
258 | reg = thermal_data->maps[i].trip_config[j].irq_en; |
259 | mask = thermal_data->maps[i].trip_config[j].irq_en_mask; |
260 | ret = regmap_update_bits(map: regmap, reg, mask, val: 0x00); |
261 | if (ret) |
262 | return ret; |
263 | } |
264 | } |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static const struct platform_device_id pmic_thermal_id_table[] = { |
270 | { |
271 | .name = "bxt_wcove_thermal" , |
272 | .driver_data = (kernel_ulong_t)&bxtwc_thermal_data, |
273 | }, |
274 | {}, |
275 | }; |
276 | |
277 | static struct platform_driver pmic_thermal_driver = { |
278 | .probe = pmic_thermal_probe, |
279 | .driver = { |
280 | .name = "pmic_thermal" , |
281 | }, |
282 | .id_table = pmic_thermal_id_table, |
283 | }; |
284 | |
285 | MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); |
286 | module_platform_driver(pmic_thermal_driver); |
287 | |
288 | MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>" ); |
289 | MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver" ); |
290 | MODULE_LICENSE("GPL v2" ); |
291 | |