Commit | Line | Data |
---|---|---|
d0482533 JW |
1 | /*-*-linux-c-*-*/ |
2 | ||
3 | /* | |
409a3e98 | 4 | Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net> |
20b93734 | 5 | Copyright (C) 2008 Peter Gruber <nokos@gmx.net> |
3a407086 | 6 | Copyright (C) 2008 Tony Vroon <tony@linx.net> |
d0482533 JW |
7 | Based on earlier work: |
8 | Copyright (C) 2003 Shane Spencer <shane@bogomip.com> | |
9 | Adrian Yee <brewt-fujitsu@brewt.org> | |
10 | ||
20b93734 JW |
11 | Templated from msi-laptop.c and thinkpad_acpi.c which is copyright |
12 | by its respective authors. | |
d0482533 JW |
13 | |
14 | This program is free software; you can redistribute it and/or modify | |
15 | it under the terms of the GNU General Public License as published by | |
16 | the Free Software Foundation; either version 2 of the License, or | |
17 | (at your option) any later version. | |
18 | ||
19 | This program is distributed in the hope that it will be useful, but | |
20 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
22 | General Public License for more details. | |
23 | ||
24 | You should have received a copy of the GNU General Public License | |
25 | along with this program; if not, write to the Free Software | |
26 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
27 | 02110-1301, USA. | |
28 | */ | |
29 | ||
30 | /* | |
31 | * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional | |
32 | * features made available on a range of Fujitsu laptops including the | |
33 | * P2xxx/P5xxx/S6xxx/S7xxx series. | |
34 | * | |
78b2602f MK |
35 | * This driver implements a vendor-specific backlight control interface for |
36 | * Fujitsu laptops and provides support for hotkeys present on certain Fujitsu | |
37 | * laptops. | |
20b93734 | 38 | * |
0e6a66e9 JW |
39 | * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and |
40 | * P8010. It should work on most P-series and S-series Lifebooks, but | |
41 | * YMMV. | |
20b93734 JW |
42 | * |
43 | * The module parameter use_alt_lcd_levels switches between different ACPI | |
44 | * brightness controls which are used by different Fujitsu laptops. In most | |
45 | * cases the correct method is automatically detected. "use_alt_lcd_levels=1" | |
46 | * is applicable for a Fujitsu Lifebook S6410 if autodetection fails. | |
47 | * | |
d0482533 JW |
48 | */ |
49 | ||
77bad7c8 JP |
50 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
51 | ||
d0482533 JW |
52 | #include <linux/module.h> |
53 | #include <linux/kernel.h> | |
54 | #include <linux/init.h> | |
55 | #include <linux/acpi.h> | |
56 | #include <linux/dmi.h> | |
57 | #include <linux/backlight.h> | |
e8549e2c | 58 | #include <linux/fb.h> |
20b93734 | 59 | #include <linux/input.h> |
f2252672 | 60 | #include <linux/input/sparse-keymap.h> |
20b93734 | 61 | #include <linux/kfifo.h> |
d89bcc83 | 62 | #include <linux/leds.h> |
d0482533 | 63 | #include <linux/platform_device.h> |
5a0e3ad6 | 64 | #include <linux/slab.h> |
413226f7 | 65 | #include <acpi/video.h> |
d0482533 | 66 | |
84a6ce26 | 67 | #define FUJITSU_DRIVER_VERSION "0.6.0" |
d0482533 JW |
68 | |
69 | #define FUJITSU_LCD_N_LEVELS 8 | |
70 | ||
9fc5cf6e AJ |
71 | #define ACPI_FUJITSU_CLASS "fujitsu" |
72 | #define ACPI_FUJITSU_BL_HID "FUJ02B1" | |
73 | #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" | |
74 | #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" | |
6942eabc AJ |
75 | #define ACPI_FUJITSU_LAPTOP_HID "FUJ02E3" |
76 | #define ACPI_FUJITSU_LAPTOP_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver" | |
77 | #define ACPI_FUJITSU_LAPTOP_DEVICE_NAME "Fujitsu FUJ02E3" | |
20b93734 JW |
78 | |
79 | #define ACPI_FUJITSU_NOTIFY_CODE1 0x80 | |
80 | ||
3a407086 | 81 | /* FUNC interface - command values */ |
8ef27bd3 | 82 | #define FUNC_FLAGS 0x1000 |
3a407086 TV |
83 | #define FUNC_LEDS 0x1001 |
84 | #define FUNC_BUTTONS 0x1002 | |
85 | #define FUNC_BACKLIGHT 0x1004 | |
86 | ||
87 | /* FUNC interface - responses */ | |
88 | #define UNSUPPORTED_CMD 0x80000000 | |
89 | ||
d3dd4480 AJ |
90 | /* FUNC interface - status flags */ |
91 | #define FLAG_RFKILL 0x020 | |
92 | #define FLAG_LID 0x100 | |
93 | #define FLAG_DOCK 0x200 | |
94 | ||
3a407086 TV |
95 | /* FUNC interface - LED control */ |
96 | #define FUNC_LED_OFF 0x1 | |
97 | #define FUNC_LED_ON 0x30001 | |
98 | #define KEYBOARD_LAMPS 0x100 | |
99 | #define LOGOLAMP_POWERON 0x2000 | |
100 | #define LOGOLAMP_ALWAYS 0x4000 | |
4f62568c | 101 | #define RADIO_LED_ON 0x20 |
d6b88f64 MG |
102 | #define ECO_LED 0x10000 |
103 | #define ECO_LED_ON 0x80000 | |
3a407086 | 104 | |
20b93734 | 105 | /* Hotkey details */ |
0e6a66e9 JW |
106 | #define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */ |
107 | #define KEY2_CODE 0x411 | |
108 | #define KEY3_CODE 0x412 | |
109 | #define KEY4_CODE 0x413 | |
b5df36cf | 110 | #define KEY5_CODE 0x420 |
20b93734 JW |
111 | |
112 | #define MAX_HOTKEY_RINGBUFFER_SIZE 100 | |
113 | #define RINGBUFFERSIZE 40 | |
114 | ||
20b93734 | 115 | /* Device controlling the backlight and associated keys */ |
9fc5cf6e | 116 | struct fujitsu_bl { |
20b93734 JW |
117 | struct input_dev *input; |
118 | char phys[32]; | |
d0482533 | 119 | struct backlight_device *bl_device; |
20b93734 | 120 | unsigned int max_brightness; |
d0482533 JW |
121 | unsigned int brightness_level; |
122 | }; | |
123 | ||
9fc5cf6e | 124 | static struct fujitsu_bl *fujitsu_bl; |
20b93734 | 125 | static int use_alt_lcd_levels = -1; |
b4bb0cfd | 126 | static bool disable_brightness_adjust; |
20b93734 | 127 | |
6942eabc AJ |
128 | /* Device used to access hotkeys and other features on the laptop */ |
129 | struct fujitsu_laptop { | |
20b93734 JW |
130 | struct input_dev *input; |
131 | char phys[32]; | |
132 | struct platform_device *pf_device; | |
45465487 | 133 | struct kfifo fifo; |
20b93734 | 134 | spinlock_t fifo_lock; |
8ef27bd3 AJ |
135 | int flags_supported; |
136 | int flags_state; | |
20b93734 | 137 | }; |
d0482533 | 138 | |
ca0d9eab | 139 | static struct acpi_device *fext; |
20b93734 | 140 | |
3a407086 TV |
141 | /* Fujitsu ACPI interface function */ |
142 | ||
84631e0c MK |
143 | static int call_fext_func(struct acpi_device *device, |
144 | int func, int op, int feature, int state) | |
3a407086 | 145 | { |
3a407086 | 146 | union acpi_object params[4] = { |
f68e492c MK |
147 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = func }, |
148 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = op }, | |
149 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature }, | |
150 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state } | |
3a407086 | 151 | }; |
b1066410 | 152 | struct acpi_object_list arg_list = { 4, params }; |
29c29a9b | 153 | unsigned long long value; |
b1066410 | 154 | acpi_status status; |
3a407086 | 155 | |
84631e0c MK |
156 | status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list, |
157 | &value); | |
3a407086 | 158 | if (ACPI_FAILURE(status)) { |
eee77da1 | 159 | acpi_handle_err(device->handle, "Failed to evaluate FUNC\n"); |
3a407086 TV |
160 | return -ENODEV; |
161 | } | |
162 | ||
eee77da1 MK |
163 | acpi_handle_debug(device->handle, |
164 | "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", | |
165 | func, op, feature, state, (int)value); | |
29c29a9b | 166 | return value; |
3a407086 TV |
167 | } |
168 | ||
20b93734 | 169 | /* Hardware access for LCD brightness control */ |
d0482533 | 170 | |
f2db7c64 | 171 | static int set_lcd_level(struct acpi_device *device, int level) |
d0482533 | 172 | { |
f2db7c64 | 173 | struct fujitsu_bl *priv = acpi_driver_data(device); |
a8779c35 | 174 | acpi_status status; |
e32c50ba MK |
175 | char *method; |
176 | ||
177 | switch (use_alt_lcd_levels) { | |
e06e4831 | 178 | case -1: |
f2db7c64 | 179 | if (acpi_has_method(device->handle, "SBL2")) |
e06e4831 MK |
180 | method = "SBL2"; |
181 | else | |
182 | method = "SBLL"; | |
183 | break; | |
e32c50ba MK |
184 | case 1: |
185 | method = "SBL2"; | |
186 | break; | |
187 | default: | |
188 | method = "SBLL"; | |
189 | break; | |
20b93734 JW |
190 | } |
191 | ||
eee77da1 MK |
192 | acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method, |
193 | level); | |
20b93734 | 194 | |
f2db7c64 | 195 | if (level < 0 || level >= priv->max_brightness) |
20b93734 JW |
196 | return -EINVAL; |
197 | ||
f2db7c64 | 198 | status = acpi_execute_simple_method(device->handle, method, level); |
20b93734 | 199 | if (ACPI_FAILURE(status)) { |
eee77da1 MK |
200 | acpi_handle_err(device->handle, "Failed to evaluate %s\n", |
201 | method); | |
d0482533 JW |
202 | return -ENODEV; |
203 | } | |
204 | ||
f2db7c64 | 205 | priv->brightness_level = level; |
bd079a2c | 206 | |
d0482533 JW |
207 | return 0; |
208 | } | |
209 | ||
f2db7c64 | 210 | static int get_lcd_level(struct acpi_device *device) |
d0482533 | 211 | { |
f2db7c64 | 212 | struct fujitsu_bl *priv = acpi_driver_data(device); |
27663c58 | 213 | unsigned long long state = 0; |
d0482533 JW |
214 | acpi_status status = AE_OK; |
215 | ||
eee77da1 | 216 | acpi_handle_debug(device->handle, "get lcd level via GBLL\n"); |
20b93734 | 217 | |
f2db7c64 | 218 | status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state); |
3b1c37ca JW |
219 | if (ACPI_FAILURE(status)) |
220 | return 0; | |
d0482533 | 221 | |
f2db7c64 | 222 | priv->brightness_level = state & 0x0fffffff; |
20b93734 | 223 | |
f2db7c64 | 224 | return priv->brightness_level; |
20b93734 JW |
225 | } |
226 | ||
f2db7c64 | 227 | static int get_max_brightness(struct acpi_device *device) |
20b93734 | 228 | { |
f2db7c64 | 229 | struct fujitsu_bl *priv = acpi_driver_data(device); |
27663c58 | 230 | unsigned long long state = 0; |
20b93734 JW |
231 | acpi_status status = AE_OK; |
232 | ||
eee77da1 | 233 | acpi_handle_debug(device->handle, "get max lcd level via RBLL\n"); |
20b93734 | 234 | |
f2db7c64 | 235 | status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state); |
3b1c37ca JW |
236 | if (ACPI_FAILURE(status)) |
237 | return -1; | |
20b93734 | 238 | |
f2db7c64 | 239 | priv->max_brightness = state; |
20b93734 | 240 | |
f2db7c64 | 241 | return priv->max_brightness; |
20b93734 JW |
242 | } |
243 | ||
d0482533 JW |
244 | /* Backlight device stuff */ |
245 | ||
246 | static int bl_get_brightness(struct backlight_device *b) | |
247 | { | |
f2db7c64 MK |
248 | struct acpi_device *device = bl_get_data(b); |
249 | ||
250 | return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(device); | |
d0482533 JW |
251 | } |
252 | ||
253 | static int bl_update_status(struct backlight_device *b) | |
254 | { | |
f2db7c64 MK |
255 | struct acpi_device *device = bl_get_data(b); |
256 | ||
e8549e2c | 257 | if (b->props.power == FB_BLANK_POWERDOWN) |
84631e0c | 258 | call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x3); |
3a407086 | 259 | else |
84631e0c | 260 | call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x0); |
3a407086 | 261 | |
f2db7c64 | 262 | return set_lcd_level(device, b->props.brightness); |
d0482533 JW |
263 | } |
264 | ||
9fc5cf6e | 265 | static const struct backlight_ops fujitsu_bl_ops = { |
d0482533 JW |
266 | .get_brightness = bl_get_brightness, |
267 | .update_status = bl_update_status, | |
268 | }; | |
269 | ||
b0c4b9c6 MK |
270 | static ssize_t lid_show(struct device *dev, struct device_attribute *attr, |
271 | char *buf) | |
3a407086 | 272 | { |
d659d11a MK |
273 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
274 | ||
275 | if (!(priv->flags_supported & FLAG_LID)) | |
3a407086 | 276 | return sprintf(buf, "unknown\n"); |
d659d11a | 277 | if (priv->flags_state & FLAG_LID) |
3a407086 TV |
278 | return sprintf(buf, "open\n"); |
279 | else | |
280 | return sprintf(buf, "closed\n"); | |
281 | } | |
20b93734 | 282 | |
b0c4b9c6 MK |
283 | static ssize_t dock_show(struct device *dev, struct device_attribute *attr, |
284 | char *buf) | |
3a407086 | 285 | { |
d659d11a MK |
286 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
287 | ||
288 | if (!(priv->flags_supported & FLAG_DOCK)) | |
3a407086 | 289 | return sprintf(buf, "unknown\n"); |
d659d11a | 290 | if (priv->flags_state & FLAG_DOCK) |
3a407086 TV |
291 | return sprintf(buf, "docked\n"); |
292 | else | |
293 | return sprintf(buf, "undocked\n"); | |
20b93734 JW |
294 | } |
295 | ||
b0c4b9c6 MK |
296 | static ssize_t radios_show(struct device *dev, struct device_attribute *attr, |
297 | char *buf) | |
20b93734 | 298 | { |
d659d11a MK |
299 | struct fujitsu_laptop *priv = dev_get_drvdata(dev); |
300 | ||
301 | if (!(priv->flags_supported & FLAG_RFKILL)) | |
3a407086 | 302 | return sprintf(buf, "unknown\n"); |
d659d11a | 303 | if (priv->flags_state & FLAG_RFKILL) |
3a407086 TV |
304 | return sprintf(buf, "on\n"); |
305 | else | |
306 | return sprintf(buf, "killed\n"); | |
20b93734 JW |
307 | } |
308 | ||
b0c4b9c6 MK |
309 | static DEVICE_ATTR_RO(lid); |
310 | static DEVICE_ATTR_RO(dock); | |
311 | static DEVICE_ATTR_RO(radios); | |
d0482533 | 312 | |
16506026 | 313 | static struct attribute *fujitsu_pf_attributes[] = { |
3a407086 TV |
314 | &dev_attr_lid.attr, |
315 | &dev_attr_dock.attr, | |
316 | &dev_attr_radios.attr, | |
d0482533 JW |
317 | NULL |
318 | }; | |
319 | ||
ee56ff71 | 320 | static const struct attribute_group fujitsu_pf_attribute_group = { |
16506026 | 321 | .attrs = fujitsu_pf_attributes |
d0482533 JW |
322 | }; |
323 | ||
16506026 | 324 | static struct platform_driver fujitsu_pf_driver = { |
d0482533 JW |
325 | .driver = { |
326 | .name = "fujitsu-laptop", | |
d0482533 JW |
327 | } |
328 | }; | |
329 | ||
20b93734 | 330 | /* ACPI device for LCD brightness control */ |
d0482533 | 331 | |
f2252672 MK |
332 | static const struct key_entry keymap_backlight[] = { |
333 | { KE_KEY, true, { KEY_BRIGHTNESSUP } }, | |
334 | { KE_KEY, false, { KEY_BRIGHTNESSDOWN } }, | |
335 | { KE_END, 0 } | |
336 | }; | |
337 | ||
7d134e43 MK |
338 | static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) |
339 | { | |
7ec3b54d | 340 | struct fujitsu_bl *priv = acpi_driver_data(device); |
f2252672 | 341 | int ret; |
7d134e43 | 342 | |
7ec3b54d MK |
343 | priv->input = devm_input_allocate_device(&device->dev); |
344 | if (!priv->input) | |
7d134e43 MK |
345 | return -ENOMEM; |
346 | ||
7ec3b54d MK |
347 | snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", |
348 | acpi_device_hid(device)); | |
7d134e43 | 349 | |
7ec3b54d MK |
350 | priv->input->name = acpi_device_name(device); |
351 | priv->input->phys = priv->phys; | |
352 | priv->input->id.bustype = BUS_HOST; | |
353 | priv->input->id.product = 0x06; | |
f2252672 | 354 | |
7ec3b54d | 355 | ret = sparse_keymap_setup(priv->input, keymap_backlight, NULL); |
f2252672 MK |
356 | if (ret) |
357 | return ret; | |
7d134e43 | 358 | |
7ec3b54d | 359 | return input_register_device(priv->input); |
7d134e43 MK |
360 | } |
361 | ||
a1aabd5f | 362 | static int fujitsu_backlight_register(struct acpi_device *device) |
b8d69c16 | 363 | { |
f2db7c64 | 364 | struct fujitsu_bl *priv = acpi_driver_data(device); |
a1aabd5f | 365 | const struct backlight_properties props = { |
f2db7c64 MK |
366 | .brightness = priv->brightness_level, |
367 | .max_brightness = priv->max_brightness - 1, | |
b8d69c16 MK |
368 | .type = BACKLIGHT_PLATFORM |
369 | }; | |
370 | struct backlight_device *bd; | |
371 | ||
a1aabd5f | 372 | bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop", |
f2db7c64 | 373 | &device->dev, device, |
a1aabd5f | 374 | &fujitsu_bl_ops, &props); |
b8d69c16 MK |
375 | if (IS_ERR(bd)) |
376 | return PTR_ERR(bd); | |
377 | ||
f2db7c64 | 378 | priv->bl_device = bd; |
b8d69c16 MK |
379 | |
380 | return 0; | |
381 | } | |
382 | ||
9fc5cf6e | 383 | static int acpi_fujitsu_bl_add(struct acpi_device *device) |
d0482533 | 384 | { |
679374e4 | 385 | struct fujitsu_bl *priv; |
20b93734 | 386 | int error; |
d0482533 | 387 | |
07acf62a MK |
388 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) |
389 | return -ENODEV; | |
390 | ||
679374e4 MK |
391 | priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); |
392 | if (!priv) | |
393 | return -ENOMEM; | |
394 | ||
395 | fujitsu_bl = priv; | |
d6a298ae MK |
396 | strcpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); |
397 | strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); | |
679374e4 | 398 | device->driver_data = priv; |
d0482533 | 399 | |
7d134e43 | 400 | error = acpi_fujitsu_bl_input_setup(device); |
20b93734 | 401 | if (error) |
f8a399dc | 402 | return error; |
20b93734 | 403 | |
1c194626 MK |
404 | pr_info("ACPI: %s [%s]\n", |
405 | acpi_device_name(device), acpi_device_bid(device)); | |
d0482533 | 406 | |
f2db7c64 MK |
407 | if (get_max_brightness(device) <= 0) |
408 | priv->max_brightness = FUJITSU_LCD_N_LEVELS; | |
409 | get_lcd_level(device); | |
20b93734 | 410 | |
a1aabd5f | 411 | error = fujitsu_backlight_register(device); |
07acf62a MK |
412 | if (error) |
413 | return error; | |
aea3137c | 414 | |
b30bb89f | 415 | return 0; |
d0482533 JW |
416 | } |
417 | ||
20b93734 JW |
418 | /* Brightness notify */ |
419 | ||
9fc5cf6e | 420 | static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event) |
20b93734 | 421 | { |
f2db7c64 | 422 | struct fujitsu_bl *priv = acpi_driver_data(device); |
f2252672 | 423 | int oldb, newb; |
20b93734 | 424 | |
5efc8004 | 425 | if (event != ACPI_FUJITSU_NOTIFY_CODE1) { |
eee77da1 MK |
426 | acpi_handle_info(device->handle, "unsupported event [0x%x]\n", |
427 | event); | |
f2db7c64 | 428 | sparse_keymap_report_event(priv->input, -1, 1, true); |
5efc8004 MK |
429 | return; |
430 | } | |
431 | ||
f2db7c64 MK |
432 | oldb = priv->brightness_level; |
433 | get_lcd_level(device); | |
434 | newb = priv->brightness_level; | |
5efc8004 | 435 | |
eee77da1 MK |
436 | acpi_handle_debug(device->handle, |
437 | "brightness button event [%i -> %i]\n", oldb, newb); | |
5efc8004 | 438 | |
d2aa3ae8 MK |
439 | if (oldb == newb) |
440 | return; | |
20b93734 | 441 | |
b4bb0cfd | 442 | if (!disable_brightness_adjust) |
f2db7c64 | 443 | set_lcd_level(device, newb); |
d2aa3ae8 | 444 | |
f2db7c64 | 445 | sparse_keymap_report_event(priv->input, oldb < newb, 1, true); |
20b93734 JW |
446 | } |
447 | ||
448 | /* ACPI device for hotkey handling */ | |
449 | ||
527483a8 MK |
450 | static const struct key_entry keymap_default[] = { |
451 | { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, | |
452 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, | |
453 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, | |
454 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, | |
455 | { KE_KEY, KEY5_CODE, { KEY_RFKILL } }, | |
456 | { KE_KEY, BIT(26), { KEY_TOUCHPAD_TOGGLE } }, | |
457 | { KE_END, 0 } | |
458 | }; | |
459 | ||
f8c94ecd MK |
460 | static const struct key_entry keymap_s64x0[] = { |
461 | { KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */ | |
462 | { KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */ | |
463 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, | |
464 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, | |
465 | { KE_END, 0 } | |
466 | }; | |
467 | ||
468 | static const struct key_entry keymap_p8010[] = { | |
469 | { KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */ | |
470 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, | |
471 | { KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */ | |
472 | { KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */ | |
473 | { KE_END, 0 } | |
474 | }; | |
475 | ||
527483a8 MK |
476 | static const struct key_entry *keymap = keymap_default; |
477 | ||
f8c94ecd MK |
478 | static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id) |
479 | { | |
480 | pr_info("Identified laptop model '%s'\n", id->ident); | |
481 | keymap = id->driver_data; | |
482 | return 1; | |
483 | } | |
484 | ||
485 | static const struct dmi_system_id fujitsu_laptop_dmi_table[] = { | |
486 | { | |
487 | .callback = fujitsu_laptop_dmi_keymap_override, | |
488 | .ident = "Fujitsu Siemens S6410", | |
489 | .matches = { | |
490 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), | |
491 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"), | |
492 | }, | |
493 | .driver_data = (void *)keymap_s64x0 | |
494 | }, | |
495 | { | |
496 | .callback = fujitsu_laptop_dmi_keymap_override, | |
497 | .ident = "Fujitsu Siemens S6420", | |
498 | .matches = { | |
499 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), | |
500 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"), | |
501 | }, | |
502 | .driver_data = (void *)keymap_s64x0 | |
503 | }, | |
504 | { | |
505 | .callback = fujitsu_laptop_dmi_keymap_override, | |
506 | .ident = "Fujitsu LifeBook P8010", | |
507 | .matches = { | |
508 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), | |
509 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"), | |
510 | }, | |
511 | .driver_data = (void *)keymap_p8010 | |
512 | }, | |
513 | {} | |
514 | }; | |
515 | ||
11182dbc MK |
516 | static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) |
517 | { | |
7ec3b54d | 518 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
527483a8 | 519 | int ret; |
11182dbc | 520 | |
7ec3b54d MK |
521 | priv->input = devm_input_allocate_device(&device->dev); |
522 | if (!priv->input) | |
11182dbc MK |
523 | return -ENOMEM; |
524 | ||
c1f51f1c | 525 | snprintf(priv->phys, sizeof(priv->phys), "%s/input0", |
7ec3b54d | 526 | acpi_device_hid(device)); |
11182dbc | 527 | |
7ec3b54d MK |
528 | priv->input->name = acpi_device_name(device); |
529 | priv->input->phys = priv->phys; | |
530 | priv->input->id.bustype = BUS_HOST; | |
f66735f8 | 531 | |
f8c94ecd | 532 | dmi_check_system(fujitsu_laptop_dmi_table); |
7ec3b54d | 533 | ret = sparse_keymap_setup(priv->input, keymap, NULL); |
527483a8 MK |
534 | if (ret) |
535 | return ret; | |
f66735f8 | 536 | |
7ec3b54d | 537 | return input_register_device(priv->input); |
11182dbc MK |
538 | } |
539 | ||
d659d11a | 540 | static int fujitsu_laptop_platform_add(struct acpi_device *device) |
d811b511 | 541 | { |
d659d11a | 542 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
d811b511 MK |
543 | int ret; |
544 | ||
d659d11a MK |
545 | priv->pf_device = platform_device_alloc("fujitsu-laptop", -1); |
546 | if (!priv->pf_device) | |
d811b511 MK |
547 | return -ENOMEM; |
548 | ||
d659d11a MK |
549 | platform_set_drvdata(priv->pf_device, priv); |
550 | ||
551 | ret = platform_device_add(priv->pf_device); | |
d811b511 MK |
552 | if (ret) |
553 | goto err_put_platform_device; | |
554 | ||
d659d11a | 555 | ret = sysfs_create_group(&priv->pf_device->dev.kobj, |
d811b511 MK |
556 | &fujitsu_pf_attribute_group); |
557 | if (ret) | |
558 | goto err_del_platform_device; | |
559 | ||
560 | return 0; | |
561 | ||
562 | err_del_platform_device: | |
d659d11a | 563 | platform_device_del(priv->pf_device); |
d811b511 | 564 | err_put_platform_device: |
d659d11a | 565 | platform_device_put(priv->pf_device); |
d811b511 MK |
566 | |
567 | return ret; | |
568 | } | |
569 | ||
d659d11a | 570 | static void fujitsu_laptop_platform_remove(struct acpi_device *device) |
d811b511 | 571 | { |
d659d11a MK |
572 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
573 | ||
574 | sysfs_remove_group(&priv->pf_device->dev.kobj, | |
d811b511 | 575 | &fujitsu_pf_attribute_group); |
d659d11a | 576 | platform_device_unregister(priv->pf_device); |
d811b511 MK |
577 | } |
578 | ||
e33ca45c MK |
579 | static int logolamp_set(struct led_classdev *cdev, |
580 | enum led_brightness brightness) | |
581 | { | |
a823f8e7 | 582 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
583 | int poweron = FUNC_LED_ON, always = FUNC_LED_ON; |
584 | int ret; | |
585 | ||
586 | if (brightness < LED_HALF) | |
587 | poweron = FUNC_LED_OFF; | |
588 | ||
589 | if (brightness < LED_FULL) | |
590 | always = FUNC_LED_OFF; | |
591 | ||
a823f8e7 | 592 | ret = call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); |
e33ca45c MK |
593 | if (ret < 0) |
594 | return ret; | |
595 | ||
a823f8e7 | 596 | return call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); |
e33ca45c MK |
597 | } |
598 | ||
599 | static enum led_brightness logolamp_get(struct led_classdev *cdev) | |
600 | { | |
a823f8e7 | 601 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
602 | int ret; |
603 | ||
a823f8e7 | 604 | ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); |
e33ca45c MK |
605 | if (ret == FUNC_LED_ON) |
606 | return LED_FULL; | |
607 | ||
a823f8e7 | 608 | ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); |
e33ca45c MK |
609 | if (ret == FUNC_LED_ON) |
610 | return LED_HALF; | |
611 | ||
612 | return LED_OFF; | |
613 | } | |
614 | ||
e33ca45c MK |
615 | static int kblamps_set(struct led_classdev *cdev, |
616 | enum led_brightness brightness) | |
617 | { | |
a823f8e7 MK |
618 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
619 | ||
e33ca45c | 620 | if (brightness >= LED_FULL) |
a823f8e7 | 621 | return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, |
e33ca45c MK |
622 | FUNC_LED_ON); |
623 | else | |
a823f8e7 | 624 | return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, |
e33ca45c MK |
625 | FUNC_LED_OFF); |
626 | } | |
627 | ||
628 | static enum led_brightness kblamps_get(struct led_classdev *cdev) | |
629 | { | |
a823f8e7 | 630 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
631 | enum led_brightness brightness = LED_OFF; |
632 | ||
a823f8e7 | 633 | if (call_fext_func(device, |
84631e0c | 634 | FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON) |
e33ca45c MK |
635 | brightness = LED_FULL; |
636 | ||
637 | return brightness; | |
638 | } | |
639 | ||
e33ca45c MK |
640 | static int radio_led_set(struct led_classdev *cdev, |
641 | enum led_brightness brightness) | |
642 | { | |
a823f8e7 MK |
643 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
644 | ||
e33ca45c | 645 | if (brightness >= LED_FULL) |
a823f8e7 | 646 | return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON, |
e33ca45c MK |
647 | RADIO_LED_ON); |
648 | else | |
a823f8e7 | 649 | return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON, |
84631e0c | 650 | 0x0); |
e33ca45c MK |
651 | } |
652 | ||
653 | static enum led_brightness radio_led_get(struct led_classdev *cdev) | |
654 | { | |
a823f8e7 | 655 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
656 | enum led_brightness brightness = LED_OFF; |
657 | ||
a823f8e7 | 658 | if (call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) |
e33ca45c MK |
659 | brightness = LED_FULL; |
660 | ||
661 | return brightness; | |
662 | } | |
663 | ||
e33ca45c MK |
664 | static int eco_led_set(struct led_classdev *cdev, |
665 | enum led_brightness brightness) | |
666 | { | |
a823f8e7 | 667 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
668 | int curr; |
669 | ||
a823f8e7 | 670 | curr = call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0); |
e33ca45c | 671 | if (brightness >= LED_FULL) |
a823f8e7 | 672 | return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED, |
e33ca45c MK |
673 | curr | ECO_LED_ON); |
674 | else | |
a823f8e7 | 675 | return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED, |
e33ca45c MK |
676 | curr & ~ECO_LED_ON); |
677 | } | |
678 | ||
679 | static enum led_brightness eco_led_get(struct led_classdev *cdev) | |
680 | { | |
a823f8e7 | 681 | struct acpi_device *device = to_acpi_device(cdev->dev->parent); |
e33ca45c MK |
682 | enum led_brightness brightness = LED_OFF; |
683 | ||
a823f8e7 | 684 | if (call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON) |
e33ca45c MK |
685 | brightness = LED_FULL; |
686 | ||
687 | return brightness; | |
688 | } | |
689 | ||
81f6821f | 690 | static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) |
20b93734 | 691 | { |
a823f8e7 | 692 | struct led_classdev *led; |
30943e14 | 693 | int result; |
7adb7b12 | 694 | |
a823f8e7 | 695 | if (call_fext_func(device, |
84631e0c | 696 | FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { |
a823f8e7 | 697 | led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); |
b77a5372 GS |
698 | if (!led) |
699 | return -ENOMEM; | |
700 | ||
a823f8e7 MK |
701 | led->name = "fujitsu::logolamp"; |
702 | led->brightness_set_blocking = logolamp_set; | |
703 | led->brightness_get = logolamp_get; | |
704 | result = devm_led_classdev_register(&device->dev, led); | |
81f6821f | 705 | if (result) |
30943e14 | 706 | return result; |
7adb7b12 MK |
707 | } |
708 | ||
a823f8e7 | 709 | if ((call_fext_func(device, |
84631e0c | 710 | FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) && |
a823f8e7 MK |
711 | (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) { |
712 | led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); | |
b77a5372 GS |
713 | if (!led) |
714 | return -ENOMEM; | |
715 | ||
a823f8e7 MK |
716 | led->name = "fujitsu::kblamps"; |
717 | led->brightness_set_blocking = kblamps_set; | |
718 | led->brightness_get = kblamps_get; | |
719 | result = devm_led_classdev_register(&device->dev, led); | |
81f6821f | 720 | if (result) |
30943e14 | 721 | return result; |
7adb7b12 MK |
722 | } |
723 | ||
724 | /* | |
725 | * BTNI bit 24 seems to indicate the presence of a radio toggle | |
726 | * button in place of a slide switch, and all such machines appear | |
727 | * to also have an RF LED. Therefore use bit 24 as an indicator | |
728 | * that an RF LED is present. | |
729 | */ | |
a823f8e7 MK |
730 | if (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { |
731 | led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); | |
b77a5372 GS |
732 | if (!led) |
733 | return -ENOMEM; | |
734 | ||
a823f8e7 MK |
735 | led->name = "fujitsu::radio_led"; |
736 | led->brightness_set_blocking = radio_led_set; | |
737 | led->brightness_get = radio_led_get; | |
738 | led->default_trigger = "rfkill-any"; | |
739 | result = devm_led_classdev_register(&device->dev, led); | |
81f6821f | 740 | if (result) |
30943e14 | 741 | return result; |
7adb7b12 MK |
742 | } |
743 | ||
744 | /* Support for eco led is not always signaled in bit corresponding | |
745 | * to the bit used to control the led. According to the DSDT table, | |
746 | * bit 14 seems to indicate presence of said led as well. | |
747 | * Confirm by testing the status. | |
748 | */ | |
a823f8e7 MK |
749 | if ((call_fext_func(device, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && |
750 | (call_fext_func(device, | |
84631e0c | 751 | FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { |
a823f8e7 | 752 | led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); |
b77a5372 GS |
753 | if (!led) |
754 | return -ENOMEM; | |
755 | ||
a823f8e7 MK |
756 | led->name = "fujitsu::eco_led"; |
757 | led->brightness_set_blocking = eco_led_set; | |
758 | led->brightness_get = eco_led_get; | |
759 | result = devm_led_classdev_register(&device->dev, led); | |
81f6821f | 760 | if (result) |
30943e14 | 761 | return result; |
7adb7b12 MK |
762 | } |
763 | ||
30943e14 | 764 | return 0; |
7adb7b12 MK |
765 | } |
766 | ||
767 | static int acpi_fujitsu_laptop_add(struct acpi_device *device) | |
768 | { | |
a4b176ea | 769 | struct fujitsu_laptop *priv; |
20b93734 JW |
770 | int error; |
771 | int i; | |
772 | ||
a4b176ea MK |
773 | priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); |
774 | if (!priv) | |
775 | return -ENOMEM; | |
776 | ||
ca0d9eab MK |
777 | WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); |
778 | fext = device; | |
779 | ||
d6a298ae MK |
780 | strcpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); |
781 | strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); | |
a4b176ea | 782 | device->driver_data = priv; |
20b93734 | 783 | |
20b93734 | 784 | /* kfifo */ |
d659d11a MK |
785 | spin_lock_init(&priv->fifo_lock); |
786 | error = kfifo_alloc(&priv->fifo, RINGBUFFERSIZE * sizeof(int), | |
787 | GFP_KERNEL); | |
45465487 | 788 | if (error) { |
77bad7c8 | 789 | pr_err("kfifo_alloc failed\n"); |
20b93734 JW |
790 | goto err_stop; |
791 | } | |
792 | ||
11182dbc | 793 | error = acpi_fujitsu_laptop_input_setup(device); |
20b93734 | 794 | if (error) |
11182dbc | 795 | goto err_free_fifo; |
20b93734 | 796 | |
1c194626 MK |
797 | pr_info("ACPI: %s [%s]\n", |
798 | acpi_device_name(device), acpi_device_bid(device)); | |
20b93734 | 799 | |
3a407086 | 800 | i = 0; |
d659d11a | 801 | while (call_fext_func(device, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 |
3a407086 TV |
802 | && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) |
803 | ; /* No action, result is discarded */ | |
eee77da1 MK |
804 | acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n", |
805 | i); | |
20b93734 | 806 | |
d659d11a MK |
807 | priv->flags_supported = call_fext_func(device, FUNC_FLAGS, 0x0, 0x0, |
808 | 0x0); | |
4898c2b2 TV |
809 | |
810 | /* Make sure our bitmask of supported functions is cleared if the | |
811 | RFKILL function block is not implemented, like on the S7020. */ | |
d659d11a MK |
812 | if (priv->flags_supported == UNSUPPORTED_CMD) |
813 | priv->flags_supported = 0; | |
4898c2b2 | 814 | |
d659d11a MK |
815 | if (priv->flags_supported) |
816 | priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, | |
817 | 0x0); | |
3a407086 TV |
818 | |
819 | /* Suspect this is a keymap of the application panel, print it */ | |
eee77da1 MK |
820 | acpi_handle_info(device->handle, "BTNI: [0x%x]\n", |
821 | call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0)); | |
3a407086 | 822 | |
1877e267 | 823 | /* Sync backlight power status */ |
679374e4 | 824 | if (fujitsu_bl && fujitsu_bl->bl_device && |
aea3137c | 825 | acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
84631e0c | 826 | if (call_fext_func(fext, FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) |
1877e267 MK |
827 | fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN; |
828 | else | |
829 | fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK; | |
830 | } | |
831 | ||
d1c7073b | 832 | error = acpi_fujitsu_laptop_leds_register(device); |
c33f4c04 | 833 | if (error) |
f66735f8 | 834 | goto err_free_fifo; |
c33f4c04 | 835 | |
d659d11a | 836 | error = fujitsu_laptop_platform_add(device); |
7adb7b12 | 837 | if (error) |
d1c7073b | 838 | goto err_free_fifo; |
3a407086 | 839 | |
7adb7b12 | 840 | return 0; |
20b93734 | 841 | |
b4ec0275 | 842 | err_free_fifo: |
d659d11a | 843 | kfifo_free(&priv->fifo); |
20b93734 | 844 | err_stop: |
b30bb89f | 845 | return error; |
20b93734 JW |
846 | } |
847 | ||
6942eabc | 848 | static int acpi_fujitsu_laptop_remove(struct acpi_device *device) |
20b93734 | 849 | { |
7ec3b54d | 850 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
20b93734 | 851 | |
d659d11a | 852 | fujitsu_laptop_platform_remove(device); |
c33f4c04 | 853 | |
7ec3b54d | 854 | kfifo_free(&priv->fifo); |
20b93734 JW |
855 | |
856 | return 0; | |
857 | } | |
858 | ||
d659d11a | 859 | static void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode) |
2451d19d | 860 | { |
d659d11a | 861 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
2451d19d MK |
862 | int status; |
863 | ||
d659d11a MK |
864 | status = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode, |
865 | sizeof(scancode), &priv->fifo_lock); | |
527483a8 | 866 | if (status != sizeof(scancode)) { |
eee77da1 MK |
867 | dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n", |
868 | scancode); | |
a28c7e93 | 869 | return; |
2451d19d | 870 | } |
d659d11a | 871 | sparse_keymap_report_event(priv->input, scancode, 1, false); |
eee77da1 MK |
872 | dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n", |
873 | scancode); | |
2451d19d MK |
874 | } |
875 | ||
d659d11a | 876 | static void acpi_fujitsu_laptop_release(struct acpi_device *device) |
2451d19d | 877 | { |
d659d11a | 878 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
527483a8 | 879 | int scancode, status; |
2451d19d | 880 | |
29544f03 | 881 | while (true) { |
d659d11a | 882 | status = kfifo_out_locked(&priv->fifo, |
527483a8 | 883 | (unsigned char *)&scancode, |
d659d11a | 884 | sizeof(scancode), &priv->fifo_lock); |
527483a8 | 885 | if (status != sizeof(scancode)) |
29544f03 | 886 | return; |
d659d11a | 887 | sparse_keymap_report_event(priv->input, scancode, 0, false); |
eee77da1 MK |
888 | dev_dbg(&priv->input->dev, |
889 | "Pop scancode from ringbuffer [0x%x]\n", scancode); | |
2451d19d MK |
890 | } |
891 | } | |
892 | ||
6942eabc | 893 | static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) |
20b93734 | 894 | { |
d659d11a | 895 | struct fujitsu_laptop *priv = acpi_driver_data(device); |
527483a8 MK |
896 | int scancode, i = 0; |
897 | unsigned int irb; | |
20b93734 | 898 | |
eb357cba | 899 | if (event != ACPI_FUJITSU_NOTIFY_CODE1) { |
eee77da1 MK |
900 | acpi_handle_info(device->handle, "Unsupported event [0x%x]\n", |
901 | event); | |
d659d11a | 902 | sparse_keymap_report_event(priv->input, -1, 1, true); |
eb357cba MK |
903 | return; |
904 | } | |
905 | ||
d659d11a MK |
906 | if (priv->flags_supported) |
907 | priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, | |
908 | 0x0); | |
20b93734 | 909 | |
d659d11a | 910 | while ((irb = call_fext_func(device, |
84631e0c | 911 | FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 && |
527483a8 MK |
912 | i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { |
913 | scancode = irb & 0x4ff; | |
d659d11a MK |
914 | if (sparse_keymap_entry_from_scancode(priv->input, scancode)) |
915 | acpi_fujitsu_laptop_press(device, scancode); | |
527483a8 | 916 | else if (scancode == 0) |
d659d11a | 917 | acpi_fujitsu_laptop_release(device); |
527483a8 | 918 | else |
eee77da1 MK |
919 | acpi_handle_info(device->handle, |
920 | "Unknown GIRB result [%x]\n", irb); | |
eb357cba | 921 | } |
20b93734 | 922 | |
eb357cba MK |
923 | /* On some models (first seen on the Skylake-based Lifebook |
924 | * E736/E746/E756), the touchpad toggle hotkey (Fn+F4) is | |
8ef27bd3 | 925 | * handled in software; its state is queried using FUNC_FLAGS |
eb357cba | 926 | */ |
d659d11a MK |
927 | if ((priv->flags_supported & BIT(26)) && |
928 | (call_fext_func(device, FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26))) | |
929 | sparse_keymap_report_event(priv->input, BIT(26), 1, true); | |
20b93734 JW |
930 | } |
931 | ||
932 | /* Initialization */ | |
933 | ||
9fc5cf6e AJ |
934 | static const struct acpi_device_id fujitsu_bl_device_ids[] = { |
935 | {ACPI_FUJITSU_BL_HID, 0}, | |
d0482533 JW |
936 | {"", 0}, |
937 | }; | |
938 | ||
9fc5cf6e AJ |
939 | static struct acpi_driver acpi_fujitsu_bl_driver = { |
940 | .name = ACPI_FUJITSU_BL_DRIVER_NAME, | |
d0482533 | 941 | .class = ACPI_FUJITSU_CLASS, |
9fc5cf6e | 942 | .ids = fujitsu_bl_device_ids, |
d0482533 | 943 | .ops = { |
9fc5cf6e | 944 | .add = acpi_fujitsu_bl_add, |
9fc5cf6e | 945 | .notify = acpi_fujitsu_bl_notify, |
d0482533 JW |
946 | }, |
947 | }; | |
948 | ||
6942eabc AJ |
949 | static const struct acpi_device_id fujitsu_laptop_device_ids[] = { |
950 | {ACPI_FUJITSU_LAPTOP_HID, 0}, | |
20b93734 JW |
951 | {"", 0}, |
952 | }; | |
953 | ||
6942eabc AJ |
954 | static struct acpi_driver acpi_fujitsu_laptop_driver = { |
955 | .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME, | |
20b93734 | 956 | .class = ACPI_FUJITSU_CLASS, |
6942eabc | 957 | .ids = fujitsu_laptop_device_ids, |
20b93734 | 958 | .ops = { |
6942eabc AJ |
959 | .add = acpi_fujitsu_laptop_add, |
960 | .remove = acpi_fujitsu_laptop_remove, | |
961 | .notify = acpi_fujitsu_laptop_notify, | |
20b93734 JW |
962 | }, |
963 | }; | |
d0482533 | 964 | |
49901414 | 965 | static const struct acpi_device_id fujitsu_ids[] __used = { |
9fc5cf6e | 966 | {ACPI_FUJITSU_BL_HID, 0}, |
6942eabc | 967 | {ACPI_FUJITSU_LAPTOP_HID, 0}, |
49901414 ZR |
968 | {"", 0} |
969 | }; | |
970 | MODULE_DEVICE_TABLE(acpi, fujitsu_ids); | |
971 | ||
d0482533 JW |
972 | static int __init fujitsu_init(void) |
973 | { | |
b8d69c16 | 974 | int ret; |
d0482533 | 975 | |
c1d1e8a0 AJ |
976 | ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver); |
977 | if (ret) | |
679374e4 | 978 | return ret; |
d0482533 | 979 | |
d0482533 JW |
980 | /* Register platform stuff */ |
981 | ||
16506026 | 982 | ret = platform_driver_register(&fujitsu_pf_driver); |
20b93734 | 983 | if (ret) |
c33f4c04 | 984 | goto err_unregister_acpi; |
20b93734 | 985 | |
6942eabc | 986 | /* Register laptop driver */ |
20b93734 | 987 | |
c1d1e8a0 AJ |
988 | ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver); |
989 | if (ret) | |
a4b176ea | 990 | goto err_unregister_platform_driver; |
20b93734 | 991 | |
77bad7c8 | 992 | pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n"); |
d0482533 JW |
993 | |
994 | return 0; | |
995 | ||
c2cddd4f | 996 | err_unregister_platform_driver: |
16506026 | 997 | platform_driver_unregister(&fujitsu_pf_driver); |
c2cddd4f | 998 | err_unregister_acpi: |
9fc5cf6e | 999 | acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); |
d0482533 JW |
1000 | |
1001 | return ret; | |
1002 | } | |
1003 | ||
1004 | static void __exit fujitsu_cleanup(void) | |
1005 | { | |
6942eabc | 1006 | acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver); |
3a407086 | 1007 | |
16506026 | 1008 | platform_driver_unregister(&fujitsu_pf_driver); |
72afeeaf | 1009 | |
9fc5cf6e | 1010 | acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); |
20b93734 | 1011 | |
77bad7c8 | 1012 | pr_info("driver unloaded\n"); |
d0482533 JW |
1013 | } |
1014 | ||
1015 | module_init(fujitsu_init); | |
1016 | module_exit(fujitsu_cleanup); | |
1017 | ||
e06e4831 MK |
1018 | module_param(use_alt_lcd_levels, int, 0644); |
1019 | MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)"); | |
b4bb0cfd MK |
1020 | module_param(disable_brightness_adjust, bool, 0644); |
1021 | MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment"); | |
20b93734 | 1022 | |
3a407086 | 1023 | MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon"); |
d0482533 JW |
1024 | MODULE_DESCRIPTION("Fujitsu laptop extras support"); |
1025 | MODULE_VERSION(FUJITSU_DRIVER_VERSION); | |
1026 | MODULE_LICENSE("GPL"); |