diff options
Diffstat (limited to 'drivers/acpi/fan.c')
-rw-r--r-- | drivers/acpi/fan.c | 338 |
1 files changed, 264 insertions, 74 deletions
diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 5328b1090e08..caf9b76b7ef8 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c | |||
@@ -30,22 +30,19 @@ | |||
30 | #include <linux/uaccess.h> | 30 | #include <linux/uaccess.h> |
31 | #include <linux/thermal.h> | 31 | #include <linux/thermal.h> |
32 | #include <linux/acpi.h> | 32 | #include <linux/acpi.h> |
33 | 33 | #include <linux/platform_device.h> | |
34 | #define ACPI_FAN_CLASS "fan" | 34 | #include <linux/sort.h> |
35 | #define ACPI_FAN_FILE_STATE "state" | ||
36 | |||
37 | #define _COMPONENT ACPI_FAN_COMPONENT | ||
38 | ACPI_MODULE_NAME("fan"); | ||
39 | 35 | ||
40 | MODULE_AUTHOR("Paul Diefenbaugh"); | 36 | MODULE_AUTHOR("Paul Diefenbaugh"); |
41 | MODULE_DESCRIPTION("ACPI Fan Driver"); | 37 | MODULE_DESCRIPTION("ACPI Fan Driver"); |
42 | MODULE_LICENSE("GPL"); | 38 | MODULE_LICENSE("GPL"); |
43 | 39 | ||
44 | static int acpi_fan_add(struct acpi_device *device); | 40 | static int acpi_fan_probe(struct platform_device *pdev); |
45 | static int acpi_fan_remove(struct acpi_device *device); | 41 | static int acpi_fan_remove(struct platform_device *pdev); |
46 | 42 | ||
47 | static const struct acpi_device_id fan_device_ids[] = { | 43 | static const struct acpi_device_id fan_device_ids[] = { |
48 | {"PNP0C0B", 0}, | 44 | {"PNP0C0B", 0}, |
45 | {"INT3404", 0}, | ||
49 | {"", 0}, | 46 | {"", 0}, |
50 | }; | 47 | }; |
51 | MODULE_DEVICE_TABLE(acpi, fan_device_ids); | 48 | MODULE_DEVICE_TABLE(acpi, fan_device_ids); |
@@ -64,37 +61,100 @@ static struct dev_pm_ops acpi_fan_pm = { | |||
64 | #define FAN_PM_OPS_PTR NULL | 61 | #define FAN_PM_OPS_PTR NULL |
65 | #endif | 62 | #endif |
66 | 63 | ||
67 | static struct acpi_driver acpi_fan_driver = { | 64 | struct acpi_fan_fps { |
68 | .name = "fan", | 65 | u64 control; |
69 | .class = ACPI_FAN_CLASS, | 66 | u64 trip_point; |
70 | .ids = fan_device_ids, | 67 | u64 speed; |
71 | .ops = { | 68 | u64 noise_level; |
72 | .add = acpi_fan_add, | 69 | u64 power; |
73 | .remove = acpi_fan_remove, | 70 | }; |
74 | }, | 71 | |
75 | .drv.pm = FAN_PM_OPS_PTR, | 72 | struct acpi_fan_fif { |
73 | u64 revision; | ||
74 | u64 fine_grain_ctrl; | ||
75 | u64 step_size; | ||
76 | u64 low_speed_notification; | ||
77 | }; | ||
78 | |||
79 | struct acpi_fan { | ||
80 | bool acpi4; | ||
81 | struct acpi_fan_fif fif; | ||
82 | struct acpi_fan_fps *fps; | ||
83 | int fps_count; | ||
84 | struct thermal_cooling_device *cdev; | ||
85 | }; | ||
86 | |||
87 | static struct platform_driver acpi_fan_driver = { | ||
88 | .probe = acpi_fan_probe, | ||
89 | .remove = acpi_fan_remove, | ||
90 | .driver = { | ||
91 | .name = "acpi-fan", | ||
92 | .acpi_match_table = fan_device_ids, | ||
93 | .pm = FAN_PM_OPS_PTR, | ||
94 | }, | ||
76 | }; | 95 | }; |
77 | 96 | ||
78 | /* thermal cooling device callbacks */ | 97 | /* thermal cooling device callbacks */ |
79 | static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long | 98 | static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long |
80 | *state) | 99 | *state) |
81 | { | 100 | { |
82 | /* ACPI fan device only support two states: ON/OFF */ | 101 | struct acpi_device *device = cdev->devdata; |
83 | *state = 1; | 102 | struct acpi_fan *fan = acpi_driver_data(device); |
103 | |||
104 | if (fan->acpi4) | ||
105 | *state = fan->fps_count - 1; | ||
106 | else | ||
107 | *state = 1; | ||
84 | return 0; | 108 | return 0; |
85 | } | 109 | } |
86 | 110 | ||
87 | static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long | 111 | static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) |
88 | *state) | 112 | { |
113 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
114 | struct acpi_fan *fan = acpi_driver_data(device); | ||
115 | union acpi_object *obj; | ||
116 | acpi_status status; | ||
117 | int control, i; | ||
118 | |||
119 | status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer); | ||
120 | if (ACPI_FAILURE(status)) { | ||
121 | dev_err(&device->dev, "Get fan state failed\n"); | ||
122 | return status; | ||
123 | } | ||
124 | |||
125 | obj = buffer.pointer; | ||
126 | if (!obj || obj->type != ACPI_TYPE_PACKAGE || | ||
127 | obj->package.count != 3 || | ||
128 | obj->package.elements[1].type != ACPI_TYPE_INTEGER) { | ||
129 | dev_err(&device->dev, "Invalid _FST data\n"); | ||
130 | status = -EINVAL; | ||
131 | goto err; | ||
132 | } | ||
133 | |||
134 | control = obj->package.elements[1].integer.value; | ||
135 | for (i = 0; i < fan->fps_count; i++) { | ||
136 | if (control == fan->fps[i].control) | ||
137 | break; | ||
138 | } | ||
139 | if (i == fan->fps_count) { | ||
140 | dev_dbg(&device->dev, "Invalid control value returned\n"); | ||
141 | status = -EINVAL; | ||
142 | goto err; | ||
143 | } | ||
144 | |||
145 | *state = i; | ||
146 | |||
147 | err: | ||
148 | kfree(obj); | ||
149 | return status; | ||
150 | } | ||
151 | |||
152 | static int fan_get_state(struct acpi_device *device, unsigned long *state) | ||
89 | { | 153 | { |
90 | struct acpi_device *device = cdev->devdata; | ||
91 | int result; | 154 | int result; |
92 | int acpi_state = ACPI_STATE_D0; | 155 | int acpi_state = ACPI_STATE_D0; |
93 | 156 | ||
94 | if (!device) | 157 | result = acpi_device_update_power(device, &acpi_state); |
95 | return -EINVAL; | ||
96 | |||
97 | result = acpi_bus_update_power(device->handle, &acpi_state); | ||
98 | if (result) | 158 | if (result) |
99 | return result; | 159 | return result; |
100 | 160 | ||
@@ -103,21 +163,57 @@ static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long | |||
103 | return 0; | 163 | return 0; |
104 | } | 164 | } |
105 | 165 | ||
106 | static int | 166 | static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long |
107 | fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | 167 | *state) |
108 | { | 168 | { |
109 | struct acpi_device *device = cdev->devdata; | 169 | struct acpi_device *device = cdev->devdata; |
110 | int result; | 170 | struct acpi_fan *fan = acpi_driver_data(device); |
111 | 171 | ||
112 | if (!device || (state != 0 && state != 1)) | 172 | if (fan->acpi4) |
173 | return fan_get_state_acpi4(device, state); | ||
174 | else | ||
175 | return fan_get_state(device, state); | ||
176 | } | ||
177 | |||
178 | static int fan_set_state(struct acpi_device *device, unsigned long state) | ||
179 | { | ||
180 | if (state != 0 && state != 1) | ||
113 | return -EINVAL; | 181 | return -EINVAL; |
114 | 182 | ||
115 | result = acpi_bus_set_power(device->handle, | 183 | return acpi_device_set_power(device, |
116 | state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); | 184 | state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); |
185 | } | ||
117 | 186 | ||
118 | return result; | 187 | static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) |
188 | { | ||
189 | struct acpi_fan *fan = acpi_driver_data(device); | ||
190 | acpi_status status; | ||
191 | |||
192 | if (state >= fan->fps_count) | ||
193 | return -EINVAL; | ||
194 | |||
195 | status = acpi_execute_simple_method(device->handle, "_FSL", | ||
196 | fan->fps[state].control); | ||
197 | if (ACPI_FAILURE(status)) { | ||
198 | dev_dbg(&device->dev, "Failed to set state by _FSL\n"); | ||
199 | return status; | ||
200 | } | ||
201 | |||
202 | return 0; | ||
119 | } | 203 | } |
120 | 204 | ||
205 | static int | ||
206 | fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | ||
207 | { | ||
208 | struct acpi_device *device = cdev->devdata; | ||
209 | struct acpi_fan *fan = acpi_driver_data(device); | ||
210 | |||
211 | if (fan->acpi4) | ||
212 | return fan_set_state_acpi4(device, state); | ||
213 | else | ||
214 | return fan_set_state(device, state); | ||
215 | } | ||
216 | |||
121 | static const struct thermal_cooling_device_ops fan_cooling_ops = { | 217 | static const struct thermal_cooling_device_ops fan_cooling_ops = { |
122 | .get_max_state = fan_get_max_state, | 218 | .get_max_state = fan_get_max_state, |
123 | .get_cur_state = fan_get_cur_state, | 219 | .get_cur_state = fan_get_cur_state, |
@@ -129,21 +225,125 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = { | |||
129 | * -------------------------------------------------------------------------- | 225 | * -------------------------------------------------------------------------- |
130 | */ | 226 | */ |
131 | 227 | ||
132 | static int acpi_fan_add(struct acpi_device *device) | 228 | static bool acpi_fan_is_acpi4(struct acpi_device *device) |
133 | { | 229 | { |
134 | int result = 0; | 230 | return acpi_has_method(device->handle, "_FIF") && |
135 | struct thermal_cooling_device *cdev; | 231 | acpi_has_method(device->handle, "_FPS") && |
232 | acpi_has_method(device->handle, "_FSL") && | ||
233 | acpi_has_method(device->handle, "_FST"); | ||
234 | } | ||
136 | 235 | ||
137 | if (!device) | 236 | static int acpi_fan_get_fif(struct acpi_device *device) |
138 | return -EINVAL; | 237 | { |
238 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
239 | struct acpi_fan *fan = acpi_driver_data(device); | ||
240 | struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; | ||
241 | struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif }; | ||
242 | union acpi_object *obj; | ||
243 | acpi_status status; | ||
244 | |||
245 | status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); | ||
246 | if (ACPI_FAILURE(status)) | ||
247 | return status; | ||
248 | |||
249 | obj = buffer.pointer; | ||
250 | if (!obj || obj->type != ACPI_TYPE_PACKAGE) { | ||
251 | dev_err(&device->dev, "Invalid _FIF data\n"); | ||
252 | status = -EINVAL; | ||
253 | goto err; | ||
254 | } | ||
139 | 255 | ||
140 | strcpy(acpi_device_name(device), "Fan"); | 256 | status = acpi_extract_package(obj, &format, &fif); |
141 | strcpy(acpi_device_class(device), ACPI_FAN_CLASS); | 257 | if (ACPI_FAILURE(status)) { |
258 | dev_err(&device->dev, "Invalid _FIF element\n"); | ||
259 | status = -EINVAL; | ||
260 | } | ||
142 | 261 | ||
143 | result = acpi_bus_update_power(device->handle, NULL); | 262 | err: |
144 | if (result) { | 263 | kfree(obj); |
145 | dev_err(&device->dev, "Setting initial power state\n"); | 264 | return status; |
146 | goto end; | 265 | } |
266 | |||
267 | static int acpi_fan_speed_cmp(const void *a, const void *b) | ||
268 | { | ||
269 | const struct acpi_fan_fps *fps1 = a; | ||
270 | const struct acpi_fan_fps *fps2 = b; | ||
271 | return fps1->speed - fps2->speed; | ||
272 | } | ||
273 | |||
274 | static int acpi_fan_get_fps(struct acpi_device *device) | ||
275 | { | ||
276 | struct acpi_fan *fan = acpi_driver_data(device); | ||
277 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
278 | union acpi_object *obj; | ||
279 | acpi_status status; | ||
280 | int i; | ||
281 | |||
282 | status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); | ||
283 | if (ACPI_FAILURE(status)) | ||
284 | return status; | ||
285 | |||
286 | obj = buffer.pointer; | ||
287 | if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { | ||
288 | dev_err(&device->dev, "Invalid _FPS data\n"); | ||
289 | status = -EINVAL; | ||
290 | goto err; | ||
291 | } | ||
292 | |||
293 | fan->fps_count = obj->package.count - 1; /* minus revision field */ | ||
294 | fan->fps = devm_kzalloc(&device->dev, | ||
295 | fan->fps_count * sizeof(struct acpi_fan_fps), | ||
296 | GFP_KERNEL); | ||
297 | if (!fan->fps) { | ||
298 | dev_err(&device->dev, "Not enough memory\n"); | ||
299 | status = -ENOMEM; | ||
300 | goto err; | ||
301 | } | ||
302 | for (i = 0; i < fan->fps_count; i++) { | ||
303 | struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; | ||
304 | struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] }; | ||
305 | status = acpi_extract_package(&obj->package.elements[i + 1], | ||
306 | &format, &fps); | ||
307 | if (ACPI_FAILURE(status)) { | ||
308 | dev_err(&device->dev, "Invalid _FPS element\n"); | ||
309 | break; | ||
310 | } | ||
311 | } | ||
312 | |||
313 | /* sort the state array according to fan speed in increase order */ | ||
314 | sort(fan->fps, fan->fps_count, sizeof(*fan->fps), | ||
315 | acpi_fan_speed_cmp, NULL); | ||
316 | |||
317 | err: | ||
318 | kfree(obj); | ||
319 | return status; | ||
320 | } | ||
321 | |||
322 | static int acpi_fan_probe(struct platform_device *pdev) | ||
323 | { | ||
324 | int result = 0; | ||
325 | struct thermal_cooling_device *cdev; | ||
326 | struct acpi_fan *fan; | ||
327 | struct acpi_device *device = ACPI_COMPANION(&pdev->dev); | ||
328 | |||
329 | fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); | ||
330 | if (!fan) { | ||
331 | dev_err(&device->dev, "No memory for fan\n"); | ||
332 | return -ENOMEM; | ||
333 | } | ||
334 | device->driver_data = fan; | ||
335 | platform_set_drvdata(pdev, fan); | ||
336 | |||
337 | if (acpi_fan_is_acpi4(device)) { | ||
338 | if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device)) | ||
339 | goto end; | ||
340 | fan->acpi4 = true; | ||
341 | } else { | ||
342 | result = acpi_device_update_power(device, NULL); | ||
343 | if (result) { | ||
344 | dev_err(&device->dev, "Setting initial power state\n"); | ||
345 | goto end; | ||
346 | } | ||
147 | } | 347 | } |
148 | 348 | ||
149 | cdev = thermal_cooling_device_register("Fan", device, | 349 | cdev = thermal_cooling_device_register("Fan", device, |
@@ -153,44 +353,32 @@ static int acpi_fan_add(struct acpi_device *device) | |||
153 | goto end; | 353 | goto end; |
154 | } | 354 | } |
155 | 355 | ||
156 | dev_dbg(&device->dev, "registered as cooling_device%d\n", cdev->id); | 356 | dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); |
157 | 357 | ||
158 | device->driver_data = cdev; | 358 | fan->cdev = cdev; |
159 | result = sysfs_create_link(&device->dev.kobj, | 359 | result = sysfs_create_link(&pdev->dev.kobj, |
160 | &cdev->device.kobj, | 360 | &cdev->device.kobj, |
161 | "thermal_cooling"); | 361 | "thermal_cooling"); |
162 | if (result) | 362 | if (result) |
163 | dev_err(&device->dev, "Failed to create sysfs link " | 363 | dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling'\n"); |
164 | "'thermal_cooling'\n"); | ||
165 | 364 | ||
166 | result = sysfs_create_link(&cdev->device.kobj, | 365 | result = sysfs_create_link(&cdev->device.kobj, |
167 | &device->dev.kobj, | 366 | &pdev->dev.kobj, |
168 | "device"); | 367 | "device"); |
169 | if (result) | 368 | if (result) |
170 | dev_err(&device->dev, "Failed to create sysfs link 'device'\n"); | 369 | dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n"); |
171 | |||
172 | dev_info(&device->dev, "ACPI: %s [%s] (%s)\n", | ||
173 | acpi_device_name(device), acpi_device_bid(device), | ||
174 | !device->power.state ? "on" : "off"); | ||
175 | 370 | ||
176 | end: | 371 | end: |
177 | return result; | 372 | return result; |
178 | } | 373 | } |
179 | 374 | ||
180 | static int acpi_fan_remove(struct acpi_device *device) | 375 | static int acpi_fan_remove(struct platform_device *pdev) |
181 | { | 376 | { |
182 | struct thermal_cooling_device *cdev; | 377 | struct acpi_fan *fan = platform_get_drvdata(pdev); |
183 | |||
184 | if (!device) | ||
185 | return -EINVAL; | ||
186 | |||
187 | cdev = acpi_driver_data(device); | ||
188 | if (!cdev) | ||
189 | return -EINVAL; | ||
190 | 378 | ||
191 | sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); | 379 | sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); |
192 | sysfs_remove_link(&cdev->device.kobj, "device"); | 380 | sysfs_remove_link(&fan->cdev->device.kobj, "device"); |
193 | thermal_cooling_device_unregister(cdev); | 381 | thermal_cooling_device_unregister(fan->cdev); |
194 | 382 | ||
195 | return 0; | 383 | return 0; |
196 | } | 384 | } |
@@ -198,10 +386,11 @@ static int acpi_fan_remove(struct acpi_device *device) | |||
198 | #ifdef CONFIG_PM_SLEEP | 386 | #ifdef CONFIG_PM_SLEEP |
199 | static int acpi_fan_suspend(struct device *dev) | 387 | static int acpi_fan_suspend(struct device *dev) |
200 | { | 388 | { |
201 | if (!dev) | 389 | struct acpi_fan *fan = dev_get_drvdata(dev); |
202 | return -EINVAL; | 390 | if (fan->acpi4) |
391 | return 0; | ||
203 | 392 | ||
204 | acpi_bus_set_power(to_acpi_device(dev)->handle, ACPI_STATE_D0); | 393 | acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); |
205 | 394 | ||
206 | return AE_OK; | 395 | return AE_OK; |
207 | } | 396 | } |
@@ -209,11 +398,12 @@ static int acpi_fan_suspend(struct device *dev) | |||
209 | static int acpi_fan_resume(struct device *dev) | 398 | static int acpi_fan_resume(struct device *dev) |
210 | { | 399 | { |
211 | int result; | 400 | int result; |
401 | struct acpi_fan *fan = dev_get_drvdata(dev); | ||
212 | 402 | ||
213 | if (!dev) | 403 | if (fan->acpi4) |
214 | return -EINVAL; | 404 | return 0; |
215 | 405 | ||
216 | result = acpi_bus_update_power(to_acpi_device(dev)->handle, NULL); | 406 | result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); |
217 | if (result) | 407 | if (result) |
218 | dev_err(dev, "Error updating fan power state\n"); | 408 | dev_err(dev, "Error updating fan power state\n"); |
219 | 409 | ||
@@ -221,4 +411,4 @@ static int acpi_fan_resume(struct device *dev) | |||
221 | } | 411 | } |
222 | #endif | 412 | #endif |
223 | 413 | ||
224 | module_acpi_driver(acpi_fan_driver); | 414 | module_platform_driver(acpi_fan_driver); |