Merge tag 'pwm/for-5.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry...
[linux-2.6-microblaze.git] / drivers / thermal / thermal_hwmon.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  thermal_hwmon.c - Generic Thermal Management hwmon support.
4  *
5  *  Code based on Intel thermal_core.c. Copyrights of the original code:
6  *  Copyright (C) 2008 Intel Corp
7  *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
8  *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
9  *
10  *  Copyright (C) 2013 Texas Instruments
11  *  Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
12  */
13 #include <linux/hwmon.h>
14 #include <linux/thermal.h>
15 #include <linux/slab.h>
16 #include <linux/err.h>
17 #include "thermal_hwmon.h"
18
19 /* hwmon sys I/F */
20 /* thermal zone devices with the same type share one hwmon device */
21 struct thermal_hwmon_device {
22         char type[THERMAL_NAME_LENGTH];
23         struct device *device;
24         int count;
25         struct list_head tz_list;
26         struct list_head node;
27 };
28
29 struct thermal_hwmon_attr {
30         struct device_attribute attr;
31         char name[16];
32 };
33
34 /* one temperature input for each thermal zone */
35 struct thermal_hwmon_temp {
36         struct list_head hwmon_node;
37         struct thermal_zone_device *tz;
38         struct thermal_hwmon_attr temp_input;   /* hwmon sys attr */
39         struct thermal_hwmon_attr temp_crit;    /* hwmon sys attr */
40 };
41
42 static LIST_HEAD(thermal_hwmon_list);
43
44 static DEFINE_MUTEX(thermal_hwmon_list_lock);
45
46 static ssize_t
47 temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
48 {
49         int temperature;
50         int ret;
51         struct thermal_hwmon_attr *hwmon_attr
52                         = container_of(attr, struct thermal_hwmon_attr, attr);
53         struct thermal_hwmon_temp *temp
54                         = container_of(hwmon_attr, struct thermal_hwmon_temp,
55                                        temp_input);
56         struct thermal_zone_device *tz = temp->tz;
57
58         ret = thermal_zone_get_temp(tz, &temperature);
59
60         if (ret)
61                 return ret;
62
63         return sprintf(buf, "%d\n", temperature);
64 }
65
66 static ssize_t
67 temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
68 {
69         struct thermal_hwmon_attr *hwmon_attr
70                         = container_of(attr, struct thermal_hwmon_attr, attr);
71         struct thermal_hwmon_temp *temp
72                         = container_of(hwmon_attr, struct thermal_hwmon_temp,
73                                        temp_crit);
74         struct thermal_zone_device *tz = temp->tz;
75         int temperature;
76         int ret;
77
78         ret = tz->ops->get_crit_temp(tz, &temperature);
79         if (ret)
80                 return ret;
81
82         return sprintf(buf, "%d\n", temperature);
83 }
84
85
86 static struct thermal_hwmon_device *
87 thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
88 {
89         struct thermal_hwmon_device *hwmon;
90         char type[THERMAL_NAME_LENGTH];
91
92         mutex_lock(&thermal_hwmon_list_lock);
93         list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
94                 strcpy(type, tz->type);
95                 strreplace(type, '-', '_');
96                 if (!strcmp(hwmon->type, type)) {
97                         mutex_unlock(&thermal_hwmon_list_lock);
98                         return hwmon;
99                 }
100         }
101         mutex_unlock(&thermal_hwmon_list_lock);
102
103         return NULL;
104 }
105
106 /* Find the temperature input matching a given thermal zone */
107 static struct thermal_hwmon_temp *
108 thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
109                           const struct thermal_zone_device *tz)
110 {
111         struct thermal_hwmon_temp *temp;
112
113         mutex_lock(&thermal_hwmon_list_lock);
114         list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
115                 if (temp->tz == tz) {
116                         mutex_unlock(&thermal_hwmon_list_lock);
117                         return temp;
118                 }
119         mutex_unlock(&thermal_hwmon_list_lock);
120
121         return NULL;
122 }
123
124 static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
125 {
126         int temp;
127         return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
128 }
129
130 int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
131 {
132         struct thermal_hwmon_device *hwmon;
133         struct thermal_hwmon_temp *temp;
134         int new_hwmon_device = 1;
135         int result;
136
137         hwmon = thermal_hwmon_lookup_by_type(tz);
138         if (hwmon) {
139                 new_hwmon_device = 0;
140                 goto register_sys_interface;
141         }
142
143         hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
144         if (!hwmon)
145                 return -ENOMEM;
146
147         INIT_LIST_HEAD(&hwmon->tz_list);
148         strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
149         strreplace(hwmon->type, '-', '_');
150         hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
151                                                         hwmon, NULL, NULL);
152         if (IS_ERR(hwmon->device)) {
153                 result = PTR_ERR(hwmon->device);
154                 goto free_mem;
155         }
156
157  register_sys_interface:
158         temp = kzalloc(sizeof(*temp), GFP_KERNEL);
159         if (!temp) {
160                 result = -ENOMEM;
161                 goto unregister_name;
162         }
163
164         temp->tz = tz;
165         hwmon->count++;
166
167         snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
168                  "temp%d_input", hwmon->count);
169         temp->temp_input.attr.attr.name = temp->temp_input.name;
170         temp->temp_input.attr.attr.mode = 0444;
171         temp->temp_input.attr.show = temp_input_show;
172         sysfs_attr_init(&temp->temp_input.attr.attr);
173         result = device_create_file(hwmon->device, &temp->temp_input.attr);
174         if (result)
175                 goto free_temp_mem;
176
177         if (thermal_zone_crit_temp_valid(tz)) {
178                 snprintf(temp->temp_crit.name,
179                                 sizeof(temp->temp_crit.name),
180                                 "temp%d_crit", hwmon->count);
181                 temp->temp_crit.attr.attr.name = temp->temp_crit.name;
182                 temp->temp_crit.attr.attr.mode = 0444;
183                 temp->temp_crit.attr.show = temp_crit_show;
184                 sysfs_attr_init(&temp->temp_crit.attr.attr);
185                 result = device_create_file(hwmon->device,
186                                             &temp->temp_crit.attr);
187                 if (result)
188                         goto unregister_input;
189         }
190
191         mutex_lock(&thermal_hwmon_list_lock);
192         if (new_hwmon_device)
193                 list_add_tail(&hwmon->node, &thermal_hwmon_list);
194         list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
195         mutex_unlock(&thermal_hwmon_list_lock);
196
197         return 0;
198
199  unregister_input:
200         device_remove_file(hwmon->device, &temp->temp_input.attr);
201  free_temp_mem:
202         kfree(temp);
203  unregister_name:
204         if (new_hwmon_device)
205                 hwmon_device_unregister(hwmon->device);
206  free_mem:
207         if (new_hwmon_device)
208                 kfree(hwmon);
209
210         return result;
211 }
212 EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
213
214 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
215 {
216         struct thermal_hwmon_device *hwmon;
217         struct thermal_hwmon_temp *temp;
218
219         hwmon = thermal_hwmon_lookup_by_type(tz);
220         if (unlikely(!hwmon)) {
221                 /* Should never happen... */
222                 dev_dbg(&tz->device, "hwmon device lookup failed!\n");
223                 return;
224         }
225
226         temp = thermal_hwmon_lookup_temp(hwmon, tz);
227         if (unlikely(!temp)) {
228                 /* Should never happen... */
229                 dev_dbg(&tz->device, "temperature input lookup failed!\n");
230                 return;
231         }
232
233         device_remove_file(hwmon->device, &temp->temp_input.attr);
234         if (thermal_zone_crit_temp_valid(tz))
235                 device_remove_file(hwmon->device, &temp->temp_crit.attr);
236
237         mutex_lock(&thermal_hwmon_list_lock);
238         list_del(&temp->hwmon_node);
239         kfree(temp);
240         if (!list_empty(&hwmon->tz_list)) {
241                 mutex_unlock(&thermal_hwmon_list_lock);
242                 return;
243         }
244         list_del(&hwmon->node);
245         mutex_unlock(&thermal_hwmon_list_lock);
246
247         hwmon_device_unregister(hwmon->device);
248         kfree(hwmon);
249 }
250 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);