diff options
author | Aaron Lu <aaron.lu@intel.com> | 2014-04-01 03:50:27 -0400 |
---|---|---|
committer | Zhang Rui <rui.zhang@intel.com> | 2014-10-10 01:57:13 -0400 |
commit | 9519a6356cbf63b1f22a7a208385dc56092c8b7d (patch) | |
tree | 6c933347a873e8c3617c26af7710bca27763b5c2 /drivers/acpi | |
parent | 19593a1fb1f6718406afca5b867dab184289d406 (diff) |
ACPI / Fan: add ACPI 4.0 style fan support
This patch adds support for ACPI 4.0 style fan, lacking part is: no
support for 'Low Speed Notification Support', 'Fine Grain Control' is
not used yet.
It's not clear what to do on suspend/resume callback for 4.0 style ACPI
fan, so it does nothing for now.
Signed-off-by: Aaron Lu <aaron.lu@intel.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/fan.c | 268 |
1 files changed, 241 insertions, 27 deletions
diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 8a5b450576df..f7d1c8027736 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c | |||
@@ -31,6 +31,7 @@ | |||
31 | #include <linux/thermal.h> | 31 | #include <linux/thermal.h> |
32 | #include <linux/acpi.h> | 32 | #include <linux/acpi.h> |
33 | #include <linux/platform_device.h> | 33 | #include <linux/platform_device.h> |
34 | #include <linux/sort.h> | ||
34 | 35 | ||
35 | MODULE_AUTHOR("Paul Diefenbaugh"); | 36 | MODULE_AUTHOR("Paul Diefenbaugh"); |
36 | MODULE_DESCRIPTION("ACPI Fan Driver"); | 37 | MODULE_DESCRIPTION("ACPI Fan Driver"); |
@@ -59,6 +60,29 @@ static struct dev_pm_ops acpi_fan_pm = { | |||
59 | #define FAN_PM_OPS_PTR NULL | 60 | #define FAN_PM_OPS_PTR NULL |
60 | #endif | 61 | #endif |
61 | 62 | ||
63 | struct acpi_fan_fps { | ||
64 | u64 control; | ||
65 | u64 trip_point; | ||
66 | u64 speed; | ||
67 | u64 noise_level; | ||
68 | u64 power; | ||
69 | }; | ||
70 | |||
71 | struct acpi_fan_fif { | ||
72 | u64 revision; | ||
73 | u64 fine_grain_ctrl; | ||
74 | u64 step_size; | ||
75 | u64 low_speed_notification; | ||
76 | }; | ||
77 | |||
78 | struct acpi_fan { | ||
79 | bool acpi4; | ||
80 | struct acpi_fan_fif fif; | ||
81 | struct acpi_fan_fps *fps; | ||
82 | int fps_count; | ||
83 | struct thermal_cooling_device *cdev; | ||
84 | }; | ||
85 | |||
62 | static struct platform_driver acpi_fan_driver = { | 86 | static struct platform_driver acpi_fan_driver = { |
63 | .probe = acpi_fan_probe, | 87 | .probe = acpi_fan_probe, |
64 | .remove = acpi_fan_remove, | 88 | .remove = acpi_fan_remove, |
@@ -73,21 +97,62 @@ static struct platform_driver acpi_fan_driver = { | |||
73 | static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long | 97 | static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long |
74 | *state) | 98 | *state) |
75 | { | 99 | { |
76 | /* ACPI fan device only support two states: ON/OFF */ | 100 | struct acpi_device *device = cdev->devdata; |
77 | *state = 1; | 101 | struct acpi_fan *fan = acpi_driver_data(device); |
102 | |||
103 | if (fan->acpi4) | ||
104 | *state = fan->fps_count - 1; | ||
105 | else | ||
106 | *state = 1; | ||
78 | return 0; | 107 | return 0; |
79 | } | 108 | } |
80 | 109 | ||
81 | static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long | 110 | static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) |
82 | *state) | 111 | { |
112 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
113 | struct acpi_fan *fan = acpi_driver_data(device); | ||
114 | union acpi_object *obj; | ||
115 | acpi_status status; | ||
116 | int control, i; | ||
117 | |||
118 | status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer); | ||
119 | if (ACPI_FAILURE(status)) { | ||
120 | dev_err(&device->dev, "Get fan state failed\n"); | ||
121 | return status; | ||
122 | } | ||
123 | |||
124 | obj = buffer.pointer; | ||
125 | if (!obj || obj->type != ACPI_TYPE_PACKAGE || | ||
126 | obj->package.count != 3 || | ||
127 | obj->package.elements[1].type != ACPI_TYPE_INTEGER) { | ||
128 | dev_err(&device->dev, "Invalid _FST data\n"); | ||
129 | status = -EINVAL; | ||
130 | goto err; | ||
131 | } | ||
132 | |||
133 | control = obj->package.elements[1].integer.value; | ||
134 | for (i = 0; i < fan->fps_count; i++) { | ||
135 | if (control == fan->fps[i].control) | ||
136 | break; | ||
137 | } | ||
138 | if (i == fan->fps_count) { | ||
139 | dev_dbg(&device->dev, "Invalid control value returned\n"); | ||
140 | status = -EINVAL; | ||
141 | goto err; | ||
142 | } | ||
143 | |||
144 | *state = i; | ||
145 | |||
146 | err: | ||
147 | kfree(obj); | ||
148 | return status; | ||
149 | } | ||
150 | |||
151 | static int fan_get_state(struct acpi_device *device, unsigned long *state) | ||
83 | { | 152 | { |
84 | struct acpi_device *device = cdev->devdata; | ||
85 | int result; | 153 | int result; |
86 | int acpi_state = ACPI_STATE_D0; | 154 | int acpi_state = ACPI_STATE_D0; |
87 | 155 | ||
88 | if (!device) | ||
89 | return -EINVAL; | ||
90 | |||
91 | result = acpi_device_update_power(device, &acpi_state); | 156 | result = acpi_device_update_power(device, &acpi_state); |
92 | if (result) | 157 | if (result) |
93 | return result; | 158 | return result; |
@@ -97,21 +162,57 @@ static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long | |||
97 | return 0; | 162 | return 0; |
98 | } | 163 | } |
99 | 164 | ||
100 | static int | 165 | static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long |
101 | fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | 166 | *state) |
102 | { | 167 | { |
103 | struct acpi_device *device = cdev->devdata; | 168 | struct acpi_device *device = cdev->devdata; |
104 | int result; | 169 | struct acpi_fan *fan = acpi_driver_data(device); |
170 | |||
171 | if (fan->acpi4) | ||
172 | return fan_get_state_acpi4(device, state); | ||
173 | else | ||
174 | return fan_get_state(device, state); | ||
175 | } | ||
105 | 176 | ||
106 | if (!device || (state != 0 && state != 1)) | 177 | static int fan_set_state(struct acpi_device *device, unsigned long state) |
178 | { | ||
179 | if (state != 0 && state != 1) | ||
107 | return -EINVAL; | 180 | return -EINVAL; |
108 | 181 | ||
109 | result = acpi_device_set_power(device, | 182 | return acpi_device_set_power(device, |
110 | state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); | 183 | state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); |
184 | } | ||
111 | 185 | ||
112 | return result; | 186 | static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) |
187 | { | ||
188 | struct acpi_fan *fan = acpi_driver_data(device); | ||
189 | acpi_status status; | ||
190 | |||
191 | if (state >= fan->fps_count) | ||
192 | return -EINVAL; | ||
193 | |||
194 | status = acpi_execute_simple_method(device->handle, "_FSL", | ||
195 | fan->fps[state].control); | ||
196 | if (ACPI_FAILURE(status)) { | ||
197 | dev_dbg(&device->dev, "Failed to set state by _FSL\n"); | ||
198 | return status; | ||
199 | } | ||
200 | |||
201 | return 0; | ||
113 | } | 202 | } |
114 | 203 | ||
204 | static int | ||
205 | fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | ||
206 | { | ||
207 | struct acpi_device *device = cdev->devdata; | ||
208 | struct acpi_fan *fan = acpi_driver_data(device); | ||
209 | |||
210 | if (fan->acpi4) | ||
211 | return fan_set_state_acpi4(device, state); | ||
212 | else | ||
213 | return fan_set_state(device, state); | ||
214 | } | ||
215 | |||
115 | static const struct thermal_cooling_device_ops fan_cooling_ops = { | 216 | static const struct thermal_cooling_device_ops fan_cooling_ops = { |
116 | .get_max_state = fan_get_max_state, | 217 | .get_max_state = fan_get_max_state, |
117 | .get_cur_state = fan_get_cur_state, | 218 | .get_cur_state = fan_get_cur_state, |
@@ -122,16 +223,125 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = { | |||
122 | Driver Interface | 223 | Driver Interface |
123 | -------------------------------------------------------------------------- */ | 224 | -------------------------------------------------------------------------- */ |
124 | 225 | ||
226 | static bool acpi_fan_is_acpi4(struct acpi_device *device) | ||
227 | { | ||
228 | return acpi_has_method(device->handle, "_FIF") && | ||
229 | acpi_has_method(device->handle, "_FPS") && | ||
230 | acpi_has_method(device->handle, "_FSL") && | ||
231 | acpi_has_method(device->handle, "_FST"); | ||
232 | } | ||
233 | |||
234 | static int acpi_fan_get_fif(struct acpi_device *device) | ||
235 | { | ||
236 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
237 | struct acpi_fan *fan = acpi_driver_data(device); | ||
238 | struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; | ||
239 | struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif }; | ||
240 | union acpi_object *obj; | ||
241 | acpi_status status; | ||
242 | |||
243 | status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); | ||
244 | if (ACPI_FAILURE(status)) | ||
245 | return status; | ||
246 | |||
247 | obj = buffer.pointer; | ||
248 | if (!obj || obj->type != ACPI_TYPE_PACKAGE) { | ||
249 | dev_err(&device->dev, "Invalid _FIF data\n"); | ||
250 | status = -EINVAL; | ||
251 | goto err; | ||
252 | } | ||
253 | |||
254 | status = acpi_extract_package(obj, &format, &fif); | ||
255 | if (ACPI_FAILURE(status)) { | ||
256 | dev_err(&device->dev, "Invalid _FIF element\n"); | ||
257 | status = -EINVAL; | ||
258 | } | ||
259 | |||
260 | err: | ||
261 | kfree(obj); | ||
262 | return status; | ||
263 | } | ||
264 | |||
265 | static int acpi_fan_speed_cmp(const void *a, const void *b) | ||
266 | { | ||
267 | const struct acpi_fan_fps *fps1 = a; | ||
268 | const struct acpi_fan_fps *fps2 = b; | ||
269 | return fps1->speed - fps2->speed; | ||
270 | } | ||
271 | |||
272 | static int acpi_fan_get_fps(struct acpi_device *device) | ||
273 | { | ||
274 | struct acpi_fan *fan = acpi_driver_data(device); | ||
275 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
276 | union acpi_object *obj; | ||
277 | acpi_status status; | ||
278 | int i; | ||
279 | |||
280 | status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); | ||
281 | if (ACPI_FAILURE(status)) | ||
282 | return status; | ||
283 | |||
284 | obj = buffer.pointer; | ||
285 | if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { | ||
286 | dev_err(&device->dev, "Invalid _FPS data\n"); | ||
287 | status = -EINVAL; | ||
288 | goto err; | ||
289 | } | ||
290 | |||
291 | fan->fps_count = obj->package.count - 1; /* minus revision field */ | ||
292 | fan->fps = devm_kzalloc(&device->dev, | ||
293 | fan->fps_count * sizeof(struct acpi_fan_fps), | ||
294 | GFP_KERNEL); | ||
295 | if (!fan->fps) { | ||
296 | dev_err(&device->dev, "Not enough memory\n"); | ||
297 | status = -ENOMEM; | ||
298 | goto err; | ||
299 | } | ||
300 | for (i = 0; i < fan->fps_count; i++) { | ||
301 | struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; | ||
302 | struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] }; | ||
303 | status = acpi_extract_package(&obj->package.elements[i + 1], | ||
304 | &format, &fps); | ||
305 | if (ACPI_FAILURE(status)) { | ||
306 | dev_err(&device->dev, "Invalid _FPS element\n"); | ||
307 | break; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | /* sort the state array according to fan speed in increase order */ | ||
312 | sort(fan->fps, fan->fps_count, sizeof(*fan->fps), | ||
313 | acpi_fan_speed_cmp, NULL); | ||
314 | |||
315 | err: | ||
316 | kfree(obj); | ||
317 | return status; | ||
318 | } | ||
319 | |||
125 | static int acpi_fan_probe(struct platform_device *pdev) | 320 | static int acpi_fan_probe(struct platform_device *pdev) |
126 | { | 321 | { |
127 | int result = 0; | 322 | int result = 0; |
128 | struct thermal_cooling_device *cdev; | 323 | struct thermal_cooling_device *cdev; |
324 | struct acpi_fan *fan; | ||
129 | struct acpi_device *device = ACPI_COMPANION(&pdev->dev); | 325 | struct acpi_device *device = ACPI_COMPANION(&pdev->dev); |
130 | 326 | ||
131 | result = acpi_device_update_power(device, NULL); | 327 | fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); |
132 | if (result) { | 328 | if (!fan) { |
133 | dev_err(&pdev->dev, "Setting initial power state\n"); | 329 | dev_err(&device->dev, "No memory for fan\n"); |
134 | goto end; | 330 | return -ENOMEM; |
331 | } | ||
332 | device->driver_data = fan; | ||
333 | platform_set_drvdata(pdev, fan); | ||
334 | |||
335 | if (acpi_fan_is_acpi4(device)) { | ||
336 | if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device)) | ||
337 | goto end; | ||
338 | fan->acpi4 = true; | ||
339 | } else { | ||
340 | result = acpi_device_update_power(device, NULL); | ||
341 | if (result) { | ||
342 | dev_err(&device->dev, "Setting initial power state\n"); | ||
343 | goto end; | ||
344 | } | ||
135 | } | 345 | } |
136 | 346 | ||
137 | cdev = thermal_cooling_device_register("Fan", device, | 347 | cdev = thermal_cooling_device_register("Fan", device, |
@@ -143,7 +353,7 @@ static int acpi_fan_probe(struct platform_device *pdev) | |||
143 | 353 | ||
144 | dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); | 354 | dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); |
145 | 355 | ||
146 | platform_set_drvdata(pdev, cdev); | 356 | fan->cdev = cdev; |
147 | result = sysfs_create_link(&pdev->dev.kobj, | 357 | result = sysfs_create_link(&pdev->dev.kobj, |
148 | &cdev->device.kobj, | 358 | &cdev->device.kobj, |
149 | "thermal_cooling"); | 359 | "thermal_cooling"); |
@@ -158,21 +368,17 @@ static int acpi_fan_probe(struct platform_device *pdev) | |||
158 | dev_err(&pdev->dev, "Failed to create sysfs link " | 368 | dev_err(&pdev->dev, "Failed to create sysfs link " |
159 | "'device'\n"); | 369 | "'device'\n"); |
160 | 370 | ||
161 | dev_info(&pdev->dev, "%s [%s] (%s)\n", | ||
162 | acpi_device_name(device), acpi_device_bid(device), | ||
163 | !device->power.state ? "on" : "off"); | ||
164 | |||
165 | end: | 371 | end: |
166 | return result; | 372 | return result; |
167 | } | 373 | } |
168 | 374 | ||
169 | static int acpi_fan_remove(struct platform_device *pdev) | 375 | static int acpi_fan_remove(struct platform_device *pdev) |
170 | { | 376 | { |
171 | struct thermal_cooling_device *cdev = platform_get_drvdata(pdev); | 377 | struct acpi_fan *fan = platform_get_drvdata(pdev); |
172 | 378 | ||
173 | sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); | 379 | sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); |
174 | sysfs_remove_link(&cdev->device.kobj, "device"); | 380 | sysfs_remove_link(&fan->cdev->device.kobj, "device"); |
175 | thermal_cooling_device_unregister(cdev); | 381 | thermal_cooling_device_unregister(fan->cdev); |
176 | 382 | ||
177 | return 0; | 383 | return 0; |
178 | } | 384 | } |
@@ -180,6 +386,10 @@ static int acpi_fan_remove(struct platform_device *pdev) | |||
180 | #ifdef CONFIG_PM_SLEEP | 386 | #ifdef CONFIG_PM_SLEEP |
181 | static int acpi_fan_suspend(struct device *dev) | 387 | static int acpi_fan_suspend(struct device *dev) |
182 | { | 388 | { |
389 | struct acpi_fan *fan = dev_get_drvdata(dev); | ||
390 | if (fan->acpi4) | ||
391 | return 0; | ||
392 | |||
183 | acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); | 393 | acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); |
184 | 394 | ||
185 | return AE_OK; | 395 | return AE_OK; |
@@ -188,6 +398,10 @@ static int acpi_fan_suspend(struct device *dev) | |||
188 | static int acpi_fan_resume(struct device *dev) | 398 | static int acpi_fan_resume(struct device *dev) |
189 | { | 399 | { |
190 | int result; | 400 | int result; |
401 | struct acpi_fan *fan = dev_get_drvdata(dev); | ||
402 | |||
403 | if (fan->acpi4) | ||
404 | return 0; | ||
191 | 405 | ||
192 | result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); | 406 | result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); |
193 | if (result) | 407 | if (result) |