]>
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 | ||
115 | /* Debugging */ | |
20b93734 JW |
116 | #define FUJLAPTOP_DBG_ERROR 0x0001 |
117 | #define FUJLAPTOP_DBG_WARN 0x0002 | |
118 | #define FUJLAPTOP_DBG_INFO 0x0004 | |
119 | #define FUJLAPTOP_DBG_TRACE 0x0008 | |
120 | ||
c4960cf0 JD |
121 | #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG |
122 | #define vdbg_printk(a_dbg_level, format, arg...) \ | |
20b93734 | 123 | do { if (dbg_level & a_dbg_level) \ |
98020a4a | 124 | printk(KERN_DEBUG pr_fmt("%s: " format), __func__, ## arg); \ |
20b93734 | 125 | } while (0) |
20b93734 | 126 | #else |
c4960cf0 JD |
127 | #define vdbg_printk(a_dbg_level, format, arg...) \ |
128 | do { } while (0) | |
20b93734 JW |
129 | #endif |
130 | ||
131 | /* Device controlling the backlight and associated keys */ | |
9fc5cf6e | 132 | struct fujitsu_bl { |
d0482533 | 133 | acpi_handle acpi_handle; |
20b93734 JW |
134 | struct input_dev *input; |
135 | char phys[32]; | |
d0482533 | 136 | struct backlight_device *bl_device; |
20b93734 | 137 | unsigned int max_brightness; |
d0482533 JW |
138 | unsigned int brightness_level; |
139 | }; | |
140 | ||
9fc5cf6e | 141 | static struct fujitsu_bl *fujitsu_bl; |
20b93734 | 142 | static int use_alt_lcd_levels = -1; |
b4bb0cfd | 143 | static bool disable_brightness_adjust; |
20b93734 | 144 | |
6942eabc AJ |
145 | /* Device used to access hotkeys and other features on the laptop */ |
146 | struct fujitsu_laptop { | |
20b93734 JW |
147 | acpi_handle acpi_handle; |
148 | struct acpi_device *dev; | |
149 | struct input_dev *input; | |
150 | char phys[32]; | |
151 | struct platform_device *pf_device; | |
45465487 | 152 | struct kfifo fifo; |
20b93734 | 153 | spinlock_t fifo_lock; |
8ef27bd3 AJ |
154 | int flags_supported; |
155 | int flags_state; | |
3a407086 TV |
156 | int logolamp_registered; |
157 | int kblamps_registered; | |
4f62568c | 158 | int radio_led_registered; |
d6b88f64 | 159 | int eco_led_registered; |
20b93734 | 160 | }; |
d0482533 | 161 | |
6942eabc | 162 | static struct fujitsu_laptop *fujitsu_laptop; |
20b93734 | 163 | |
20b93734 JW |
164 | #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG |
165 | static u32 dbg_level = 0x03; | |
166 | #endif | |
167 | ||
3a407086 TV |
168 | /* Fujitsu ACPI interface function */ |
169 | ||
f68e492c | 170 | static int call_fext_func(int func, int op, int feature, int state) |
3a407086 | 171 | { |
3a407086 | 172 | union acpi_object params[4] = { |
f68e492c MK |
173 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = func }, |
174 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = op }, | |
175 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature }, | |
176 | { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state } | |
3a407086 | 177 | }; |
b1066410 | 178 | struct acpi_object_list arg_list = { 4, params }; |
29c29a9b | 179 | unsigned long long value; |
b1066410 | 180 | acpi_status status; |
3a407086 | 181 | |
17e23555 MK |
182 | status = acpi_evaluate_integer(fujitsu_laptop->acpi_handle, "FUNC", |
183 | &arg_list, &value); | |
3a407086 | 184 | if (ACPI_FAILURE(status)) { |
09b29e1e | 185 | vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate FUNC\n"); |
3a407086 TV |
186 | return -ENODEV; |
187 | } | |
188 | ||
f68e492c MK |
189 | vdbg_printk(FUJLAPTOP_DBG_TRACE, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", |
190 | func, op, feature, state, (int)value); | |
29c29a9b | 191 | return value; |
3a407086 TV |
192 | } |
193 | ||
20b93734 | 194 | /* Hardware access for LCD brightness control */ |
d0482533 JW |
195 | |
196 | static int set_lcd_level(int level) | |
197 | { | |
a8779c35 | 198 | acpi_status status; |
e32c50ba MK |
199 | char *method; |
200 | ||
201 | switch (use_alt_lcd_levels) { | |
e06e4831 MK |
202 | case -1: |
203 | if (acpi_has_method(fujitsu_bl->acpi_handle, "SBL2")) | |
204 | method = "SBL2"; | |
205 | else | |
206 | method = "SBLL"; | |
207 | break; | |
e32c50ba MK |
208 | case 1: |
209 | method = "SBL2"; | |
210 | break; | |
211 | default: | |
212 | method = "SBLL"; | |
213 | break; | |
20b93734 JW |
214 | } |
215 | ||
e32c50ba MK |
216 | vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via %s [%d]\n", |
217 | method, level); | |
20b93734 | 218 | |
9fc5cf6e | 219 | if (level < 0 || level >= fujitsu_bl->max_brightness) |
20b93734 JW |
220 | return -EINVAL; |
221 | ||
a8779c35 MK |
222 | status = acpi_execute_simple_method(fujitsu_bl->acpi_handle, method, |
223 | level); | |
20b93734 | 224 | if (ACPI_FAILURE(status)) { |
a8779c35 MK |
225 | vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate %s\n", |
226 | method); | |
d0482533 JW |
227 | return -ENODEV; |
228 | } | |
229 | ||
bd079a2c MK |
230 | fujitsu_bl->brightness_level = level; |
231 | ||
d0482533 JW |
232 | return 0; |
233 | } | |
234 | ||
235 | static int get_lcd_level(void) | |
236 | { | |
27663c58 | 237 | unsigned long long state = 0; |
d0482533 JW |
238 | acpi_status status = AE_OK; |
239 | ||
20b93734 JW |
240 | vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLL\n"); |
241 | ||
9fc5cf6e AJ |
242 | status = acpi_evaluate_integer(fujitsu_bl->acpi_handle, "GBLL", NULL, |
243 | &state); | |
3b1c37ca JW |
244 | if (ACPI_FAILURE(status)) |
245 | return 0; | |
d0482533 | 246 | |
9fc5cf6e | 247 | fujitsu_bl->brightness_level = state & 0x0fffffff; |
20b93734 | 248 | |
9fc5cf6e | 249 | return fujitsu_bl->brightness_level; |
20b93734 JW |
250 | } |
251 | ||
252 | static int get_max_brightness(void) | |
253 | { | |
27663c58 | 254 | unsigned long long state = 0; |
20b93734 JW |
255 | acpi_status status = AE_OK; |
256 | ||
257 | vdbg_printk(FUJLAPTOP_DBG_TRACE, "get max lcd level via RBLL\n"); | |
258 | ||
9fc5cf6e AJ |
259 | status = acpi_evaluate_integer(fujitsu_bl->acpi_handle, "RBLL", NULL, |
260 | &state); | |
3b1c37ca JW |
261 | if (ACPI_FAILURE(status)) |
262 | return -1; | |
20b93734 | 263 | |
9fc5cf6e | 264 | fujitsu_bl->max_brightness = state; |
20b93734 | 265 | |
9fc5cf6e | 266 | return fujitsu_bl->max_brightness; |
20b93734 JW |
267 | } |
268 | ||
d0482533 JW |
269 | /* Backlight device stuff */ |
270 | ||
271 | static int bl_get_brightness(struct backlight_device *b) | |
272 | { | |
5959ddd0 | 273 | return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(); |
d0482533 JW |
274 | } |
275 | ||
276 | static int bl_update_status(struct backlight_device *b) | |
277 | { | |
e8549e2c | 278 | if (b->props.power == FB_BLANK_POWERDOWN) |
f7c4c3fa | 279 | call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3); |
3a407086 | 280 | else |
f7c4c3fa | 281 | call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0); |
3a407086 | 282 | |
d75a4a97 | 283 | return set_lcd_level(b->props.brightness); |
d0482533 JW |
284 | } |
285 | ||
9fc5cf6e | 286 | static const struct backlight_ops fujitsu_bl_ops = { |
d0482533 JW |
287 | .get_brightness = bl_get_brightness, |
288 | .update_status = bl_update_status, | |
289 | }; | |
290 | ||
b0c4b9c6 MK |
291 | static ssize_t lid_show(struct device *dev, struct device_attribute *attr, |
292 | char *buf) | |
3a407086 | 293 | { |
d3dd4480 | 294 | if (!(fujitsu_laptop->flags_supported & FLAG_LID)) |
3a407086 | 295 | return sprintf(buf, "unknown\n"); |
d3dd4480 | 296 | if (fujitsu_laptop->flags_state & FLAG_LID) |
3a407086 TV |
297 | return sprintf(buf, "open\n"); |
298 | else | |
299 | return sprintf(buf, "closed\n"); | |
300 | } | |
20b93734 | 301 | |
b0c4b9c6 MK |
302 | static ssize_t dock_show(struct device *dev, struct device_attribute *attr, |
303 | char *buf) | |
3a407086 | 304 | { |
d3dd4480 | 305 | if (!(fujitsu_laptop->flags_supported & FLAG_DOCK)) |
3a407086 | 306 | return sprintf(buf, "unknown\n"); |
d3dd4480 | 307 | if (fujitsu_laptop->flags_state & FLAG_DOCK) |
3a407086 TV |
308 | return sprintf(buf, "docked\n"); |
309 | else | |
310 | return sprintf(buf, "undocked\n"); | |
20b93734 JW |
311 | } |
312 | ||
b0c4b9c6 MK |
313 | static ssize_t radios_show(struct device *dev, struct device_attribute *attr, |
314 | char *buf) | |
20b93734 | 315 | { |
d3dd4480 | 316 | if (!(fujitsu_laptop->flags_supported & FLAG_RFKILL)) |
3a407086 | 317 | return sprintf(buf, "unknown\n"); |
d3dd4480 | 318 | if (fujitsu_laptop->flags_state & FLAG_RFKILL) |
3a407086 TV |
319 | return sprintf(buf, "on\n"); |
320 | else | |
321 | return sprintf(buf, "killed\n"); | |
20b93734 JW |
322 | } |
323 | ||
b0c4b9c6 MK |
324 | static DEVICE_ATTR_RO(lid); |
325 | static DEVICE_ATTR_RO(dock); | |
326 | static DEVICE_ATTR_RO(radios); | |
d0482533 | 327 | |
16506026 | 328 | static struct attribute *fujitsu_pf_attributes[] = { |
3a407086 TV |
329 | &dev_attr_lid.attr, |
330 | &dev_attr_dock.attr, | |
331 | &dev_attr_radios.attr, | |
d0482533 JW |
332 | NULL |
333 | }; | |
334 | ||
16506026 AJ |
335 | static struct attribute_group fujitsu_pf_attribute_group = { |
336 | .attrs = fujitsu_pf_attributes | |
d0482533 JW |
337 | }; |
338 | ||
16506026 | 339 | static struct platform_driver fujitsu_pf_driver = { |
d0482533 JW |
340 | .driver = { |
341 | .name = "fujitsu-laptop", | |
d0482533 JW |
342 | } |
343 | }; | |
344 | ||
20b93734 | 345 | /* ACPI device for LCD brightness control */ |
d0482533 | 346 | |
f2252672 MK |
347 | static const struct key_entry keymap_backlight[] = { |
348 | { KE_KEY, true, { KEY_BRIGHTNESSUP } }, | |
349 | { KE_KEY, false, { KEY_BRIGHTNESSDOWN } }, | |
350 | { KE_END, 0 } | |
351 | }; | |
352 | ||
7d134e43 MK |
353 | static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) |
354 | { | |
355 | struct fujitsu_bl *fujitsu_bl = acpi_driver_data(device); | |
f2252672 | 356 | int ret; |
7d134e43 | 357 | |
f8a399dc MK |
358 | fujitsu_bl->input = devm_input_allocate_device(&device->dev); |
359 | if (!fujitsu_bl->input) | |
7d134e43 MK |
360 | return -ENOMEM; |
361 | ||
362 | snprintf(fujitsu_bl->phys, sizeof(fujitsu_bl->phys), | |
363 | "%s/video/input0", acpi_device_hid(device)); | |
364 | ||
f8a399dc MK |
365 | fujitsu_bl->input->name = acpi_device_name(device); |
366 | fujitsu_bl->input->phys = fujitsu_bl->phys; | |
367 | fujitsu_bl->input->id.bustype = BUS_HOST; | |
368 | fujitsu_bl->input->id.product = 0x06; | |
f2252672 MK |
369 | |
370 | ret = sparse_keymap_setup(fujitsu_bl->input, keymap_backlight, NULL); | |
371 | if (ret) | |
372 | return ret; | |
7d134e43 | 373 | |
f8a399dc | 374 | return input_register_device(fujitsu_bl->input); |
7d134e43 MK |
375 | } |
376 | ||
a1aabd5f | 377 | static int fujitsu_backlight_register(struct acpi_device *device) |
b8d69c16 | 378 | { |
a1aabd5f | 379 | const struct backlight_properties props = { |
b8d69c16 MK |
380 | .brightness = fujitsu_bl->brightness_level, |
381 | .max_brightness = fujitsu_bl->max_brightness - 1, | |
382 | .type = BACKLIGHT_PLATFORM | |
383 | }; | |
384 | struct backlight_device *bd; | |
385 | ||
a1aabd5f MK |
386 | bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop", |
387 | &device->dev, NULL, | |
388 | &fujitsu_bl_ops, &props); | |
b8d69c16 MK |
389 | if (IS_ERR(bd)) |
390 | return PTR_ERR(bd); | |
391 | ||
392 | fujitsu_bl->bl_device = bd; | |
393 | ||
394 | return 0; | |
395 | } | |
396 | ||
9fc5cf6e | 397 | static int acpi_fujitsu_bl_add(struct acpi_device *device) |
d0482533 | 398 | { |
d0482533 | 399 | int state = 0; |
20b93734 | 400 | int error; |
d0482533 | 401 | |
07acf62a MK |
402 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) |
403 | return -ENODEV; | |
404 | ||
d0482533 JW |
405 | if (!device) |
406 | return -EINVAL; | |
407 | ||
9fc5cf6e AJ |
408 | fujitsu_bl->acpi_handle = device->handle; |
409 | sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_BL_DEVICE_NAME); | |
d0482533 | 410 | sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); |
9fc5cf6e | 411 | device->driver_data = fujitsu_bl; |
d0482533 | 412 | |
7d134e43 | 413 | error = acpi_fujitsu_bl_input_setup(device); |
20b93734 | 414 | if (error) |
f8a399dc | 415 | return error; |
20b93734 | 416 | |
9fc5cf6e | 417 | error = acpi_bus_update_power(fujitsu_bl->acpi_handle, &state); |
b30bb89f | 418 | if (error) { |
77bad7c8 | 419 | pr_err("Error reading power state\n"); |
f8a399dc | 420 | return error; |
d0482533 JW |
421 | } |
422 | ||
77bad7c8 | 423 | pr_info("ACPI: %s [%s] (%s)\n", |
d0482533 JW |
424 | acpi_device_name(device), acpi_device_bid(device), |
425 | !device->power.state ? "on" : "off"); | |
426 | ||
dd13b9a6 | 427 | if (acpi_has_method(device->handle, METHOD_NAME__INI)) { |
20b93734 JW |
428 | vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n"); |
429 | if (ACPI_FAILURE | |
430 | (acpi_evaluate_object | |
431 | (device->handle, METHOD_NAME__INI, NULL, NULL))) | |
77bad7c8 | 432 | pr_err("_INI Method failed\n"); |
20b93734 JW |
433 | } |
434 | ||
20b93734 | 435 | if (get_max_brightness() <= 0) |
9fc5cf6e | 436 | fujitsu_bl->max_brightness = FUJITSU_LCD_N_LEVELS; |
f87a1a5f | 437 | get_lcd_level(); |
20b93734 | 438 | |
a1aabd5f | 439 | error = fujitsu_backlight_register(device); |
07acf62a MK |
440 | if (error) |
441 | return error; | |
aea3137c | 442 | |
b30bb89f | 443 | return 0; |
d0482533 JW |
444 | } |
445 | ||
20b93734 JW |
446 | /* Brightness notify */ |
447 | ||
9fc5cf6e | 448 | static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event) |
20b93734 JW |
449 | { |
450 | struct input_dev *input; | |
f2252672 | 451 | int oldb, newb; |
20b93734 | 452 | |
9fc5cf6e | 453 | input = fujitsu_bl->input; |
20b93734 | 454 | |
5efc8004 | 455 | if (event != ACPI_FUJITSU_NOTIFY_CODE1) { |
20b93734 JW |
456 | vdbg_printk(FUJLAPTOP_DBG_WARN, |
457 | "unsupported event [0x%x]\n", event); | |
f2252672 | 458 | sparse_keymap_report_event(input, -1, 1, true); |
5efc8004 MK |
459 | return; |
460 | } | |
461 | ||
5efc8004 MK |
462 | oldb = fujitsu_bl->brightness_level; |
463 | get_lcd_level(); | |
464 | newb = fujitsu_bl->brightness_level; | |
465 | ||
5c495d62 MK |
466 | vdbg_printk(FUJLAPTOP_DBG_TRACE, "brightness button event [%i -> %i]\n", |
467 | oldb, newb); | |
5efc8004 | 468 | |
d2aa3ae8 MK |
469 | if (oldb == newb) |
470 | return; | |
20b93734 | 471 | |
b4bb0cfd | 472 | if (!disable_brightness_adjust) |
e32c50ba | 473 | set_lcd_level(newb); |
d2aa3ae8 | 474 | |
f2252672 | 475 | sparse_keymap_report_event(input, oldb < newb, 1, true); |
20b93734 JW |
476 | } |
477 | ||
478 | /* ACPI device for hotkey handling */ | |
479 | ||
527483a8 MK |
480 | static const struct key_entry keymap_default[] = { |
481 | { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, | |
482 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, | |
483 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, | |
484 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, | |
485 | { KE_KEY, KEY5_CODE, { KEY_RFKILL } }, | |
486 | { KE_KEY, BIT(26), { KEY_TOUCHPAD_TOGGLE } }, | |
487 | { KE_END, 0 } | |
488 | }; | |
489 | ||
f8c94ecd MK |
490 | static const struct key_entry keymap_s64x0[] = { |
491 | { KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */ | |
492 | { KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */ | |
493 | { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, | |
494 | { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, | |
495 | { KE_END, 0 } | |
496 | }; | |
497 | ||
498 | static const struct key_entry keymap_p8010[] = { | |
499 | { KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */ | |
500 | { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, | |
501 | { KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */ | |
502 | { KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */ | |
503 | { KE_END, 0 } | |
504 | }; | |
505 | ||
527483a8 MK |
506 | static const struct key_entry *keymap = keymap_default; |
507 | ||
f8c94ecd MK |
508 | static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id) |
509 | { | |
510 | pr_info("Identified laptop model '%s'\n", id->ident); | |
511 | keymap = id->driver_data; | |
512 | return 1; | |
513 | } | |
514 | ||
515 | static const struct dmi_system_id fujitsu_laptop_dmi_table[] = { | |
516 | { | |
517 | .callback = fujitsu_laptop_dmi_keymap_override, | |
518 | .ident = "Fujitsu Siemens S6410", | |
519 | .matches = { | |
520 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), | |
521 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"), | |
522 | }, | |
523 | .driver_data = (void *)keymap_s64x0 | |
524 | }, | |
525 | { | |
526 | .callback = fujitsu_laptop_dmi_keymap_override, | |
527 | .ident = "Fujitsu Siemens S6420", | |
528 | .matches = { | |
529 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), | |
530 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"), | |
531 | }, | |
532 | .driver_data = (void *)keymap_s64x0 | |
533 | }, | |
534 | { | |
535 | .callback = fujitsu_laptop_dmi_keymap_override, | |
536 | .ident = "Fujitsu LifeBook P8010", | |
537 | .matches = { | |
538 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), | |
539 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"), | |
540 | }, | |
541 | .driver_data = (void *)keymap_p8010 | |
542 | }, | |
543 | {} | |
544 | }; | |
545 | ||
11182dbc MK |
546 | static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) |
547 | { | |
548 | struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device); | |
527483a8 | 549 | int ret; |
11182dbc | 550 | |
f66735f8 MK |
551 | fujitsu_laptop->input = devm_input_allocate_device(&device->dev); |
552 | if (!fujitsu_laptop->input) | |
11182dbc MK |
553 | return -ENOMEM; |
554 | ||
555 | snprintf(fujitsu_laptop->phys, sizeof(fujitsu_laptop->phys), | |
556 | "%s/video/input0", acpi_device_hid(device)); | |
557 | ||
f66735f8 MK |
558 | fujitsu_laptop->input->name = acpi_device_name(device); |
559 | fujitsu_laptop->input->phys = fujitsu_laptop->phys; | |
560 | fujitsu_laptop->input->id.bustype = BUS_HOST; | |
561 | fujitsu_laptop->input->id.product = 0x06; | |
562 | ||
f8c94ecd | 563 | dmi_check_system(fujitsu_laptop_dmi_table); |
527483a8 MK |
564 | ret = sparse_keymap_setup(fujitsu_laptop->input, keymap, NULL); |
565 | if (ret) | |
566 | return ret; | |
f66735f8 MK |
567 | |
568 | return input_register_device(fujitsu_laptop->input); | |
11182dbc MK |
569 | } |
570 | ||
d811b511 MK |
571 | static int fujitsu_laptop_platform_add(void) |
572 | { | |
573 | int ret; | |
574 | ||
979800e7 MK |
575 | fujitsu_laptop->pf_device = platform_device_alloc("fujitsu-laptop", -1); |
576 | if (!fujitsu_laptop->pf_device) | |
d811b511 MK |
577 | return -ENOMEM; |
578 | ||
979800e7 | 579 | ret = platform_device_add(fujitsu_laptop->pf_device); |
d811b511 MK |
580 | if (ret) |
581 | goto err_put_platform_device; | |
582 | ||
979800e7 | 583 | ret = sysfs_create_group(&fujitsu_laptop->pf_device->dev.kobj, |
d811b511 MK |
584 | &fujitsu_pf_attribute_group); |
585 | if (ret) | |
586 | goto err_del_platform_device; | |
587 | ||
588 | return 0; | |
589 | ||
590 | err_del_platform_device: | |
979800e7 | 591 | platform_device_del(fujitsu_laptop->pf_device); |
d811b511 | 592 | err_put_platform_device: |
979800e7 | 593 | platform_device_put(fujitsu_laptop->pf_device); |
d811b511 MK |
594 | |
595 | return ret; | |
596 | } | |
597 | ||
598 | static void fujitsu_laptop_platform_remove(void) | |
599 | { | |
979800e7 | 600 | sysfs_remove_group(&fujitsu_laptop->pf_device->dev.kobj, |
d811b511 | 601 | &fujitsu_pf_attribute_group); |
979800e7 | 602 | platform_device_unregister(fujitsu_laptop->pf_device); |
d811b511 MK |
603 | } |
604 | ||
e33ca45c MK |
605 | static int logolamp_set(struct led_classdev *cdev, |
606 | enum led_brightness brightness) | |
607 | { | |
608 | int poweron = FUNC_LED_ON, always = FUNC_LED_ON; | |
609 | int ret; | |
610 | ||
611 | if (brightness < LED_HALF) | |
612 | poweron = FUNC_LED_OFF; | |
613 | ||
614 | if (brightness < LED_FULL) | |
615 | always = FUNC_LED_OFF; | |
616 | ||
617 | ret = call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); | |
618 | if (ret < 0) | |
619 | return ret; | |
620 | ||
621 | return call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); | |
622 | } | |
623 | ||
624 | static enum led_brightness logolamp_get(struct led_classdev *cdev) | |
625 | { | |
626 | int ret; | |
627 | ||
628 | ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); | |
629 | if (ret == FUNC_LED_ON) | |
630 | return LED_FULL; | |
631 | ||
632 | ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); | |
633 | if (ret == FUNC_LED_ON) | |
634 | return LED_HALF; | |
635 | ||
636 | return LED_OFF; | |
637 | } | |
638 | ||
639 | static struct led_classdev logolamp_led = { | |
640 | .name = "fujitsu::logolamp", | |
641 | .brightness_set_blocking = logolamp_set, | |
642 | .brightness_get = logolamp_get | |
643 | }; | |
644 | ||
645 | static int kblamps_set(struct led_classdev *cdev, | |
646 | enum led_brightness brightness) | |
647 | { | |
648 | if (brightness >= LED_FULL) | |
649 | return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, | |
650 | FUNC_LED_ON); | |
651 | else | |
652 | return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, | |
653 | FUNC_LED_OFF); | |
654 | } | |
655 | ||
656 | static enum led_brightness kblamps_get(struct led_classdev *cdev) | |
657 | { | |
658 | enum led_brightness brightness = LED_OFF; | |
659 | ||
660 | if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON) | |
661 | brightness = LED_FULL; | |
662 | ||
663 | return brightness; | |
664 | } | |
665 | ||
666 | static struct led_classdev kblamps_led = { | |
667 | .name = "fujitsu::kblamps", | |
668 | .brightness_set_blocking = kblamps_set, | |
669 | .brightness_get = kblamps_get | |
670 | }; | |
671 | ||
672 | static int radio_led_set(struct led_classdev *cdev, | |
673 | enum led_brightness brightness) | |
674 | { | |
675 | if (brightness >= LED_FULL) | |
676 | return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, | |
677 | RADIO_LED_ON); | |
678 | else | |
679 | return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0); | |
680 | } | |
681 | ||
682 | static enum led_brightness radio_led_get(struct led_classdev *cdev) | |
683 | { | |
684 | enum led_brightness brightness = LED_OFF; | |
685 | ||
686 | if (call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) | |
687 | brightness = LED_FULL; | |
688 | ||
689 | return brightness; | |
690 | } | |
691 | ||
692 | static struct led_classdev radio_led = { | |
693 | .name = "fujitsu::radio_led", | |
694 | .brightness_set_blocking = radio_led_set, | |
695 | .brightness_get = radio_led_get, | |
696 | .default_trigger = "rfkill-any" | |
697 | }; | |
698 | ||
699 | static int eco_led_set(struct led_classdev *cdev, | |
700 | enum led_brightness brightness) | |
701 | { | |
702 | int curr; | |
703 | ||
704 | curr = call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0); | |
705 | if (brightness >= LED_FULL) | |
706 | return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, | |
707 | curr | ECO_LED_ON); | |
708 | else | |
709 | return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, | |
710 | curr & ~ECO_LED_ON); | |
711 | } | |
712 | ||
713 | static enum led_brightness eco_led_get(struct led_classdev *cdev) | |
714 | { | |
715 | enum led_brightness brightness = LED_OFF; | |
716 | ||
717 | if (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON) | |
718 | brightness = LED_FULL; | |
719 | ||
720 | return brightness; | |
721 | } | |
722 | ||
723 | static struct led_classdev eco_led = { | |
724 | .name = "fujitsu::eco_led", | |
725 | .brightness_set_blocking = eco_led_set, | |
726 | .brightness_get = eco_led_get | |
727 | }; | |
728 | ||
7adb7b12 | 729 | static int acpi_fujitsu_laptop_leds_register(void) |
20b93734 | 730 | { |
20b93734 | 731 | int result = 0; |
7adb7b12 MK |
732 | |
733 | if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { | |
734 | result = led_classdev_register(&fujitsu_laptop->pf_device->dev, | |
735 | &logolamp_led); | |
736 | if (result == 0) { | |
737 | fujitsu_laptop->logolamp_registered = 1; | |
738 | } else { | |
739 | pr_err("Could not register LED handler for logo lamp, error %i\n", | |
740 | result); | |
741 | } | |
742 | } | |
743 | ||
744 | if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) && | |
745 | (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) { | |
746 | result = led_classdev_register(&fujitsu_laptop->pf_device->dev, | |
747 | &kblamps_led); | |
748 | if (result == 0) { | |
749 | fujitsu_laptop->kblamps_registered = 1; | |
750 | } else { | |
751 | pr_err("Could not register LED handler for keyboard lamps, error %i\n", | |
752 | result); | |
753 | } | |
754 | } | |
755 | ||
756 | /* | |
757 | * BTNI bit 24 seems to indicate the presence of a radio toggle | |
758 | * button in place of a slide switch, and all such machines appear | |
759 | * to also have an RF LED. Therefore use bit 24 as an indicator | |
760 | * that an RF LED is present. | |
761 | */ | |
762 | if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { | |
763 | result = led_classdev_register(&fujitsu_laptop->pf_device->dev, | |
764 | &radio_led); | |
765 | if (result == 0) { | |
766 | fujitsu_laptop->radio_led_registered = 1; | |
767 | } else { | |
768 | pr_err("Could not register LED handler for radio LED, error %i\n", | |
769 | result); | |
770 | } | |
771 | } | |
772 | ||
773 | /* Support for eco led is not always signaled in bit corresponding | |
774 | * to the bit used to control the led. According to the DSDT table, | |
775 | * bit 14 seems to indicate presence of said led as well. | |
776 | * Confirm by testing the status. | |
777 | */ | |
778 | if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && | |
779 | (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { | |
780 | result = led_classdev_register(&fujitsu_laptop->pf_device->dev, | |
781 | &eco_led); | |
782 | if (result == 0) { | |
783 | fujitsu_laptop->eco_led_registered = 1; | |
784 | } else { | |
785 | pr_err("Could not register LED handler for eco LED, error %i\n", | |
786 | result); | |
787 | } | |
788 | } | |
789 | ||
790 | return result; | |
791 | } | |
792 | ||
793 | static int acpi_fujitsu_laptop_add(struct acpi_device *device) | |
794 | { | |
20b93734 | 795 | int state = 0; |
20b93734 JW |
796 | int error; |
797 | int i; | |
798 | ||
799 | if (!device) | |
800 | return -EINVAL; | |
801 | ||
6942eabc | 802 | fujitsu_laptop->acpi_handle = device->handle; |
20b93734 | 803 | sprintf(acpi_device_name(device), "%s", |
6942eabc | 804 | ACPI_FUJITSU_LAPTOP_DEVICE_NAME); |
20b93734 | 805 | sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); |
6942eabc | 806 | device->driver_data = fujitsu_laptop; |
20b93734 | 807 | |
20b93734 | 808 | /* kfifo */ |
6942eabc AJ |
809 | spin_lock_init(&fujitsu_laptop->fifo_lock); |
810 | error = kfifo_alloc(&fujitsu_laptop->fifo, RINGBUFFERSIZE * sizeof(int), | |
c1e13f25 | 811 | GFP_KERNEL); |
45465487 | 812 | if (error) { |
77bad7c8 | 813 | pr_err("kfifo_alloc failed\n"); |
20b93734 JW |
814 | goto err_stop; |
815 | } | |
816 | ||
11182dbc | 817 | error = acpi_fujitsu_laptop_input_setup(device); |
20b93734 | 818 | if (error) |
11182dbc | 819 | goto err_free_fifo; |
20b93734 | 820 | |
6942eabc | 821 | error = acpi_bus_update_power(fujitsu_laptop->acpi_handle, &state); |
b30bb89f | 822 | if (error) { |
77bad7c8 | 823 | pr_err("Error reading power state\n"); |
f66735f8 | 824 | goto err_free_fifo; |
20b93734 JW |
825 | } |
826 | ||
77bad7c8 JP |
827 | pr_info("ACPI: %s [%s] (%s)\n", |
828 | acpi_device_name(device), acpi_device_bid(device), | |
829 | !device->power.state ? "on" : "off"); | |
20b93734 | 830 | |
6942eabc | 831 | fujitsu_laptop->dev = device; |
20b93734 | 832 | |
dd13b9a6 | 833 | if (acpi_has_method(device->handle, METHOD_NAME__INI)) { |
20b93734 JW |
834 | vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n"); |
835 | if (ACPI_FAILURE | |
836 | (acpi_evaluate_object | |
837 | (device->handle, METHOD_NAME__INI, NULL, NULL))) | |
77bad7c8 | 838 | pr_err("_INI Method failed\n"); |
20b93734 JW |
839 | } |
840 | ||
3a407086 TV |
841 | i = 0; |
842 | while (call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 | |
843 | && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) | |
844 | ; /* No action, result is discarded */ | |
20b93734 JW |
845 | vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i); |
846 | ||
8ef27bd3 AJ |
847 | fujitsu_laptop->flags_supported = |
848 | call_fext_func(FUNC_FLAGS, 0x0, 0x0, 0x0); | |
4898c2b2 TV |
849 | |
850 | /* Make sure our bitmask of supported functions is cleared if the | |
851 | RFKILL function block is not implemented, like on the S7020. */ | |
8ef27bd3 AJ |
852 | if (fujitsu_laptop->flags_supported == UNSUPPORTED_CMD) |
853 | fujitsu_laptop->flags_supported = 0; | |
4898c2b2 | 854 | |
8ef27bd3 AJ |
855 | if (fujitsu_laptop->flags_supported) |
856 | fujitsu_laptop->flags_state = | |
857 | call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0); | |
3a407086 TV |
858 | |
859 | /* Suspect this is a keymap of the application panel, print it */ | |
77bad7c8 | 860 | pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0)); |
3a407086 | 861 | |
1877e267 | 862 | /* Sync backlight power status */ |
aea3137c MK |
863 | if (fujitsu_bl->bl_device && |
864 | acpi_video_get_backlight_type() == acpi_backlight_vendor) { | |
1877e267 MK |
865 | if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) |
866 | fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN; | |
867 | else | |
868 | fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK; | |
869 | } | |
870 | ||
c33f4c04 MK |
871 | error = fujitsu_laptop_platform_add(); |
872 | if (error) | |
f66735f8 | 873 | goto err_free_fifo; |
c33f4c04 | 874 | |
7adb7b12 MK |
875 | error = acpi_fujitsu_laptop_leds_register(); |
876 | if (error) | |
877 | goto err_remove_platform_device; | |
3a407086 | 878 | |
7adb7b12 | 879 | return 0; |
20b93734 | 880 | |
7adb7b12 MK |
881 | err_remove_platform_device: |
882 | fujitsu_laptop_platform_remove(); | |
b4ec0275 | 883 | err_free_fifo: |
6942eabc | 884 | kfifo_free(&fujitsu_laptop->fifo); |
20b93734 | 885 | err_stop: |
b30bb89f | 886 | return error; |
20b93734 JW |
887 | } |
888 | ||
6942eabc | 889 | static int acpi_fujitsu_laptop_remove(struct acpi_device *device) |
20b93734 | 890 | { |
6942eabc | 891 | struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device); |
20b93734 | 892 | |
6942eabc | 893 | if (fujitsu_laptop->logolamp_registered) |
72afeeaf | 894 | led_classdev_unregister(&logolamp_led); |
20b93734 | 895 | |
6942eabc | 896 | if (fujitsu_laptop->kblamps_registered) |
72afeeaf | 897 | led_classdev_unregister(&kblamps_led); |
4f62568c | 898 | |
6942eabc | 899 | if (fujitsu_laptop->radio_led_registered) |
4f62568c | 900 | led_classdev_unregister(&radio_led); |
d6b88f64 | 901 | |
6942eabc | 902 | if (fujitsu_laptop->eco_led_registered) |
d6b88f64 | 903 | led_classdev_unregister(&eco_led); |
20b93734 | 904 | |
c33f4c04 MK |
905 | fujitsu_laptop_platform_remove(); |
906 | ||
6942eabc | 907 | kfifo_free(&fujitsu_laptop->fifo); |
20b93734 | 908 | |
6942eabc | 909 | fujitsu_laptop->acpi_handle = NULL; |
72afeeaf | 910 | |
20b93734 JW |
911 | return 0; |
912 | } | |
913 | ||
527483a8 | 914 | static void acpi_fujitsu_laptop_press(int scancode) |
2451d19d | 915 | { |
6942eabc | 916 | struct input_dev *input = fujitsu_laptop->input; |
2451d19d MK |
917 | int status; |
918 | ||
6942eabc | 919 | status = kfifo_in_locked(&fujitsu_laptop->fifo, |
527483a8 | 920 | (unsigned char *)&scancode, sizeof(scancode), |
6942eabc | 921 | &fujitsu_laptop->fifo_lock); |
527483a8 | 922 | if (status != sizeof(scancode)) { |
2451d19d | 923 | vdbg_printk(FUJLAPTOP_DBG_WARN, |
527483a8 | 924 | "Could not push scancode [0x%x]\n", scancode); |
a28c7e93 | 925 | return; |
2451d19d | 926 | } |
527483a8 | 927 | sparse_keymap_report_event(input, scancode, 1, false); |
a28c7e93 | 928 | vdbg_printk(FUJLAPTOP_DBG_TRACE, |
527483a8 | 929 | "Push scancode into ringbuffer [0x%x]\n", scancode); |
2451d19d MK |
930 | } |
931 | ||
6942eabc | 932 | static void acpi_fujitsu_laptop_release(void) |
2451d19d | 933 | { |
6942eabc | 934 | struct input_dev *input = fujitsu_laptop->input; |
527483a8 | 935 | int scancode, status; |
2451d19d | 936 | |
29544f03 | 937 | while (true) { |
6942eabc | 938 | status = kfifo_out_locked(&fujitsu_laptop->fifo, |
527483a8 MK |
939 | (unsigned char *)&scancode, |
940 | sizeof(scancode), | |
6942eabc | 941 | &fujitsu_laptop->fifo_lock); |
527483a8 | 942 | if (status != sizeof(scancode)) |
29544f03 | 943 | return; |
527483a8 | 944 | sparse_keymap_report_event(input, scancode, 0, false); |
2451d19d | 945 | vdbg_printk(FUJLAPTOP_DBG_TRACE, |
527483a8 | 946 | "Pop scancode from ringbuffer [0x%x]\n", scancode); |
2451d19d MK |
947 | } |
948 | } | |
949 | ||
6942eabc | 950 | static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) |
20b93734 JW |
951 | { |
952 | struct input_dev *input; | |
527483a8 MK |
953 | int scancode, i = 0; |
954 | unsigned int irb; | |
20b93734 | 955 | |
6942eabc | 956 | input = fujitsu_laptop->input; |
20b93734 | 957 | |
eb357cba | 958 | if (event != ACPI_FUJITSU_NOTIFY_CODE1) { |
eb357cba MK |
959 | vdbg_printk(FUJLAPTOP_DBG_WARN, |
960 | "Unsupported event [0x%x]\n", event); | |
527483a8 | 961 | sparse_keymap_report_event(input, -1, 1, true); |
eb357cba MK |
962 | return; |
963 | } | |
964 | ||
8ef27bd3 AJ |
965 | if (fujitsu_laptop->flags_supported) |
966 | fujitsu_laptop->flags_state = | |
967 | call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0); | |
20b93734 | 968 | |
527483a8 MK |
969 | while ((irb = call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 && |
970 | i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { | |
971 | scancode = irb & 0x4ff; | |
972 | if (sparse_keymap_entry_from_scancode(input, scancode)) | |
973 | acpi_fujitsu_laptop_press(scancode); | |
974 | else if (scancode == 0) | |
975 | acpi_fujitsu_laptop_release(); | |
976 | else | |
eb357cba MK |
977 | vdbg_printk(FUJLAPTOP_DBG_WARN, |
978 | "Unknown GIRB result [%x]\n", irb); | |
eb357cba | 979 | } |
20b93734 | 980 | |
eb357cba MK |
981 | /* On some models (first seen on the Skylake-based Lifebook |
982 | * E736/E746/E756), the touchpad toggle hotkey (Fn+F4) is | |
8ef27bd3 | 983 | * handled in software; its state is queried using FUNC_FLAGS |
eb357cba | 984 | */ |
8ef27bd3 | 985 | if ((fujitsu_laptop->flags_supported & BIT(26)) && |
527483a8 MK |
986 | (call_fext_func(FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26))) |
987 | sparse_keymap_report_event(input, BIT(26), 1, true); | |
20b93734 JW |
988 | } |
989 | ||
990 | /* Initialization */ | |
991 | ||
9fc5cf6e AJ |
992 | static const struct acpi_device_id fujitsu_bl_device_ids[] = { |
993 | {ACPI_FUJITSU_BL_HID, 0}, | |
d0482533 JW |
994 | {"", 0}, |
995 | }; | |
996 | ||
9fc5cf6e AJ |
997 | static struct acpi_driver acpi_fujitsu_bl_driver = { |
998 | .name = ACPI_FUJITSU_BL_DRIVER_NAME, | |
d0482533 | 999 | .class = ACPI_FUJITSU_CLASS, |
9fc5cf6e | 1000 | .ids = fujitsu_bl_device_ids, |
d0482533 | 1001 | .ops = { |
9fc5cf6e | 1002 | .add = acpi_fujitsu_bl_add, |
9fc5cf6e | 1003 | .notify = acpi_fujitsu_bl_notify, |
d0482533 JW |
1004 | }, |
1005 | }; | |
1006 | ||
6942eabc AJ |
1007 | static const struct acpi_device_id fujitsu_laptop_device_ids[] = { |
1008 | {ACPI_FUJITSU_LAPTOP_HID, 0}, | |
20b93734 JW |
1009 | {"", 0}, |
1010 | }; | |
1011 | ||
6942eabc AJ |
1012 | static struct acpi_driver acpi_fujitsu_laptop_driver = { |
1013 | .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME, | |
20b93734 | 1014 | .class = ACPI_FUJITSU_CLASS, |
6942eabc | 1015 | .ids = fujitsu_laptop_device_ids, |
20b93734 | 1016 | .ops = { |
6942eabc AJ |
1017 | .add = acpi_fujitsu_laptop_add, |
1018 | .remove = acpi_fujitsu_laptop_remove, | |
1019 | .notify = acpi_fujitsu_laptop_notify, | |
20b93734 JW |
1020 | }, |
1021 | }; | |
d0482533 | 1022 | |
49901414 | 1023 | static const struct acpi_device_id fujitsu_ids[] __used = { |
9fc5cf6e | 1024 | {ACPI_FUJITSU_BL_HID, 0}, |
6942eabc | 1025 | {ACPI_FUJITSU_LAPTOP_HID, 0}, |
49901414 ZR |
1026 | {"", 0} |
1027 | }; | |
1028 | MODULE_DEVICE_TABLE(acpi, fujitsu_ids); | |
1029 | ||
d0482533 JW |
1030 | static int __init fujitsu_init(void) |
1031 | { | |
b8d69c16 | 1032 | int ret; |
d0482533 JW |
1033 | |
1034 | if (acpi_disabled) | |
1035 | return -ENODEV; | |
1036 | ||
9fc5cf6e AJ |
1037 | fujitsu_bl = kzalloc(sizeof(struct fujitsu_bl), GFP_KERNEL); |
1038 | if (!fujitsu_bl) | |
d0482533 | 1039 | return -ENOMEM; |
d0482533 | 1040 | |
c1d1e8a0 AJ |
1041 | ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver); |
1042 | if (ret) | |
c2cddd4f | 1043 | goto err_free_fujitsu_bl; |
d0482533 | 1044 | |
d0482533 JW |
1045 | /* Register platform stuff */ |
1046 | ||
16506026 | 1047 | ret = platform_driver_register(&fujitsu_pf_driver); |
20b93734 | 1048 | if (ret) |
c33f4c04 | 1049 | goto err_unregister_acpi; |
20b93734 | 1050 | |
6942eabc | 1051 | /* Register laptop driver */ |
20b93734 | 1052 | |
6942eabc AJ |
1053 | fujitsu_laptop = kzalloc(sizeof(struct fujitsu_laptop), GFP_KERNEL); |
1054 | if (!fujitsu_laptop) { | |
20b93734 | 1055 | ret = -ENOMEM; |
c2cddd4f | 1056 | goto err_unregister_platform_driver; |
20b93734 | 1057 | } |
20b93734 | 1058 | |
c1d1e8a0 AJ |
1059 | ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver); |
1060 | if (ret) | |
c2cddd4f | 1061 | goto err_free_fujitsu_laptop; |
20b93734 | 1062 | |
77bad7c8 | 1063 | pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n"); |
d0482533 JW |
1064 | |
1065 | return 0; | |
1066 | ||
c2cddd4f | 1067 | err_free_fujitsu_laptop: |
6942eabc | 1068 | kfree(fujitsu_laptop); |
c2cddd4f | 1069 | err_unregister_platform_driver: |
16506026 | 1070 | platform_driver_unregister(&fujitsu_pf_driver); |
c2cddd4f | 1071 | err_unregister_acpi: |
9fc5cf6e | 1072 | acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); |
c2cddd4f | 1073 | err_free_fujitsu_bl: |
9fc5cf6e | 1074 | kfree(fujitsu_bl); |
d0482533 JW |
1075 | |
1076 | return ret; | |
1077 | } | |
1078 | ||
1079 | static void __exit fujitsu_cleanup(void) | |
1080 | { | |
6942eabc | 1081 | acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver); |
3a407086 | 1082 | |
6942eabc | 1083 | kfree(fujitsu_laptop); |
3a407086 | 1084 | |
16506026 | 1085 | platform_driver_unregister(&fujitsu_pf_driver); |
72afeeaf | 1086 | |
9fc5cf6e | 1087 | acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); |
20b93734 | 1088 | |
9fc5cf6e | 1089 | kfree(fujitsu_bl); |
20b93734 | 1090 | |
77bad7c8 | 1091 | pr_info("driver unloaded\n"); |
d0482533 JW |
1092 | } |
1093 | ||
1094 | module_init(fujitsu_init); | |
1095 | module_exit(fujitsu_cleanup); | |
1096 | ||
e06e4831 MK |
1097 | module_param(use_alt_lcd_levels, int, 0644); |
1098 | MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)"); | |
b4bb0cfd MK |
1099 | module_param(disable_brightness_adjust, bool, 0644); |
1100 | MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment"); | |
20b93734 JW |
1101 | #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG |
1102 | module_param_named(debug, dbg_level, uint, 0644); | |
1103 | MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); | |
1104 | #endif | |
1105 | ||
3a407086 | 1106 | MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon"); |
d0482533 JW |
1107 | MODULE_DESCRIPTION("Fujitsu laptop extras support"); |
1108 | MODULE_VERSION(FUJITSU_DRIVER_VERSION); | |
1109 | MODULE_LICENSE("GPL"); |