Merge branch 'parisc-4.19-2' of git://git.kernel.org/pub/scm/linux/kernel/git/deller...
[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
91         mutex_lock(&thermal_hwmon_list_lock);
92         list_for_each_entry(hwmon, &thermal_hwmon_list, node)
93                 if (!strcmp(hwmon->type, tz->type)) {
94                         mutex_unlock(&thermal_hwmon_list_lock);
95                         return hwmon;
96                 }
97         mutex_unlock(&thermal_hwmon_list_lock);
98
99         return NULL;
100 }
101
102 /* Find the temperature input matching a given thermal zone */
103 static struct thermal_hwmon_temp *
104 thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
105                           const struct thermal_zone_device *tz)
106 {
107         struct thermal_hwmon_temp *temp;
108
109         mutex_lock(&thermal_hwmon_list_lock);
110         list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
111                 if (temp->tz == tz) {
112                         mutex_unlock(&thermal_hwmon_list_lock);
113                         return temp;
114                 }
115         mutex_unlock(&thermal_hwmon_list_lock);
116
117         return NULL;
118 }
119
120 static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
121 {
122         int temp;
123         return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
124 }
125
126 int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
127 {
128         struct thermal_hwmon_device *hwmon;
129         struct thermal_hwmon_temp *temp;
130         int new_hwmon_device = 1;
131         int result;
132
133         hwmon = thermal_hwmon_lookup_by_type(tz);
134         if (hwmon) {
135                 new_hwmon_device = 0;
136                 goto register_sys_interface;
137         }
138
139         hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
140         if (!hwmon)
141                 return -ENOMEM;
142
143         INIT_LIST_HEAD(&hwmon->tz_list);
144         strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
145         strreplace(hwmon->type, '-', '_');
146         hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
147                                                         hwmon, NULL, NULL);
148         if (IS_ERR(hwmon->device)) {
149                 result = PTR_ERR(hwmon->device);
150                 goto free_mem;
151         }
152
153  register_sys_interface:
154         temp = kzalloc(sizeof(*temp), GFP_KERNEL);
155         if (!temp) {
156                 result = -ENOMEM;
157                 goto unregister_name;
158         }
159
160         temp->tz = tz;
161         hwmon->count++;
162
163         snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
164                  "temp%d_input", hwmon->count);
165         temp->temp_input.attr.attr.name = temp->temp_input.name;
166         temp->temp_input.attr.attr.mode = 0444;
167         temp->temp_input.attr.show = temp_input_show;
168         sysfs_attr_init(&temp->temp_input.attr.attr);
169         result = device_create_file(hwmon->device, &temp->temp_input.attr);
170         if (result)
171                 goto free_temp_mem;
172
173         if (thermal_zone_crit_temp_valid(tz)) {
174                 snprintf(temp->temp_crit.name,
175                                 sizeof(temp->temp_crit.name),
176                                 "temp%d_crit", hwmon->count);
177                 temp->temp_crit.attr.attr.name = temp->temp_crit.name;
178                 temp->temp_crit.attr.attr.mode = 0444;
179                 temp->temp_crit.attr.show = temp_crit_show;
180                 sysfs_attr_init(&temp->temp_crit.attr.attr);
181                 result = device_create_file(hwmon->device,
182                                             &temp->temp_crit.attr);
183                 if (result)
184                         goto unregister_input;
185         }
186
187         mutex_lock(&thermal_hwmon_list_lock);
188         if (new_hwmon_device)
189                 list_add_tail(&hwmon->node, &thermal_hwmon_list);
190         list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
191         mutex_unlock(&thermal_hwmon_list_lock);
192
193         return 0;
194
195  unregister_input:
196         device_remove_file(hwmon->device, &temp->temp_input.attr);
197  free_temp_mem:
198         kfree(temp);
199  unregister_name:
200         if (new_hwmon_device)
201                 hwmon_device_unregister(hwmon->device);
202  free_mem:
203         if (new_hwmon_device)
204                 kfree(hwmon);
205
206         return result;
207 }
208 EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
209
210 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
211 {
212         struct thermal_hwmon_device *hwmon;
213         struct thermal_hwmon_temp *temp;
214
215         hwmon = thermal_hwmon_lookup_by_type(tz);
216         if (unlikely(!hwmon)) {
217                 /* Should never happen... */
218                 dev_dbg(&tz->device, "hwmon device lookup failed!\n");
219                 return;
220         }
221
222         temp = thermal_hwmon_lookup_temp(hwmon, tz);
223         if (unlikely(!temp)) {
224                 /* Should never happen... */
225                 dev_dbg(&tz->device, "temperature input lookup failed!\n");
226                 return;
227         }
228
229         device_remove_file(hwmon->device, &temp->temp_input.attr);
230         if (thermal_zone_crit_temp_valid(tz))
231                 device_remove_file(hwmon->device, &temp->temp_crit.attr);
232
233         mutex_lock(&thermal_hwmon_list_lock);
234         list_del(&temp->hwmon_node);
235         kfree(temp);
236         if (!list_empty(&hwmon->tz_list)) {
237                 mutex_unlock(&thermal_hwmon_list_lock);
238                 return;
239         }
240         list_del(&hwmon->node);
241         mutex_unlock(&thermal_hwmon_list_lock);
242
243         hwmon_device_unregister(hwmon->device);
244         kfree(hwmon);
245 }
246 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);