]>
Commit | Line | Data |
---|---|---|
0dd88793 EV |
1 | /* |
2 | * thermal_hwmon.c - Generic Thermal Management hwmon support. | |
3 | * | |
4 | * Code based on Intel thermal_core.c. Copyrights of the original code: | |
5 | * Copyright (C) 2008 Intel Corp | |
6 | * Copyright (C) 2008 Zhang Rui <[email protected]> | |
7 | * Copyright (C) 2008 Sujith Thomas <[email protected]> | |
8 | * | |
9 | * Copyright (C) 2013 Texas Instruments | |
10 | * Copyright (C) 2013 Eduardo Valentin <[email protected]> | |
11 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License as published by | |
15 | * the Free Software Foundation; version 2 of the License. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, but | |
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 | * General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License along | |
23 | * with this program; if not, write to the Free Software Foundation, Inc., | |
24 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
25 | * | |
26 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
27 | */ | |
28 | #include <linux/hwmon.h> | |
29 | #include <linux/thermal.h> | |
30 | #include <linux/slab.h> | |
31 | #include <linux/err.h> | |
32 | #include "thermal_hwmon.h" | |
33 | ||
34 | /* hwmon sys I/F */ | |
35 | /* thermal zone devices with the same type share one hwmon device */ | |
36 | struct thermal_hwmon_device { | |
37 | char type[THERMAL_NAME_LENGTH]; | |
38 | struct device *device; | |
39 | int count; | |
40 | struct list_head tz_list; | |
41 | struct list_head node; | |
42 | }; | |
43 | ||
44 | struct thermal_hwmon_attr { | |
45 | struct device_attribute attr; | |
46 | char name[16]; | |
47 | }; | |
48 | ||
49 | /* one temperature input for each thermal zone */ | |
50 | struct thermal_hwmon_temp { | |
51 | struct list_head hwmon_node; | |
52 | struct thermal_zone_device *tz; | |
53 | struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ | |
54 | struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ | |
55 | }; | |
56 | ||
57 | static LIST_HEAD(thermal_hwmon_list); | |
58 | ||
59 | static DEFINE_MUTEX(thermal_hwmon_list_lock); | |
60 | ||
61 | static ssize_t | |
62 | name_show(struct device *dev, struct device_attribute *attr, char *buf) | |
63 | { | |
64 | struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); | |
65 | return sprintf(buf, "%s\n", hwmon->type); | |
66 | } | |
67 | static DEVICE_ATTR(name, 0444, name_show, NULL); | |
68 | ||
69 | static ssize_t | |
70 | temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) | |
71 | { | |
17e8351a | 72 | int temperature; |
0dd88793 EV |
73 | int ret; |
74 | struct thermal_hwmon_attr *hwmon_attr | |
75 | = container_of(attr, struct thermal_hwmon_attr, attr); | |
76 | struct thermal_hwmon_temp *temp | |
77 | = container_of(hwmon_attr, struct thermal_hwmon_temp, | |
78 | temp_input); | |
79 | struct thermal_zone_device *tz = temp->tz; | |
80 | ||
81 | ret = thermal_zone_get_temp(tz, &temperature); | |
82 | ||
83 | if (ret) | |
84 | return ret; | |
85 | ||
17e8351a | 86 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
87 | } |
88 | ||
89 | static ssize_t | |
90 | temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) | |
91 | { | |
92 | struct thermal_hwmon_attr *hwmon_attr | |
93 | = container_of(attr, struct thermal_hwmon_attr, attr); | |
94 | struct thermal_hwmon_temp *temp | |
95 | = container_of(hwmon_attr, struct thermal_hwmon_temp, | |
96 | temp_crit); | |
97 | struct thermal_zone_device *tz = temp->tz; | |
17e8351a | 98 | int temperature; |
0dd88793 EV |
99 | int ret; |
100 | ||
101 | ret = tz->ops->get_trip_temp(tz, 0, &temperature); | |
102 | if (ret) | |
103 | return ret; | |
104 | ||
17e8351a | 105 | return sprintf(buf, "%d\n", temperature); |
0dd88793 EV |
106 | } |
107 | ||
108 | ||
109 | static struct thermal_hwmon_device * | |
110 | thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) | |
111 | { | |
112 | struct thermal_hwmon_device *hwmon; | |
113 | ||
114 | mutex_lock(&thermal_hwmon_list_lock); | |
115 | list_for_each_entry(hwmon, &thermal_hwmon_list, node) | |
116 | if (!strcmp(hwmon->type, tz->type)) { | |
117 | mutex_unlock(&thermal_hwmon_list_lock); | |
118 | return hwmon; | |
119 | } | |
120 | mutex_unlock(&thermal_hwmon_list_lock); | |
121 | ||
122 | return NULL; | |
123 | } | |
124 | ||
125 | /* Find the temperature input matching a given thermal zone */ | |
126 | static struct thermal_hwmon_temp * | |
127 | thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, | |
128 | const struct thermal_zone_device *tz) | |
129 | { | |
130 | struct thermal_hwmon_temp *temp; | |
131 | ||
132 | mutex_lock(&thermal_hwmon_list_lock); | |
133 | list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) | |
134 | if (temp->tz == tz) { | |
135 | mutex_unlock(&thermal_hwmon_list_lock); | |
136 | return temp; | |
137 | } | |
138 | mutex_unlock(&thermal_hwmon_list_lock); | |
139 | ||
140 | return NULL; | |
141 | } | |
142 | ||
e8db5d67 AL |
143 | static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) |
144 | { | |
17e8351a | 145 | int temp; |
e8db5d67 AL |
146 | return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); |
147 | } | |
148 | ||
0dd88793 EV |
149 | int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) |
150 | { | |
151 | struct thermal_hwmon_device *hwmon; | |
152 | struct thermal_hwmon_temp *temp; | |
153 | int new_hwmon_device = 1; | |
154 | int result; | |
155 | ||
156 | hwmon = thermal_hwmon_lookup_by_type(tz); | |
157 | if (hwmon) { | |
158 | new_hwmon_device = 0; | |
159 | goto register_sys_interface; | |
160 | } | |
161 | ||
162 | hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); | |
163 | if (!hwmon) | |
164 | return -ENOMEM; | |
165 | ||
166 | INIT_LIST_HEAD(&hwmon->tz_list); | |
167 | strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); | |
6ddcb7e6 | 168 | hwmon->device = hwmon_device_register(NULL); |
0dd88793 EV |
169 | if (IS_ERR(hwmon->device)) { |
170 | result = PTR_ERR(hwmon->device); | |
171 | goto free_mem; | |
172 | } | |
173 | dev_set_drvdata(hwmon->device, hwmon); | |
174 | result = device_create_file(hwmon->device, &dev_attr_name); | |
175 | if (result) | |
176 | goto free_mem; | |
177 | ||
178 | register_sys_interface: | |
179 | temp = kzalloc(sizeof(*temp), GFP_KERNEL); | |
180 | if (!temp) { | |
181 | result = -ENOMEM; | |
182 | goto unregister_name; | |
183 | } | |
184 | ||
185 | temp->tz = tz; | |
186 | hwmon->count++; | |
187 | ||
188 | snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), | |
189 | "temp%d_input", hwmon->count); | |
190 | temp->temp_input.attr.attr.name = temp->temp_input.name; | |
191 | temp->temp_input.attr.attr.mode = 0444; | |
192 | temp->temp_input.attr.show = temp_input_show; | |
193 | sysfs_attr_init(&temp->temp_input.attr.attr); | |
194 | result = device_create_file(hwmon->device, &temp->temp_input.attr); | |
195 | if (result) | |
196 | goto free_temp_mem; | |
197 | ||
e8db5d67 AL |
198 | if (thermal_zone_crit_temp_valid(tz)) { |
199 | snprintf(temp->temp_crit.name, | |
200 | sizeof(temp->temp_crit.name), | |
0dd88793 | 201 | "temp%d_crit", hwmon->count); |
e8db5d67 AL |
202 | temp->temp_crit.attr.attr.name = temp->temp_crit.name; |
203 | temp->temp_crit.attr.attr.mode = 0444; | |
204 | temp->temp_crit.attr.show = temp_crit_show; | |
205 | sysfs_attr_init(&temp->temp_crit.attr.attr); | |
206 | result = device_create_file(hwmon->device, | |
207 | &temp->temp_crit.attr); | |
208 | if (result) | |
209 | goto unregister_input; | |
0dd88793 EV |
210 | } |
211 | ||
212 | mutex_lock(&thermal_hwmon_list_lock); | |
213 | if (new_hwmon_device) | |
214 | list_add_tail(&hwmon->node, &thermal_hwmon_list); | |
215 | list_add_tail(&temp->hwmon_node, &hwmon->tz_list); | |
216 | mutex_unlock(&thermal_hwmon_list_lock); | |
217 | ||
218 | return 0; | |
219 | ||
220 | unregister_input: | |
221 | device_remove_file(hwmon->device, &temp->temp_input.attr); | |
222 | free_temp_mem: | |
223 | kfree(temp); | |
224 | unregister_name: | |
225 | if (new_hwmon_device) { | |
226 | device_remove_file(hwmon->device, &dev_attr_name); | |
227 | hwmon_device_unregister(hwmon->device); | |
228 | } | |
229 | free_mem: | |
230 | if (new_hwmon_device) | |
231 | kfree(hwmon); | |
232 | ||
233 | return result; | |
234 | } | |
235 | ||
236 | void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) | |
237 | { | |
238 | struct thermal_hwmon_device *hwmon; | |
239 | struct thermal_hwmon_temp *temp; | |
240 | ||
241 | hwmon = thermal_hwmon_lookup_by_type(tz); | |
242 | if (unlikely(!hwmon)) { | |
243 | /* Should never happen... */ | |
244 | dev_dbg(&tz->device, "hwmon device lookup failed!\n"); | |
245 | return; | |
246 | } | |
247 | ||
248 | temp = thermal_hwmon_lookup_temp(hwmon, tz); | |
249 | if (unlikely(!temp)) { | |
250 | /* Should never happen... */ | |
251 | dev_dbg(&tz->device, "temperature input lookup failed!\n"); | |
252 | return; | |
253 | } | |
254 | ||
255 | device_remove_file(hwmon->device, &temp->temp_input.attr); | |
e8db5d67 | 256 | if (thermal_zone_crit_temp_valid(tz)) |
0dd88793 EV |
257 | device_remove_file(hwmon->device, &temp->temp_crit.attr); |
258 | ||
259 | mutex_lock(&thermal_hwmon_list_lock); | |
260 | list_del(&temp->hwmon_node); | |
261 | kfree(temp); | |
262 | if (!list_empty(&hwmon->tz_list)) { | |
263 | mutex_unlock(&thermal_hwmon_list_lock); | |
264 | return; | |
265 | } | |
266 | list_del(&hwmon->node); | |
267 | mutex_unlock(&thermal_hwmon_list_lock); | |
268 | ||
269 | device_remove_file(hwmon->device, &dev_attr_name); | |
270 | hwmon_device_unregister(hwmon->device); | |
271 | kfree(hwmon); | |
272 | } |