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