1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP thermal driver interface |
4 | * |
5 | * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ |
6 | * Contact: |
7 | * Eduardo Valentin <eduardo.valentin@ti.com> |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/err.h> |
12 | #include <linux/mutex.h> |
13 | #include <linux/gfp.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/workqueue.h> |
16 | #include <linux/thermal.h> |
17 | #include <linux/cpufreq.h> |
18 | #include <linux/cpumask.h> |
19 | #include <linux/cpu_cooling.h> |
20 | #include <linux/of.h> |
21 | |
22 | #include "ti-thermal.h" |
23 | #include "ti-bandgap.h" |
24 | #include "../thermal_hwmon.h" |
25 | |
26 | #define TI_BANDGAP_UPDATE_INTERVAL_MS 250 |
27 | |
28 | /* common data structures */ |
29 | struct ti_thermal_data { |
30 | struct cpufreq_policy *policy; |
31 | struct thermal_zone_device *ti_thermal; |
32 | struct thermal_zone_device *pcb_tz; |
33 | struct thermal_cooling_device *cool_dev; |
34 | struct ti_bandgap *bgp; |
35 | enum thermal_device_mode mode; |
36 | struct work_struct thermal_wq; |
37 | int sensor_id; |
38 | bool our_zone; |
39 | }; |
40 | |
41 | static void ti_thermal_work(struct work_struct *work) |
42 | { |
43 | struct ti_thermal_data *data = container_of(work, |
44 | struct ti_thermal_data, thermal_wq); |
45 | |
46 | thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED); |
47 | |
48 | dev_dbg(data->bgp->dev, "updated thermal zone %s\n" , |
49 | thermal_zone_device_type(data->ti_thermal)); |
50 | } |
51 | |
52 | /** |
53 | * ti_thermal_hotspot_temperature - returns sensor extrapolated temperature |
54 | * @t: omap sensor temperature |
55 | * @s: omap sensor slope value |
56 | * @c: omap sensor const value |
57 | */ |
58 | static inline int ti_thermal_hotspot_temperature(int t, int s, int c) |
59 | { |
60 | int delta = t * s / 1000 + c; |
61 | |
62 | if (delta < 0) |
63 | delta = 0; |
64 | |
65 | return t + delta; |
66 | } |
67 | |
68 | /* thermal zone ops */ |
69 | /* Get temperature callback function for thermal zone */ |
70 | static inline int __ti_thermal_get_temp(struct thermal_zone_device *tz, int *temp) |
71 | { |
72 | struct thermal_zone_device *pcb_tz = NULL; |
73 | struct ti_thermal_data *data = thermal_zone_device_priv(tzd: tz); |
74 | struct ti_bandgap *bgp; |
75 | const struct ti_temp_sensor *s; |
76 | int ret, tmp, slope, constant; |
77 | int pcb_temp; |
78 | |
79 | if (!data) |
80 | return 0; |
81 | |
82 | bgp = data->bgp; |
83 | s = &bgp->conf->sensors[data->sensor_id]; |
84 | |
85 | ret = ti_bandgap_read_temperature(bgp, id: data->sensor_id, temperature: &tmp); |
86 | if (ret) |
87 | return ret; |
88 | |
89 | /* Default constants */ |
90 | slope = thermal_zone_get_slope(tz); |
91 | constant = thermal_zone_get_offset(tz); |
92 | |
93 | pcb_tz = data->pcb_tz; |
94 | /* In case pcb zone is available, use the extrapolation rule with it */ |
95 | if (!IS_ERR(ptr: pcb_tz)) { |
96 | ret = thermal_zone_get_temp(tz: pcb_tz, temp: &pcb_temp); |
97 | if (!ret) { |
98 | tmp -= pcb_temp; /* got a valid PCB temp */ |
99 | slope = s->slope_pcb; |
100 | constant = s->constant_pcb; |
101 | } else { |
102 | dev_err(bgp->dev, |
103 | "Failed to read PCB state. Using defaults\n" ); |
104 | ret = 0; |
105 | } |
106 | } |
107 | *temp = ti_thermal_hotspot_temperature(t: tmp, s: slope, c: constant); |
108 | |
109 | return ret; |
110 | } |
111 | |
112 | static int __ti_thermal_get_trend(struct thermal_zone_device *tz, |
113 | const struct thermal_trip *trip, |
114 | enum thermal_trend *trend) |
115 | { |
116 | struct ti_thermal_data *data = thermal_zone_device_priv(tzd: tz); |
117 | struct ti_bandgap *bgp; |
118 | int id, tr, ret = 0; |
119 | |
120 | bgp = data->bgp; |
121 | id = data->sensor_id; |
122 | |
123 | ret = ti_bandgap_get_trend(bgp, id, trend: &tr); |
124 | if (ret) |
125 | return ret; |
126 | |
127 | if (tr > 0) |
128 | *trend = THERMAL_TREND_RAISING; |
129 | else if (tr < 0) |
130 | *trend = THERMAL_TREND_DROPPING; |
131 | else |
132 | *trend = THERMAL_TREND_STABLE; |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | static const struct thermal_zone_device_ops ti_of_thermal_ops = { |
138 | .get_temp = __ti_thermal_get_temp, |
139 | .get_trend = __ti_thermal_get_trend, |
140 | }; |
141 | |
142 | static struct ti_thermal_data |
143 | *ti_thermal_build_data(struct ti_bandgap *bgp, int id) |
144 | { |
145 | struct ti_thermal_data *data; |
146 | |
147 | data = devm_kzalloc(dev: bgp->dev, size: sizeof(*data), GFP_KERNEL); |
148 | if (!data) { |
149 | dev_err(bgp->dev, "kzalloc fail\n" ); |
150 | return NULL; |
151 | } |
152 | data->sensor_id = id; |
153 | data->bgp = bgp; |
154 | data->mode = THERMAL_DEVICE_ENABLED; |
155 | /* pcb_tz will be either valid or PTR_ERR() */ |
156 | data->pcb_tz = thermal_zone_get_zone_by_name(name: "pcb" ); |
157 | INIT_WORK(&data->thermal_wq, ti_thermal_work); |
158 | |
159 | return data; |
160 | } |
161 | |
162 | int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, |
163 | char *domain) |
164 | { |
165 | struct ti_thermal_data *data; |
166 | |
167 | data = ti_bandgap_get_sensor_data(bgp, id); |
168 | |
169 | if (IS_ERR_OR_NULL(ptr: data)) |
170 | data = ti_thermal_build_data(bgp, id); |
171 | |
172 | if (!data) |
173 | return -EINVAL; |
174 | |
175 | /* in case this is specified by DT */ |
176 | data->ti_thermal = devm_thermal_of_zone_register(dev: bgp->dev, id, |
177 | data, ops: &ti_of_thermal_ops); |
178 | if (IS_ERR(ptr: data->ti_thermal)) { |
179 | dev_err(bgp->dev, "thermal zone device is NULL\n" ); |
180 | return PTR_ERR(ptr: data->ti_thermal); |
181 | } |
182 | |
183 | ti_bandgap_set_sensor_data(bgp, id, data); |
184 | ti_bandgap_write_update_interval(bgp, id: data->sensor_id, |
185 | TI_BANDGAP_UPDATE_INTERVAL_MS); |
186 | |
187 | devm_thermal_add_hwmon_sysfs(dev: bgp->dev, tz: data->ti_thermal); |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) |
193 | { |
194 | struct ti_thermal_data *data; |
195 | |
196 | data = ti_bandgap_get_sensor_data(bgp, id); |
197 | |
198 | if (!IS_ERR_OR_NULL(ptr: data) && data->ti_thermal) { |
199 | if (data->our_zone) |
200 | thermal_zone_device_unregister(tz: data->ti_thermal); |
201 | } |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id) |
207 | { |
208 | struct ti_thermal_data *data; |
209 | |
210 | data = ti_bandgap_get_sensor_data(bgp, id); |
211 | |
212 | schedule_work(work: &data->thermal_wq); |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id) |
218 | { |
219 | struct ti_thermal_data *data; |
220 | struct device_node *np = bgp->dev->of_node; |
221 | |
222 | /* |
223 | * We are assuming here that if one deploys the zone |
224 | * using DT, then it must be aware that the cooling device |
225 | * loading has to happen via cpufreq driver. |
226 | */ |
227 | if (of_property_present(np, propname: "#thermal-sensor-cells" )) |
228 | return 0; |
229 | |
230 | data = ti_bandgap_get_sensor_data(bgp, id); |
231 | if (!data || IS_ERR(ptr: data)) |
232 | data = ti_thermal_build_data(bgp, id); |
233 | |
234 | if (!data) |
235 | return -EINVAL; |
236 | |
237 | data->policy = cpufreq_cpu_get(cpu: 0); |
238 | if (!data->policy) { |
239 | pr_debug("%s: CPUFreq policy not found\n" , __func__); |
240 | return -EPROBE_DEFER; |
241 | } |
242 | |
243 | /* Register cooling device */ |
244 | data->cool_dev = cpufreq_cooling_register(policy: data->policy); |
245 | if (IS_ERR(ptr: data->cool_dev)) { |
246 | int ret = PTR_ERR(ptr: data->cool_dev); |
247 | dev_err(bgp->dev, "Failed to register cpu cooling device %d\n" , |
248 | ret); |
249 | cpufreq_cpu_put(policy: data->policy); |
250 | |
251 | return ret; |
252 | } |
253 | ti_bandgap_set_sensor_data(bgp, id, data); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id) |
259 | { |
260 | struct ti_thermal_data *data; |
261 | |
262 | data = ti_bandgap_get_sensor_data(bgp, id); |
263 | |
264 | if (!IS_ERR_OR_NULL(ptr: data)) { |
265 | cpufreq_cooling_unregister(cdev: data->cool_dev); |
266 | if (data->policy) |
267 | cpufreq_cpu_put(policy: data->policy); |
268 | } |
269 | |
270 | return 0; |
271 | } |
272 | |