Commit 26b6f2236615649a0ae6a0de2e9e71a2f9ffeba7
Exists in
master
and in
4 other branches
Merge branches 'release' and 'menlo' into release
Conflicts: drivers/acpi/video.c Signed-off-by: Len Brown <len.brown@intel.com>
Showing 19 changed files Side-by-side Diff
- Documentation/thermal/sysfs-api.txt
- drivers/Kconfig
- drivers/Makefile
- drivers/acpi/Kconfig
- drivers/acpi/bus.c
- drivers/acpi/fan.c
- drivers/acpi/processor_core.c
- drivers/acpi/processor_thermal.c
- drivers/acpi/thermal.c
- drivers/acpi/video.c
- drivers/misc/Kconfig
- drivers/misc/Makefile
- drivers/misc/intel_menlow.c
- drivers/thermal/Kconfig
- drivers/thermal/Makefile
- drivers/thermal/thermal.c
- include/acpi/acpi_bus.h
- include/acpi/processor.h
- include/linux/thermal.h
Documentation/thermal/sysfs-api.txt
1 | +Generic Thermal Sysfs driver How To | |
2 | +========================= | |
3 | + | |
4 | +Written by Sujith Thomas <sujith.thomas@intel.com>, Zhang Rui <rui.zhang@intel.com> | |
5 | + | |
6 | +Updated: 2 January 2008 | |
7 | + | |
8 | +Copyright (c) 2008 Intel Corporation | |
9 | + | |
10 | + | |
11 | +0. Introduction | |
12 | + | |
13 | +The generic thermal sysfs provides a set of interfaces for thermal zone devices (sensors) | |
14 | +and thermal cooling devices (fan, processor...) to register with the thermal management | |
15 | +solution and to be a part of it. | |
16 | + | |
17 | +This how-to focusses on enabling new thermal zone and cooling devices to participate | |
18 | +in thermal management. | |
19 | +This solution is platform independent and any type of thermal zone devices and | |
20 | +cooling devices should be able to make use of the infrastructure. | |
21 | + | |
22 | +The main task of the thermal sysfs driver is to expose thermal zone attributes as well | |
23 | +as cooling device attributes to the user space. | |
24 | +An intelligent thermal management application can make decisions based on inputs | |
25 | +from thermal zone attributes (the current temperature and trip point temperature) | |
26 | +and throttle appropriate devices. | |
27 | + | |
28 | +[0-*] denotes any positive number starting from 0 | |
29 | +[1-*] denotes any positive number starting from 1 | |
30 | + | |
31 | +1. thermal sysfs driver interface functions | |
32 | + | |
33 | +1.1 thermal zone device interface | |
34 | +1.1.1 struct thermal_zone_device *thermal_zone_device_register(char *name, int trips, | |
35 | + void *devdata, struct thermal_zone_device_ops *ops) | |
36 | + | |
37 | + This interface function adds a new thermal zone device (sensor) to | |
38 | + /sys/class/thermal folder as thermal_zone[0-*]. | |
39 | + It tries to bind all the thermal cooling devices registered at the same time. | |
40 | + | |
41 | + name: the thermal zone name. | |
42 | + trips: the total number of trip points this thermal zone supports. | |
43 | + devdata: device private data | |
44 | + ops: thermal zone device callbacks. | |
45 | + .bind: bind the thermal zone device with a thermal cooling device. | |
46 | + .unbind: unbing the thermal zone device with a thermal cooling device. | |
47 | + .get_temp: get the current temperature of the thermal zone. | |
48 | + .get_mode: get the current mode (user/kernel) of the thermal zone. | |
49 | + "kernel" means thermal management is done in kernel. | |
50 | + "user" will prevent kernel thermal driver actions upon trip points | |
51 | + so that user applications can take charge of thermal management. | |
52 | + .set_mode: set the mode (user/kernel) of the thermal zone. | |
53 | + .get_trip_type: get the type of certain trip point. | |
54 | + .get_trip_temp: get the temperature above which the certain trip point | |
55 | + will be fired. | |
56 | + | |
57 | +1.1.2 void thermal_zone_device_unregister(struct thermal_zone_device *tz) | |
58 | + | |
59 | + This interface function removes the thermal zone device. | |
60 | + It deletes the corresponding entry form /sys/class/thermal folder and unbind all | |
61 | + the thermal cooling devices it uses. | |
62 | + | |
63 | +1.2 thermal cooling device interface | |
64 | +1.2.1 struct thermal_cooling_device *thermal_cooling_device_register(char *name, | |
65 | + void *devdata, struct thermal_cooling_device_ops *) | |
66 | + | |
67 | + This interface function adds a new thermal cooling device (fan/processor/...) to | |
68 | + /sys/class/thermal/ folder as cooling_device[0-*]. | |
69 | + It tries to bind itself to all the thermal zone devices register at the same time. | |
70 | + name: the cooling device name. | |
71 | + devdata: device private data. | |
72 | + ops: thermal cooling devices callbacks. | |
73 | + .get_max_state: get the Maximum throttle state of the cooling device. | |
74 | + .get_cur_state: get the Current throttle state of the cooling device. | |
75 | + .set_cur_state: set the Current throttle state of the cooling device. | |
76 | + | |
77 | +1.2.2 void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev) | |
78 | + | |
79 | + This interface function remove the thermal cooling device. | |
80 | + It deletes the corresponding entry form /sys/class/thermal folder and unbind | |
81 | + itself from all the thermal zone devices using it. | |
82 | + | |
83 | +1.3 interface for binding a thermal zone device with a thermal cooling device | |
84 | +1.3.1 int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, | |
85 | + int trip, struct thermal_cooling_device *cdev); | |
86 | + | |
87 | + This interface function bind a thermal cooling device to the certain trip point | |
88 | + of a thermal zone device. | |
89 | + This function is usually called in the thermal zone device .bind callback. | |
90 | + tz: the thermal zone device | |
91 | + cdev: thermal cooling device | |
92 | + trip: indicates which trip point the cooling devices is associated with | |
93 | + in this thermal zone. | |
94 | + | |
95 | +1.3.2 int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, | |
96 | + int trip, struct thermal_cooling_device *cdev); | |
97 | + | |
98 | + This interface function unbind a thermal cooling device from the certain trip point | |
99 | + of a thermal zone device. | |
100 | + This function is usually called in the thermal zone device .unbind callback. | |
101 | + tz: the thermal zone device | |
102 | + cdev: thermal cooling device | |
103 | + trip: indicates which trip point the cooling devices is associated with | |
104 | + in this thermal zone. | |
105 | + | |
106 | +2. sysfs attributes structure | |
107 | + | |
108 | +RO read only value | |
109 | +RW read/write value | |
110 | + | |
111 | +All thermal sysfs attributes will be represented under /sys/class/thermal | |
112 | +/sys/class/thermal/ | |
113 | + | |
114 | +Thermal zone device sys I/F, created once it's registered: | |
115 | +|thermal_zone[0-*]: | |
116 | + |-----type: Type of the thermal zone | |
117 | + |-----temp: Current temperature | |
118 | + |-----mode: Working mode of the thermal zone | |
119 | + |-----trip_point_[0-*]_temp: Trip point temperature | |
120 | + |-----trip_point_[0-*]_type: Trip point type | |
121 | + | |
122 | +Thermal cooling device sys I/F, created once it's registered: | |
123 | +|cooling_device[0-*]: | |
124 | + |-----type : Type of the cooling device(processor/fan/...) | |
125 | + |-----max_state: Maximum cooling state of the cooling device | |
126 | + |-----cur_state: Current cooling state of the cooling device | |
127 | + | |
128 | + | |
129 | +These two dynamic attributes are created/removed in pairs. | |
130 | +They represent the relationship between a thermal zone and its associated cooling device. | |
131 | +They are created/removed for each | |
132 | +thermal_zone_bind_cooling_device/thermal_zone_unbind_cooling_device successful exection. | |
133 | + | |
134 | +|thermal_zone[0-*] | |
135 | + |-----cdev[0-*]: The [0-*]th cooling device in the current thermal zone | |
136 | + |-----cdev[0-*]_trip_point: Trip point that cdev[0-*] is associated with | |
137 | + | |
138 | + | |
139 | +*************************** | |
140 | +* Thermal zone attributes * | |
141 | +*************************** | |
142 | + | |
143 | +type Strings which represent the thermal zone type. | |
144 | + This is given by thermal zone driver as part of registration. | |
145 | + Eg: "ACPI thermal zone" indicates it's a ACPI thermal device | |
146 | + RO | |
147 | + Optional | |
148 | + | |
149 | +temp Current temperature as reported by thermal zone (sensor) | |
150 | + Unit: degree celsius | |
151 | + RO | |
152 | + Required | |
153 | + | |
154 | +mode One of the predifned values in [kernel, user] | |
155 | + This file gives information about the algorithm | |
156 | + that is currently managing the thermal zone. | |
157 | + It can be either default kernel based algorithm | |
158 | + or user space application. | |
159 | + RW | |
160 | + Optional | |
161 | + kernel = Thermal management in kernel thermal zone driver. | |
162 | + user = Preventing kernel thermal zone driver actions upon | |
163 | + trip points so that user application can take full | |
164 | + charge of the thermal management. | |
165 | + | |
166 | +trip_point_[0-*]_temp The temperature above which trip point will be fired | |
167 | + Unit: degree celsius | |
168 | + RO | |
169 | + Optional | |
170 | + | |
171 | +trip_point_[0-*]_type Strings which indicate the type of the trip point | |
172 | + Eg. it can be one of critical, hot, passive, | |
173 | + active[0-*] for ACPI thermal zone. | |
174 | + RO | |
175 | + Optional | |
176 | + | |
177 | +cdev[0-*] Sysfs link to the thermal cooling device node where the sys I/F | |
178 | + for cooling device throttling control represents. | |
179 | + RO | |
180 | + Optional | |
181 | + | |
182 | +cdev[0-*]_trip_point The trip point with which cdev[0-*] is assocated in this thermal zone | |
183 | + -1 means the cooling device is not associated with any trip point. | |
184 | + RO | |
185 | + Optional | |
186 | + | |
187 | +****************************** | |
188 | +* Cooling device attributes * | |
189 | +****************************** | |
190 | + | |
191 | +type String which represents the type of device | |
192 | + eg: For generic ACPI: this should be "Fan", | |
193 | + "Processor" or "LCD" | |
194 | + eg. For memory controller device on intel_menlow platform: | |
195 | + this should be "Memory controller" | |
196 | + RO | |
197 | + Optional | |
198 | + | |
199 | +max_state The maximum permissible cooling state of this cooling device. | |
200 | + RO | |
201 | + Required | |
202 | + | |
203 | +cur_state The current cooling state of this cooling device. | |
204 | + the value can any integer numbers between 0 and max_state, | |
205 | + cur_state == 0 means no cooling | |
206 | + cur_state == max_state means the maximum cooling. | |
207 | + RW | |
208 | + Required | |
209 | + | |
210 | +3. A simple implementation | |
211 | + | |
212 | +ACPI thermal zone may support multiple trip points like critical/hot/passive/active. | |
213 | +If an ACPI thermal zone supports critical, passive, active[0] and active[1] at the same time, | |
214 | +it may register itself as a thermale_zone_device (thermal_zone1) with 4 trip points in all. | |
215 | +It has one processor and one fan, which are both registered as thermal_cooling_device. | |
216 | +If the processor is listed in _PSL method, and the fan is listed in _AL0 method, | |
217 | +the sys I/F structure will be built like this: | |
218 | + | |
219 | +/sys/class/thermal: | |
220 | + | |
221 | +|thermal_zone1: | |
222 | + |-----type: ACPI thermal zone | |
223 | + |-----temp: 37 | |
224 | + |-----mode: kernel | |
225 | + |-----trip_point_0_temp: 100 | |
226 | + |-----trip_point_0_type: critical | |
227 | + |-----trip_point_1_temp: 80 | |
228 | + |-----trip_point_1_type: passive | |
229 | + |-----trip_point_2_temp: 70 | |
230 | + |-----trip_point_2_type: active[0] | |
231 | + |-----trip_point_3_temp: 60 | |
232 | + |-----trip_point_3_type: active[1] | |
233 | + |-----cdev0: --->/sys/class/thermal/cooling_device0 | |
234 | + |-----cdev0_trip_point: 1 /* cdev0 can be used for passive */ | |
235 | + |-----cdev1: --->/sys/class/thermal/cooling_device3 | |
236 | + |-----cdev1_trip_point: 2 /* cdev1 can be used for active[0]*/ | |
237 | + | |
238 | +|cooling_device0: | |
239 | + |-----type: Processor | |
240 | + |-----max_state: 8 | |
241 | + |-----cur_state: 0 | |
242 | + | |
243 | +|cooling_device3: | |
244 | + |-----type: Fan | |
245 | + |-----max_state: 2 | |
246 | + |-----cur_state: 0 |
drivers/Kconfig
drivers/Makefile
drivers/acpi/Kconfig
drivers/acpi/bus.c
... | ... | @@ -122,6 +122,31 @@ |
122 | 122 | |
123 | 123 | EXPORT_SYMBOL(acpi_bus_get_status); |
124 | 124 | |
125 | +void acpi_bus_private_data_handler(acpi_handle handle, | |
126 | + u32 function, void *context) | |
127 | +{ | |
128 | + return; | |
129 | +} | |
130 | +EXPORT_SYMBOL(acpi_bus_private_data_handler); | |
131 | + | |
132 | +int acpi_bus_get_private_data(acpi_handle handle, void **data) | |
133 | +{ | |
134 | + acpi_status status = AE_OK; | |
135 | + | |
136 | + if (!*data) | |
137 | + return -EINVAL; | |
138 | + | |
139 | + status = acpi_get_data(handle, acpi_bus_private_data_handler, data); | |
140 | + if (ACPI_FAILURE(status) || !*data) { | |
141 | + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No context for object [%p]\n", | |
142 | + handle)); | |
143 | + return -ENODEV; | |
144 | + } | |
145 | + | |
146 | + return 0; | |
147 | +} | |
148 | +EXPORT_SYMBOL(acpi_bus_get_private_data); | |
149 | + | |
125 | 150 | /* -------------------------------------------------------------------------- |
126 | 151 | Power Management |
127 | 152 | -------------------------------------------------------------------------- */ |
drivers/acpi/fan.c
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 | #include <linux/proc_fs.h> |
31 | 31 | #include <linux/seq_file.h> |
32 | 32 | #include <asm/uaccess.h> |
33 | - | |
33 | +#include <linux/thermal.h> | |
34 | 34 | #include <acpi/acpi_bus.h> |
35 | 35 | #include <acpi/acpi_drivers.h> |
36 | 36 | |
37 | 37 | |
... | ... | @@ -68,9 +68,55 @@ |
68 | 68 | }, |
69 | 69 | }; |
70 | 70 | |
71 | +/* thermal cooling device callbacks */ | |
72 | +static int fan_get_max_state(struct thermal_cooling_device *cdev, char *buf) | |
73 | +{ | |
74 | + /* ACPI fan device only support two states: ON/OFF */ | |
75 | + return sprintf(buf, "1\n"); | |
76 | +} | |
77 | + | |
78 | +static int fan_get_cur_state(struct thermal_cooling_device *cdev, char *buf) | |
79 | +{ | |
80 | + struct acpi_device *device = cdev->devdata; | |
81 | + int state; | |
82 | + int result; | |
83 | + | |
84 | + if (!device) | |
85 | + return -EINVAL; | |
86 | + | |
87 | + result = acpi_bus_get_power(device->handle, &state); | |
88 | + if (result) | |
89 | + return result; | |
90 | + | |
91 | + return sprintf(buf, "%s\n", state == ACPI_STATE_D3 ? "0" : | |
92 | + (state == ACPI_STATE_D0 ? "1" : "unknown")); | |
93 | +} | |
94 | + | |
95 | +static int | |
96 | +fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned int state) | |
97 | +{ | |
98 | + struct acpi_device *device = cdev->devdata; | |
99 | + int result; | |
100 | + | |
101 | + if (!device || (state != 0 && state != 1)) | |
102 | + return -EINVAL; | |
103 | + | |
104 | + result = acpi_bus_set_power(device->handle, | |
105 | + state ? ACPI_STATE_D0 : ACPI_STATE_D3); | |
106 | + | |
107 | + return result; | |
108 | +} | |
109 | + | |
110 | +static struct thermal_cooling_device_ops fan_cooling_ops = { | |
111 | + .get_max_state = fan_get_max_state, | |
112 | + .get_cur_state = fan_get_cur_state, | |
113 | + .set_cur_state = fan_set_cur_state, | |
114 | +}; | |
115 | + | |
71 | 116 | /* -------------------------------------------------------------------------- |
72 | 117 | FS Interface (/proc) |
73 | 118 | -------------------------------------------------------------------------- */ |
119 | +#ifdef CONFIG_ACPI_PROCFS | |
74 | 120 | |
75 | 121 | static struct proc_dir_entry *acpi_fan_dir; |
76 | 122 | |
77 | 123 | |
... | ... | @@ -171,7 +217,17 @@ |
171 | 217 | |
172 | 218 | return 0; |
173 | 219 | } |
220 | +#else | |
221 | +static int acpi_fan_add_fs(struct acpi_device *device) | |
222 | +{ | |
223 | + return 0; | |
224 | +} | |
174 | 225 | |
226 | +static int acpi_fan_remove_fs(struct acpi_device *device) | |
227 | +{ | |
228 | + return 0; | |
229 | +} | |
230 | +#endif | |
175 | 231 | /* -------------------------------------------------------------------------- |
176 | 232 | Driver Interface |
177 | 233 | -------------------------------------------------------------------------- */ |
178 | 234 | |
179 | 235 | |
... | ... | @@ -179,10 +235,9 @@ |
179 | 235 | static int acpi_fan_add(struct acpi_device *device) |
180 | 236 | { |
181 | 237 | int result = 0; |
182 | - struct acpi_fan *fan = NULL; | |
183 | 238 | int state = 0; |
239 | + struct thermal_cooling_device *cdev; | |
184 | 240 | |
185 | - | |
186 | 241 | if (!device) |
187 | 242 | return -EINVAL; |
188 | 243 | |
... | ... | @@ -199,6 +254,25 @@ |
199 | 254 | acpi_bus_set_power(device->handle, state); |
200 | 255 | device->flags.force_power_state = 0; |
201 | 256 | |
257 | + cdev = thermal_cooling_device_register("Fan", device, | |
258 | + &fan_cooling_ops); | |
259 | + if (cdev) | |
260 | + printk(KERN_INFO PREFIX | |
261 | + "%s is registered as cooling_device%d\n", | |
262 | + device->dev.bus_id, cdev->id); | |
263 | + else | |
264 | + goto end; | |
265 | + acpi_driver_data(device) = cdev; | |
266 | + result = sysfs_create_link(&device->dev.kobj, &cdev->device.kobj, | |
267 | + "thermal_cooling"); | |
268 | + if (result) | |
269 | + return result; | |
270 | + | |
271 | + result = sysfs_create_link(&cdev->device.kobj, &device->dev.kobj, | |
272 | + "device"); | |
273 | + if (result) | |
274 | + return result; | |
275 | + | |
202 | 276 | result = acpi_fan_add_fs(device); |
203 | 277 | if (result) |
204 | 278 | goto end; |
205 | 279 | |
206 | 280 | |
... | ... | @@ -208,18 +282,20 @@ |
208 | 282 | !device->power.state ? "on" : "off"); |
209 | 283 | |
210 | 284 | end: |
211 | - if (result) | |
212 | - kfree(fan); | |
213 | - | |
214 | 285 | return result; |
215 | 286 | } |
216 | 287 | |
217 | 288 | static int acpi_fan_remove(struct acpi_device *device, int type) |
218 | 289 | { |
219 | - if (!device || !acpi_driver_data(device)) | |
290 | + struct thermal_cooling_device *cdev = acpi_driver_data(device); | |
291 | + | |
292 | + if (!device || !cdev) | |
220 | 293 | return -EINVAL; |
221 | 294 | |
222 | 295 | acpi_fan_remove_fs(device); |
296 | + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); | |
297 | + sysfs_remove_link(&cdev->device.kobj, "device"); | |
298 | + thermal_cooling_device_unregister(cdev); | |
223 | 299 | |
224 | 300 | return 0; |
225 | 301 | } |
226 | 302 | |
... | ... | @@ -261,10 +337,12 @@ |
261 | 337 | int result = 0; |
262 | 338 | |
263 | 339 | |
340 | +#ifdef CONFIG_ACPI_PROCFS | |
264 | 341 | acpi_fan_dir = proc_mkdir(ACPI_FAN_CLASS, acpi_root_dir); |
265 | 342 | if (!acpi_fan_dir) |
266 | 343 | return -ENODEV; |
267 | 344 | acpi_fan_dir->owner = THIS_MODULE; |
345 | +#endif | |
268 | 346 | |
269 | 347 | result = acpi_bus_register_driver(&acpi_fan_driver); |
270 | 348 | if (result < 0) { |
drivers/acpi/processor_core.c
... | ... | @@ -668,6 +668,24 @@ |
668 | 668 | |
669 | 669 | acpi_processor_power_init(pr, device); |
670 | 670 | |
671 | + pr->cdev = thermal_cooling_device_register("Processor", device, | |
672 | + &processor_cooling_ops); | |
673 | + if (pr->cdev) | |
674 | + printk(KERN_INFO PREFIX | |
675 | + "%s is registered as cooling_device%d\n", | |
676 | + device->dev.bus_id, pr->cdev->id); | |
677 | + else | |
678 | + goto end; | |
679 | + | |
680 | + result = sysfs_create_link(&device->dev.kobj, &pr->cdev->device.kobj, | |
681 | + "thermal_cooling"); | |
682 | + if (result) | |
683 | + return result; | |
684 | + result = sysfs_create_link(&pr->cdev->device.kobj, &device->dev.kobj, | |
685 | + "device"); | |
686 | + if (result) | |
687 | + return result; | |
688 | + | |
671 | 689 | if (pr->flags.throttling) { |
672 | 690 | printk(KERN_INFO PREFIX "%s [%s] (supports", |
673 | 691 | acpi_device_name(device), acpi_device_bid(device)); |
... | ... | @@ -790,6 +808,11 @@ |
790 | 808 | acpi_processor_notify); |
791 | 809 | |
792 | 810 | acpi_processor_remove_fs(device); |
811 | + | |
812 | + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); | |
813 | + sysfs_remove_link(&pr->cdev->device.kobj, "device"); | |
814 | + thermal_cooling_device_unregister(pr->cdev); | |
815 | + pr->cdev = NULL; | |
793 | 816 | |
794 | 817 | processors[pr->id] = NULL; |
795 | 818 |
drivers/acpi/processor_thermal.c
... | ... | @@ -32,6 +32,7 @@ |
32 | 32 | #include <linux/cpufreq.h> |
33 | 33 | #include <linux/proc_fs.h> |
34 | 34 | #include <linux/seq_file.h> |
35 | +#include <linux/sysdev.h> | |
35 | 36 | |
36 | 37 | #include <asm/uaccess.h> |
37 | 38 | |
... | ... | @@ -93,6 +94,9 @@ |
93 | 94 | * _any_ cpufreq driver and not only the acpi-cpufreq driver. |
94 | 95 | */ |
95 | 96 | |
97 | +#define CPUFREQ_THERMAL_MIN_STEP 0 | |
98 | +#define CPUFREQ_THERMAL_MAX_STEP 3 | |
99 | + | |
96 | 100 | static unsigned int cpufreq_thermal_reduction_pctg[NR_CPUS]; |
97 | 101 | static unsigned int acpi_thermal_cpufreq_is_init = 0; |
98 | 102 | |
... | ... | @@ -109,8 +113,9 @@ |
109 | 113 | if (!cpu_has_cpufreq(cpu)) |
110 | 114 | return -ENODEV; |
111 | 115 | |
112 | - if (cpufreq_thermal_reduction_pctg[cpu] < 60) { | |
113 | - cpufreq_thermal_reduction_pctg[cpu] += 20; | |
116 | + if (cpufreq_thermal_reduction_pctg[cpu] < | |
117 | + CPUFREQ_THERMAL_MAX_STEP) { | |
118 | + cpufreq_thermal_reduction_pctg[cpu]++; | |
114 | 119 | cpufreq_update_policy(cpu); |
115 | 120 | return 0; |
116 | 121 | } |
... | ... | @@ -123,8 +128,9 @@ |
123 | 128 | if (!cpu_has_cpufreq(cpu)) |
124 | 129 | return -ENODEV; |
125 | 130 | |
126 | - if (cpufreq_thermal_reduction_pctg[cpu] > 20) | |
127 | - cpufreq_thermal_reduction_pctg[cpu] -= 20; | |
131 | + if (cpufreq_thermal_reduction_pctg[cpu] > | |
132 | + (CPUFREQ_THERMAL_MIN_STEP + 1)) | |
133 | + cpufreq_thermal_reduction_pctg[cpu]--; | |
128 | 134 | else |
129 | 135 | cpufreq_thermal_reduction_pctg[cpu] = 0; |
130 | 136 | cpufreq_update_policy(cpu); |
... | ... | @@ -143,7 +149,7 @@ |
143 | 149 | |
144 | 150 | max_freq = |
145 | 151 | (policy->cpuinfo.max_freq * |
146 | - (100 - cpufreq_thermal_reduction_pctg[policy->cpu])) / 100; | |
152 | + (100 - cpufreq_thermal_reduction_pctg[policy->cpu] * 20)) / 100; | |
147 | 153 | |
148 | 154 | cpufreq_verify_within_limits(policy, 0, max_freq); |
149 | 155 | |
... | ... | @@ -155,6 +161,32 @@ |
155 | 161 | .notifier_call = acpi_thermal_cpufreq_notifier, |
156 | 162 | }; |
157 | 163 | |
164 | +static int cpufreq_get_max_state(unsigned int cpu) | |
165 | +{ | |
166 | + if (!cpu_has_cpufreq(cpu)) | |
167 | + return 0; | |
168 | + | |
169 | + return CPUFREQ_THERMAL_MAX_STEP; | |
170 | +} | |
171 | + | |
172 | +static int cpufreq_get_cur_state(unsigned int cpu) | |
173 | +{ | |
174 | + if (!cpu_has_cpufreq(cpu)) | |
175 | + return 0; | |
176 | + | |
177 | + return cpufreq_thermal_reduction_pctg[cpu]; | |
178 | +} | |
179 | + | |
180 | +static int cpufreq_set_cur_state(unsigned int cpu, int state) | |
181 | +{ | |
182 | + if (!cpu_has_cpufreq(cpu)) | |
183 | + return 0; | |
184 | + | |
185 | + cpufreq_thermal_reduction_pctg[cpu] = state; | |
186 | + cpufreq_update_policy(cpu); | |
187 | + return 0; | |
188 | +} | |
189 | + | |
158 | 190 | void acpi_thermal_cpufreq_init(void) |
159 | 191 | { |
160 | 192 | int i; |
161 | 193 | |
... | ... | @@ -179,7 +211,21 @@ |
179 | 211 | } |
180 | 212 | |
181 | 213 | #else /* ! CONFIG_CPU_FREQ */ |
214 | +static int cpufreq_get_max_state(unsigned int cpu) | |
215 | +{ | |
216 | + return 0; | |
217 | +} | |
182 | 218 | |
219 | +static int cpufreq_get_cur_state(unsigned int cpu) | |
220 | +{ | |
221 | + return 0; | |
222 | +} | |
223 | + | |
224 | +static int cpufreq_set_cur_state(unsigned int cpu, int state) | |
225 | +{ | |
226 | + return 0; | |
227 | +} | |
228 | + | |
183 | 229 | static int acpi_thermal_cpufreq_increase(unsigned int cpu) |
184 | 230 | { |
185 | 231 | return -ENODEV; |
... | ... | @@ -309,6 +355,84 @@ |
309 | 355 | |
310 | 356 | return 0; |
311 | 357 | } |
358 | + | |
359 | +/* thermal coolign device callbacks */ | |
360 | +static int acpi_processor_max_state(struct acpi_processor *pr) | |
361 | +{ | |
362 | + int max_state = 0; | |
363 | + | |
364 | + /* | |
365 | + * There exists four states according to | |
366 | + * cpufreq_thermal_reduction_ptg. 0, 1, 2, 3 | |
367 | + */ | |
368 | + max_state += cpufreq_get_max_state(pr->id); | |
369 | + if (pr->flags.throttling) | |
370 | + max_state += (pr->throttling.state_count -1); | |
371 | + | |
372 | + return max_state; | |
373 | +} | |
374 | +static int | |
375 | +processor_get_max_state(struct thermal_cooling_device *cdev, char *buf) | |
376 | +{ | |
377 | + struct acpi_device *device = cdev->devdata; | |
378 | + struct acpi_processor *pr = acpi_driver_data(device); | |
379 | + | |
380 | + if (!device || !pr) | |
381 | + return -EINVAL; | |
382 | + | |
383 | + return sprintf(buf, "%d\n", acpi_processor_max_state(pr)); | |
384 | +} | |
385 | + | |
386 | +static int | |
387 | +processor_get_cur_state(struct thermal_cooling_device *cdev, char *buf) | |
388 | +{ | |
389 | + struct acpi_device *device = cdev->devdata; | |
390 | + struct acpi_processor *pr = acpi_driver_data(device); | |
391 | + int cur_state; | |
392 | + | |
393 | + if (!device || !pr) | |
394 | + return -EINVAL; | |
395 | + | |
396 | + cur_state = cpufreq_get_cur_state(pr->id); | |
397 | + if (pr->flags.throttling) | |
398 | + cur_state += pr->throttling.state; | |
399 | + | |
400 | + return sprintf(buf, "%d\n", cur_state); | |
401 | +} | |
402 | + | |
403 | +static int | |
404 | +processor_set_cur_state(struct thermal_cooling_device *cdev, unsigned int state) | |
405 | +{ | |
406 | + struct acpi_device *device = cdev->devdata; | |
407 | + struct acpi_processor *pr = acpi_driver_data(device); | |
408 | + int result = 0; | |
409 | + int max_pstate; | |
410 | + | |
411 | + if (!device || !pr) | |
412 | + return -EINVAL; | |
413 | + | |
414 | + max_pstate = cpufreq_get_max_state(pr->id); | |
415 | + | |
416 | + if (state > acpi_processor_max_state(pr)) | |
417 | + return -EINVAL; | |
418 | + | |
419 | + if (state <= max_pstate) { | |
420 | + if (pr->flags.throttling && pr->throttling.state) | |
421 | + result = acpi_processor_set_throttling(pr, 0); | |
422 | + cpufreq_set_cur_state(pr->id, state); | |
423 | + } else { | |
424 | + cpufreq_set_cur_state(pr->id, max_pstate); | |
425 | + result = acpi_processor_set_throttling(pr, | |
426 | + state - max_pstate); | |
427 | + } | |
428 | + return result; | |
429 | +} | |
430 | + | |
431 | +struct thermal_cooling_device_ops processor_cooling_ops = { | |
432 | + .get_max_state = processor_get_max_state, | |
433 | + .get_cur_state = processor_get_cur_state, | |
434 | + .set_cur_state = processor_set_cur_state, | |
435 | +}; | |
312 | 436 | |
313 | 437 | /* /proc interface */ |
314 | 438 |
drivers/acpi/thermal.c
... | ... | @@ -43,7 +43,7 @@ |
43 | 43 | #include <linux/seq_file.h> |
44 | 44 | #include <linux/reboot.h> |
45 | 45 | #include <asm/uaccess.h> |
46 | - | |
46 | +#include <linux/thermal.h> | |
47 | 47 | #include <acpi/acpi_bus.h> |
48 | 48 | #include <acpi/acpi_drivers.h> |
49 | 49 | |
... | ... | @@ -65,9 +65,6 @@ |
65 | 65 | #define ACPI_THERMAL_MAX_ACTIVE 10 |
66 | 66 | #define ACPI_THERMAL_MAX_LIMIT_STR_LEN 65 |
67 | 67 | |
68 | -#define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732>=0) ? ((long)t-2732+5)/10 : ((long)t-2732-5)/10) | |
69 | -#define CELSIUS_TO_KELVIN(t) ((t+273)*10) | |
70 | - | |
71 | 68 | #define _COMPONENT ACPI_THERMAL_COMPONENT |
72 | 69 | ACPI_MODULE_NAME("thermal"); |
73 | 70 | |
... | ... | @@ -195,6 +192,8 @@ |
195 | 192 | struct acpi_thermal_trips trips; |
196 | 193 | struct acpi_handle_list devices; |
197 | 194 | struct timer_list timer; |
195 | + struct thermal_zone_device *thermal_zone; | |
196 | + int tz_enabled; | |
198 | 197 | struct mutex lock; |
199 | 198 | }; |
200 | 199 | |
201 | 200 | |
202 | 201 | |
203 | 202 | |
204 | 203 | |
205 | 204 | |
206 | 205 | |
207 | 206 | |
208 | 207 | |
209 | 208 | |
210 | 209 | |
211 | 210 | |
212 | 211 | |
213 | 212 | |
214 | 213 | |
215 | 214 | |
216 | 215 | |
217 | 216 | |
218 | 217 | |
219 | 218 | |
220 | 219 | |
221 | 220 | |
222 | 221 | |
223 | 222 | |
224 | 223 | |
225 | 224 | |
... | ... | @@ -321,173 +320,221 @@ |
321 | 320 | return 0; |
322 | 321 | } |
323 | 322 | |
324 | -static int acpi_thermal_get_trip_points(struct acpi_thermal *tz) | |
325 | -{ | |
326 | - acpi_status status = AE_OK; | |
327 | - int i = 0; | |
323 | +#define ACPI_TRIPS_CRITICAL 0x01 | |
324 | +#define ACPI_TRIPS_HOT 0x02 | |
325 | +#define ACPI_TRIPS_PASSIVE 0x04 | |
326 | +#define ACPI_TRIPS_ACTIVE 0x08 | |
327 | +#define ACPI_TRIPS_DEVICES 0x10 | |
328 | 328 | |
329 | +#define ACPI_TRIPS_REFRESH_THRESHOLDS (ACPI_TRIPS_PASSIVE | ACPI_TRIPS_ACTIVE) | |
330 | +#define ACPI_TRIPS_REFRESH_DEVICES ACPI_TRIPS_DEVICES | |
329 | 331 | |
330 | - if (!tz) | |
331 | - return -EINVAL; | |
332 | +#define ACPI_TRIPS_INIT (ACPI_TRIPS_CRITICAL | ACPI_TRIPS_HOT | \ | |
333 | + ACPI_TRIPS_PASSIVE | ACPI_TRIPS_ACTIVE | \ | |
334 | + ACPI_TRIPS_DEVICES) | |
332 | 335 | |
333 | - /* Critical Shutdown (required) */ | |
336 | +/* | |
337 | + * This exception is thrown out in two cases: | |
338 | + * 1.An invalid trip point becomes invalid or a valid trip point becomes invalid | |
339 | + * when re-evaluating the AML code. | |
340 | + * 2.TODO: Devices listed in _PSL, _ALx, _TZD may change. | |
341 | + * We need to re-bind the cooling devices of a thermal zone when this occurs. | |
342 | + */ | |
343 | +#define ACPI_THERMAL_TRIPS_EXCEPTION(flags, str) \ | |
344 | +do { \ | |
345 | + if (flags != ACPI_TRIPS_INIT) \ | |
346 | + ACPI_EXCEPTION((AE_INFO, AE_ERROR, \ | |
347 | + "ACPI thermal trip point %s changed\n" \ | |
348 | + "Please send acpidump to linux-acpi@vger.kernel.org\n", str)); \ | |
349 | +} while (0) | |
334 | 350 | |
335 | - status = acpi_evaluate_integer(tz->device->handle, "_CRT", NULL, | |
336 | - &tz->trips.critical.temperature); | |
337 | - if (ACPI_FAILURE(status)) { | |
338 | - tz->trips.critical.flags.valid = 0; | |
339 | - ACPI_EXCEPTION((AE_INFO, status, "No critical threshold")); | |
340 | - return -ENODEV; | |
341 | - } else { | |
342 | - tz->trips.critical.flags.valid = 1; | |
343 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
344 | - "Found critical threshold [%lu]\n", | |
345 | - tz->trips.critical.temperature)); | |
346 | - } | |
351 | +static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) | |
352 | +{ | |
353 | + acpi_status status = AE_OK; | |
354 | + struct acpi_handle_list devices; | |
355 | + int valid = 0; | |
356 | + int i; | |
347 | 357 | |
348 | - if (tz->trips.critical.flags.valid == 1) { | |
349 | - if (crt == -1) { | |
358 | + /* Critical Shutdown (required) */ | |
359 | + if (flag & ACPI_TRIPS_CRITICAL) { | |
360 | + status = acpi_evaluate_integer(tz->device->handle, | |
361 | + "_CRT", NULL, &tz->trips.critical.temperature); | |
362 | + if (ACPI_FAILURE(status)) { | |
350 | 363 | tz->trips.critical.flags.valid = 0; |
351 | - } else if (crt > 0) { | |
352 | - unsigned long crt_k = CELSIUS_TO_KELVIN(crt); | |
353 | - | |
354 | - /* | |
355 | - * Allow override to lower critical threshold | |
356 | - */ | |
357 | - if (crt_k < tz->trips.critical.temperature) | |
358 | - tz->trips.critical.temperature = crt_k; | |
364 | + ACPI_EXCEPTION((AE_INFO, status, | |
365 | + "No critical threshold")); | |
366 | + return -ENODEV; | |
367 | + } else { | |
368 | + tz->trips.critical.flags.valid = 1; | |
369 | + ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
370 | + "Found critical threshold [%lu]\n", | |
371 | + tz->trips.critical.temperature)); | |
359 | 372 | } |
373 | + if (tz->trips.critical.flags.valid == 1) { | |
374 | + if (crt == -1) { | |
375 | + tz->trips.critical.flags.valid = 0; | |
376 | + } else if (crt > 0) { | |
377 | + unsigned long crt_k = CELSIUS_TO_KELVIN(crt); | |
378 | + /* | |
379 | + * Allow override to lower critical threshold | |
380 | + */ | |
381 | + if (crt_k < tz->trips.critical.temperature) | |
382 | + tz->trips.critical.temperature = crt_k; | |
383 | + } | |
384 | + } | |
360 | 385 | } |
361 | 386 | |
362 | 387 | /* Critical Sleep (optional) */ |
363 | - | |
364 | - status = | |
365 | - acpi_evaluate_integer(tz->device->handle, "_HOT", NULL, | |
366 | - &tz->trips.hot.temperature); | |
367 | - if (ACPI_FAILURE(status)) { | |
368 | - tz->trips.hot.flags.valid = 0; | |
369 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No hot threshold\n")); | |
370 | - } else { | |
371 | - tz->trips.hot.flags.valid = 1; | |
372 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found hot threshold [%lu]\n", | |
373 | - tz->trips.hot.temperature)); | |
374 | - } | |
375 | - | |
376 | - /* Passive: Processors (optional) */ | |
377 | - | |
378 | - if (psv == -1) { | |
379 | - status = AE_SUPPORT; | |
380 | - } else if (psv > 0) { | |
381 | - tz->trips.passive.temperature = CELSIUS_TO_KELVIN(psv); | |
382 | - status = AE_OK; | |
383 | - } else { | |
388 | + if (flag & ACPI_TRIPS_HOT) { | |
384 | 389 | status = acpi_evaluate_integer(tz->device->handle, |
385 | - "_PSV", NULL, &tz->trips.passive.temperature); | |
390 | + "_HOT", NULL, &tz->trips.hot.temperature); | |
391 | + if (ACPI_FAILURE(status)) { | |
392 | + tz->trips.hot.flags.valid = 0; | |
393 | + ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
394 | + "No hot threshold\n")); | |
395 | + } else { | |
396 | + tz->trips.hot.flags.valid = 1; | |
397 | + ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
398 | + "Found hot threshold [%lu]\n", | |
399 | + tz->trips.critical.temperature)); | |
400 | + } | |
386 | 401 | } |
387 | 402 | |
388 | - if (ACPI_FAILURE(status)) { | |
389 | - tz->trips.passive.flags.valid = 0; | |
390 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No passive threshold\n")); | |
391 | - } else { | |
392 | - tz->trips.passive.flags.valid = 1; | |
403 | + /* Passive (optional) */ | |
404 | + if (flag & ACPI_TRIPS_PASSIVE) { | |
405 | + valid = tz->trips.passive.flags.valid; | |
406 | + if (psv == -1) { | |
407 | + status = AE_SUPPORT; | |
408 | + } else if (psv > 0) { | |
409 | + tz->trips.passive.temperature = CELSIUS_TO_KELVIN(psv); | |
410 | + status = AE_OK; | |
411 | + } else { | |
412 | + status = acpi_evaluate_integer(tz->device->handle, | |
413 | + "_PSV", NULL, &tz->trips.passive.temperature); | |
414 | + } | |
393 | 415 | |
394 | - status = | |
395 | - acpi_evaluate_integer(tz->device->handle, "_TC1", NULL, | |
396 | - &tz->trips.passive.tc1); | |
397 | 416 | if (ACPI_FAILURE(status)) |
398 | 417 | tz->trips.passive.flags.valid = 0; |
399 | - | |
400 | - status = | |
401 | - acpi_evaluate_integer(tz->device->handle, "_TC2", NULL, | |
402 | - &tz->trips.passive.tc2); | |
418 | + else { | |
419 | + tz->trips.passive.flags.valid = 1; | |
420 | + if (flag == ACPI_TRIPS_INIT) { | |
421 | + status = acpi_evaluate_integer( | |
422 | + tz->device->handle, "_TC1", | |
423 | + NULL, &tz->trips.passive.tc1); | |
424 | + if (ACPI_FAILURE(status)) | |
425 | + tz->trips.passive.flags.valid = 0; | |
426 | + status = acpi_evaluate_integer( | |
427 | + tz->device->handle, "_TC2", | |
428 | + NULL, &tz->trips.passive.tc2); | |
429 | + if (ACPI_FAILURE(status)) | |
430 | + tz->trips.passive.flags.valid = 0; | |
431 | + status = acpi_evaluate_integer( | |
432 | + tz->device->handle, "_TSP", | |
433 | + NULL, &tz->trips.passive.tsp); | |
434 | + if (ACPI_FAILURE(status)) | |
435 | + tz->trips.passive.flags.valid = 0; | |
436 | + } | |
437 | + } | |
438 | + } | |
439 | + if ((flag & ACPI_TRIPS_DEVICES) && tz->trips.passive.flags.valid) { | |
440 | + memset(&devices, 0, sizeof(struct acpi_handle_list)); | |
441 | + status = acpi_evaluate_reference(tz->device->handle, "_PSL", | |
442 | + NULL, &devices); | |
403 | 443 | if (ACPI_FAILURE(status)) |
404 | 444 | tz->trips.passive.flags.valid = 0; |
405 | - | |
406 | - status = | |
407 | - acpi_evaluate_integer(tz->device->handle, "_TSP", NULL, | |
408 | - &tz->trips.passive.tsp); | |
409 | - if (ACPI_FAILURE(status)) | |
410 | - tz->trips.passive.flags.valid = 0; | |
411 | - | |
412 | - status = | |
413 | - acpi_evaluate_reference(tz->device->handle, "_PSL", NULL, | |
414 | - &tz->trips.passive.devices); | |
415 | - if (ACPI_FAILURE(status)) | |
416 | - tz->trips.passive.flags.valid = 0; | |
417 | - | |
418 | - if (!tz->trips.passive.flags.valid) | |
419 | - printk(KERN_WARNING PREFIX "Invalid passive threshold\n"); | |
420 | 445 | else |
421 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
422 | - "Found passive threshold [%lu]\n", | |
423 | - tz->trips.passive.temperature)); | |
446 | + tz->trips.passive.flags.valid = 1; | |
447 | + | |
448 | + if (memcmp(&tz->trips.passive.devices, &devices, | |
449 | + sizeof(struct acpi_handle_list))) { | |
450 | + memcpy(&tz->trips.passive.devices, &devices, | |
451 | + sizeof(struct acpi_handle_list)); | |
452 | + ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); | |
453 | + } | |
424 | 454 | } |
455 | + if ((flag & ACPI_TRIPS_PASSIVE) || (flag & ACPI_TRIPS_DEVICES)) { | |
456 | + if (valid != tz->trips.passive.flags.valid) | |
457 | + ACPI_THERMAL_TRIPS_EXCEPTION(flag, "state"); | |
458 | + } | |
425 | 459 | |
426 | - /* Active: Fans, etc. (optional) */ | |
427 | - | |
460 | + /* Active (optional) */ | |
428 | 461 | for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { |
429 | - | |
430 | 462 | char name[5] = { '_', 'A', 'C', ('0' + i), '\0' }; |
463 | + valid = tz->trips.active[i].flags.valid; | |
431 | 464 | |
432 | 465 | if (act == -1) |
433 | - break; /* disable all active trip points */ | |
466 | + break; /* disable all active trip points */ | |
434 | 467 | |
435 | - status = acpi_evaluate_integer(tz->device->handle, | |
436 | - name, NULL, &tz->trips.active[i].temperature); | |
437 | - | |
438 | - if (ACPI_FAILURE(status)) { | |
439 | - if (i == 0) /* no active trip points */ | |
468 | + if (flag & ACPI_TRIPS_ACTIVE) { | |
469 | + status = acpi_evaluate_integer(tz->device->handle, | |
470 | + name, NULL, &tz->trips.active[i].temperature); | |
471 | + if (ACPI_FAILURE(status)) { | |
472 | + tz->trips.active[i].flags.valid = 0; | |
473 | + if (i == 0) | |
474 | + break; | |
475 | + if (act <= 0) | |
476 | + break; | |
477 | + if (i == 1) | |
478 | + tz->trips.active[0].temperature = | |
479 | + CELSIUS_TO_KELVIN(act); | |
480 | + else | |
481 | + /* | |
482 | + * Don't allow override higher than | |
483 | + * the next higher trip point | |
484 | + */ | |
485 | + tz->trips.active[i - 1].temperature = | |
486 | + (tz->trips.active[i - 2].temperature < | |
487 | + CELSIUS_TO_KELVIN(act) ? | |
488 | + tz->trips.active[i - 2].temperature : | |
489 | + CELSIUS_TO_KELVIN(act)); | |
440 | 490 | break; |
441 | - if (act <= 0) /* no override requested */ | |
442 | - break; | |
443 | - if (i == 1) { /* 1 trip point */ | |
444 | - tz->trips.active[0].temperature = | |
445 | - CELSIUS_TO_KELVIN(act); | |
446 | - } else { /* multiple trips */ | |
447 | - /* | |
448 | - * Don't allow override higher than | |
449 | - * the next higher trip point | |
450 | - */ | |
451 | - tz->trips.active[i - 1].temperature = | |
452 | - (tz->trips.active[i - 2].temperature < | |
453 | - CELSIUS_TO_KELVIN(act) ? | |
454 | - tz->trips.active[i - 2].temperature : | |
455 | - CELSIUS_TO_KELVIN(act)); | |
456 | - } | |
457 | - break; | |
491 | + } else | |
492 | + tz->trips.active[i].flags.valid = 1; | |
458 | 493 | } |
459 | 494 | |
460 | 495 | name[2] = 'L'; |
461 | - status = | |
462 | - acpi_evaluate_reference(tz->device->handle, name, NULL, | |
463 | - &tz->trips.active[i].devices); | |
464 | - if (ACPI_SUCCESS(status)) { | |
465 | - tz->trips.active[i].flags.valid = 1; | |
466 | - ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
467 | - "Found active threshold [%d]:[%lu]\n", | |
468 | - i, tz->trips.active[i].temperature)); | |
469 | - } else | |
470 | - ACPI_EXCEPTION((AE_INFO, status, | |
471 | - "Invalid active threshold [%d]", i)); | |
496 | + if ((flag & ACPI_TRIPS_DEVICES) && tz->trips.active[i].flags.valid ) { | |
497 | + memset(&devices, 0, sizeof(struct acpi_handle_list)); | |
498 | + status = acpi_evaluate_reference(tz->device->handle, | |
499 | + name, NULL, &devices); | |
500 | + if (ACPI_FAILURE(status)) | |
501 | + tz->trips.active[i].flags.valid = 0; | |
502 | + else | |
503 | + tz->trips.active[i].flags.valid = 1; | |
504 | + | |
505 | + if (memcmp(&tz->trips.active[i].devices, &devices, | |
506 | + sizeof(struct acpi_handle_list))) { | |
507 | + memcpy(&tz->trips.active[i].devices, &devices, | |
508 | + sizeof(struct acpi_handle_list)); | |
509 | + ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); | |
510 | + } | |
511 | + } | |
512 | + if ((flag & ACPI_TRIPS_ACTIVE) || (flag & ACPI_TRIPS_DEVICES)) | |
513 | + if (valid != tz->trips.active[i].flags.valid) | |
514 | + ACPI_THERMAL_TRIPS_EXCEPTION(flag, "state"); | |
515 | + | |
516 | + if (!tz->trips.active[i].flags.valid) | |
517 | + break; | |
472 | 518 | } |
473 | 519 | |
520 | + if (flag & ACPI_TRIPS_DEVICES) { | |
521 | + memset(&devices, 0, sizeof(struct acpi_handle_list)); | |
522 | + status = acpi_evaluate_reference(tz->device->handle, "_TZD", | |
523 | + NULL, &devices); | |
524 | + if (memcmp(&tz->devices, &devices, | |
525 | + sizeof(struct acpi_handle_list))) { | |
526 | + memcpy(&tz->devices, &devices, | |
527 | + sizeof(struct acpi_handle_list)); | |
528 | + ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); | |
529 | + } | |
530 | + } | |
531 | + | |
474 | 532 | return 0; |
475 | 533 | } |
476 | 534 | |
477 | -static int acpi_thermal_get_devices(struct acpi_thermal *tz) | |
535 | +static int acpi_thermal_get_trip_points(struct acpi_thermal *tz) | |
478 | 536 | { |
479 | - acpi_status status = AE_OK; | |
480 | - | |
481 | - | |
482 | - if (!tz) | |
483 | - return -EINVAL; | |
484 | - | |
485 | - status = | |
486 | - acpi_evaluate_reference(tz->device->handle, "_TZD", NULL, &tz->devices); | |
487 | - if (ACPI_FAILURE(status)) | |
488 | - return -ENODEV; | |
489 | - | |
490 | - return 0; | |
537 | + return acpi_thermal_trips_update(tz, ACPI_TRIPS_INIT); | |
491 | 538 | } |
492 | 539 | |
493 | 540 | static int acpi_thermal_critical(struct acpi_thermal *tz) |
... | ... | @@ -735,6 +782,9 @@ |
735 | 782 | if (result) |
736 | 783 | goto unlock; |
737 | 784 | |
785 | + if (!tz->tz_enabled) | |
786 | + goto unlock; | |
787 | + | |
738 | 788 | memset(&tz->state, 0, sizeof(tz->state)); |
739 | 789 | |
740 | 790 | /* |
... | ... | @@ -828,6 +878,290 @@ |
828 | 878 | mutex_unlock(&tz->lock); |
829 | 879 | } |
830 | 880 | |
881 | +/* sys I/F for generic thermal sysfs support */ | |
882 | +static int thermal_get_temp(struct thermal_zone_device *thermal, char *buf) | |
883 | +{ | |
884 | + struct acpi_thermal *tz = thermal->devdata; | |
885 | + | |
886 | + if (!tz) | |
887 | + return -EINVAL; | |
888 | + | |
889 | + return sprintf(buf, "%ld\n", KELVIN_TO_CELSIUS(tz->temperature)); | |
890 | +} | |
891 | + | |
892 | +static const char enabled[] = "kernel"; | |
893 | +static const char disabled[] = "user"; | |
894 | +static int thermal_get_mode(struct thermal_zone_device *thermal, | |
895 | + char *buf) | |
896 | +{ | |
897 | + struct acpi_thermal *tz = thermal->devdata; | |
898 | + | |
899 | + if (!tz) | |
900 | + return -EINVAL; | |
901 | + | |
902 | + return sprintf(buf, "%s\n", tz->tz_enabled ? | |
903 | + enabled : disabled); | |
904 | +} | |
905 | + | |
906 | +static int thermal_set_mode(struct thermal_zone_device *thermal, | |
907 | + const char *buf) | |
908 | +{ | |
909 | + struct acpi_thermal *tz = thermal->devdata; | |
910 | + int enable; | |
911 | + | |
912 | + if (!tz) | |
913 | + return -EINVAL; | |
914 | + | |
915 | + /* | |
916 | + * enable/disable thermal management from ACPI thermal driver | |
917 | + */ | |
918 | + if (!strncmp(buf, enabled, sizeof enabled - 1)) | |
919 | + enable = 1; | |
920 | + else if (!strncmp(buf, disabled, sizeof disabled - 1)) | |
921 | + enable = 0; | |
922 | + else | |
923 | + return -EINVAL; | |
924 | + | |
925 | + if (enable != tz->tz_enabled) { | |
926 | + tz->tz_enabled = enable; | |
927 | + ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
928 | + "%s ACPI thermal control\n", | |
929 | + tz->tz_enabled ? enabled : disabled)); | |
930 | + acpi_thermal_check(tz); | |
931 | + } | |
932 | + return 0; | |
933 | +} | |
934 | + | |
935 | +static int thermal_get_trip_type(struct thermal_zone_device *thermal, | |
936 | + int trip, char *buf) | |
937 | +{ | |
938 | + struct acpi_thermal *tz = thermal->devdata; | |
939 | + int i; | |
940 | + | |
941 | + if (!tz || trip < 0) | |
942 | + return -EINVAL; | |
943 | + | |
944 | + if (tz->trips.critical.flags.valid) { | |
945 | + if (!trip) | |
946 | + return sprintf(buf, "critical\n"); | |
947 | + trip--; | |
948 | + } | |
949 | + | |
950 | + if (tz->trips.hot.flags.valid) { | |
951 | + if (!trip) | |
952 | + return sprintf(buf, "hot\n"); | |
953 | + trip--; | |
954 | + } | |
955 | + | |
956 | + if (tz->trips.passive.flags.valid) { | |
957 | + if (!trip) | |
958 | + return sprintf(buf, "passive\n"); | |
959 | + trip--; | |
960 | + } | |
961 | + | |
962 | + for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE && | |
963 | + tz->trips.active[i].flags.valid; i++) { | |
964 | + if (!trip) | |
965 | + return sprintf(buf, "active%d\n", i); | |
966 | + trip--; | |
967 | + } | |
968 | + | |
969 | + return -EINVAL; | |
970 | +} | |
971 | + | |
972 | +static int thermal_get_trip_temp(struct thermal_zone_device *thermal, | |
973 | + int trip, char *buf) | |
974 | +{ | |
975 | + struct acpi_thermal *tz = thermal->devdata; | |
976 | + int i; | |
977 | + | |
978 | + if (!tz || trip < 0) | |
979 | + return -EINVAL; | |
980 | + | |
981 | + if (tz->trips.critical.flags.valid) { | |
982 | + if (!trip) | |
983 | + return sprintf(buf, "%ld\n", KELVIN_TO_CELSIUS( | |
984 | + tz->trips.critical.temperature)); | |
985 | + trip--; | |
986 | + } | |
987 | + | |
988 | + if (tz->trips.hot.flags.valid) { | |
989 | + if (!trip) | |
990 | + return sprintf(buf, "%ld\n", KELVIN_TO_CELSIUS( | |
991 | + tz->trips.hot.temperature)); | |
992 | + trip--; | |
993 | + } | |
994 | + | |
995 | + if (tz->trips.passive.flags.valid) { | |
996 | + if (!trip) | |
997 | + return sprintf(buf, "%ld\n", KELVIN_TO_CELSIUS( | |
998 | + tz->trips.passive.temperature)); | |
999 | + trip--; | |
1000 | + } | |
1001 | + | |
1002 | + for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE && | |
1003 | + tz->trips.active[i].flags.valid; i++) { | |
1004 | + if (!trip) | |
1005 | + return sprintf(buf, "%ld\n", KELVIN_TO_CELSIUS( | |
1006 | + tz->trips.active[i].temperature)); | |
1007 | + trip--; | |
1008 | + } | |
1009 | + | |
1010 | + return -EINVAL; | |
1011 | +} | |
1012 | + | |
1013 | +typedef int (*cb)(struct thermal_zone_device *, int, | |
1014 | + struct thermal_cooling_device *); | |
1015 | +static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, | |
1016 | + struct thermal_cooling_device *cdev, | |
1017 | + cb action) | |
1018 | +{ | |
1019 | + struct acpi_device *device = cdev->devdata; | |
1020 | + struct acpi_thermal *tz = thermal->devdata; | |
1021 | + struct acpi_device *dev; | |
1022 | + acpi_status status; | |
1023 | + acpi_handle handle; | |
1024 | + int i; | |
1025 | + int j; | |
1026 | + int trip = -1; | |
1027 | + int result = 0; | |
1028 | + | |
1029 | + if (tz->trips.critical.flags.valid) | |
1030 | + trip++; | |
1031 | + | |
1032 | + if (tz->trips.hot.flags.valid) | |
1033 | + trip++; | |
1034 | + | |
1035 | + if (tz->trips.passive.flags.valid) { | |
1036 | + trip++; | |
1037 | + for (i = 0; i < tz->trips.passive.devices.count; | |
1038 | + i++) { | |
1039 | + handle = tz->trips.passive.devices.handles[i]; | |
1040 | + status = acpi_bus_get_device(handle, &dev); | |
1041 | + if (ACPI_SUCCESS(status) && (dev == device)) { | |
1042 | + result = action(thermal, trip, cdev); | |
1043 | + if (result) | |
1044 | + goto failed; | |
1045 | + } | |
1046 | + } | |
1047 | + } | |
1048 | + | |
1049 | + for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { | |
1050 | + if (!tz->trips.active[i].flags.valid) | |
1051 | + break; | |
1052 | + trip++; | |
1053 | + for (j = 0; | |
1054 | + j < tz->trips.active[i].devices.count; | |
1055 | + j++) { | |
1056 | + handle = tz->trips.active[i].devices.handles[j]; | |
1057 | + status = acpi_bus_get_device(handle, &dev); | |
1058 | + if (ACPI_SUCCESS(status) && (dev == device)) { | |
1059 | + result = action(thermal, trip, cdev); | |
1060 | + if (result) | |
1061 | + goto failed; | |
1062 | + } | |
1063 | + } | |
1064 | + } | |
1065 | + | |
1066 | + for (i = 0; i < tz->devices.count; i++) { | |
1067 | + handle = tz->devices.handles[i]; | |
1068 | + status = acpi_bus_get_device(handle, &dev); | |
1069 | + if (ACPI_SUCCESS(status) && (dev == device)) { | |
1070 | + result = action(thermal, -1, cdev); | |
1071 | + if (result) | |
1072 | + goto failed; | |
1073 | + } | |
1074 | + } | |
1075 | + | |
1076 | +failed: | |
1077 | + return result; | |
1078 | +} | |
1079 | + | |
1080 | +static int | |
1081 | +acpi_thermal_bind_cooling_device(struct thermal_zone_device *thermal, | |
1082 | + struct thermal_cooling_device *cdev) | |
1083 | +{ | |
1084 | + return acpi_thermal_cooling_device_cb(thermal, cdev, | |
1085 | + thermal_zone_bind_cooling_device); | |
1086 | +} | |
1087 | + | |
1088 | +static int | |
1089 | +acpi_thermal_unbind_cooling_device(struct thermal_zone_device *thermal, | |
1090 | + struct thermal_cooling_device *cdev) | |
1091 | +{ | |
1092 | + return acpi_thermal_cooling_device_cb(thermal, cdev, | |
1093 | + thermal_zone_unbind_cooling_device); | |
1094 | +} | |
1095 | + | |
1096 | +static struct thermal_zone_device_ops acpi_thermal_zone_ops = { | |
1097 | + .bind = acpi_thermal_bind_cooling_device, | |
1098 | + .unbind = acpi_thermal_unbind_cooling_device, | |
1099 | + .get_temp = thermal_get_temp, | |
1100 | + .get_mode = thermal_get_mode, | |
1101 | + .set_mode = thermal_set_mode, | |
1102 | + .get_trip_type = thermal_get_trip_type, | |
1103 | + .get_trip_temp = thermal_get_trip_temp, | |
1104 | +}; | |
1105 | + | |
1106 | +static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz) | |
1107 | +{ | |
1108 | + int trips = 0; | |
1109 | + int result; | |
1110 | + acpi_status status; | |
1111 | + int i; | |
1112 | + | |
1113 | + if (tz->trips.critical.flags.valid) | |
1114 | + trips++; | |
1115 | + | |
1116 | + if (tz->trips.hot.flags.valid) | |
1117 | + trips++; | |
1118 | + | |
1119 | + if (tz->trips.passive.flags.valid) | |
1120 | + trips++; | |
1121 | + | |
1122 | + for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE && | |
1123 | + tz->trips.active[i].flags.valid; i++, trips++); | |
1124 | + tz->thermal_zone = thermal_zone_device_register("ACPI thermal zone", | |
1125 | + trips, tz, &acpi_thermal_zone_ops); | |
1126 | + if (!tz->thermal_zone) | |
1127 | + return -ENODEV; | |
1128 | + | |
1129 | + result = sysfs_create_link(&tz->device->dev.kobj, | |
1130 | + &tz->thermal_zone->device.kobj, "thermal_zone"); | |
1131 | + if (result) | |
1132 | + return result; | |
1133 | + | |
1134 | + result = sysfs_create_link(&tz->thermal_zone->device.kobj, | |
1135 | + &tz->device->dev.kobj, "device"); | |
1136 | + if (result) | |
1137 | + return result; | |
1138 | + | |
1139 | + status = acpi_attach_data(tz->device->handle, | |
1140 | + acpi_bus_private_data_handler, | |
1141 | + tz->thermal_zone); | |
1142 | + if (ACPI_FAILURE(status)) { | |
1143 | + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, | |
1144 | + "Error attaching device data\n")); | |
1145 | + return -ENODEV; | |
1146 | + } | |
1147 | + | |
1148 | + tz->tz_enabled = 1; | |
1149 | + | |
1150 | + printk(KERN_INFO PREFIX "%s is registered as thermal_zone%d\n", | |
1151 | + tz->device->dev.bus_id, tz->thermal_zone->id); | |
1152 | + return 0; | |
1153 | +} | |
1154 | + | |
1155 | +static void acpi_thermal_unregister_thermal_zone(struct acpi_thermal *tz) | |
1156 | +{ | |
1157 | + sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone"); | |
1158 | + sysfs_remove_link(&tz->thermal_zone->device.kobj, "device"); | |
1159 | + thermal_zone_device_unregister(tz->thermal_zone); | |
1160 | + tz->thermal_zone = NULL; | |
1161 | + acpi_detach_data(tz->device->handle, acpi_bus_private_data_handler); | |
1162 | +} | |
1163 | + | |
1164 | + | |
831 | 1165 | /* -------------------------------------------------------------------------- |
832 | 1166 | FS Interface (/proc) |
833 | 1167 | -------------------------------------------------------------------------- */ |
834 | 1168 | |
... | ... | @@ -1184,15 +1518,15 @@ |
1184 | 1518 | acpi_thermal_check(tz); |
1185 | 1519 | break; |
1186 | 1520 | case ACPI_THERMAL_NOTIFY_THRESHOLDS: |
1187 | - acpi_thermal_get_trip_points(tz); | |
1521 | + acpi_thermal_trips_update(tz, ACPI_TRIPS_REFRESH_THRESHOLDS); | |
1188 | 1522 | acpi_thermal_check(tz); |
1189 | 1523 | acpi_bus_generate_proc_event(device, event, 0); |
1190 | 1524 | acpi_bus_generate_netlink_event(device->pnp.device_class, |
1191 | 1525 | device->dev.bus_id, event, 0); |
1192 | 1526 | break; |
1193 | 1527 | case ACPI_THERMAL_NOTIFY_DEVICES: |
1194 | - if (tz->flags.devices) | |
1195 | - acpi_thermal_get_devices(tz); | |
1528 | + acpi_thermal_trips_update(tz, ACPI_TRIPS_REFRESH_DEVICES); | |
1529 | + acpi_thermal_check(tz); | |
1196 | 1530 | acpi_bus_generate_proc_event(device, event, 0); |
1197 | 1531 | acpi_bus_generate_netlink_event(device->pnp.device_class, |
1198 | 1532 | device->dev.bus_id, event, 0); |
... | ... | @@ -1235,11 +1569,6 @@ |
1235 | 1569 | else |
1236 | 1570 | acpi_thermal_get_polling_frequency(tz); |
1237 | 1571 | |
1238 | - /* Get devices in this thermal zone [_TZD] (optional) */ | |
1239 | - result = acpi_thermal_get_devices(tz); | |
1240 | - if (!result) | |
1241 | - tz->flags.devices = 1; | |
1242 | - | |
1243 | 1572 | return 0; |
1244 | 1573 | } |
1245 | 1574 | |
1246 | 1575 | |
1247 | 1576 | |
1248 | 1577 | |
... | ... | @@ -1263,13 +1592,19 @@ |
1263 | 1592 | strcpy(acpi_device_class(device), ACPI_THERMAL_CLASS); |
1264 | 1593 | acpi_driver_data(device) = tz; |
1265 | 1594 | mutex_init(&tz->lock); |
1595 | + | |
1596 | + | |
1266 | 1597 | result = acpi_thermal_get_info(tz); |
1267 | 1598 | if (result) |
1268 | - goto end; | |
1599 | + goto free_memory; | |
1269 | 1600 | |
1601 | + result = acpi_thermal_register_thermal_zone(tz); | |
1602 | + if (result) | |
1603 | + goto free_memory; | |
1604 | + | |
1270 | 1605 | result = acpi_thermal_add_fs(device); |
1271 | 1606 | if (result) |
1272 | - goto end; | |
1607 | + goto unregister_thermal_zone; | |
1273 | 1608 | |
1274 | 1609 | init_timer(&tz->timer); |
1275 | 1610 | |
1276 | 1611 | |
1277 | 1612 | |
... | ... | @@ -1280,19 +1615,21 @@ |
1280 | 1615 | acpi_thermal_notify, tz); |
1281 | 1616 | if (ACPI_FAILURE(status)) { |
1282 | 1617 | result = -ENODEV; |
1283 | - goto end; | |
1618 | + goto remove_fs; | |
1284 | 1619 | } |
1285 | 1620 | |
1286 | 1621 | printk(KERN_INFO PREFIX "%s [%s] (%ld C)\n", |
1287 | 1622 | acpi_device_name(device), acpi_device_bid(device), |
1288 | 1623 | KELVIN_TO_CELSIUS(tz->temperature)); |
1624 | + goto end; | |
1289 | 1625 | |
1290 | - end: | |
1291 | - if (result) { | |
1292 | - acpi_thermal_remove_fs(device); | |
1293 | - kfree(tz); | |
1294 | - } | |
1295 | - | |
1626 | +remove_fs: | |
1627 | + acpi_thermal_remove_fs(device); | |
1628 | +unregister_thermal_zone: | |
1629 | + thermal_zone_device_unregister(tz->thermal_zone); | |
1630 | +free_memory: | |
1631 | + kfree(tz); | |
1632 | +end: | |
1296 | 1633 | return result; |
1297 | 1634 | } |
1298 | 1635 | |
... | ... | @@ -1332,6 +1669,7 @@ |
1332 | 1669 | } |
1333 | 1670 | |
1334 | 1671 | acpi_thermal_remove_fs(device); |
1672 | + acpi_thermal_unregister_thermal_zone(tz); | |
1335 | 1673 | mutex_destroy(&tz->lock); |
1336 | 1674 | kfree(tz); |
1337 | 1675 | return 0; |
drivers/acpi/video.c
... | ... | @@ -34,6 +34,7 @@ |
34 | 34 | #include <linux/seq_file.h> |
35 | 35 | #include <linux/input.h> |
36 | 36 | #include <linux/backlight.h> |
37 | +#include <linux/thermal.h> | |
37 | 38 | #include <linux/video_output.h> |
38 | 39 | #include <asm/uaccess.h> |
39 | 40 | |
... | ... | @@ -179,6 +180,7 @@ |
179 | 180 | struct acpi_device *dev; |
180 | 181 | struct acpi_video_device_brightness *brightness; |
181 | 182 | struct backlight_device *backlight; |
183 | + struct thermal_cooling_device *cdev; | |
182 | 184 | struct output_device *output_dev; |
183 | 185 | }; |
184 | 186 | |
... | ... | @@ -342,6 +344,54 @@ |
342 | 344 | .set_state = acpi_video_output_set, |
343 | 345 | .get_status = acpi_video_output_get, |
344 | 346 | }; |
347 | + | |
348 | + | |
349 | +/* thermal cooling device callbacks */ | |
350 | +static int video_get_max_state(struct thermal_cooling_device *cdev, char *buf) | |
351 | +{ | |
352 | + struct acpi_device *device = cdev->devdata; | |
353 | + struct acpi_video_device *video = acpi_driver_data(device); | |
354 | + | |
355 | + return sprintf(buf, "%d\n", video->brightness->count - 3); | |
356 | +} | |
357 | + | |
358 | +static int video_get_cur_state(struct thermal_cooling_device *cdev, char *buf) | |
359 | +{ | |
360 | + struct acpi_device *device = cdev->devdata; | |
361 | + struct acpi_video_device *video = acpi_driver_data(device); | |
362 | + unsigned long level; | |
363 | + int state; | |
364 | + | |
365 | + acpi_video_device_lcd_get_level_current(video, &level); | |
366 | + for (state = 2; state < video->brightness->count; state++) | |
367 | + if (level == video->brightness->levels[state]) | |
368 | + return sprintf(buf, "%d\n", | |
369 | + video->brightness->count - state - 1); | |
370 | + | |
371 | + return -EINVAL; | |
372 | +} | |
373 | + | |
374 | +static int | |
375 | +video_set_cur_state(struct thermal_cooling_device *cdev, unsigned int state) | |
376 | +{ | |
377 | + struct acpi_device *device = cdev->devdata; | |
378 | + struct acpi_video_device *video = acpi_driver_data(device); | |
379 | + int level; | |
380 | + | |
381 | + if ( state >= video->brightness->count - 2) | |
382 | + return -EINVAL; | |
383 | + | |
384 | + state = video->brightness->count - state; | |
385 | + level = video->brightness->levels[state -1]; | |
386 | + return acpi_video_device_lcd_set_level(video, level); | |
387 | +} | |
388 | + | |
389 | +static struct thermal_cooling_device_ops video_cooling_ops = { | |
390 | + .get_max_state = video_get_max_state, | |
391 | + .get_cur_state = video_get_cur_state, | |
392 | + .set_cur_state = video_set_cur_state, | |
393 | +}; | |
394 | + | |
345 | 395 | /* -------------------------------------------------------------------------- |
346 | 396 | Video Management |
347 | 397 | -------------------------------------------------------------------------- */ |
... | ... | @@ -660,6 +710,7 @@ |
660 | 710 | kfree(obj); |
661 | 711 | |
662 | 712 | if (device->cap._BCL && device->cap._BCM && device->cap._BQC && max_level > 0){ |
713 | + int result; | |
663 | 714 | static int count = 0; |
664 | 715 | char *name; |
665 | 716 | name = kzalloc(MAX_NAME_LEN, GFP_KERNEL); |
666 | 717 | |
... | ... | @@ -672,8 +723,25 @@ |
672 | 723 | device->backlight->props.max_brightness = device->brightness->count-3; |
673 | 724 | device->backlight->props.brightness = acpi_video_get_brightness(device->backlight); |
674 | 725 | backlight_update_status(device->backlight); |
675 | - | |
676 | 726 | kfree(name); |
727 | + | |
728 | + device->cdev = thermal_cooling_device_register("LCD", | |
729 | + device->dev, &video_cooling_ops); | |
730 | + if (device->cdev) { | |
731 | + printk(KERN_INFO PREFIX | |
732 | + "%s is registered as cooling_device%d\n", | |
733 | + device->dev->dev.bus_id, device->cdev->id); | |
734 | + result = sysfs_create_link(&device->dev->dev.kobj, | |
735 | + &device->cdev->device.kobj, | |
736 | + "thermal_cooling"); | |
737 | + if (result) | |
738 | + printk(KERN_ERR PREFIX "Create sysfs link\n"); | |
739 | + result = sysfs_create_link(&device->cdev->device.kobj, | |
740 | + &device->dev->dev.kobj, | |
741 | + "device"); | |
742 | + if (result) | |
743 | + printk(KERN_ERR PREFIX "Create sysfs link\n"); | |
744 | + } | |
677 | 745 | } |
678 | 746 | if (device->cap._DCS && device->cap._DSS){ |
679 | 747 | static int count = 0; |
... | ... | @@ -1764,6 +1832,14 @@ |
1764 | 1832 | ACPI_DEVICE_NOTIFY, |
1765 | 1833 | acpi_video_device_notify); |
1766 | 1834 | backlight_device_unregister(device->backlight); |
1835 | + if (device->cdev) { | |
1836 | + sysfs_remove_link(&device->dev->dev.kobj, | |
1837 | + "thermal_cooling"); | |
1838 | + sysfs_remove_link(&device->cdev->device.kobj, | |
1839 | + "device"); | |
1840 | + thermal_cooling_device_unregister(device->cdev); | |
1841 | + device->cdev = NULL; | |
1842 | + } | |
1767 | 1843 | video_output_unregister(device->output_dev); |
1768 | 1844 | |
1769 | 1845 | return 0; |
drivers/misc/Kconfig
... | ... | @@ -251,5 +251,14 @@ |
251 | 251 | |
252 | 252 | If unsure, say N. |
253 | 253 | |
254 | +config INTEL_MENLOW | |
255 | + tristate "Thermal Management driver for Intel menlow platform" | |
256 | + depends on ACPI_THERMAL | |
257 | + ---help--- | |
258 | + ACPI thermal management enhancement driver on | |
259 | + Intel Menlow platform. | |
260 | + | |
261 | + If unsure, say N. | |
262 | + | |
254 | 263 | endif # MISC_DEVICES |
drivers/misc/Makefile
drivers/misc/intel_menlow.c
1 | +/* | |
2 | + * intel_menlow.c - Intel menlow Driver for thermal management extension | |
3 | + * | |
4 | + * Copyright (C) 2008 Intel Corp | |
5 | + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | |
6 | + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> | |
7 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
8 | + * | |
9 | + * This program is free software; you can redistribute it and/or modify | |
10 | + * it under the terms of the GNU General Public License as published by | |
11 | + * the Free Software Foundation; version 2 of the License. | |
12 | + * | |
13 | + * This program is distributed in the hope that it will be useful, but | |
14 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | + * General Public License for more details. | |
17 | + * | |
18 | + * You should have received a copy of the GNU General Public License along | |
19 | + * with this program; if not, write to the Free Software Foundation, Inc., | |
20 | + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
21 | + * | |
22 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
23 | + * | |
24 | + * This driver creates the sys I/F for programming the sensors. | |
25 | + * It also implements the driver for intel menlow memory controller (hardware | |
26 | + * id is INT0002) which makes use of the platform specific ACPI methods | |
27 | + * to get/set bandwidth. | |
28 | + */ | |
29 | + | |
30 | +#include <linux/kernel.h> | |
31 | +#include <linux/module.h> | |
32 | +#include <linux/init.h> | |
33 | +#include <linux/types.h> | |
34 | +#include <linux/pci.h> | |
35 | +#include <linux/pm.h> | |
36 | + | |
37 | +#include <linux/thermal.h> | |
38 | +#include <acpi/acpi_bus.h> | |
39 | +#include <acpi/acpi_drivers.h> | |
40 | + | |
41 | +MODULE_AUTHOR("Thomas Sujith"); | |
42 | +MODULE_AUTHOR("Zhang Rui"); | |
43 | +MODULE_DESCRIPTION("Intel Menlow platform specific driver"); | |
44 | +MODULE_LICENSE("GPL"); | |
45 | + | |
46 | +/* | |
47 | + * Memory controller device control | |
48 | + */ | |
49 | + | |
50 | +#define MEMORY_GET_BANDWIDTH "GTHS" | |
51 | +#define MEMORY_SET_BANDWIDTH "STHS" | |
52 | +#define MEMORY_ARG_CUR_BANDWIDTH 1 | |
53 | +#define MEMORY_ARG_MAX_BANDWIDTH 0 | |
54 | + | |
55 | +static int memory_get_int_max_bandwidth(struct thermal_cooling_device *cdev, | |
56 | + unsigned long *max_state) | |
57 | +{ | |
58 | + struct acpi_device *device = cdev->devdata; | |
59 | + acpi_handle handle = device->handle; | |
60 | + unsigned long value; | |
61 | + struct acpi_object_list arg_list; | |
62 | + union acpi_object arg; | |
63 | + acpi_status status = AE_OK; | |
64 | + | |
65 | + arg_list.count = 1; | |
66 | + arg_list.pointer = &arg; | |
67 | + arg.type = ACPI_TYPE_INTEGER; | |
68 | + arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH; | |
69 | + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, | |
70 | + &arg_list, &value); | |
71 | + if (ACPI_FAILURE(status)) | |
72 | + return -EFAULT; | |
73 | + | |
74 | + *max_state = value - 1; | |
75 | + return 0; | |
76 | +} | |
77 | + | |
78 | +static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev, | |
79 | + char *buf) | |
80 | +{ | |
81 | + unsigned long value; | |
82 | + if (memory_get_int_max_bandwidth(cdev, &value)) | |
83 | + return -EINVAL; | |
84 | + | |
85 | + return sprintf(buf, "%ld\n", value); | |
86 | +} | |
87 | + | |
88 | +static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, | |
89 | + char *buf) | |
90 | +{ | |
91 | + struct acpi_device *device = cdev->devdata; | |
92 | + acpi_handle handle = device->handle; | |
93 | + unsigned long value; | |
94 | + struct acpi_object_list arg_list; | |
95 | + union acpi_object arg; | |
96 | + acpi_status status = AE_OK; | |
97 | + | |
98 | + arg_list.count = 1; | |
99 | + arg_list.pointer = &arg; | |
100 | + arg.type = ACPI_TYPE_INTEGER; | |
101 | + arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH; | |
102 | + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, | |
103 | + &arg_list, &value); | |
104 | + if (ACPI_FAILURE(status)) | |
105 | + return -EFAULT; | |
106 | + | |
107 | + return sprintf(buf, "%ld\n", value); | |
108 | +} | |
109 | + | |
110 | +static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, | |
111 | + unsigned int state) | |
112 | +{ | |
113 | + struct acpi_device *device = cdev->devdata; | |
114 | + acpi_handle handle = device->handle; | |
115 | + struct acpi_object_list arg_list; | |
116 | + union acpi_object arg; | |
117 | + acpi_status status; | |
118 | + int temp; | |
119 | + unsigned long max_state; | |
120 | + | |
121 | + if (memory_get_int_max_bandwidth(cdev, &max_state)) | |
122 | + return -EFAULT; | |
123 | + | |
124 | + if (max_state < 0 || state > max_state) | |
125 | + return -EINVAL; | |
126 | + | |
127 | + arg_list.count = 1; | |
128 | + arg_list.pointer = &arg; | |
129 | + arg.type = ACPI_TYPE_INTEGER; | |
130 | + arg.integer.value = state; | |
131 | + | |
132 | + status = | |
133 | + acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, | |
134 | + (unsigned long *)&temp); | |
135 | + | |
136 | + printk(KERN_INFO | |
137 | + "Bandwidth value was %d: status is %d\n", state, status); | |
138 | + if (ACPI_FAILURE(status)) | |
139 | + return -EFAULT; | |
140 | + | |
141 | + return 0; | |
142 | +} | |
143 | + | |
144 | +static struct thermal_cooling_device_ops memory_cooling_ops = { | |
145 | + .get_max_state = memory_get_max_bandwidth, | |
146 | + .get_cur_state = memory_get_cur_bandwidth, | |
147 | + .set_cur_state = memory_set_cur_bandwidth, | |
148 | +}; | |
149 | + | |
150 | +/* | |
151 | + * Memory Device Management | |
152 | + */ | |
153 | +static int intel_menlow_memory_add(struct acpi_device *device) | |
154 | +{ | |
155 | + int result = -ENODEV; | |
156 | + acpi_status status = AE_OK; | |
157 | + acpi_handle dummy; | |
158 | + struct thermal_cooling_device *cdev; | |
159 | + | |
160 | + if (!device) | |
161 | + return -EINVAL; | |
162 | + | |
163 | + status = acpi_get_handle(device->handle, MEMORY_GET_BANDWIDTH, &dummy); | |
164 | + if (ACPI_FAILURE(status)) | |
165 | + goto end; | |
166 | + | |
167 | + status = acpi_get_handle(device->handle, MEMORY_SET_BANDWIDTH, &dummy); | |
168 | + if (ACPI_FAILURE(status)) | |
169 | + goto end; | |
170 | + | |
171 | + cdev = thermal_cooling_device_register("Memory controller", device, | |
172 | + &memory_cooling_ops); | |
173 | + acpi_driver_data(device) = cdev; | |
174 | + if (!cdev) | |
175 | + result = -ENODEV; | |
176 | + else { | |
177 | + result = sysfs_create_link(&device->dev.kobj, | |
178 | + &cdev->device.kobj, "thermal_cooling"); | |
179 | + if (result) | |
180 | + goto unregister; | |
181 | + | |
182 | + result = sysfs_create_link(&cdev->device.kobj, | |
183 | + &device->dev.kobj, "device"); | |
184 | + if (result) { | |
185 | + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); | |
186 | + goto unregister; | |
187 | + } | |
188 | + } | |
189 | + | |
190 | + end: | |
191 | + return result; | |
192 | + | |
193 | + unregister: | |
194 | + thermal_cooling_device_unregister(cdev); | |
195 | + return result; | |
196 | + | |
197 | +} | |
198 | + | |
199 | +static int intel_menlow_memory_remove(struct acpi_device *device, int type) | |
200 | +{ | |
201 | + struct thermal_cooling_device *cdev = acpi_driver_data(device); | |
202 | + | |
203 | + if (!device || !cdev) | |
204 | + return -EINVAL; | |
205 | + | |
206 | + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); | |
207 | + sysfs_remove_link(&cdev->device.kobj, "device"); | |
208 | + thermal_cooling_device_unregister(cdev); | |
209 | + | |
210 | + return 0; | |
211 | +} | |
212 | + | |
213 | +const static struct acpi_device_id intel_menlow_memory_ids[] = { | |
214 | + {"INT0002", 0}, | |
215 | + {"", 0}, | |
216 | +}; | |
217 | + | |
218 | +static struct acpi_driver intel_menlow_memory_driver = { | |
219 | + .name = "intel_menlow_thermal_control", | |
220 | + .ids = intel_menlow_memory_ids, | |
221 | + .ops = { | |
222 | + .add = intel_menlow_memory_add, | |
223 | + .remove = intel_menlow_memory_remove, | |
224 | + }, | |
225 | +}; | |
226 | + | |
227 | +/* | |
228 | + * Sensor control on menlow platform | |
229 | + */ | |
230 | + | |
231 | +#define THERMAL_AUX0 0 | |
232 | +#define THERMAL_AUX1 1 | |
233 | +#define GET_AUX0 "GAX0" | |
234 | +#define GET_AUX1 "GAX1" | |
235 | +#define SET_AUX0 "SAX0" | |
236 | +#define SET_AUX1 "SAX1" | |
237 | + | |
238 | +struct intel_menlow_attribute { | |
239 | + struct device_attribute attr; | |
240 | + struct device *device; | |
241 | + acpi_handle handle; | |
242 | + struct list_head node; | |
243 | +}; | |
244 | + | |
245 | +static LIST_HEAD(intel_menlow_attr_list); | |
246 | +static DEFINE_MUTEX(intel_menlow_attr_lock); | |
247 | + | |
248 | +/* | |
249 | + * sensor_get_auxtrip - get the current auxtrip value from sensor | |
250 | + * @name: Thermalzone name | |
251 | + * @auxtype : AUX0/AUX1 | |
252 | + * @buf: syfs buffer | |
253 | + */ | |
254 | +static int sensor_get_auxtrip(acpi_handle handle, int index, int *value) | |
255 | +{ | |
256 | + acpi_status status; | |
257 | + | |
258 | + if ((index != 0 && index != 1) || !value) | |
259 | + return -EINVAL; | |
260 | + | |
261 | + status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, | |
262 | + NULL, (unsigned long *)value); | |
263 | + if (ACPI_FAILURE(status)) | |
264 | + return -EIO; | |
265 | + | |
266 | + return 0; | |
267 | +} | |
268 | + | |
269 | +/* | |
270 | + * sensor_set_auxtrip - set the new auxtrip value to sensor | |
271 | + * @name: Thermalzone name | |
272 | + * @auxtype : AUX0/AUX1 | |
273 | + * @buf: syfs buffer | |
274 | + */ | |
275 | +static int sensor_set_auxtrip(acpi_handle handle, int index, int value) | |
276 | +{ | |
277 | + acpi_status status; | |
278 | + union acpi_object arg = { | |
279 | + ACPI_TYPE_INTEGER | |
280 | + }; | |
281 | + struct acpi_object_list args = { | |
282 | + 1, &arg | |
283 | + }; | |
284 | + int temp; | |
285 | + | |
286 | + if (index != 0 && index != 1) | |
287 | + return -EINVAL; | |
288 | + | |
289 | + status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, | |
290 | + NULL, (unsigned long *)&temp); | |
291 | + if (ACPI_FAILURE(status)) | |
292 | + return -EIO; | |
293 | + if ((index && value < temp) || (!index && value > temp)) | |
294 | + return -EINVAL; | |
295 | + | |
296 | + arg.integer.value = value; | |
297 | + status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, | |
298 | + &args, (unsigned long *)&temp); | |
299 | + if (ACPI_FAILURE(status)) | |
300 | + return -EIO; | |
301 | + | |
302 | + /* do we need to check the return value of SAX0/SAX1 ? */ | |
303 | + | |
304 | + return 0; | |
305 | +} | |
306 | + | |
307 | +#define to_intel_menlow_attr(_attr) \ | |
308 | + container_of(_attr, struct intel_menlow_attribute, attr) | |
309 | + | |
310 | +static ssize_t aux0_show(struct device *dev, | |
311 | + struct device_attribute *dev_attr, char *buf) | |
312 | +{ | |
313 | + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); | |
314 | + int value; | |
315 | + int result; | |
316 | + | |
317 | + result = sensor_get_auxtrip(attr->handle, 0, &value); | |
318 | + | |
319 | + return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value)); | |
320 | +} | |
321 | + | |
322 | +static ssize_t aux1_show(struct device *dev, | |
323 | + struct device_attribute *dev_attr, char *buf) | |
324 | +{ | |
325 | + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); | |
326 | + int value; | |
327 | + int result; | |
328 | + | |
329 | + result = sensor_get_auxtrip(attr->handle, 1, &value); | |
330 | + | |
331 | + return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value)); | |
332 | +} | |
333 | + | |
334 | +static ssize_t aux0_store(struct device *dev, | |
335 | + struct device_attribute *dev_attr, | |
336 | + const char *buf, size_t count) | |
337 | +{ | |
338 | + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); | |
339 | + int value; | |
340 | + int result; | |
341 | + | |
342 | + /*Sanity check; should be a positive integer */ | |
343 | + if (!sscanf(buf, "%d", &value)) | |
344 | + return -EINVAL; | |
345 | + | |
346 | + if (value < 0) | |
347 | + return -EINVAL; | |
348 | + | |
349 | + result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_KELVIN(value)); | |
350 | + return result ? result : count; | |
351 | +} | |
352 | + | |
353 | +static ssize_t aux1_store(struct device *dev, | |
354 | + struct device_attribute *dev_attr, | |
355 | + const char *buf, size_t count) | |
356 | +{ | |
357 | + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); | |
358 | + int value; | |
359 | + int result; | |
360 | + | |
361 | + /*Sanity check; should be a positive integer */ | |
362 | + if (!sscanf(buf, "%d", &value)) | |
363 | + return -EINVAL; | |
364 | + | |
365 | + if (value < 0) | |
366 | + return -EINVAL; | |
367 | + | |
368 | + result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_KELVIN(value)); | |
369 | + return result ? result : count; | |
370 | +} | |
371 | + | |
372 | +/* BIOS can enable/disable the thermal user application in dabney platform */ | |
373 | +#define BIOS_ENABLED "\\_TZ.GSTS" | |
374 | +static ssize_t bios_enabled_show(struct device *dev, | |
375 | + struct device_attribute *attr, char *buf) | |
376 | +{ | |
377 | + acpi_status status; | |
378 | + unsigned long bios_enabled; | |
379 | + | |
380 | + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); | |
381 | + if (ACPI_FAILURE(status)) | |
382 | + return -ENODEV; | |
383 | + | |
384 | + return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); | |
385 | +} | |
386 | + | |
387 | +static int intel_menlow_add_one_attribute(char *name, int mode, void *show, | |
388 | + void *store, struct device *dev, | |
389 | + acpi_handle handle) | |
390 | +{ | |
391 | + struct intel_menlow_attribute *attr; | |
392 | + int result; | |
393 | + | |
394 | + attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL); | |
395 | + if (!attr) | |
396 | + return -ENOMEM; | |
397 | + | |
398 | + attr->attr.attr.name = name; | |
399 | + attr->attr.attr.mode = mode; | |
400 | + attr->attr.show = show; | |
401 | + attr->attr.store = store; | |
402 | + attr->device = dev; | |
403 | + attr->handle = handle; | |
404 | + | |
405 | + result = device_create_file(dev, &attr->attr); | |
406 | + if (result) | |
407 | + return result; | |
408 | + | |
409 | + mutex_lock(&intel_menlow_attr_lock); | |
410 | + list_add_tail(&attr->node, &intel_menlow_attr_list); | |
411 | + mutex_unlock(&intel_menlow_attr_lock); | |
412 | + | |
413 | + return 0; | |
414 | +} | |
415 | + | |
416 | +static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, | |
417 | + void *context, void **rv) | |
418 | +{ | |
419 | + acpi_status status; | |
420 | + acpi_handle dummy; | |
421 | + struct thermal_zone_device *thermal; | |
422 | + int result; | |
423 | + | |
424 | + result = acpi_bus_get_private_data(handle, (void **)&thermal); | |
425 | + if (result) | |
426 | + return 0; | |
427 | + | |
428 | + /* _TZ must have the AUX0/1 methods */ | |
429 | + status = acpi_get_handle(handle, GET_AUX0, &dummy); | |
430 | + if (ACPI_FAILURE(status)) | |
431 | + goto not_found; | |
432 | + | |
433 | + status = acpi_get_handle(handle, SET_AUX0, &dummy); | |
434 | + if (ACPI_FAILURE(status)) | |
435 | + goto not_found; | |
436 | + | |
437 | + result = intel_menlow_add_one_attribute("aux0", 0644, | |
438 | + aux0_show, aux0_store, | |
439 | + &thermal->device, handle); | |
440 | + if (result) | |
441 | + return AE_ERROR; | |
442 | + | |
443 | + status = acpi_get_handle(handle, GET_AUX1, &dummy); | |
444 | + if (ACPI_FAILURE(status)) | |
445 | + goto not_found; | |
446 | + | |
447 | + status = acpi_get_handle(handle, SET_AUX1, &dummy); | |
448 | + if (ACPI_FAILURE(status)) | |
449 | + goto not_found; | |
450 | + | |
451 | + result = intel_menlow_add_one_attribute("aux1", 0644, | |
452 | + aux1_show, aux1_store, | |
453 | + &thermal->device, handle); | |
454 | + if (result) | |
455 | + return AE_ERROR; | |
456 | + | |
457 | + /* | |
458 | + * create the "dabney_enabled" attribute which means the user app | |
459 | + * should be loaded or not | |
460 | + */ | |
461 | + | |
462 | + result = intel_menlow_add_one_attribute("bios_enabled", 0444, | |
463 | + bios_enabled_show, NULL, | |
464 | + &thermal->device, handle); | |
465 | + if (result) | |
466 | + return AE_ERROR; | |
467 | + | |
468 | + not_found: | |
469 | + if (status == AE_NOT_FOUND) | |
470 | + return AE_OK; | |
471 | + else | |
472 | + return status; | |
473 | +} | |
474 | + | |
475 | +static void intel_menlow_unregister_sensor(void) | |
476 | +{ | |
477 | + struct intel_menlow_attribute *pos, *next; | |
478 | + | |
479 | + mutex_lock(&intel_menlow_attr_lock); | |
480 | + list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) { | |
481 | + list_del(&pos->node); | |
482 | + device_remove_file(pos->device, &pos->attr); | |
483 | + kfree(pos); | |
484 | + } | |
485 | + mutex_unlock(&intel_menlow_attr_lock); | |
486 | + | |
487 | + return; | |
488 | +} | |
489 | + | |
490 | +static int __init intel_menlow_module_init(void) | |
491 | +{ | |
492 | + int result = -ENODEV; | |
493 | + acpi_status status; | |
494 | + unsigned long enable; | |
495 | + | |
496 | + if (acpi_disabled) | |
497 | + return result; | |
498 | + | |
499 | + /* Looking for the \_TZ.GSTS method */ | |
500 | + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable); | |
501 | + if (ACPI_FAILURE(status) || !enable) | |
502 | + return -ENODEV; | |
503 | + | |
504 | + /* Looking for ACPI device MEM0 with hardware id INT0002 */ | |
505 | + result = acpi_bus_register_driver(&intel_menlow_memory_driver); | |
506 | + if (result) | |
507 | + return result; | |
508 | + | |
509 | + /* Looking for sensors in each ACPI thermal zone */ | |
510 | + status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, | |
511 | + ACPI_UINT32_MAX, | |
512 | + intel_menlow_register_sensor, NULL, NULL); | |
513 | + if (ACPI_FAILURE(status)) | |
514 | + return -ENODEV; | |
515 | + | |
516 | + return 0; | |
517 | +} | |
518 | + | |
519 | +static void __exit intel_menlow_module_exit(void) | |
520 | +{ | |
521 | + acpi_bus_unregister_driver(&intel_menlow_memory_driver); | |
522 | + intel_menlow_unregister_sensor(); | |
523 | +} | |
524 | + | |
525 | +module_init(intel_menlow_module_init); | |
526 | +module_exit(intel_menlow_module_exit); |
drivers/thermal/Kconfig
1 | +# | |
2 | +# Generic thermal sysfs drivers configuration | |
3 | +# | |
4 | + | |
5 | +menuconfig THERMAL | |
6 | + bool "Generic Thermal sysfs driver" | |
7 | + default y | |
8 | + help | |
9 | + Generic Thermal Sysfs driver offers a generic mechanism for | |
10 | + thermal management. Usually it's made up of one or more thermal | |
11 | + zone and cooling device. | |
12 | + each thermal zone contains its own temperature, trip points, | |
13 | + cooling devices. | |
14 | + All platforms with ACPI thermal support can use this driver. | |
15 | + If you want this support, you should say Y here |
drivers/thermal/Makefile
drivers/thermal/thermal.c
1 | +/* | |
2 | + * thermal.c - Generic Thermal Management Sysfs support. | |
3 | + * | |
4 | + * Copyright (C) 2008 Intel Corp | |
5 | + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> | |
6 | + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | |
7 | + * | |
8 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
9 | + * | |
10 | + * This program is free software; you can redistribute it and/or modify | |
11 | + * it under the terms of the GNU General Public License as published by | |
12 | + * the Free Software Foundation; version 2 of the License. | |
13 | + * | |
14 | + * This program is distributed in the hope that it will be useful, but | |
15 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | + * General Public License for more details. | |
18 | + * | |
19 | + * You should have received a copy of the GNU General Public License along | |
20 | + * with this program; if not, write to the Free Software Foundation, Inc., | |
21 | + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
22 | + * | |
23 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
24 | + */ | |
25 | + | |
26 | +#include <linux/module.h> | |
27 | +#include <linux/device.h> | |
28 | +#include <linux/err.h> | |
29 | +#include <linux/kdev_t.h> | |
30 | +#include <linux/idr.h> | |
31 | +#include <linux/thermal.h> | |
32 | +#include <linux/spinlock.h> | |
33 | + | |
34 | +MODULE_AUTHOR("Zhang Rui") | |
35 | +MODULE_DESCRIPTION("Generic thermal management sysfs support"); | |
36 | +MODULE_LICENSE("GPL"); | |
37 | + | |
38 | +#define PREFIX "Thermal: " | |
39 | + | |
40 | +struct thermal_cooling_device_instance { | |
41 | + int id; | |
42 | + char name[THERMAL_NAME_LENGTH]; | |
43 | + struct thermal_zone_device *tz; | |
44 | + struct thermal_cooling_device *cdev; | |
45 | + int trip; | |
46 | + char attr_name[THERMAL_NAME_LENGTH]; | |
47 | + struct device_attribute attr; | |
48 | + struct list_head node; | |
49 | +}; | |
50 | + | |
51 | +static DEFINE_IDR(thermal_tz_idr); | |
52 | +static DEFINE_IDR(thermal_cdev_idr); | |
53 | +static DEFINE_MUTEX(thermal_idr_lock); | |
54 | + | |
55 | +static LIST_HEAD(thermal_tz_list); | |
56 | +static LIST_HEAD(thermal_cdev_list); | |
57 | +static DEFINE_MUTEX(thermal_list_lock); | |
58 | + | |
59 | +static int get_idr(struct idr *idr, struct mutex *lock, int *id) | |
60 | +{ | |
61 | + int err; | |
62 | + | |
63 | + again: | |
64 | + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) | |
65 | + return -ENOMEM; | |
66 | + | |
67 | + if (lock) | |
68 | + mutex_lock(lock); | |
69 | + err = idr_get_new(idr, NULL, id); | |
70 | + if (lock) | |
71 | + mutex_unlock(lock); | |
72 | + if (unlikely(err == -EAGAIN)) | |
73 | + goto again; | |
74 | + else if (unlikely(err)) | |
75 | + return err; | |
76 | + | |
77 | + *id = *id & MAX_ID_MASK; | |
78 | + return 0; | |
79 | +} | |
80 | + | |
81 | +static void release_idr(struct idr *idr, struct mutex *lock, int id) | |
82 | +{ | |
83 | + if (lock) | |
84 | + mutex_lock(lock); | |
85 | + idr_remove(idr, id); | |
86 | + if (lock) | |
87 | + mutex_unlock(lock); | |
88 | +} | |
89 | + | |
90 | +/* sys I/F for thermal zone */ | |
91 | + | |
92 | +#define to_thermal_zone(_dev) \ | |
93 | + container_of(_dev, struct thermal_zone_device, device) | |
94 | + | |
95 | +static ssize_t | |
96 | +type_show(struct device *dev, struct device_attribute *attr, char *buf) | |
97 | +{ | |
98 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
99 | + | |
100 | + return sprintf(buf, "%s\n", tz->type); | |
101 | +} | |
102 | + | |
103 | +static ssize_t | |
104 | +temp_show(struct device *dev, struct device_attribute *attr, char *buf) | |
105 | +{ | |
106 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
107 | + | |
108 | + if (!tz->ops->get_temp) | |
109 | + return -EPERM; | |
110 | + | |
111 | + return tz->ops->get_temp(tz, buf); | |
112 | +} | |
113 | + | |
114 | +static ssize_t | |
115 | +mode_show(struct device *dev, struct device_attribute *attr, char *buf) | |
116 | +{ | |
117 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
118 | + | |
119 | + if (!tz->ops->get_mode) | |
120 | + return -EPERM; | |
121 | + | |
122 | + return tz->ops->get_mode(tz, buf); | |
123 | +} | |
124 | + | |
125 | +static ssize_t | |
126 | +mode_store(struct device *dev, struct device_attribute *attr, | |
127 | + const char *buf, size_t count) | |
128 | +{ | |
129 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
130 | + int result; | |
131 | + | |
132 | + if (!tz->ops->set_mode) | |
133 | + return -EPERM; | |
134 | + | |
135 | + result = tz->ops->set_mode(tz, buf); | |
136 | + if (result) | |
137 | + return result; | |
138 | + | |
139 | + return count; | |
140 | +} | |
141 | + | |
142 | +static ssize_t | |
143 | +trip_point_type_show(struct device *dev, struct device_attribute *attr, | |
144 | + char *buf) | |
145 | +{ | |
146 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
147 | + int trip; | |
148 | + | |
149 | + if (!tz->ops->get_trip_type) | |
150 | + return -EPERM; | |
151 | + | |
152 | + if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip)) | |
153 | + return -EINVAL; | |
154 | + | |
155 | + return tz->ops->get_trip_type(tz, trip, buf); | |
156 | +} | |
157 | + | |
158 | +static ssize_t | |
159 | +trip_point_temp_show(struct device *dev, struct device_attribute *attr, | |
160 | + char *buf) | |
161 | +{ | |
162 | + struct thermal_zone_device *tz = to_thermal_zone(dev); | |
163 | + int trip; | |
164 | + | |
165 | + if (!tz->ops->get_trip_temp) | |
166 | + return -EPERM; | |
167 | + | |
168 | + if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip)) | |
169 | + return -EINVAL; | |
170 | + | |
171 | + return tz->ops->get_trip_temp(tz, trip, buf); | |
172 | +} | |
173 | + | |
174 | +static DEVICE_ATTR(type, 0444, type_show, NULL); | |
175 | +static DEVICE_ATTR(temp, 0444, temp_show, NULL); | |
176 | +static DEVICE_ATTR(mode, 0644, mode_show, mode_store); | |
177 | + | |
178 | +static struct device_attribute trip_point_attrs[] = { | |
179 | + __ATTR(trip_point_0_type, 0444, trip_point_type_show, NULL), | |
180 | + __ATTR(trip_point_0_temp, 0444, trip_point_temp_show, NULL), | |
181 | + __ATTR(trip_point_1_type, 0444, trip_point_type_show, NULL), | |
182 | + __ATTR(trip_point_1_temp, 0444, trip_point_temp_show, NULL), | |
183 | + __ATTR(trip_point_2_type, 0444, trip_point_type_show, NULL), | |
184 | + __ATTR(trip_point_2_temp, 0444, trip_point_temp_show, NULL), | |
185 | + __ATTR(trip_point_3_type, 0444, trip_point_type_show, NULL), | |
186 | + __ATTR(trip_point_3_temp, 0444, trip_point_temp_show, NULL), | |
187 | + __ATTR(trip_point_4_type, 0444, trip_point_type_show, NULL), | |
188 | + __ATTR(trip_point_4_temp, 0444, trip_point_temp_show, NULL), | |
189 | + __ATTR(trip_point_5_type, 0444, trip_point_type_show, NULL), | |
190 | + __ATTR(trip_point_5_temp, 0444, trip_point_temp_show, NULL), | |
191 | + __ATTR(trip_point_6_type, 0444, trip_point_type_show, NULL), | |
192 | + __ATTR(trip_point_6_temp, 0444, trip_point_temp_show, NULL), | |
193 | + __ATTR(trip_point_7_type, 0444, trip_point_type_show, NULL), | |
194 | + __ATTR(trip_point_7_temp, 0444, trip_point_temp_show, NULL), | |
195 | + __ATTR(trip_point_8_type, 0444, trip_point_type_show, NULL), | |
196 | + __ATTR(trip_point_8_temp, 0444, trip_point_temp_show, NULL), | |
197 | + __ATTR(trip_point_9_type, 0444, trip_point_type_show, NULL), | |
198 | + __ATTR(trip_point_9_temp, 0444, trip_point_temp_show, NULL), | |
199 | +}; | |
200 | + | |
201 | +#define TRIP_POINT_ATTR_ADD(_dev, _index, result) \ | |
202 | +do { \ | |
203 | + result = device_create_file(_dev, \ | |
204 | + &trip_point_attrs[_index * 2]); \ | |
205 | + if (result) \ | |
206 | + break; \ | |
207 | + result = device_create_file(_dev, \ | |
208 | + &trip_point_attrs[_index * 2 + 1]); \ | |
209 | +} while (0) | |
210 | + | |
211 | +#define TRIP_POINT_ATTR_REMOVE(_dev, _index) \ | |
212 | +do { \ | |
213 | + device_remove_file(_dev, &trip_point_attrs[_index * 2]); \ | |
214 | + device_remove_file(_dev, &trip_point_attrs[_index * 2 + 1]); \ | |
215 | +} while (0) | |
216 | + | |
217 | +/* sys I/F for cooling device */ | |
218 | +#define to_cooling_device(_dev) \ | |
219 | + container_of(_dev, struct thermal_cooling_device, device) | |
220 | + | |
221 | +static ssize_t | |
222 | +thermal_cooling_device_type_show(struct device *dev, | |
223 | + struct device_attribute *attr, char *buf) | |
224 | +{ | |
225 | + struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
226 | + | |
227 | + return sprintf(buf, "%s\n", cdev->type); | |
228 | +} | |
229 | + | |
230 | +static ssize_t | |
231 | +thermal_cooling_device_max_state_show(struct device *dev, | |
232 | + struct device_attribute *attr, char *buf) | |
233 | +{ | |
234 | + struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
235 | + | |
236 | + return cdev->ops->get_max_state(cdev, buf); | |
237 | +} | |
238 | + | |
239 | +static ssize_t | |
240 | +thermal_cooling_device_cur_state_show(struct device *dev, | |
241 | + struct device_attribute *attr, char *buf) | |
242 | +{ | |
243 | + struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
244 | + | |
245 | + return cdev->ops->get_cur_state(cdev, buf); | |
246 | +} | |
247 | + | |
248 | +static ssize_t | |
249 | +thermal_cooling_device_cur_state_store(struct device *dev, | |
250 | + struct device_attribute *attr, | |
251 | + const char *buf, size_t count) | |
252 | +{ | |
253 | + struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
254 | + int state; | |
255 | + int result; | |
256 | + | |
257 | + if (!sscanf(buf, "%d\n", &state)) | |
258 | + return -EINVAL; | |
259 | + | |
260 | + if (state < 0) | |
261 | + return -EINVAL; | |
262 | + | |
263 | + result = cdev->ops->set_cur_state(cdev, state); | |
264 | + if (result) | |
265 | + return result; | |
266 | + return count; | |
267 | +} | |
268 | + | |
269 | +static struct device_attribute dev_attr_cdev_type = | |
270 | + __ATTR(type, 0444, thermal_cooling_device_type_show, NULL); | |
271 | +static DEVICE_ATTR(max_state, 0444, | |
272 | + thermal_cooling_device_max_state_show, NULL); | |
273 | +static DEVICE_ATTR(cur_state, 0644, | |
274 | + thermal_cooling_device_cur_state_show, | |
275 | + thermal_cooling_device_cur_state_store); | |
276 | + | |
277 | +static ssize_t | |
278 | +thermal_cooling_device_trip_point_show(struct device *dev, | |
279 | + struct device_attribute *attr, char *buf) | |
280 | +{ | |
281 | + struct thermal_cooling_device_instance *instance; | |
282 | + | |
283 | + instance = | |
284 | + container_of(attr, struct thermal_cooling_device_instance, attr); | |
285 | + | |
286 | + if (instance->trip == THERMAL_TRIPS_NONE) | |
287 | + return sprintf(buf, "-1\n"); | |
288 | + else | |
289 | + return sprintf(buf, "%d\n", instance->trip); | |
290 | +} | |
291 | + | |
292 | +/* Device management */ | |
293 | + | |
294 | +/** | |
295 | + * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone | |
296 | + * this function is usually called in the thermal zone device .bind callback. | |
297 | + * @tz: thermal zone device | |
298 | + * @trip: indicates which trip point the cooling devices is | |
299 | + * associated with in this thermal zone. | |
300 | + * @cdev: thermal cooling device | |
301 | + */ | |
302 | +int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, | |
303 | + int trip, | |
304 | + struct thermal_cooling_device *cdev) | |
305 | +{ | |
306 | + struct thermal_cooling_device_instance *dev; | |
307 | + struct thermal_cooling_device_instance *pos; | |
308 | + int result; | |
309 | + | |
310 | + if (trip >= tz->trips || | |
311 | + (trip < 0 && trip != THERMAL_TRIPS_NONE)) | |
312 | + return -EINVAL; | |
313 | + | |
314 | + if (!tz || !cdev) | |
315 | + return -EINVAL; | |
316 | + | |
317 | + dev = | |
318 | + kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL); | |
319 | + if (!dev) | |
320 | + return -ENOMEM; | |
321 | + dev->tz = tz; | |
322 | + dev->cdev = cdev; | |
323 | + dev->trip = trip; | |
324 | + result = get_idr(&tz->idr, &tz->lock, &dev->id); | |
325 | + if (result) | |
326 | + goto free_mem; | |
327 | + | |
328 | + sprintf(dev->name, "cdev%d", dev->id); | |
329 | + result = | |
330 | + sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name); | |
331 | + if (result) | |
332 | + goto release_idr; | |
333 | + | |
334 | + sprintf(dev->attr_name, "cdev%d_trip_point", dev->id); | |
335 | + dev->attr.attr.name = dev->attr_name; | |
336 | + dev->attr.attr.mode = 0444; | |
337 | + dev->attr.show = thermal_cooling_device_trip_point_show; | |
338 | + result = device_create_file(&tz->device, &dev->attr); | |
339 | + if (result) | |
340 | + goto remove_symbol_link; | |
341 | + | |
342 | + mutex_lock(&tz->lock); | |
343 | + list_for_each_entry(pos, &tz->cooling_devices, node) | |
344 | + if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { | |
345 | + result = -EEXIST; | |
346 | + break; | |
347 | + } | |
348 | + if (!result) | |
349 | + list_add_tail(&dev->node, &tz->cooling_devices); | |
350 | + mutex_unlock(&tz->lock); | |
351 | + | |
352 | + if (!result) | |
353 | + return 0; | |
354 | + | |
355 | + device_remove_file(&tz->device, &dev->attr); | |
356 | + remove_symbol_link: | |
357 | + sysfs_remove_link(&tz->device.kobj, dev->name); | |
358 | + release_idr: | |
359 | + release_idr(&tz->idr, &tz->lock, dev->id); | |
360 | + free_mem: | |
361 | + kfree(dev); | |
362 | + return result; | |
363 | +} | |
364 | +EXPORT_SYMBOL(thermal_zone_bind_cooling_device); | |
365 | + | |
366 | +/** | |
367 | + * thermal_zone_unbind_cooling_device - unbind a cooling device from a thermal zone | |
368 | + * this function is usually called in the thermal zone device .unbind callback. | |
369 | + * @tz: thermal zone device | |
370 | + * @trip: indicates which trip point the cooling devices is | |
371 | + * associated with in this thermal zone. | |
372 | + * @cdev: thermal cooling device | |
373 | + */ | |
374 | +int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, | |
375 | + int trip, | |
376 | + struct thermal_cooling_device *cdev) | |
377 | +{ | |
378 | + struct thermal_cooling_device_instance *pos, *next; | |
379 | + | |
380 | + mutex_lock(&tz->lock); | |
381 | + list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) { | |
382 | + if (pos->tz == tz && pos->trip == trip | |
383 | + && pos->cdev == cdev) { | |
384 | + list_del(&pos->node); | |
385 | + mutex_unlock(&tz->lock); | |
386 | + goto unbind; | |
387 | + } | |
388 | + } | |
389 | + mutex_unlock(&tz->lock); | |
390 | + | |
391 | + return -ENODEV; | |
392 | + | |
393 | + unbind: | |
394 | + device_remove_file(&tz->device, &pos->attr); | |
395 | + sysfs_remove_link(&tz->device.kobj, pos->name); | |
396 | + release_idr(&tz->idr, &tz->lock, pos->id); | |
397 | + kfree(pos); | |
398 | + return 0; | |
399 | +} | |
400 | +EXPORT_SYMBOL(thermal_zone_unbind_cooling_device); | |
401 | + | |
402 | +static void thermal_release(struct device *dev) | |
403 | +{ | |
404 | + struct thermal_zone_device *tz; | |
405 | + struct thermal_cooling_device *cdev; | |
406 | + | |
407 | + if (!strncmp(dev->bus_id, "thermal_zone", sizeof "thermal_zone" - 1)) { | |
408 | + tz = to_thermal_zone(dev); | |
409 | + kfree(tz); | |
410 | + } else { | |
411 | + cdev = to_cooling_device(dev); | |
412 | + kfree(cdev); | |
413 | + } | |
414 | +} | |
415 | + | |
416 | +static struct class thermal_class = { | |
417 | + .name = "thermal", | |
418 | + .dev_release = thermal_release, | |
419 | +}; | |
420 | + | |
421 | +/** | |
422 | + * thermal_cooling_device_register - register a new thermal cooling device | |
423 | + * @type: the thermal cooling device type. | |
424 | + * @devdata: device private data. | |
425 | + * @ops: standard thermal cooling devices callbacks. | |
426 | + */ | |
427 | +struct thermal_cooling_device *thermal_cooling_device_register(char *type, | |
428 | + void *devdata, struct thermal_cooling_device_ops *ops) | |
429 | +{ | |
430 | + struct thermal_cooling_device *cdev; | |
431 | + struct thermal_zone_device *pos; | |
432 | + int result; | |
433 | + | |
434 | + if (strlen(type) >= THERMAL_NAME_LENGTH) | |
435 | + return NULL; | |
436 | + | |
437 | + if (!ops || !ops->get_max_state || !ops->get_cur_state || | |
438 | + !ops->set_cur_state) | |
439 | + return NULL; | |
440 | + | |
441 | + cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL); | |
442 | + if (!cdev) | |
443 | + return NULL; | |
444 | + | |
445 | + result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id); | |
446 | + if (result) { | |
447 | + kfree(cdev); | |
448 | + return NULL; | |
449 | + } | |
450 | + | |
451 | + strcpy(cdev->type, type); | |
452 | + cdev->ops = ops; | |
453 | + cdev->device.class = &thermal_class; | |
454 | + cdev->devdata = devdata; | |
455 | + sprintf(cdev->device.bus_id, "cooling_device%d", cdev->id); | |
456 | + result = device_register(&cdev->device); | |
457 | + if (result) { | |
458 | + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); | |
459 | + kfree(cdev); | |
460 | + return NULL; | |
461 | + } | |
462 | + | |
463 | + /* sys I/F */ | |
464 | + if (type) { | |
465 | + result = device_create_file(&cdev->device, | |
466 | + &dev_attr_cdev_type); | |
467 | + if (result) | |
468 | + goto unregister; | |
469 | + } | |
470 | + | |
471 | + result = device_create_file(&cdev->device, &dev_attr_max_state); | |
472 | + if (result) | |
473 | + goto unregister; | |
474 | + | |
475 | + result = device_create_file(&cdev->device, &dev_attr_cur_state); | |
476 | + if (result) | |
477 | + goto unregister; | |
478 | + | |
479 | + mutex_lock(&thermal_list_lock); | |
480 | + list_add(&cdev->node, &thermal_cdev_list); | |
481 | + list_for_each_entry(pos, &thermal_tz_list, node) { | |
482 | + if (!pos->ops->bind) | |
483 | + continue; | |
484 | + result = pos->ops->bind(pos, cdev); | |
485 | + if (result) | |
486 | + break; | |
487 | + | |
488 | + } | |
489 | + mutex_unlock(&thermal_list_lock); | |
490 | + | |
491 | + if (!result) | |
492 | + return cdev; | |
493 | + | |
494 | + unregister: | |
495 | + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); | |
496 | + device_unregister(&cdev->device); | |
497 | + return NULL; | |
498 | +} | |
499 | +EXPORT_SYMBOL(thermal_cooling_device_register); | |
500 | + | |
501 | +/** | |
502 | + * thermal_cooling_device_unregister - removes the registered thermal cooling device | |
503 | + * | |
504 | + * @cdev: the thermal cooling device to remove. | |
505 | + * | |
506 | + * thermal_cooling_device_unregister() must be called when the device is no | |
507 | + * longer needed. | |
508 | + */ | |
509 | +void thermal_cooling_device_unregister(struct | |
510 | + thermal_cooling_device | |
511 | + *cdev) | |
512 | +{ | |
513 | + struct thermal_zone_device *tz; | |
514 | + struct thermal_cooling_device *pos = NULL; | |
515 | + | |
516 | + if (!cdev) | |
517 | + return; | |
518 | + | |
519 | + mutex_lock(&thermal_list_lock); | |
520 | + list_for_each_entry(pos, &thermal_cdev_list, node) | |
521 | + if (pos == cdev) | |
522 | + break; | |
523 | + if (pos != cdev) { | |
524 | + /* thermal cooling device not found */ | |
525 | + mutex_unlock(&thermal_list_lock); | |
526 | + return; | |
527 | + } | |
528 | + list_del(&cdev->node); | |
529 | + list_for_each_entry(tz, &thermal_tz_list, node) { | |
530 | + if (!tz->ops->unbind) | |
531 | + continue; | |
532 | + tz->ops->unbind(tz, cdev); | |
533 | + } | |
534 | + mutex_unlock(&thermal_list_lock); | |
535 | + if (cdev->type[0]) | |
536 | + device_remove_file(&cdev->device, | |
537 | + &dev_attr_cdev_type); | |
538 | + device_remove_file(&cdev->device, &dev_attr_max_state); | |
539 | + device_remove_file(&cdev->device, &dev_attr_cur_state); | |
540 | + | |
541 | + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); | |
542 | + device_unregister(&cdev->device); | |
543 | + return; | |
544 | +} | |
545 | +EXPORT_SYMBOL(thermal_cooling_device_unregister); | |
546 | + | |
547 | +/** | |
548 | + * thermal_zone_device_register - register a new thermal zone device | |
549 | + * @type: the thermal zone device type | |
550 | + * @trips: the number of trip points the thermal zone support | |
551 | + * @devdata: private device data | |
552 | + * @ops: standard thermal zone device callbacks | |
553 | + * | |
554 | + * thermal_zone_device_unregister() must be called when the device is no | |
555 | + * longer needed. | |
556 | + */ | |
557 | +struct thermal_zone_device *thermal_zone_device_register(char *type, | |
558 | + int trips, void *devdata, | |
559 | + struct thermal_zone_device_ops *ops) | |
560 | +{ | |
561 | + struct thermal_zone_device *tz; | |
562 | + struct thermal_cooling_device *pos; | |
563 | + int result; | |
564 | + int count; | |
565 | + | |
566 | + if (strlen(type) >= THERMAL_NAME_LENGTH) | |
567 | + return NULL; | |
568 | + | |
569 | + if (trips > THERMAL_MAX_TRIPS || trips < 0) | |
570 | + return NULL; | |
571 | + | |
572 | + if (!ops || !ops->get_temp) | |
573 | + return NULL; | |
574 | + | |
575 | + tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); | |
576 | + if (!tz) | |
577 | + return NULL; | |
578 | + | |
579 | + INIT_LIST_HEAD(&tz->cooling_devices); | |
580 | + idr_init(&tz->idr); | |
581 | + mutex_init(&tz->lock); | |
582 | + result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id); | |
583 | + if (result) { | |
584 | + kfree(tz); | |
585 | + return NULL; | |
586 | + } | |
587 | + | |
588 | + strcpy(tz->type, type); | |
589 | + tz->ops = ops; | |
590 | + tz->device.class = &thermal_class; | |
591 | + tz->devdata = devdata; | |
592 | + tz->trips = trips; | |
593 | + sprintf(tz->device.bus_id, "thermal_zone%d", tz->id); | |
594 | + result = device_register(&tz->device); | |
595 | + if (result) { | |
596 | + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); | |
597 | + kfree(tz); | |
598 | + return NULL; | |
599 | + } | |
600 | + | |
601 | + /* sys I/F */ | |
602 | + if (type) { | |
603 | + result = device_create_file(&tz->device, &dev_attr_type); | |
604 | + if (result) | |
605 | + goto unregister; | |
606 | + } | |
607 | + | |
608 | + result = device_create_file(&tz->device, &dev_attr_temp); | |
609 | + if (result) | |
610 | + goto unregister; | |
611 | + | |
612 | + if (ops->get_mode) { | |
613 | + result = device_create_file(&tz->device, &dev_attr_mode); | |
614 | + if (result) | |
615 | + goto unregister; | |
616 | + } | |
617 | + | |
618 | + for (count = 0; count < trips; count++) { | |
619 | + TRIP_POINT_ATTR_ADD(&tz->device, count, result); | |
620 | + if (result) | |
621 | + goto unregister; | |
622 | + } | |
623 | + | |
624 | + mutex_lock(&thermal_list_lock); | |
625 | + list_add_tail(&tz->node, &thermal_tz_list); | |
626 | + if (ops->bind) | |
627 | + list_for_each_entry(pos, &thermal_cdev_list, node) { | |
628 | + result = ops->bind(tz, pos); | |
629 | + if (result) | |
630 | + break; | |
631 | + } | |
632 | + mutex_unlock(&thermal_list_lock); | |
633 | + | |
634 | + if (!result) | |
635 | + return tz; | |
636 | + | |
637 | + unregister: | |
638 | + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); | |
639 | + device_unregister(&tz->device); | |
640 | + return NULL; | |
641 | +} | |
642 | +EXPORT_SYMBOL(thermal_zone_device_register); | |
643 | + | |
644 | +/** | |
645 | + * thermal_device_unregister - removes the registered thermal zone device | |
646 | + * | |
647 | + * @tz: the thermal zone device to remove | |
648 | + */ | |
649 | +void thermal_zone_device_unregister(struct thermal_zone_device *tz) | |
650 | +{ | |
651 | + struct thermal_cooling_device *cdev; | |
652 | + struct thermal_zone_device *pos = NULL; | |
653 | + int count; | |
654 | + | |
655 | + if (!tz) | |
656 | + return; | |
657 | + | |
658 | + mutex_lock(&thermal_list_lock); | |
659 | + list_for_each_entry(pos, &thermal_tz_list, node) | |
660 | + if (pos == tz) | |
661 | + break; | |
662 | + if (pos != tz) { | |
663 | + /* thermal zone device not found */ | |
664 | + mutex_unlock(&thermal_list_lock); | |
665 | + return; | |
666 | + } | |
667 | + list_del(&tz->node); | |
668 | + if (tz->ops->unbind) | |
669 | + list_for_each_entry(cdev, &thermal_cdev_list, node) | |
670 | + tz->ops->unbind(tz, cdev); | |
671 | + mutex_unlock(&thermal_list_lock); | |
672 | + | |
673 | + if (tz->type[0]) | |
674 | + device_remove_file(&tz->device, &dev_attr_type); | |
675 | + device_remove_file(&tz->device, &dev_attr_temp); | |
676 | + if (tz->ops->get_mode) | |
677 | + device_remove_file(&tz->device, &dev_attr_mode); | |
678 | + | |
679 | + for (count = 0; count < tz->trips; count++) | |
680 | + TRIP_POINT_ATTR_REMOVE(&tz->device, count); | |
681 | + | |
682 | + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); | |
683 | + idr_destroy(&tz->idr); | |
684 | + mutex_destroy(&tz->lock); | |
685 | + device_unregister(&tz->device); | |
686 | + return; | |
687 | +} | |
688 | +EXPORT_SYMBOL(thermal_zone_device_unregister); | |
689 | + | |
690 | +static int __init thermal_init(void) | |
691 | +{ | |
692 | + int result = 0; | |
693 | + | |
694 | + result = class_register(&thermal_class); | |
695 | + if (result) { | |
696 | + idr_destroy(&thermal_tz_idr); | |
697 | + idr_destroy(&thermal_cdev_idr); | |
698 | + mutex_destroy(&thermal_idr_lock); | |
699 | + mutex_destroy(&thermal_list_lock); | |
700 | + } | |
701 | + return result; | |
702 | +} | |
703 | + | |
704 | +static void __exit thermal_exit(void) | |
705 | +{ | |
706 | + class_unregister(&thermal_class); | |
707 | + idr_destroy(&thermal_tz_idr); | |
708 | + idr_destroy(&thermal_cdev_idr); | |
709 | + mutex_destroy(&thermal_idr_lock); | |
710 | + mutex_destroy(&thermal_list_lock); | |
711 | +} | |
712 | + | |
713 | +subsys_initcall(thermal_init); | |
714 | +module_exit(thermal_exit); |
include/acpi/acpi_bus.h
... | ... | @@ -321,6 +321,8 @@ |
321 | 321 | |
322 | 322 | extern struct kobject *acpi_kobj; |
323 | 323 | extern int acpi_bus_generate_netlink_event(const char*, const char*, u8, int); |
324 | +void acpi_bus_private_data_handler(acpi_handle, u32, void *); | |
325 | +int acpi_bus_get_private_data(acpi_handle, void **); | |
324 | 326 | /* |
325 | 327 | * External Functions |
326 | 328 | */ |
include/acpi/processor.h
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | #include <linux/kernel.h> |
5 | 5 | #include <linux/cpu.h> |
6 | 6 | #include <linux/cpuidle.h> |
7 | - | |
7 | +#include <linux/thermal.h> | |
8 | 8 | #include <asm/acpi.h> |
9 | 9 | |
10 | 10 | #define ACPI_PROCESSOR_BUSY_METRIC 10 |
... | ... | @@ -219,7 +219,7 @@ |
219 | 219 | struct acpi_processor_performance *performance; |
220 | 220 | struct acpi_processor_throttling throttling; |
221 | 221 | struct acpi_processor_limit limit; |
222 | - | |
222 | + struct thermal_cooling_device *cdev; | |
223 | 223 | /* the _PDC objects for this processor, if any */ |
224 | 224 | struct acpi_object_list *pdc; |
225 | 225 | }; |
... | ... | @@ -331,7 +331,7 @@ |
331 | 331 | /* in processor_thermal.c */ |
332 | 332 | int acpi_processor_get_limit_info(struct acpi_processor *pr); |
333 | 333 | extern struct file_operations acpi_processor_limit_fops; |
334 | - | |
334 | +extern struct thermal_cooling_device_ops processor_cooling_ops; | |
335 | 335 | #ifdef CONFIG_CPU_FREQ |
336 | 336 | void acpi_thermal_cpufreq_init(void); |
337 | 337 | void acpi_thermal_cpufreq_exit(void); |
include/linux/thermal.h
1 | +/* | |
2 | + * thermal.h ($Revision: 0 $) | |
3 | + * | |
4 | + * Copyright (C) 2008 Intel Corp | |
5 | + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> | |
6 | + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | |
7 | + * | |
8 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
9 | + * This program is free software; you can redistribute it and/or modify | |
10 | + * it under the terms of the GNU General Public License as published by | |
11 | + * the Free Software Foundation; version 2 of the License. | |
12 | + * | |
13 | + * This program is distributed in the hope that it will be useful, but | |
14 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | + * General Public License for more details. | |
17 | + * | |
18 | + * You should have received a copy of the GNU General Public License along | |
19 | + * with this program; if not, write to the Free Software Foundation, Inc., | |
20 | + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
21 | + * | |
22 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
23 | + */ | |
24 | + | |
25 | +#ifndef __THERMAL_H__ | |
26 | +#define __THERMAL_H__ | |
27 | + | |
28 | +#include <linux/idr.h> | |
29 | +#include <linux/device.h> | |
30 | + | |
31 | +struct thermal_zone_device; | |
32 | +struct thermal_cooling_device; | |
33 | + | |
34 | +struct thermal_zone_device_ops { | |
35 | + int (*bind) (struct thermal_zone_device *, | |
36 | + struct thermal_cooling_device *); | |
37 | + int (*unbind) (struct thermal_zone_device *, | |
38 | + struct thermal_cooling_device *); | |
39 | + int (*get_temp) (struct thermal_zone_device *, char *); | |
40 | + int (*get_mode) (struct thermal_zone_device *, char *); | |
41 | + int (*set_mode) (struct thermal_zone_device *, const char *); | |
42 | + int (*get_trip_type) (struct thermal_zone_device *, int, char *); | |
43 | + int (*get_trip_temp) (struct thermal_zone_device *, int, char *); | |
44 | +}; | |
45 | + | |
46 | +struct thermal_cooling_device_ops { | |
47 | + int (*get_max_state) (struct thermal_cooling_device *, char *); | |
48 | + int (*get_cur_state) (struct thermal_cooling_device *, char *); | |
49 | + int (*set_cur_state) (struct thermal_cooling_device *, unsigned int); | |
50 | +}; | |
51 | + | |
52 | +#define THERMAL_TRIPS_NONE -1 | |
53 | +#define THERMAL_MAX_TRIPS 10 | |
54 | +#define THERMAL_NAME_LENGTH 20 | |
55 | +struct thermal_cooling_device { | |
56 | + int id; | |
57 | + char type[THERMAL_NAME_LENGTH]; | |
58 | + struct device device; | |
59 | + void *devdata; | |
60 | + struct thermal_cooling_device_ops *ops; | |
61 | + struct list_head node; | |
62 | +}; | |
63 | + | |
64 | +#define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732 >= 0) ? \ | |
65 | + ((long)t-2732+5)/10 : ((long)t-2732-5)/10) | |
66 | +#define CELSIUS_TO_KELVIN(t) ((t)*10+2732) | |
67 | + | |
68 | +struct thermal_zone_device { | |
69 | + int id; | |
70 | + char type[THERMAL_NAME_LENGTH]; | |
71 | + struct device device; | |
72 | + void *devdata; | |
73 | + int trips; | |
74 | + struct thermal_zone_device_ops *ops; | |
75 | + struct list_head cooling_devices; | |
76 | + struct idr idr; | |
77 | + struct mutex lock; /* protect cooling devices list */ | |
78 | + struct list_head node; | |
79 | +}; | |
80 | + | |
81 | +struct thermal_zone_device *thermal_zone_device_register(char *, int, void *, | |
82 | + struct thermal_zone_device_ops *); | |
83 | +void thermal_zone_device_unregister(struct thermal_zone_device *); | |
84 | + | |
85 | +int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, | |
86 | + struct thermal_cooling_device *); | |
87 | +int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, | |
88 | + struct thermal_cooling_device *); | |
89 | + | |
90 | +struct thermal_cooling_device *thermal_cooling_device_register(char *, void *, | |
91 | + struct thermal_cooling_device_ops *); | |
92 | +void thermal_cooling_device_unregister(struct thermal_cooling_device *); | |
93 | + | |
94 | +#endif /* __THERMAL_H__ */ |