12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2eb982001SEduardo Valentin /*
3eb982001SEduardo Valentin * OMAP thermal driver interface
4eb982001SEduardo Valentin *
5eb982001SEduardo Valentin * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
6eb982001SEduardo Valentin * Contact:
7eb982001SEduardo Valentin * Eduardo Valentin <eduardo.valentin@ti.com>
8eb982001SEduardo Valentin */
9eb982001SEduardo Valentin
10eb982001SEduardo Valentin #include <linux/device.h>
11eb982001SEduardo Valentin #include <linux/err.h>
12eb982001SEduardo Valentin #include <linux/mutex.h>
13eb982001SEduardo Valentin #include <linux/gfp.h>
14eb982001SEduardo Valentin #include <linux/kernel.h>
15eb982001SEduardo Valentin #include <linux/workqueue.h>
16eb982001SEduardo Valentin #include <linux/thermal.h>
174d753aa7SViresh Kumar #include <linux/cpufreq.h>
18eb982001SEduardo Valentin #include <linux/cpumask.h>
19eb982001SEduardo Valentin #include <linux/cpu_cooling.h>
2026d9cc65SEduardo Valentin #include <linux/of.h>
21eb982001SEduardo Valentin
22eb982001SEduardo Valentin #include "ti-thermal.h"
23eb982001SEduardo Valentin #include "ti-bandgap.h"
243a9abd6cSRomain Naour #include "../thermal_hwmon.h"
25eb982001SEduardo Valentin
260c492be4SDaniel Lezcano #define TI_BANDGAP_UPDATE_INTERVAL_MS 250
270c492be4SDaniel Lezcano
28eb982001SEduardo Valentin /* common data structures */
29eb982001SEduardo Valentin struct ti_thermal_data {
304d753aa7SViresh Kumar struct cpufreq_policy *policy;
31eb982001SEduardo Valentin struct thermal_zone_device *ti_thermal;
32359836e1SEduardo Valentin struct thermal_zone_device *pcb_tz;
33eb982001SEduardo Valentin struct thermal_cooling_device *cool_dev;
34eb982001SEduardo Valentin struct ti_bandgap *bgp;
35eb982001SEduardo Valentin enum thermal_device_mode mode;
36eb982001SEduardo Valentin struct work_struct thermal_wq;
37eb982001SEduardo Valentin int sensor_id;
3826d9cc65SEduardo Valentin bool our_zone;
39eb982001SEduardo Valentin };
40eb982001SEduardo Valentin
ti_thermal_work(struct work_struct * work)41eb982001SEduardo Valentin static void ti_thermal_work(struct work_struct *work)
42eb982001SEduardo Valentin {
43eb982001SEduardo Valentin struct ti_thermal_data *data = container_of(work,
44eb982001SEduardo Valentin struct ti_thermal_data, thermal_wq);
45eb982001SEduardo Valentin
460e70f466SSrinivas Pandruvada thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED);
47eb982001SEduardo Valentin
48dec07d39SDaniel Lezcano dev_dbg(data->bgp->dev, "updated thermal zone %s\n",
49dbb0ea15SDaniel Lezcano thermal_zone_device_type(data->ti_thermal));
50eb982001SEduardo Valentin }
51eb982001SEduardo Valentin
52eb982001SEduardo Valentin /**
53eb982001SEduardo Valentin * ti_thermal_hotspot_temperature - returns sensor extrapolated temperature
54eb982001SEduardo Valentin * @t: omap sensor temperature
55eb982001SEduardo Valentin * @s: omap sensor slope value
56eb982001SEduardo Valentin * @c: omap sensor const value
57eb982001SEduardo Valentin */
ti_thermal_hotspot_temperature(int t,int s,int c)58eb982001SEduardo Valentin static inline int ti_thermal_hotspot_temperature(int t, int s, int c)
59eb982001SEduardo Valentin {
60eb982001SEduardo Valentin int delta = t * s / 1000 + c;
61eb982001SEduardo Valentin
62eb982001SEduardo Valentin if (delta < 0)
63eb982001SEduardo Valentin delta = 0;
64eb982001SEduardo Valentin
65eb982001SEduardo Valentin return t + delta;
66eb982001SEduardo Valentin }
67eb982001SEduardo Valentin
68eb982001SEduardo Valentin /* thermal zone ops */
69eb982001SEduardo Valentin /* Get temperature callback function for thermal zone */
__ti_thermal_get_temp(struct thermal_zone_device * tz,int * temp)702cf3c72aSDaniel Lezcano static inline int __ti_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
71eb982001SEduardo Valentin {
72359836e1SEduardo Valentin struct thermal_zone_device *pcb_tz = NULL;
735f68d078SDaniel Lezcano struct ti_thermal_data *data = thermal_zone_device_priv(tz);
74eb982001SEduardo Valentin struct ti_bandgap *bgp;
75eb982001SEduardo Valentin const struct ti_temp_sensor *s;
76359836e1SEduardo Valentin int ret, tmp, slope, constant;
7717e8351aSSascha Hauer int pcb_temp;
78eb982001SEduardo Valentin
79eb982001SEduardo Valentin if (!data)
80eb982001SEduardo Valentin return 0;
81eb982001SEduardo Valentin
82eb982001SEduardo Valentin bgp = data->bgp;
83eb982001SEduardo Valentin s = &bgp->conf->sensors[data->sensor_id];
84eb982001SEduardo Valentin
85eb982001SEduardo Valentin ret = ti_bandgap_read_temperature(bgp, data->sensor_id, &tmp);
86eb982001SEduardo Valentin if (ret)
87eb982001SEduardo Valentin return ret;
88eb982001SEduardo Valentin
89359836e1SEduardo Valentin /* Default constants */
902cf3c72aSDaniel Lezcano slope = thermal_zone_get_slope(tz);
912cf3c72aSDaniel Lezcano constant = thermal_zone_get_offset(tz);
92359836e1SEduardo Valentin
93359836e1SEduardo Valentin pcb_tz = data->pcb_tz;
94eb982001SEduardo Valentin /* In case pcb zone is available, use the extrapolation rule with it */
950c12b5acSEduardo Valentin if (!IS_ERR(pcb_tz)) {
96359836e1SEduardo Valentin ret = thermal_zone_get_temp(pcb_tz, &pcb_temp);
97359836e1SEduardo Valentin if (!ret) {
98359836e1SEduardo Valentin tmp -= pcb_temp; /* got a valid PCB temp */
99eb982001SEduardo Valentin slope = s->slope_pcb;
100eb982001SEduardo Valentin constant = s->constant_pcb;
101eb982001SEduardo Valentin } else {
102359836e1SEduardo Valentin dev_err(bgp->dev,
103359836e1SEduardo Valentin "Failed to read PCB state. Using defaults\n");
104df8f1347SEduardo Valentin ret = 0;
105359836e1SEduardo Valentin }
106eb982001SEduardo Valentin }
107eb982001SEduardo Valentin *temp = ti_thermal_hotspot_temperature(tmp, slope, constant);
108eb982001SEduardo Valentin
109eb982001SEduardo Valentin return ret;
110eb982001SEduardo Valentin }
111eb982001SEduardo Valentin
__ti_thermal_get_trend(struct thermal_zone_device * tz,const struct thermal_trip * trip,enum thermal_trend * trend)1128289d810SRafael J. Wysocki static int __ti_thermal_get_trend(struct thermal_zone_device *tz,
113*ebc7abb3SRafael J. Wysocki const struct thermal_trip *trip,
114*ebc7abb3SRafael J. Wysocki enum thermal_trend *trend)
115eb982001SEduardo Valentin {
1165f68d078SDaniel Lezcano struct ti_thermal_data *data = thermal_zone_device_priv(tz);
117eb982001SEduardo Valentin struct ti_bandgap *bgp;
118eb982001SEduardo Valentin int id, tr, ret = 0;
119eb982001SEduardo Valentin
120eb982001SEduardo Valentin bgp = data->bgp;
121eb982001SEduardo Valentin id = data->sensor_id;
122eb982001SEduardo Valentin
123eb982001SEduardo Valentin ret = ti_bandgap_get_trend(bgp, id, &tr);
124eb982001SEduardo Valentin if (ret)
125eb982001SEduardo Valentin return ret;
126eb982001SEduardo Valentin
127eb982001SEduardo Valentin if (tr > 0)
128eb982001SEduardo Valentin *trend = THERMAL_TREND_RAISING;
129eb982001SEduardo Valentin else if (tr < 0)
130eb982001SEduardo Valentin *trend = THERMAL_TREND_DROPPING;
131eb982001SEduardo Valentin else
132eb982001SEduardo Valentin *trend = THERMAL_TREND_STABLE;
133eb982001SEduardo Valentin
134eb982001SEduardo Valentin return 0;
135eb982001SEduardo Valentin }
136eb982001SEduardo Valentin
1372cf3c72aSDaniel Lezcano static const struct thermal_zone_device_ops ti_of_thermal_ops = {
1382251aef6SEduardo Valentin .get_temp = __ti_thermal_get_temp,
1392251aef6SEduardo Valentin .get_trend = __ti_thermal_get_trend,
1402251aef6SEduardo Valentin };
1412251aef6SEduardo Valentin
142eb982001SEduardo Valentin static struct ti_thermal_data
ti_thermal_build_data(struct ti_bandgap * bgp,int id)143eb982001SEduardo Valentin *ti_thermal_build_data(struct ti_bandgap *bgp, int id)
144eb982001SEduardo Valentin {
145eb982001SEduardo Valentin struct ti_thermal_data *data;
146eb982001SEduardo Valentin
147eb982001SEduardo Valentin data = devm_kzalloc(bgp->dev, sizeof(*data), GFP_KERNEL);
148eb982001SEduardo Valentin if (!data) {
149eb982001SEduardo Valentin dev_err(bgp->dev, "kzalloc fail\n");
150eb982001SEduardo Valentin return NULL;
151eb982001SEduardo Valentin }
152eb982001SEduardo Valentin data->sensor_id = id;
153eb982001SEduardo Valentin data->bgp = bgp;
154eb982001SEduardo Valentin data->mode = THERMAL_DEVICE_ENABLED;
1550c12b5acSEduardo Valentin /* pcb_tz will be either valid or PTR_ERR() */
156359836e1SEduardo Valentin data->pcb_tz = thermal_zone_get_zone_by_name("pcb");
157eb982001SEduardo Valentin INIT_WORK(&data->thermal_wq, ti_thermal_work);
158eb982001SEduardo Valentin
159eb982001SEduardo Valentin return data;
160eb982001SEduardo Valentin }
161eb982001SEduardo Valentin
ti_thermal_expose_sensor(struct ti_bandgap * bgp,int id,char * domain)162eb982001SEduardo Valentin int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
163eb982001SEduardo Valentin char *domain)
164eb982001SEduardo Valentin {
165eb982001SEduardo Valentin struct ti_thermal_data *data;
166eb982001SEduardo Valentin
167eb982001SEduardo Valentin data = ti_bandgap_get_sensor_data(bgp, id);
168eb982001SEduardo Valentin
1690f348db0SDan Carpenter if (IS_ERR_OR_NULL(data))
170eb982001SEduardo Valentin data = ti_thermal_build_data(bgp, id);
171eb982001SEduardo Valentin
172eb982001SEduardo Valentin if (!data)
173eb982001SEduardo Valentin return -EINVAL;
174eb982001SEduardo Valentin
17526d9cc65SEduardo Valentin /* in case this is specified by DT */
1762cf3c72aSDaniel Lezcano data->ti_thermal = devm_thermal_of_zone_register(bgp->dev, id,
1772251aef6SEduardo Valentin data, &ti_of_thermal_ops);
17826d9cc65SEduardo Valentin if (IS_ERR(data->ti_thermal)) {
179eb982001SEduardo Valentin dev_err(bgp->dev, "thermal zone device is NULL\n");
180eb982001SEduardo Valentin return PTR_ERR(data->ti_thermal);
181eb982001SEduardo Valentin }
182b263b473SKeerthy
183eb982001SEduardo Valentin ti_bandgap_set_sensor_data(bgp, id, data);
1840c492be4SDaniel Lezcano ti_bandgap_write_update_interval(bgp, data->sensor_id,
1850c492be4SDaniel Lezcano TI_BANDGAP_UPDATE_INTERVAL_MS);
186eb982001SEduardo Valentin
187a4ebd423SYangtao Li devm_thermal_add_hwmon_sysfs(bgp->dev, data->ti_thermal);
1883a9abd6cSRomain Naour
189eb982001SEduardo Valentin return 0;
190eb982001SEduardo Valentin }
191eb982001SEduardo Valentin
ti_thermal_remove_sensor(struct ti_bandgap * bgp,int id)192eb982001SEduardo Valentin int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
193eb982001SEduardo Valentin {
194eb982001SEduardo Valentin struct ti_thermal_data *data;
195eb982001SEduardo Valentin
196eb982001SEduardo Valentin data = ti_bandgap_get_sensor_data(bgp, id);
197eb982001SEduardo Valentin
1987440f518SSudip Mukherjee if (!IS_ERR_OR_NULL(data) && data->ti_thermal) {
19926d9cc65SEduardo Valentin if (data->our_zone)
200eb982001SEduardo Valentin thermal_zone_device_unregister(data->ti_thermal);
20126d9cc65SEduardo Valentin }
202eb982001SEduardo Valentin
203eb982001SEduardo Valentin return 0;
204eb982001SEduardo Valentin }
205eb982001SEduardo Valentin
ti_thermal_report_sensor_temperature(struct ti_bandgap * bgp,int id)206eb982001SEduardo Valentin int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id)
207eb982001SEduardo Valentin {
208eb982001SEduardo Valentin struct ti_thermal_data *data;
209eb982001SEduardo Valentin
210eb982001SEduardo Valentin data = ti_bandgap_get_sensor_data(bgp, id);
211eb982001SEduardo Valentin
212eb982001SEduardo Valentin schedule_work(&data->thermal_wq);
213eb982001SEduardo Valentin
214eb982001SEduardo Valentin return 0;
215eb982001SEduardo Valentin }
216eb982001SEduardo Valentin
ti_thermal_register_cpu_cooling(struct ti_bandgap * bgp,int id)217eb982001SEduardo Valentin int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id)
218eb982001SEduardo Valentin {
219eb982001SEduardo Valentin struct ti_thermal_data *data;
22026d9cc65SEduardo Valentin struct device_node *np = bgp->dev->of_node;
22126d9cc65SEduardo Valentin
22226d9cc65SEduardo Valentin /*
22326d9cc65SEduardo Valentin * We are assuming here that if one deploys the zone
22426d9cc65SEduardo Valentin * using DT, then it must be aware that the cooling device
22526d9cc65SEduardo Valentin * loading has to happen via cpufreq driver.
22626d9cc65SEduardo Valentin */
22786df7d19SRob Herring if (of_property_present(np, "#thermal-sensor-cells"))
22826d9cc65SEduardo Valentin return 0;
229eb982001SEduardo Valentin
230eb982001SEduardo Valentin data = ti_bandgap_get_sensor_data(bgp, id);
2310c12b5acSEduardo Valentin if (!data || IS_ERR(data))
232eb982001SEduardo Valentin data = ti_thermal_build_data(bgp, id);
233eb982001SEduardo Valentin
234eb982001SEduardo Valentin if (!data)
235eb982001SEduardo Valentin return -EINVAL;
236eb982001SEduardo Valentin
2374d753aa7SViresh Kumar data->policy = cpufreq_cpu_get(0);
2384d753aa7SViresh Kumar if (!data->policy) {
2394d753aa7SViresh Kumar pr_debug("%s: CPUFreq policy not found\n", __func__);
2404d753aa7SViresh Kumar return -EPROBE_DEFER;
2414d753aa7SViresh Kumar }
2424d753aa7SViresh Kumar
243eb982001SEduardo Valentin /* Register cooling device */
2444d753aa7SViresh Kumar data->cool_dev = cpufreq_cooling_register(data->policy);
2450c12b5acSEduardo Valentin if (IS_ERR(data->cool_dev)) {
246cffafc32SEduardo Valentin int ret = PTR_ERR(data->cool_dev);
2474d753aa7SViresh Kumar dev_err(bgp->dev, "Failed to register cpu cooling device %d\n",
248cffafc32SEduardo Valentin ret);
2494d753aa7SViresh Kumar cpufreq_cpu_put(data->policy);
250cffafc32SEduardo Valentin
251cffafc32SEduardo Valentin return ret;
252eb982001SEduardo Valentin }
253eb982001SEduardo Valentin ti_bandgap_set_sensor_data(bgp, id, data);
254eb982001SEduardo Valentin
255eb982001SEduardo Valentin return 0;
256eb982001SEduardo Valentin }
257eb982001SEduardo Valentin
ti_thermal_unregister_cpu_cooling(struct ti_bandgap * bgp,int id)258eb982001SEduardo Valentin int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
259eb982001SEduardo Valentin {
260eb982001SEduardo Valentin struct ti_thermal_data *data;
261eb982001SEduardo Valentin
262eb982001SEduardo Valentin data = ti_bandgap_get_sensor_data(bgp, id);
26326d9cc65SEduardo Valentin
2647440f518SSudip Mukherjee if (!IS_ERR_OR_NULL(data)) {
265eb982001SEduardo Valentin cpufreq_cooling_unregister(data->cool_dev);
266ba817a8cSTony Lindgren if (data->policy)
2674d753aa7SViresh Kumar cpufreq_cpu_put(data->policy);
2684d753aa7SViresh Kumar }
269eb982001SEduardo Valentin
270eb982001SEduardo Valentin return 0;
271eb982001SEduardo Valentin }
272