1 | // SPDX-License-Identifier: GPL-2.0-or-later |
---|---|
2 | /*-*-linux-c-*-*/ |
3 | |
4 | /* |
5 | Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net> |
6 | Copyright (C) 2008 Peter Gruber <nokos@gmx.net> |
7 | Copyright (C) 2008 Tony Vroon <tony@linx.net> |
8 | Based on earlier work: |
9 | Copyright (C) 2003 Shane Spencer <shane@bogomip.com> |
10 | Adrian Yee <brewt-fujitsu@brewt.org> |
11 | |
12 | Templated from msi-laptop.c and thinkpad_acpi.c which is copyright |
13 | by its respective authors. |
14 | |
15 | */ |
16 | |
17 | /* |
18 | * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional |
19 | * features made available on a range of Fujitsu laptops including the |
20 | * P2xxx/P5xxx/S2xxx/S6xxx/S7xxx series. |
21 | * |
22 | * This driver implements a vendor-specific backlight control interface for |
23 | * Fujitsu laptops and provides support for hotkeys present on certain Fujitsu |
24 | * laptops. |
25 | * |
26 | * This driver has been tested on a Fujitsu Lifebook S2110, S6410, S7020 and |
27 | * P8010. It should work on most P-series and S-series Lifebooks, but |
28 | * YMMV. |
29 | * |
30 | * The module parameter use_alt_lcd_levels switches between different ACPI |
31 | * brightness controls which are used by different Fujitsu laptops. In most |
32 | * cases the correct method is automatically detected. "use_alt_lcd_levels=1" |
33 | * is applicable for a Fujitsu Lifebook S6410 if autodetection fails. |
34 | * |
35 | */ |
36 | |
37 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
38 | |
39 | #include <linux/module.h> |
40 | #include <linux/kernel.h> |
41 | #include <linux/init.h> |
42 | #include <linux/acpi.h> |
43 | #include <linux/bitops.h> |
44 | #include <linux/dmi.h> |
45 | #include <linux/backlight.h> |
46 | #include <linux/input.h> |
47 | #include <linux/input/sparse-keymap.h> |
48 | #include <linux/kfifo.h> |
49 | #include <linux/leds.h> |
50 | #include <linux/platform_device.h> |
51 | #include <linux/power_supply.h> |
52 | #include <acpi/battery.h> |
53 | #include <acpi/video.h> |
54 | |
55 | #define FUJITSU_DRIVER_VERSION "0.6.0" |
56 | |
57 | #define FUJITSU_LCD_N_LEVELS 8 |
58 | |
59 | #define ACPI_FUJITSU_CLASS "fujitsu" |
60 | #define ACPI_FUJITSU_BL_HID "FUJ02B1" |
61 | #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" |
62 | #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" |
63 | #define ACPI_FUJITSU_LAPTOP_HID "FUJ02E3" |
64 | #define ACPI_FUJITSU_LAPTOP_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver" |
65 | #define ACPI_FUJITSU_LAPTOP_DEVICE_NAME "Fujitsu FUJ02E3" |
66 | |
67 | #define ACPI_FUJITSU_NOTIFY_CODE 0x80 |
68 | |
69 | /* FUNC interface - command values */ |
70 | #define FUNC_FLAGS BIT(12) |
71 | #define FUNC_LEDS (BIT(12) | BIT(0)) |
72 | #define FUNC_BUTTONS (BIT(12) | BIT(1)) |
73 | #define FUNC_BACKLIGHT (BIT(12) | BIT(2)) |
74 | |
75 | /* FUNC interface - responses */ |
76 | #define UNSUPPORTED_CMD 0x80000000 |
77 | |
78 | /* FUNC interface - status flags */ |
79 | #define FLAG_RFKILL BIT(5) |
80 | #define FLAG_LID BIT(8) |
81 | #define FLAG_DOCK BIT(9) |
82 | #define FLAG_TOUCHPAD_TOGGLE BIT(26) |
83 | #define FLAG_MICMUTE BIT(29) |
84 | #define FLAG_SOFTKEYS (FLAG_RFKILL | FLAG_TOUCHPAD_TOGGLE | FLAG_MICMUTE) |
85 | |
86 | /* FUNC interface - LED control */ |
87 | #define FUNC_LED_OFF BIT(0) |
88 | #define FUNC_LED_ON (BIT(0) | BIT(16) | BIT(17)) |
89 | #define LOGOLAMP_POWERON BIT(13) |
90 | #define LOGOLAMP_ALWAYS BIT(14) |
91 | #define KEYBOARD_LAMPS BIT(8) |
92 | #define RADIO_LED_ON BIT(5) |
93 | #define ECO_LED BIT(16) |
94 | #define ECO_LED_ON BIT(19) |
95 | |
96 | /* FUNC interface - backlight power control */ |
97 | #define BACKLIGHT_PARAM_POWER BIT(2) |
98 | #define BACKLIGHT_OFF (BIT(0) | BIT(1)) |
99 | #define BACKLIGHT_ON 0 |
100 | |
101 | /* FUNC interface - battery control interface */ |
102 | #define FUNC_S006_METHOD 0x1006 |
103 | #define CHARGE_CONTROL_RW 0x21 |
104 | |
105 | /* Scancodes read from the GIRB register */ |
106 | #define KEY1_CODE 0x410 |
107 | #define KEY2_CODE 0x411 |
108 | #define KEY3_CODE 0x412 |
109 | #define KEY4_CODE 0x413 |
110 | #define KEY5_CODE 0x414 |
111 | #define KEY6_CODE 0x415 |
112 | #define KEY7_CODE 0x416 |
113 | #define KEY8_CODE 0x417 |
114 | #define KEY9_CODE 0x420 |
115 | |
116 | /* Hotkey ringbuffer limits */ |
117 | #define MAX_HOTKEY_RINGBUFFER_SIZE 100 |
118 | #define RINGBUFFERSIZE 40 |
119 | |
120 | /* Module parameters */ |
121 | static int use_alt_lcd_levels = -1; |
122 | static bool disable_brightness_adjust; |
123 | |
124 | /* Device controlling the backlight and associated keys */ |
125 | struct fujitsu_bl { |
126 | struct input_dev *input; |
127 | char phys[32]; |
128 | struct backlight_device *bl_device; |
129 | unsigned int max_brightness; |
130 | unsigned int brightness_level; |
131 | }; |
132 | |
133 | static struct fujitsu_bl *fujitsu_bl; |
134 | |
135 | /* Device used to access hotkeys and other features on the laptop */ |
136 | struct fujitsu_laptop { |
137 | struct input_dev *input; |
138 | char phys[32]; |
139 | struct platform_device *pf_device; |
140 | struct kfifo fifo; |
141 | spinlock_t fifo_lock; |
142 | int flags_supported; |
143 | int flags_state; |
144 | bool charge_control_supported; |
145 | }; |
146 | |
147 | static struct acpi_device *fext; |
148 | |
149 | /* Fujitsu ACPI interface function */ |
150 | |
151 | static int call_fext_func(struct acpi_device *device, |
152 | int func, int op, int feature, int state) |
153 | { |
154 | union acpi_object params[4] = { |
155 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = func }, |
156 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = op }, |
157 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature }, |
158 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state } |
159 | }; |
160 | struct acpi_object_list arg_list = { 4, params }; |
161 | unsigned long long value; |
162 | acpi_status status; |
163 | |
164 | status = acpi_evaluate_integer(handle: device->handle, pathname: "FUNC", arguments: &arg_list, |
165 | data: &value); |
166 | if (ACPI_FAILURE(status)) { |
167 | acpi_handle_err(device->handle, "Failed to evaluate FUNC\n"); |
168 | return -ENODEV; |
169 | } |
170 | |
171 | acpi_handle_debug(device->handle, |
172 | "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", |
173 | func, op, feature, state, (int)value); |
174 | return value; |
175 | } |
176 | |
177 | /* Battery charge control code */ |
178 | static ssize_t charge_control_end_threshold_store(struct device *dev, |
179 | struct device_attribute *attr, |
180 | const char *buf, size_t count) |
181 | { |
182 | int cc_end_value, s006_cc_return; |
183 | int value, ret; |
184 | |
185 | ret = kstrtouint(s: buf, base: 10, res: &value); |
186 | if (ret) |
187 | return ret; |
188 | |
189 | if (value < 50 || value > 100) |
190 | return -EINVAL; |
191 | |
192 | cc_end_value = value * 0x100 + 0x20; |
193 | s006_cc_return = call_fext_func(device: fext, FUNC_S006_METHOD, |
194 | CHARGE_CONTROL_RW, feature: cc_end_value, state: 0x0); |
195 | if (s006_cc_return < 0) |
196 | return s006_cc_return; |
197 | /* |
198 | * The S006 0x21 method returns 0x00 in case the provided value |
199 | * is invalid. |
200 | */ |
201 | if (s006_cc_return == 0x00) |
202 | return -EINVAL; |
203 | |
204 | return count; |
205 | } |
206 | |
207 | static ssize_t charge_control_end_threshold_show(struct device *dev, |
208 | struct device_attribute *attr, |
209 | char *buf) |
210 | { |
211 | int status; |
212 | |
213 | status = call_fext_func(device: fext, FUNC_S006_METHOD, |
214 | CHARGE_CONTROL_RW, feature: 0x21, state: 0x0); |
215 | if (status < 0) |
216 | return status; |
217 | |
218 | return sysfs_emit(buf, fmt: "%d\n", status); |
219 | } |
220 | |
221 | static DEVICE_ATTR_RW(charge_control_end_threshold); |
222 | |
223 | /* ACPI battery hook */ |
224 | static int fujitsu_battery_add_hook(struct power_supply *battery, |
225 | struct acpi_battery_hook *hook) |
226 | { |
227 | return device_create_file(device: &battery->dev, |
228 | entry: &dev_attr_charge_control_end_threshold); |
229 | } |
230 | |
231 | static int fujitsu_battery_remove_hook(struct power_supply *battery, |
232 | struct acpi_battery_hook *hook) |
233 | { |
234 | device_remove_file(dev: &battery->dev, |
235 | attr: &dev_attr_charge_control_end_threshold); |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static struct acpi_battery_hook battery_hook = { |
241 | .add_battery = fujitsu_battery_add_hook, |
242 | .remove_battery = fujitsu_battery_remove_hook, |
243 | .name = "Fujitsu Battery Extension", |
244 | }; |
245 | |
246 | /* |
247 | * These functions are intended to be called from acpi_fujitsu_laptop_add and |
248 | * acpi_fujitsu_laptop_remove. |
249 | */ |
250 | static int fujitsu_battery_charge_control_add(struct acpi_device *device) |
251 | { |
252 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
253 | int s006_cc_return; |
254 | |
255 | priv->charge_control_supported = false; |
256 | /* |
257 | * Check if the S006 0x21 method exists by trying to get the current |
258 | * battery charge limit. |
259 | */ |
260 | s006_cc_return = call_fext_func(device: fext, FUNC_S006_METHOD, |
261 | CHARGE_CONTROL_RW, feature: 0x21, state: 0x0); |
262 | if (s006_cc_return < 0) |
263 | return s006_cc_return; |
264 | if (s006_cc_return == UNSUPPORTED_CMD) |
265 | return -ENODEV; |
266 | |
267 | priv->charge_control_supported = true; |
268 | battery_hook_register(hook: &battery_hook); |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | static void fujitsu_battery_charge_control_remove(struct acpi_device *device) |
274 | { |
275 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
276 | |
277 | if (priv->charge_control_supported) |
278 | battery_hook_unregister(hook: &battery_hook); |
279 | } |
280 | |
281 | /* Hardware access for LCD brightness control */ |
282 | |
283 | static int set_lcd_level(struct acpi_device *device, int level) |
284 | { |
285 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
286 | acpi_status status; |
287 | char *method; |
288 | |
289 | switch (use_alt_lcd_levels) { |
290 | case -1: |
291 | if (acpi_has_method(handle: device->handle, name: "SBL2")) |
292 | method = "SBL2"; |
293 | else |
294 | method = "SBLL"; |
295 | break; |
296 | case 1: |
297 | method = "SBL2"; |
298 | break; |
299 | default: |
300 | method = "SBLL"; |
301 | break; |
302 | } |
303 | |
304 | acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method, |
305 | level); |
306 | |
307 | if (level < 0 || level >= priv->max_brightness) |
308 | return -EINVAL; |
309 | |
310 | status = acpi_execute_simple_method(handle: device->handle, method, arg: level); |
311 | if (ACPI_FAILURE(status)) { |
312 | acpi_handle_err(device->handle, "Failed to evaluate %s\n", |
313 | method); |
314 | return -ENODEV; |
315 | } |
316 | |
317 | priv->brightness_level = level; |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static int get_lcd_level(struct acpi_device *device) |
323 | { |
324 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
325 | unsigned long long state = 0; |
326 | acpi_status status = AE_OK; |
327 | |
328 | acpi_handle_debug(device->handle, "get lcd level via GBLL\n"); |
329 | |
330 | status = acpi_evaluate_integer(handle: device->handle, pathname: "GBLL", NULL, data: &state); |
331 | if (ACPI_FAILURE(status)) |
332 | return 0; |
333 | |
334 | priv->brightness_level = state & 0x0fffffff; |
335 | |
336 | return priv->brightness_level; |
337 | } |
338 | |
339 | static int get_max_brightness(struct acpi_device *device) |
340 | { |
341 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
342 | unsigned long long state = 0; |
343 | acpi_status status = AE_OK; |
344 | |
345 | acpi_handle_debug(device->handle, "get max lcd level via RBLL\n"); |
346 | |
347 | status = acpi_evaluate_integer(handle: device->handle, pathname: "RBLL", NULL, data: &state); |
348 | if (ACPI_FAILURE(status)) |
349 | return -1; |
350 | |
351 | priv->max_brightness = state; |
352 | |
353 | return priv->max_brightness; |
354 | } |
355 | |
356 | /* Backlight device stuff */ |
357 | |
358 | static int bl_get_brightness(struct backlight_device *b) |
359 | { |
360 | struct acpi_device *device = bl_get_data(bl_dev: b); |
361 | |
362 | return b->props.power == BACKLIGHT_POWER_OFF ? 0 : get_lcd_level(device); |
363 | } |
364 | |
365 | static int bl_update_status(struct backlight_device *b) |
366 | { |
367 | struct acpi_device *device = bl_get_data(bl_dev: b); |
368 | |
369 | if (fext) { |
370 | if (b->props.power == BACKLIGHT_POWER_OFF) |
371 | call_fext_func(device: fext, FUNC_BACKLIGHT, op: 0x1, |
372 | BACKLIGHT_PARAM_POWER, BACKLIGHT_OFF); |
373 | else |
374 | call_fext_func(device: fext, FUNC_BACKLIGHT, op: 0x1, |
375 | BACKLIGHT_PARAM_POWER, BACKLIGHT_ON); |
376 | } |
377 | |
378 | return set_lcd_level(device, level: b->props.brightness); |
379 | } |
380 | |
381 | static const struct backlight_ops fujitsu_bl_ops = { |
382 | .get_brightness = bl_get_brightness, |
383 | .update_status = bl_update_status, |
384 | }; |
385 | |
386 | static ssize_t lid_show(struct device *dev, struct device_attribute *attr, |
387 | char *buf) |
388 | { |
389 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
390 | |
391 | if (!(priv->flags_supported & FLAG_LID)) |
392 | return sysfs_emit(buf, fmt: "unknown\n"); |
393 | if (priv->flags_state & FLAG_LID) |
394 | return sysfs_emit(buf, fmt: "open\n"); |
395 | else |
396 | return sysfs_emit(buf, fmt: "closed\n"); |
397 | } |
398 | |
399 | static ssize_t dock_show(struct device *dev, struct device_attribute *attr, |
400 | char *buf) |
401 | { |
402 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
403 | |
404 | if (!(priv->flags_supported & FLAG_DOCK)) |
405 | return sysfs_emit(buf, fmt: "unknown\n"); |
406 | if (priv->flags_state & FLAG_DOCK) |
407 | return sysfs_emit(buf, fmt: "docked\n"); |
408 | else |
409 | return sysfs_emit(buf, fmt: "undocked\n"); |
410 | } |
411 | |
412 | static ssize_t radios_show(struct device *dev, struct device_attribute *attr, |
413 | char *buf) |
414 | { |
415 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
416 | |
417 | if (!(priv->flags_supported & FLAG_RFKILL)) |
418 | return sysfs_emit(buf, fmt: "unknown\n"); |
419 | if (priv->flags_state & FLAG_RFKILL) |
420 | return sysfs_emit(buf, fmt: "on\n"); |
421 | else |
422 | return sysfs_emit(buf, fmt: "killed\n"); |
423 | } |
424 | |
425 | static DEVICE_ATTR_RO(lid); |
426 | static DEVICE_ATTR_RO(dock); |
427 | static DEVICE_ATTR_RO(radios); |
428 | |
429 | static struct attribute *fujitsu_pf_attributes[] = { |
430 | &dev_attr_lid.attr, |
431 | &dev_attr_dock.attr, |
432 | &dev_attr_radios.attr, |
433 | NULL |
434 | }; |
435 | |
436 | static const struct attribute_group fujitsu_pf_attribute_group = { |
437 | .attrs = fujitsu_pf_attributes |
438 | }; |
439 | |
440 | static struct platform_driver fujitsu_pf_driver = { |
441 | .driver = { |
442 | .name = "fujitsu-laptop", |
443 | } |
444 | }; |
445 | |
446 | /* ACPI device for LCD brightness control */ |
447 | |
448 | static const struct key_entry keymap_backlight[] = { |
449 | { KE_KEY, true, { KEY_BRIGHTNESSUP } }, |
450 | { KE_KEY, false, { KEY_BRIGHTNESSDOWN } }, |
451 | { KE_END, 0 } |
452 | }; |
453 | |
454 | static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) |
455 | { |
456 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
457 | int ret; |
458 | |
459 | priv->input = devm_input_allocate_device(&device->dev); |
460 | if (!priv->input) |
461 | return -ENOMEM; |
462 | |
463 | snprintf(buf: priv->phys, size: sizeof(priv->phys), fmt: "%s/video/input0", |
464 | acpi_device_hid(device)); |
465 | |
466 | priv->input->name = acpi_device_name(device); |
467 | priv->input->phys = priv->phys; |
468 | priv->input->id.bustype = BUS_HOST; |
469 | priv->input->id.product = 0x06; |
470 | |
471 | ret = sparse_keymap_setup(dev: priv->input, keymap: keymap_backlight, NULL); |
472 | if (ret) |
473 | return ret; |
474 | |
475 | return input_register_device(priv->input); |
476 | } |
477 | |
478 | static int fujitsu_backlight_register(struct acpi_device *device) |
479 | { |
480 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
481 | const struct backlight_properties props = { |
482 | .brightness = priv->brightness_level, |
483 | .max_brightness = priv->max_brightness - 1, |
484 | .type = BACKLIGHT_PLATFORM |
485 | }; |
486 | struct backlight_device *bd; |
487 | |
488 | bd = devm_backlight_device_register(dev: &device->dev, name: "fujitsu-laptop", |
489 | parent: &device->dev, devdata: device, |
490 | ops: &fujitsu_bl_ops, props: &props); |
491 | if (IS_ERR(ptr: bd)) |
492 | return PTR_ERR(ptr: bd); |
493 | |
494 | priv->bl_device = bd; |
495 | |
496 | return 0; |
497 | } |
498 | |
499 | static int acpi_fujitsu_bl_add(struct acpi_device *device) |
500 | { |
501 | struct fujitsu_bl *priv; |
502 | int ret; |
503 | |
504 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) |
505 | return -ENODEV; |
506 | |
507 | priv = devm_kzalloc(dev: &device->dev, size: sizeof(*priv), GFP_KERNEL); |
508 | if (!priv) |
509 | return -ENOMEM; |
510 | |
511 | fujitsu_bl = priv; |
512 | strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); |
513 | strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); |
514 | device->driver_data = priv; |
515 | |
516 | pr_info("ACPI: %s [%s]\n", |
517 | acpi_device_name(device), acpi_device_bid(device)); |
518 | |
519 | if (get_max_brightness(device) <= 0) |
520 | priv->max_brightness = FUJITSU_LCD_N_LEVELS; |
521 | get_lcd_level(device); |
522 | |
523 | ret = acpi_fujitsu_bl_input_setup(device); |
524 | if (ret) |
525 | return ret; |
526 | |
527 | return fujitsu_backlight_register(device); |
528 | } |
529 | |
530 | /* Brightness notify */ |
531 | |
532 | static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event) |
533 | { |
534 | struct fujitsu_bl *priv = acpi_driver_data(d: device); |
535 | int oldb, newb; |
536 | |
537 | if (event != ACPI_FUJITSU_NOTIFY_CODE) { |
538 | acpi_handle_info(device->handle, "unsupported event [0x%x]\n", |
539 | event); |
540 | sparse_keymap_report_event(dev: priv->input, code: -1, value: 1, autorelease: true); |
541 | return; |
542 | } |
543 | |
544 | oldb = priv->brightness_level; |
545 | get_lcd_level(device); |
546 | newb = priv->brightness_level; |
547 | |
548 | acpi_handle_debug(device->handle, |
549 | "brightness button event [%i -> %i]\n", oldb, newb); |
550 | |
551 | if (oldb == newb) |
552 | return; |
553 | |
554 | if (!disable_brightness_adjust) |
555 | set_lcd_level(device, level: newb); |
556 | |
557 | sparse_keymap_report_event(dev: priv->input, code: oldb < newb, value: 1, autorelease: true); |
558 | } |
559 | |
560 | /* ACPI device for hotkey handling */ |
561 | |
562 | static const struct key_entry keymap_default[] = { |
563 | { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, |
564 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, |
565 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, |
566 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, |
567 | { KE_KEY, KEY9_CODE, { KEY_RFKILL } }, |
568 | /* Soft keys read from status flags */ |
569 | { KE_KEY, FLAG_RFKILL, { KEY_RFKILL } }, |
570 | { KE_KEY, FLAG_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } }, |
571 | { KE_KEY, FLAG_MICMUTE, { KEY_MICMUTE } }, |
572 | { KE_END, 0 } |
573 | }; |
574 | |
575 | static const struct key_entry keymap_s64x0[] = { |
576 | { KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */ |
577 | { KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */ |
578 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, |
579 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, |
580 | { KE_END, 0 } |
581 | }; |
582 | |
583 | static const struct key_entry keymap_p8010[] = { |
584 | { KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */ |
585 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, |
586 | { KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */ |
587 | { KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */ |
588 | { KE_END, 0 } |
589 | }; |
590 | |
591 | static const struct key_entry keymap_s2110[] = { |
592 | { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, /* "A" */ |
593 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, /* "B" */ |
594 | { KE_KEY, KEY3_CODE, { KEY_WWW } }, /* "Internet" */ |
595 | { KE_KEY, KEY4_CODE, { KEY_EMAIL } }, /* "E-mail" */ |
596 | { KE_KEY, KEY5_CODE, { KEY_STOPCD } }, |
597 | { KE_KEY, KEY6_CODE, { KEY_PLAYPAUSE } }, |
598 | { KE_KEY, KEY7_CODE, { KEY_PREVIOUSSONG } }, |
599 | { KE_KEY, KEY8_CODE, { KEY_NEXTSONG } }, |
600 | { KE_END, 0 } |
601 | }; |
602 | |
603 | static const struct key_entry *keymap = keymap_default; |
604 | |
605 | static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id) |
606 | { |
607 | pr_info("Identified laptop model '%s'\n", id->ident); |
608 | keymap = id->driver_data; |
609 | return 1; |
610 | } |
611 | |
612 | static const struct dmi_system_id fujitsu_laptop_dmi_table[] = { |
613 | { |
614 | .callback = fujitsu_laptop_dmi_keymap_override, |
615 | .ident = "Fujitsu Siemens S6410", |
616 | .matches = { |
617 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), |
618 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"), |
619 | }, |
620 | .driver_data = (void *)keymap_s64x0 |
621 | }, |
622 | { |
623 | .callback = fujitsu_laptop_dmi_keymap_override, |
624 | .ident = "Fujitsu Siemens S6420", |
625 | .matches = { |
626 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), |
627 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"), |
628 | }, |
629 | .driver_data = (void *)keymap_s64x0 |
630 | }, |
631 | { |
632 | .callback = fujitsu_laptop_dmi_keymap_override, |
633 | .ident = "Fujitsu LifeBook P8010", |
634 | .matches = { |
635 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), |
636 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"), |
637 | }, |
638 | .driver_data = (void *)keymap_p8010 |
639 | }, |
640 | { |
641 | .callback = fujitsu_laptop_dmi_keymap_override, |
642 | .ident = "Fujitsu LifeBook S2110", |
643 | .matches = { |
644 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), |
645 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S2110"), |
646 | }, |
647 | .driver_data = (void *)keymap_s2110 |
648 | }, |
649 | {} |
650 | }; |
651 | |
652 | static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) |
653 | { |
654 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
655 | int ret; |
656 | |
657 | priv->input = devm_input_allocate_device(&device->dev); |
658 | if (!priv->input) |
659 | return -ENOMEM; |
660 | |
661 | snprintf(buf: priv->phys, size: sizeof(priv->phys), fmt: "%s/input0", |
662 | acpi_device_hid(device)); |
663 | |
664 | priv->input->name = acpi_device_name(device); |
665 | priv->input->phys = priv->phys; |
666 | priv->input->id.bustype = BUS_HOST; |
667 | |
668 | dmi_check_system(list: fujitsu_laptop_dmi_table); |
669 | ret = sparse_keymap_setup(dev: priv->input, keymap, NULL); |
670 | if (ret) |
671 | return ret; |
672 | |
673 | return input_register_device(priv->input); |
674 | } |
675 | |
676 | static int fujitsu_laptop_platform_add(struct acpi_device *device) |
677 | { |
678 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
679 | int ret; |
680 | |
681 | priv->pf_device = platform_device_alloc(name: "fujitsu-laptop", PLATFORM_DEVID_NONE); |
682 | if (!priv->pf_device) |
683 | return -ENOMEM; |
684 | |
685 | platform_set_drvdata(pdev: priv->pf_device, data: priv); |
686 | |
687 | ret = platform_device_add(pdev: priv->pf_device); |
688 | if (ret) |
689 | goto err_put_platform_device; |
690 | |
691 | ret = sysfs_create_group(kobj: &priv->pf_device->dev.kobj, |
692 | grp: &fujitsu_pf_attribute_group); |
693 | if (ret) |
694 | goto err_del_platform_device; |
695 | |
696 | return 0; |
697 | |
698 | err_del_platform_device: |
699 | platform_device_del(pdev: priv->pf_device); |
700 | err_put_platform_device: |
701 | platform_device_put(pdev: priv->pf_device); |
702 | |
703 | return ret; |
704 | } |
705 | |
706 | static void fujitsu_laptop_platform_remove(struct acpi_device *device) |
707 | { |
708 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
709 | |
710 | sysfs_remove_group(kobj: &priv->pf_device->dev.kobj, |
711 | grp: &fujitsu_pf_attribute_group); |
712 | platform_device_unregister(priv->pf_device); |
713 | } |
714 | |
715 | static int logolamp_set(struct led_classdev *cdev, |
716 | enum led_brightness brightness) |
717 | { |
718 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
719 | int poweron = FUNC_LED_ON, always = FUNC_LED_ON; |
720 | int ret; |
721 | |
722 | if (brightness < LED_HALF) |
723 | poweron = FUNC_LED_OFF; |
724 | |
725 | if (brightness < LED_FULL) |
726 | always = FUNC_LED_OFF; |
727 | |
728 | ret = call_fext_func(device, FUNC_LEDS, op: 0x1, LOGOLAMP_POWERON, state: poweron); |
729 | if (ret < 0) |
730 | return ret; |
731 | |
732 | return call_fext_func(device, FUNC_LEDS, op: 0x1, LOGOLAMP_ALWAYS, state: always); |
733 | } |
734 | |
735 | static enum led_brightness logolamp_get(struct led_classdev *cdev) |
736 | { |
737 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
738 | int ret; |
739 | |
740 | ret = call_fext_func(device, FUNC_LEDS, op: 0x2, LOGOLAMP_ALWAYS, state: 0x0); |
741 | if (ret == FUNC_LED_ON) |
742 | return LED_FULL; |
743 | |
744 | ret = call_fext_func(device, FUNC_LEDS, op: 0x2, LOGOLAMP_POWERON, state: 0x0); |
745 | if (ret == FUNC_LED_ON) |
746 | return LED_HALF; |
747 | |
748 | return LED_OFF; |
749 | } |
750 | |
751 | static int kblamps_set(struct led_classdev *cdev, |
752 | enum led_brightness brightness) |
753 | { |
754 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
755 | |
756 | if (brightness >= LED_FULL) |
757 | return call_fext_func(device, FUNC_LEDS, op: 0x1, KEYBOARD_LAMPS, |
758 | FUNC_LED_ON); |
759 | else |
760 | return call_fext_func(device, FUNC_LEDS, op: 0x1, KEYBOARD_LAMPS, |
761 | FUNC_LED_OFF); |
762 | } |
763 | |
764 | static enum led_brightness kblamps_get(struct led_classdev *cdev) |
765 | { |
766 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
767 | enum led_brightness brightness = LED_OFF; |
768 | |
769 | if (call_fext_func(device, |
770 | FUNC_LEDS, op: 0x2, KEYBOARD_LAMPS, state: 0x0) == FUNC_LED_ON) |
771 | brightness = LED_FULL; |
772 | |
773 | return brightness; |
774 | } |
775 | |
776 | static int radio_led_set(struct led_classdev *cdev, |
777 | enum led_brightness brightness) |
778 | { |
779 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
780 | |
781 | if (brightness >= LED_FULL) |
782 | return call_fext_func(device, FUNC_FLAGS, op: 0x5, RADIO_LED_ON, |
783 | RADIO_LED_ON); |
784 | else |
785 | return call_fext_func(device, FUNC_FLAGS, op: 0x5, RADIO_LED_ON, |
786 | state: 0x0); |
787 | } |
788 | |
789 | static enum led_brightness radio_led_get(struct led_classdev *cdev) |
790 | { |
791 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
792 | enum led_brightness brightness = LED_OFF; |
793 | |
794 | if (call_fext_func(device, FUNC_FLAGS, op: 0x4, feature: 0x0, state: 0x0) & RADIO_LED_ON) |
795 | brightness = LED_FULL; |
796 | |
797 | return brightness; |
798 | } |
799 | |
800 | static int eco_led_set(struct led_classdev *cdev, |
801 | enum led_brightness brightness) |
802 | { |
803 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
804 | int curr; |
805 | |
806 | curr = call_fext_func(device, FUNC_LEDS, op: 0x2, ECO_LED, state: 0x0); |
807 | if (brightness >= LED_FULL) |
808 | return call_fext_func(device, FUNC_LEDS, op: 0x1, ECO_LED, |
809 | state: curr | ECO_LED_ON); |
810 | else |
811 | return call_fext_func(device, FUNC_LEDS, op: 0x1, ECO_LED, |
812 | state: curr & ~ECO_LED_ON); |
813 | } |
814 | |
815 | static enum led_brightness eco_led_get(struct led_classdev *cdev) |
816 | { |
817 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
818 | enum led_brightness brightness = LED_OFF; |
819 | |
820 | if (call_fext_func(device, FUNC_LEDS, op: 0x2, ECO_LED, state: 0x0) & ECO_LED_ON) |
821 | brightness = LED_FULL; |
822 | |
823 | return brightness; |
824 | } |
825 | |
826 | static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) |
827 | { |
828 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
829 | struct led_classdev *led; |
830 | int ret; |
831 | |
832 | if (call_fext_func(device, |
833 | FUNC_LEDS, op: 0x0, feature: 0x0, state: 0x0) & LOGOLAMP_POWERON) { |
834 | led = devm_kzalloc(dev: &device->dev, size: sizeof(*led), GFP_KERNEL); |
835 | if (!led) |
836 | return -ENOMEM; |
837 | |
838 | led->name = "fujitsu::logolamp"; |
839 | led->brightness_set_blocking = logolamp_set; |
840 | led->brightness_get = logolamp_get; |
841 | ret = devm_led_classdev_register(parent: &device->dev, led_cdev: led); |
842 | if (ret) |
843 | return ret; |
844 | } |
845 | |
846 | if ((call_fext_func(device, |
847 | FUNC_LEDS, op: 0x0, feature: 0x0, state: 0x0) & KEYBOARD_LAMPS) && |
848 | (call_fext_func(device, FUNC_BUTTONS, op: 0x0, feature: 0x0, state: 0x0) == 0x0)) { |
849 | led = devm_kzalloc(dev: &device->dev, size: sizeof(*led), GFP_KERNEL); |
850 | if (!led) |
851 | return -ENOMEM; |
852 | |
853 | led->name = "fujitsu::kblamps"; |
854 | led->brightness_set_blocking = kblamps_set; |
855 | led->brightness_get = kblamps_get; |
856 | ret = devm_led_classdev_register(parent: &device->dev, led_cdev: led); |
857 | if (ret) |
858 | return ret; |
859 | } |
860 | |
861 | /* |
862 | * Some Fujitsu laptops have a radio toggle button in place of a slide |
863 | * switch and all such machines appear to also have an RF LED. Based on |
864 | * comparing DSDT tables of four Fujitsu Lifebook models (E744, E751, |
865 | * S7110, S8420; the first one has a radio toggle button, the other |
866 | * three have slide switches), bit 17 of flags_supported (the value |
867 | * returned by method S000 of ACPI device FUJ02E3) seems to indicate |
868 | * whether given model has a radio toggle button. |
869 | */ |
870 | if (priv->flags_supported & BIT(17)) { |
871 | led = devm_kzalloc(dev: &device->dev, size: sizeof(*led), GFP_KERNEL); |
872 | if (!led) |
873 | return -ENOMEM; |
874 | |
875 | led->name = "fujitsu::radio_led"; |
876 | led->brightness_set_blocking = radio_led_set; |
877 | led->brightness_get = radio_led_get; |
878 | led->default_trigger = "rfkill-any"; |
879 | ret = devm_led_classdev_register(parent: &device->dev, led_cdev: led); |
880 | if (ret) |
881 | return ret; |
882 | } |
883 | |
884 | /* Support for eco led is not always signaled in bit corresponding |
885 | * to the bit used to control the led. According to the DSDT table, |
886 | * bit 14 seems to indicate presence of said led as well. |
887 | * Confirm by testing the status. |
888 | */ |
889 | if ((call_fext_func(device, FUNC_LEDS, op: 0x0, feature: 0x0, state: 0x0) & BIT(14)) && |
890 | (call_fext_func(device, |
891 | FUNC_LEDS, op: 0x2, ECO_LED, state: 0x0) != UNSUPPORTED_CMD)) { |
892 | led = devm_kzalloc(dev: &device->dev, size: sizeof(*led), GFP_KERNEL); |
893 | if (!led) |
894 | return -ENOMEM; |
895 | |
896 | led->name = "fujitsu::eco_led"; |
897 | led->brightness_set_blocking = eco_led_set; |
898 | led->brightness_get = eco_led_get; |
899 | ret = devm_led_classdev_register(parent: &device->dev, led_cdev: led); |
900 | if (ret) |
901 | return ret; |
902 | } |
903 | |
904 | return 0; |
905 | } |
906 | |
907 | static int acpi_fujitsu_laptop_add(struct acpi_device *device) |
908 | { |
909 | struct fujitsu_laptop *priv; |
910 | int ret, i = 0; |
911 | |
912 | priv = devm_kzalloc(dev: &device->dev, size: sizeof(*priv), GFP_KERNEL); |
913 | if (!priv) |
914 | return -ENOMEM; |
915 | |
916 | WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); |
917 | fext = device; |
918 | |
919 | strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); |
920 | strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); |
921 | device->driver_data = priv; |
922 | |
923 | /* kfifo */ |
924 | spin_lock_init(&priv->fifo_lock); |
925 | ret = kfifo_alloc(&priv->fifo, RINGBUFFERSIZE * sizeof(int), |
926 | GFP_KERNEL); |
927 | if (ret) |
928 | return ret; |
929 | |
930 | pr_info("ACPI: %s [%s]\n", |
931 | acpi_device_name(device), acpi_device_bid(device)); |
932 | |
933 | while (call_fext_func(device, FUNC_BUTTONS, op: 0x1, feature: 0x0, state: 0x0) != 0 && |
934 | i++ < MAX_HOTKEY_RINGBUFFER_SIZE) |
935 | ; /* No action, result is discarded */ |
936 | acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n", |
937 | i); |
938 | |
939 | priv->flags_supported = call_fext_func(device, FUNC_FLAGS, op: 0x0, feature: 0x0, |
940 | state: 0x0); |
941 | |
942 | /* Make sure our bitmask of supported functions is cleared if the |
943 | RFKILL function block is not implemented, like on the S7020. */ |
944 | if (priv->flags_supported == UNSUPPORTED_CMD) |
945 | priv->flags_supported = 0; |
946 | |
947 | if (priv->flags_supported) |
948 | priv->flags_state = call_fext_func(device, FUNC_FLAGS, op: 0x4, feature: 0x0, |
949 | state: 0x0); |
950 | |
951 | /* Suspect this is a keymap of the application panel, print it */ |
952 | acpi_handle_info(device->handle, "BTNI: [0x%x]\n", |
953 | call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0)); |
954 | |
955 | /* Sync backlight power status */ |
956 | if (fujitsu_bl && fujitsu_bl->bl_device && |
957 | acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
958 | if (call_fext_func(device: fext, FUNC_BACKLIGHT, op: 0x2, |
959 | BACKLIGHT_PARAM_POWER, state: 0x0) == BACKLIGHT_OFF) |
960 | fujitsu_bl->bl_device->props.power = BACKLIGHT_POWER_OFF; |
961 | else |
962 | fujitsu_bl->bl_device->props.power = BACKLIGHT_POWER_ON; |
963 | } |
964 | |
965 | ret = acpi_fujitsu_laptop_input_setup(device); |
966 | if (ret) |
967 | goto err_free_fifo; |
968 | |
969 | ret = acpi_fujitsu_laptop_leds_register(device); |
970 | if (ret) |
971 | goto err_free_fifo; |
972 | |
973 | ret = fujitsu_laptop_platform_add(device); |
974 | if (ret) |
975 | goto err_free_fifo; |
976 | |
977 | ret = fujitsu_battery_charge_control_add(device); |
978 | if (ret < 0) |
979 | pr_warn("Unable to register battery charge control: %d\n", ret); |
980 | |
981 | return 0; |
982 | |
983 | err_free_fifo: |
984 | kfifo_free(&priv->fifo); |
985 | |
986 | return ret; |
987 | } |
988 | |
989 | static void acpi_fujitsu_laptop_remove(struct acpi_device *device) |
990 | { |
991 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
992 | |
993 | fujitsu_battery_charge_control_remove(device); |
994 | |
995 | fujitsu_laptop_platform_remove(device); |
996 | |
997 | kfifo_free(&priv->fifo); |
998 | } |
999 | |
1000 | static void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode) |
1001 | { |
1002 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
1003 | int ret; |
1004 | |
1005 | ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode, |
1006 | sizeof(scancode), &priv->fifo_lock); |
1007 | if (ret != sizeof(scancode)) { |
1008 | dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n", |
1009 | scancode); |
1010 | return; |
1011 | } |
1012 | sparse_keymap_report_event(dev: priv->input, code: scancode, value: 1, autorelease: false); |
1013 | dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n", |
1014 | scancode); |
1015 | } |
1016 | |
1017 | static void acpi_fujitsu_laptop_release(struct acpi_device *device) |
1018 | { |
1019 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
1020 | int scancode, ret; |
1021 | |
1022 | while (true) { |
1023 | ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode, |
1024 | sizeof(scancode), &priv->fifo_lock); |
1025 | if (ret != sizeof(scancode)) |
1026 | return; |
1027 | sparse_keymap_report_event(dev: priv->input, code: scancode, value: 0, autorelease: false); |
1028 | dev_dbg(&priv->input->dev, |
1029 | "Pop scancode from ringbuffer [0x%x]\n", scancode); |
1030 | } |
1031 | } |
1032 | |
1033 | static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) |
1034 | { |
1035 | struct fujitsu_laptop *priv = acpi_driver_data(d: device); |
1036 | unsigned long flags; |
1037 | int scancode, i = 0; |
1038 | unsigned int irb; |
1039 | |
1040 | if (event != ACPI_FUJITSU_NOTIFY_CODE) { |
1041 | acpi_handle_info(device->handle, "Unsupported event [0x%x]\n", |
1042 | event); |
1043 | sparse_keymap_report_event(dev: priv->input, code: -1, value: 1, autorelease: true); |
1044 | return; |
1045 | } |
1046 | |
1047 | if (priv->flags_supported) |
1048 | priv->flags_state = call_fext_func(device, FUNC_FLAGS, op: 0x4, feature: 0x0, |
1049 | state: 0x0); |
1050 | |
1051 | while ((irb = call_fext_func(device, |
1052 | FUNC_BUTTONS, op: 0x1, feature: 0x0, state: 0x0)) != 0 && |
1053 | i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { |
1054 | scancode = irb & 0x4ff; |
1055 | if (sparse_keymap_entry_from_scancode(dev: priv->input, code: scancode)) |
1056 | acpi_fujitsu_laptop_press(device, scancode); |
1057 | else if (scancode == 0) |
1058 | acpi_fujitsu_laptop_release(device); |
1059 | else |
1060 | acpi_handle_info(device->handle, |
1061 | "Unknown GIRB result [%x]\n", irb); |
1062 | } |
1063 | |
1064 | /* |
1065 | * First seen on the Skylake-based Lifebook E736/E746/E756), the |
1066 | * touchpad toggle hotkey (Fn+F4) is handled in software. Other models |
1067 | * have since added additional "soft keys". These are reported in the |
1068 | * status flags queried using FUNC_FLAGS. |
1069 | */ |
1070 | if (priv->flags_supported & (FLAG_SOFTKEYS)) { |
1071 | flags = call_fext_func(device, FUNC_FLAGS, op: 0x1, feature: 0x0, state: 0x0); |
1072 | flags &= (FLAG_SOFTKEYS); |
1073 | for_each_set_bit(i, &flags, BITS_PER_LONG) |
1074 | sparse_keymap_report_event(dev: priv->input, BIT(i), value: 1, autorelease: true); |
1075 | } |
1076 | } |
1077 | |
1078 | /* Initialization */ |
1079 | |
1080 | static const struct acpi_device_id fujitsu_bl_device_ids[] = { |
1081 | {ACPI_FUJITSU_BL_HID, 0}, |
1082 | {"", 0}, |
1083 | }; |
1084 | |
1085 | static struct acpi_driver acpi_fujitsu_bl_driver = { |
1086 | .name = ACPI_FUJITSU_BL_DRIVER_NAME, |
1087 | .class = ACPI_FUJITSU_CLASS, |
1088 | .ids = fujitsu_bl_device_ids, |
1089 | .ops = { |
1090 | .add = acpi_fujitsu_bl_add, |
1091 | .notify = acpi_fujitsu_bl_notify, |
1092 | }, |
1093 | }; |
1094 | |
1095 | static const struct acpi_device_id fujitsu_laptop_device_ids[] = { |
1096 | {ACPI_FUJITSU_LAPTOP_HID, 0}, |
1097 | {"", 0}, |
1098 | }; |
1099 | |
1100 | static struct acpi_driver acpi_fujitsu_laptop_driver = { |
1101 | .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME, |
1102 | .class = ACPI_FUJITSU_CLASS, |
1103 | .ids = fujitsu_laptop_device_ids, |
1104 | .ops = { |
1105 | .add = acpi_fujitsu_laptop_add, |
1106 | .remove = acpi_fujitsu_laptop_remove, |
1107 | .notify = acpi_fujitsu_laptop_notify, |
1108 | }, |
1109 | }; |
1110 | |
1111 | static const struct acpi_device_id fujitsu_ids[] __used = { |
1112 | {ACPI_FUJITSU_BL_HID, 0}, |
1113 | {ACPI_FUJITSU_LAPTOP_HID, 0}, |
1114 | {"", 0} |
1115 | }; |
1116 | MODULE_DEVICE_TABLE(acpi, fujitsu_ids); |
1117 | |
1118 | static int __init fujitsu_init(void) |
1119 | { |
1120 | int ret; |
1121 | |
1122 | ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver); |
1123 | if (ret) |
1124 | return ret; |
1125 | |
1126 | /* Register platform stuff */ |
1127 | |
1128 | ret = platform_driver_register(&fujitsu_pf_driver); |
1129 | if (ret) |
1130 | goto err_unregister_acpi; |
1131 | |
1132 | /* Register laptop driver */ |
1133 | |
1134 | ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver); |
1135 | if (ret) |
1136 | goto err_unregister_platform_driver; |
1137 | |
1138 | pr_info("driver "FUJITSU_DRIVER_VERSION " successfully loaded\n"); |
1139 | |
1140 | return 0; |
1141 | |
1142 | err_unregister_platform_driver: |
1143 | platform_driver_unregister(&fujitsu_pf_driver); |
1144 | err_unregister_acpi: |
1145 | acpi_bus_unregister_driver(driver: &acpi_fujitsu_bl_driver); |
1146 | |
1147 | return ret; |
1148 | } |
1149 | |
1150 | static void __exit fujitsu_cleanup(void) |
1151 | { |
1152 | acpi_bus_unregister_driver(driver: &acpi_fujitsu_laptop_driver); |
1153 | |
1154 | platform_driver_unregister(&fujitsu_pf_driver); |
1155 | |
1156 | acpi_bus_unregister_driver(driver: &acpi_fujitsu_bl_driver); |
1157 | |
1158 | pr_info("driver unloaded\n"); |
1159 | } |
1160 | |
1161 | module_init(fujitsu_init); |
1162 | module_exit(fujitsu_cleanup); |
1163 | |
1164 | module_param(use_alt_lcd_levels, int, 0644); |
1165 | MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)"); |
1166 | module_param(disable_brightness_adjust, bool, 0644); |
1167 | MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment"); |
1168 | |
1169 | MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon"); |
1170 | MODULE_DESCRIPTION("Fujitsu laptop extras support"); |
1171 | MODULE_VERSION(FUJITSU_DRIVER_VERSION); |
1172 | MODULE_LICENSE("GPL"); |
1173 |
Definitions
- use_alt_lcd_levels
- disable_brightness_adjust
- fujitsu_bl
- fujitsu_bl
- fujitsu_laptop
- fext
- call_fext_func
- charge_control_end_threshold_store
- charge_control_end_threshold_show
- fujitsu_battery_add_hook
- fujitsu_battery_remove_hook
- battery_hook
- fujitsu_battery_charge_control_add
- fujitsu_battery_charge_control_remove
- set_lcd_level
- get_lcd_level
- get_max_brightness
- bl_get_brightness
- bl_update_status
- fujitsu_bl_ops
- lid_show
- dock_show
- radios_show
- fujitsu_pf_attributes
- fujitsu_pf_attribute_group
- fujitsu_pf_driver
- keymap_backlight
- acpi_fujitsu_bl_input_setup
- fujitsu_backlight_register
- acpi_fujitsu_bl_add
- acpi_fujitsu_bl_notify
- keymap_default
- keymap_s64x0
- keymap_p8010
- keymap_s2110
- keymap
- fujitsu_laptop_dmi_keymap_override
- fujitsu_laptop_dmi_table
- acpi_fujitsu_laptop_input_setup
- fujitsu_laptop_platform_add
- fujitsu_laptop_platform_remove
- logolamp_set
- logolamp_get
- kblamps_set
- kblamps_get
- radio_led_set
- radio_led_get
- eco_led_set
- eco_led_get
- acpi_fujitsu_laptop_leds_register
- acpi_fujitsu_laptop_add
- acpi_fujitsu_laptop_remove
- acpi_fujitsu_laptop_press
- acpi_fujitsu_laptop_release
- acpi_fujitsu_laptop_notify
- fujitsu_bl_device_ids
- acpi_fujitsu_bl_driver
- fujitsu_laptop_device_ids
- acpi_fujitsu_laptop_driver
- fujitsu_ids
- fujitsu_init
Improve your Profiling and Debugging skills
Find out more