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 EV |
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 | ||
0dd88793 EV |
46 | static ssize_t |
47 | temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) | |
48 | { | |
17e8351a | 49 | int temperature; |
0dd88793 EV |
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 | ||
17e8351a | 63 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
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; | |
17e8351a | 75 | int temperature; |
0dd88793 EV |
76 | int ret; |
77 | ||
f37fabb8 | 78 | ret = tz->ops->get_crit_temp(tz, &temperature); |
0dd88793 EV |
79 | if (ret) |
80 | return ret; | |
81 | ||
17e8351a | 82 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
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 | ||
e8db5d67 AL |
120 | static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) |
121 | { | |
17e8351a | 122 | int temp; |
e8db5d67 AL |
123 | return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); |
124 | } | |
125 | ||
0dd88793 EV |
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); | |
409ef0ba | 145 | strreplace(hwmon->type, '-', '_'); |
f6b6b52e | 146 | hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, |
e782bc16 | 147 | hwmon, NULL, NULL); |
0dd88793 EV |
148 | if (IS_ERR(hwmon->device)) { |
149 | result = PTR_ERR(hwmon->device); | |
150 | goto free_mem; | |
151 | } | |
0dd88793 EV |
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 | ||
e8db5d67 AL |
173 | if (thermal_zone_crit_temp_valid(tz)) { |
174 | snprintf(temp->temp_crit.name, | |
175 | sizeof(temp->temp_crit.name), | |
0dd88793 | 176 | "temp%d_crit", hwmon->count); |
e8db5d67 AL |
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; | |
0dd88793 EV |
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: | |
e782bc16 | 200 | if (new_hwmon_device) |
0dd88793 | 201 | hwmon_device_unregister(hwmon->device); |
0dd88793 EV |
202 | free_mem: |
203 | if (new_hwmon_device) | |
204 | kfree(hwmon); | |
205 | ||
206 | return result; | |
207 | } | |
f4c59243 | 208 | EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); |
0dd88793 EV |
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); | |
e8db5d67 | 230 | if (thermal_zone_crit_temp_valid(tz)) |
0dd88793 EV |
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 | ||
0dd88793 EV |
243 | hwmon_device_unregister(hwmon->device); |
244 | kfree(hwmon); | |
245 | } | |
f4c59243 | 246 | EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); |