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