diff options
Diffstat (limited to 'drivers/thermal/thermal_hwmon.c')
-rw-r--r-- | drivers/thermal/thermal_hwmon.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c new file mode 100644 index 000000000000..fdb07199d9c2 --- /dev/null +++ b/drivers/thermal/thermal_hwmon.c | |||
@@ -0,0 +1,269 @@ | |||
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 <rui.zhang@intel.com> | ||
7 | * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | ||
8 | * | ||
9 | * Copyright (C) 2013 Texas Instruments | ||
10 | * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> | ||
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 | { | ||
72 | long temperature; | ||
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 | |||
86 | return sprintf(buf, "%ld\n", temperature); | ||
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; | ||
98 | long temperature; | ||
99 | int ret; | ||
100 | |||
101 | ret = tz->ops->get_trip_temp(tz, 0, &temperature); | ||
102 | if (ret) | ||
103 | return ret; | ||
104 | |||
105 | return sprintf(buf, "%ld\n", temperature); | ||
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 | |||
143 | int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) | ||
144 | { | ||
145 | struct thermal_hwmon_device *hwmon; | ||
146 | struct thermal_hwmon_temp *temp; | ||
147 | int new_hwmon_device = 1; | ||
148 | int result; | ||
149 | |||
150 | hwmon = thermal_hwmon_lookup_by_type(tz); | ||
151 | if (hwmon) { | ||
152 | new_hwmon_device = 0; | ||
153 | goto register_sys_interface; | ||
154 | } | ||
155 | |||
156 | hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); | ||
157 | if (!hwmon) | ||
158 | return -ENOMEM; | ||
159 | |||
160 | INIT_LIST_HEAD(&hwmon->tz_list); | ||
161 | strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); | ||
162 | hwmon->device = hwmon_device_register(NULL); | ||
163 | if (IS_ERR(hwmon->device)) { | ||
164 | result = PTR_ERR(hwmon->device); | ||
165 | goto free_mem; | ||
166 | } | ||
167 | dev_set_drvdata(hwmon->device, hwmon); | ||
168 | result = device_create_file(hwmon->device, &dev_attr_name); | ||
169 | if (result) | ||
170 | goto free_mem; | ||
171 | |||
172 | register_sys_interface: | ||
173 | temp = kzalloc(sizeof(*temp), GFP_KERNEL); | ||
174 | if (!temp) { | ||
175 | result = -ENOMEM; | ||
176 | goto unregister_name; | ||
177 | } | ||
178 | |||
179 | temp->tz = tz; | ||
180 | hwmon->count++; | ||
181 | |||
182 | snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), | ||
183 | "temp%d_input", hwmon->count); | ||
184 | temp->temp_input.attr.attr.name = temp->temp_input.name; | ||
185 | temp->temp_input.attr.attr.mode = 0444; | ||
186 | temp->temp_input.attr.show = temp_input_show; | ||
187 | sysfs_attr_init(&temp->temp_input.attr.attr); | ||
188 | result = device_create_file(hwmon->device, &temp->temp_input.attr); | ||
189 | if (result) | ||
190 | goto free_temp_mem; | ||
191 | |||
192 | if (tz->ops->get_crit_temp) { | ||
193 | unsigned long temperature; | ||
194 | if (!tz->ops->get_crit_temp(tz, &temperature)) { | ||
195 | snprintf(temp->temp_crit.name, | ||
196 | sizeof(temp->temp_crit.name), | ||
197 | "temp%d_crit", hwmon->count); | ||
198 | temp->temp_crit.attr.attr.name = temp->temp_crit.name; | ||
199 | temp->temp_crit.attr.attr.mode = 0444; | ||
200 | temp->temp_crit.attr.show = temp_crit_show; | ||
201 | sysfs_attr_init(&temp->temp_crit.attr.attr); | ||
202 | result = device_create_file(hwmon->device, | ||
203 | &temp->temp_crit.attr); | ||
204 | if (result) | ||
205 | goto unregister_input; | ||
206 | } | ||
207 | } | ||
208 | |||
209 | mutex_lock(&thermal_hwmon_list_lock); | ||
210 | if (new_hwmon_device) | ||
211 | list_add_tail(&hwmon->node, &thermal_hwmon_list); | ||
212 | list_add_tail(&temp->hwmon_node, &hwmon->tz_list); | ||
213 | mutex_unlock(&thermal_hwmon_list_lock); | ||
214 | |||
215 | return 0; | ||
216 | |||
217 | unregister_input: | ||
218 | device_remove_file(hwmon->device, &temp->temp_input.attr); | ||
219 | free_temp_mem: | ||
220 | kfree(temp); | ||
221 | unregister_name: | ||
222 | if (new_hwmon_device) { | ||
223 | device_remove_file(hwmon->device, &dev_attr_name); | ||
224 | hwmon_device_unregister(hwmon->device); | ||
225 | } | ||
226 | free_mem: | ||
227 | if (new_hwmon_device) | ||
228 | kfree(hwmon); | ||
229 | |||
230 | return result; | ||
231 | } | ||
232 | |||
233 | void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) | ||
234 | { | ||
235 | struct thermal_hwmon_device *hwmon; | ||
236 | struct thermal_hwmon_temp *temp; | ||
237 | |||
238 | hwmon = thermal_hwmon_lookup_by_type(tz); | ||
239 | if (unlikely(!hwmon)) { | ||
240 | /* Should never happen... */ | ||
241 | dev_dbg(&tz->device, "hwmon device lookup failed!\n"); | ||
242 | return; | ||
243 | } | ||
244 | |||
245 | temp = thermal_hwmon_lookup_temp(hwmon, tz); | ||
246 | if (unlikely(!temp)) { | ||
247 | /* Should never happen... */ | ||
248 | dev_dbg(&tz->device, "temperature input lookup failed!\n"); | ||
249 | return; | ||
250 | } | ||
251 | |||
252 | device_remove_file(hwmon->device, &temp->temp_input.attr); | ||
253 | if (tz->ops->get_crit_temp) | ||
254 | device_remove_file(hwmon->device, &temp->temp_crit.attr); | ||
255 | |||
256 | mutex_lock(&thermal_hwmon_list_lock); | ||
257 | list_del(&temp->hwmon_node); | ||
258 | kfree(temp); | ||
259 | if (!list_empty(&hwmon->tz_list)) { | ||
260 | mutex_unlock(&thermal_hwmon_list_lock); | ||
261 | return; | ||
262 | } | ||
263 | list_del(&hwmon->node); | ||
264 | mutex_unlock(&thermal_hwmon_list_lock); | ||
265 | |||
266 | device_remove_file(hwmon->device, &dev_attr_name); | ||
267 | hwmon_device_unregister(hwmon->device); | ||
268 | kfree(hwmon); | ||
269 | } | ||