Commit | Line | Data |
---|---|---|
7e3c0381 | 1 | // SPDX-License-Identifier: GPL-2.0 |
0dd88793 EV |
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> | |
0dd88793 | 12 | */ |
1330e04f | 13 | #include <linux/err.h> |
e5ebf357 | 14 | #include <linux/export.h> |
0dd88793 | 15 | #include <linux/hwmon.h> |
0dd88793 | 16 | #include <linux/slab.h> |
1330e04f AK |
17 | #include <linux/thermal.h> |
18 | ||
0dd88793 | 19 | #include "thermal_hwmon.h" |
ec2c8aae | 20 | #include "thermal_core.h" |
0dd88793 EV |
21 | |
22 | /* hwmon sys I/F */ | |
23 | /* thermal zone devices with the same type share one hwmon device */ | |
24 | struct thermal_hwmon_device { | |
25 | char type[THERMAL_NAME_LENGTH]; | |
26 | struct device *device; | |
27 | int count; | |
28 | struct list_head tz_list; | |
29 | struct list_head node; | |
30 | }; | |
31 | ||
32 | struct thermal_hwmon_attr { | |
33 | struct device_attribute attr; | |
34 | char name[16]; | |
35 | }; | |
36 | ||
37 | /* one temperature input for each thermal zone */ | |
38 | struct thermal_hwmon_temp { | |
39 | struct list_head hwmon_node; | |
40 | struct thermal_zone_device *tz; | |
41 | struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ | |
42 | struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ | |
43 | }; | |
44 | ||
45 | static LIST_HEAD(thermal_hwmon_list); | |
46 | ||
47 | static DEFINE_MUTEX(thermal_hwmon_list_lock); | |
48 | ||
0dd88793 EV |
49 | static ssize_t |
50 | temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) | |
51 | { | |
17e8351a | 52 | int temperature; |
0dd88793 EV |
53 | int ret; |
54 | struct thermal_hwmon_attr *hwmon_attr | |
55 | = container_of(attr, struct thermal_hwmon_attr, attr); | |
56 | struct thermal_hwmon_temp *temp | |
57 | = container_of(hwmon_attr, struct thermal_hwmon_temp, | |
58 | temp_input); | |
59 | struct thermal_zone_device *tz = temp->tz; | |
60 | ||
61 | ret = thermal_zone_get_temp(tz, &temperature); | |
62 | ||
63 | if (ret) | |
64 | return ret; | |
65 | ||
17e8351a | 66 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
67 | } |
68 | ||
69 | static ssize_t | |
70 | temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) | |
71 | { | |
72 | struct thermal_hwmon_attr *hwmon_attr | |
73 | = container_of(attr, struct thermal_hwmon_attr, attr); | |
74 | struct thermal_hwmon_temp *temp | |
75 | = container_of(hwmon_attr, struct thermal_hwmon_temp, | |
76 | temp_crit); | |
77 | struct thermal_zone_device *tz = temp->tz; | |
17e8351a | 78 | int temperature; |
0dd88793 EV |
79 | int ret; |
80 | ||
ea37bec5 GR |
81 | mutex_lock(&tz->lock); |
82 | ||
83 | if (device_is_registered(&tz->device)) | |
84 | ret = tz->ops->get_crit_temp(tz, &temperature); | |
85 | else | |
86 | ret = -ENODEV; | |
87 | ||
88 | mutex_unlock(&tz->lock); | |
89 | ||
0dd88793 EV |
90 | if (ret) |
91 | return ret; | |
92 | ||
17e8351a | 93 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
94 | } |
95 | ||
96 | ||
97 | static struct thermal_hwmon_device * | |
98 | thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) | |
99 | { | |
100 | struct thermal_hwmon_device *hwmon; | |
8c7aa184 | 101 | char type[THERMAL_NAME_LENGTH]; |
0dd88793 EV |
102 | |
103 | mutex_lock(&thermal_hwmon_list_lock); | |
8c7aa184 SM |
104 | list_for_each_entry(hwmon, &thermal_hwmon_list, node) { |
105 | strcpy(type, tz->type); | |
106 | strreplace(type, '-', '_'); | |
107 | if (!strcmp(hwmon->type, type)) { | |
0dd88793 EV |
108 | mutex_unlock(&thermal_hwmon_list_lock); |
109 | return hwmon; | |
110 | } | |
8c7aa184 | 111 | } |
0dd88793 EV |
112 | mutex_unlock(&thermal_hwmon_list_lock); |
113 | ||
114 | return NULL; | |
115 | } | |
116 | ||
117 | /* Find the temperature input matching a given thermal zone */ | |
118 | static struct thermal_hwmon_temp * | |
119 | thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, | |
120 | const struct thermal_zone_device *tz) | |
121 | { | |
122 | struct thermal_hwmon_temp *temp; | |
123 | ||
124 | mutex_lock(&thermal_hwmon_list_lock); | |
125 | list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) | |
126 | if (temp->tz == tz) { | |
127 | mutex_unlock(&thermal_hwmon_list_lock); | |
128 | return temp; | |
129 | } | |
130 | mutex_unlock(&thermal_hwmon_list_lock); | |
131 | ||
132 | return NULL; | |
133 | } | |
134 | ||
e8db5d67 AL |
135 | static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) |
136 | { | |
17e8351a | 137 | int temp; |
e8db5d67 AL |
138 | return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); |
139 | } | |
140 | ||
0dd88793 EV |
141 | int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) |
142 | { | |
143 | struct thermal_hwmon_device *hwmon; | |
144 | struct thermal_hwmon_temp *temp; | |
145 | int new_hwmon_device = 1; | |
146 | int result; | |
147 | ||
148 | hwmon = thermal_hwmon_lookup_by_type(tz); | |
149 | if (hwmon) { | |
150 | new_hwmon_device = 0; | |
151 | goto register_sys_interface; | |
152 | } | |
153 | ||
154 | hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); | |
155 | if (!hwmon) | |
156 | return -ENOMEM; | |
157 | ||
158 | INIT_LIST_HEAD(&hwmon->tz_list); | |
1e6c8fb8 | 159 | strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); |
409ef0ba | 160 | strreplace(hwmon->type, '-', '_'); |
87743bcf GR |
161 | hwmon->device = hwmon_device_register_for_thermal(&tz->device, |
162 | hwmon->type, hwmon); | |
0dd88793 EV |
163 | if (IS_ERR(hwmon->device)) { |
164 | result = PTR_ERR(hwmon->device); | |
165 | goto free_mem; | |
166 | } | |
0dd88793 EV |
167 | |
168 | register_sys_interface: | |
169 | temp = kzalloc(sizeof(*temp), GFP_KERNEL); | |
170 | if (!temp) { | |
171 | result = -ENOMEM; | |
172 | goto unregister_name; | |
173 | } | |
174 | ||
175 | temp->tz = tz; | |
176 | hwmon->count++; | |
177 | ||
178 | snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), | |
179 | "temp%d_input", hwmon->count); | |
180 | temp->temp_input.attr.attr.name = temp->temp_input.name; | |
181 | temp->temp_input.attr.attr.mode = 0444; | |
182 | temp->temp_input.attr.show = temp_input_show; | |
183 | sysfs_attr_init(&temp->temp_input.attr.attr); | |
184 | result = device_create_file(hwmon->device, &temp->temp_input.attr); | |
185 | if (result) | |
186 | goto free_temp_mem; | |
187 | ||
e8db5d67 AL |
188 | if (thermal_zone_crit_temp_valid(tz)) { |
189 | snprintf(temp->temp_crit.name, | |
190 | sizeof(temp->temp_crit.name), | |
0dd88793 | 191 | "temp%d_crit", hwmon->count); |
e8db5d67 AL |
192 | temp->temp_crit.attr.attr.name = temp->temp_crit.name; |
193 | temp->temp_crit.attr.attr.mode = 0444; | |
194 | temp->temp_crit.attr.show = temp_crit_show; | |
195 | sysfs_attr_init(&temp->temp_crit.attr.attr); | |
196 | result = device_create_file(hwmon->device, | |
197 | &temp->temp_crit.attr); | |
198 | if (result) | |
199 | goto unregister_input; | |
0dd88793 EV |
200 | } |
201 | ||
202 | mutex_lock(&thermal_hwmon_list_lock); | |
203 | if (new_hwmon_device) | |
204 | list_add_tail(&hwmon->node, &thermal_hwmon_list); | |
205 | list_add_tail(&temp->hwmon_node, &hwmon->tz_list); | |
206 | mutex_unlock(&thermal_hwmon_list_lock); | |
207 | ||
208 | return 0; | |
209 | ||
210 | unregister_input: | |
211 | device_remove_file(hwmon->device, &temp->temp_input.attr); | |
212 | free_temp_mem: | |
213 | kfree(temp); | |
214 | unregister_name: | |
e782bc16 | 215 | if (new_hwmon_device) |
0dd88793 | 216 | hwmon_device_unregister(hwmon->device); |
0dd88793 | 217 | free_mem: |
030a48b0 | 218 | kfree(hwmon); |
0dd88793 EV |
219 | |
220 | return result; | |
221 | } | |
f4c59243 | 222 | EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); |
0dd88793 EV |
223 | |
224 | void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) | |
225 | { | |
226 | struct thermal_hwmon_device *hwmon; | |
227 | struct thermal_hwmon_temp *temp; | |
228 | ||
229 | hwmon = thermal_hwmon_lookup_by_type(tz); | |
230 | if (unlikely(!hwmon)) { | |
231 | /* Should never happen... */ | |
4eb7c2f3 | 232 | dev_dbg(&tz->device, "hwmon device lookup failed!\n"); |
0dd88793 EV |
233 | return; |
234 | } | |
235 | ||
236 | temp = thermal_hwmon_lookup_temp(hwmon, tz); | |
237 | if (unlikely(!temp)) { | |
238 | /* Should never happen... */ | |
d9dc0600 | 239 | dev_dbg(&tz->device, "temperature input lookup failed!\n"); |
0dd88793 EV |
240 | return; |
241 | } | |
242 | ||
243 | device_remove_file(hwmon->device, &temp->temp_input.attr); | |
e8db5d67 | 244 | if (thermal_zone_crit_temp_valid(tz)) |
0dd88793 EV |
245 | device_remove_file(hwmon->device, &temp->temp_crit.attr); |
246 | ||
247 | mutex_lock(&thermal_hwmon_list_lock); | |
248 | list_del(&temp->hwmon_node); | |
249 | kfree(temp); | |
250 | if (!list_empty(&hwmon->tz_list)) { | |
251 | mutex_unlock(&thermal_hwmon_list_lock); | |
252 | return; | |
253 | } | |
254 | list_del(&hwmon->node); | |
255 | mutex_unlock(&thermal_hwmon_list_lock); | |
256 | ||
0dd88793 EV |
257 | hwmon_device_unregister(hwmon->device); |
258 | kfree(hwmon); | |
259 | } | |
f4c59243 | 260 | EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); |
c7fc403e AS |
261 | |
262 | static void devm_thermal_hwmon_release(struct device *dev, void *res) | |
263 | { | |
264 | thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); | |
265 | } | |
266 | ||
4a16c190 | 267 | int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz) |
c7fc403e AS |
268 | { |
269 | struct thermal_zone_device **ptr; | |
270 | int ret; | |
271 | ||
272 | ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), | |
273 | GFP_KERNEL); | |
8416ecfb YL |
274 | if (!ptr) { |
275 | dev_warn(dev, "Failed to allocate device resource data\n"); | |
c7fc403e | 276 | return -ENOMEM; |
8416ecfb | 277 | } |
c7fc403e AS |
278 | |
279 | ret = thermal_add_hwmon_sysfs(tz); | |
280 | if (ret) { | |
8416ecfb | 281 | dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); |
c7fc403e AS |
282 | devres_free(ptr); |
283 | return ret; | |
284 | } | |
285 | ||
286 | *ptr = tz; | |
4a16c190 | 287 | devres_add(dev, ptr); |
c7fc403e AS |
288 | |
289 | return ret; | |
290 | } | |
291 | EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); | |
87743bcf GR |
292 | |
293 | MODULE_IMPORT_NS(HWMON_THERMAL); |