1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
4 *
5 * Jianmin Lv <lvjianmin@loongson.cn>
6 * Huacai Chen <chenhuacai@loongson.cn>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 */
18
19 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20
21 #include <linux/init.h>
22 #include <linux/kernel.h>
23 #include <linux/module.h>
24 #include <linux/acpi.h>
25 #include <linux/backlight.h>
26 #include <linux/device.h>
27 #include <linux/input.h>
28 #include <linux/input/sparse-keymap.h>
29 #include <linux/platform_device.h>
30 #include <linux/string.h>
31 #include <linux/types.h>
32 #include <acpi/video.h>
33
34 /* 1. Driver-wide structs and misc. variables */
35
36 /* ACPI HIDs */
37 #define LOONGSON_ACPI_EC_HID "PNP0C09"
38 #define LOONGSON_ACPI_HKEY_HID "LOON0000"
39
40 #define ACPI_LAPTOP_NAME "loongson-laptop"
41 #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
42
43 #define MAX_ACPI_ARGS 3
44 #define GENERIC_HOTKEY_MAP_MAX 64
45
46 #define GENERIC_EVENT_TYPE_OFF 12
47 #define GENERIC_EVENT_TYPE_MASK 0xF000
48 #define GENERIC_EVENT_CODE_MASK 0x0FFF
49
50 struct generic_sub_driver {
51 u32 type;
52 char *name;
53 acpi_handle *handle;
54 struct acpi_device *device;
55 struct platform_driver *driver;
56 int (*init)(struct generic_sub_driver *sub_driver);
57 void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
58 u8 acpi_notify_installed;
59 };
60
61 static u32 input_device_registered;
62 static struct input_dev *generic_inputdev;
63
64 static acpi_handle hotkey_handle;
65 static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
66
67 int loongson_laptop_turn_on_backlight(void);
68 int loongson_laptop_turn_off_backlight(void);
69 static int loongson_laptop_backlight_update(struct backlight_device *bd);
70
71 /* 2. ACPI Helpers and device model */
72
acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)73 static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
74 {
75 char res_type;
76 char *fmt0 = fmt;
77 va_list ap;
78 int success, quiet;
79 acpi_status status;
80 struct acpi_object_list params;
81 struct acpi_buffer result, *resultp;
82 union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
83
84 if (!*fmt) {
85 pr_err("acpi_evalf() called with empty format\n");
86 return 0;
87 }
88
89 if (*fmt == 'q') {
90 quiet = 1;
91 fmt++;
92 } else
93 quiet = 0;
94
95 res_type = *(fmt++);
96
97 params.count = 0;
98 params.pointer = &in_objs[0];
99
100 va_start(ap, fmt);
101 while (*fmt) {
102 char c = *(fmt++);
103 switch (c) {
104 case 'd': /* int */
105 in_objs[params.count].integer.value = va_arg(ap, int);
106 in_objs[params.count++].type = ACPI_TYPE_INTEGER;
107 break;
108 /* add more types as needed */
109 default:
110 pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
111 va_end(ap);
112 return 0;
113 }
114 }
115 va_end(ap);
116
117 if (res_type != 'v') {
118 result.length = sizeof(out_obj);
119 result.pointer = &out_obj;
120 resultp = &result;
121 } else
122 resultp = NULL;
123
124 status = acpi_evaluate_object(handle, method, ¶ms, resultp);
125
126 switch (res_type) {
127 case 'd': /* int */
128 success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
129 if (success && res)
130 *res = out_obj.integer.value;
131 break;
132 case 'v': /* void */
133 success = status == AE_OK;
134 break;
135 /* add more types as needed */
136 default:
137 pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
138 return 0;
139 }
140
141 if (!success && !quiet)
142 pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
143 method, fmt0, acpi_format_exception(status));
144
145 return success;
146 }
147
hotkey_status_get(int *status)148 static int hotkey_status_get(int *status)
149 {
150 if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
151 return -EIO;
152
153 return 0;
154 }
155
dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)156 static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
157 {
158 struct generic_sub_driver *sub_driver = data;
159
160 if (!sub_driver || !sub_driver->notify)
161 return;
162 sub_driver->notify(sub_driver, event);
163 }
164
setup_acpi_notify(struct generic_sub_driver *sub_driver)165 static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
166 {
167 int rc;
168 acpi_status status;
169
170 if (!*sub_driver->handle)
171 return 0;
172
173 rc = acpi_bus_get_device(*sub_driver->handle, &sub_driver->device);
174 if (rc < 0) {
175 pr_err("acpi_bus_get_device(%s) failed: %d\n", sub_driver->name, rc);
176 return -ENODEV;
177 }
178
179 sub_driver->device->driver_data = sub_driver;
180 sprintf(acpi_device_class(sub_driver->device), "%s/%s",
181 ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
182
183 status = acpi_install_notify_handler(*sub_driver->handle,
184 sub_driver->type, dispatch_acpi_notify, sub_driver);
185 if (ACPI_FAILURE(status)) {
186 if (status == AE_ALREADY_EXISTS) {
187 pr_notice("Another device driver is already "
188 "handling %s events\n", sub_driver->name);
189 } else {
190 pr_err("acpi_install_notify_handler(%s) failed: %s\n",
191 sub_driver->name, acpi_format_exception(status));
192 }
193 return -ENODEV;
194 }
195 sub_driver->acpi_notify_installed = 1;
196
197 return 0;
198 }
199
200 #ifdef CONFIG_PM
loongson_hotkey_suspend(struct device *dev)201 static int loongson_hotkey_suspend(struct device *dev)
202 {
203 return 0;
204 }
205
loongson_hotkey_resume(struct device *dev)206 static int loongson_hotkey_resume(struct device *dev)
207 {
208 int status = 0;
209 struct key_entry ke;
210 struct backlight_device *bd;
211
212 bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
213 if (bd) {
214 loongson_laptop_backlight_update(bd) ?
215 pr_warn("Loongson_backlight: resume brightness failed") :
216 pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
217 }
218
219 /*
220 * Only if the firmware supports SW_LID event model, we can handle the
221 * event. This is for the consideration of development board without EC.
222 */
223 if (test_bit(SW_LID, generic_inputdev->swbit)) {
224 if (hotkey_status_get(&status) < 0)
225 return -EIO;
226 /*
227 * The input device sw element records the last lid status.
228 * When the system is awakened by other wake-up sources,
229 * the lid event will also be reported. The judgment of
230 * adding SW_LID bit which in sw element can avoid this
231 * case.
232 *
233 * Input system will drop lid event when current lid event
234 * value and last lid status in the same. So laptop driver
235 * doesn't report repeated events.
236 *
237 * Lid status is generally 0, but hardware exception is
238 * considered. So add lid status confirmation.
239 */
240 if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
241 ke.type = KE_SW;
242 ke.sw.value = (u8)status;
243 ke.sw.code = SW_LID;
244 sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
245 }
246 }
247
248 return 0;
249 }
250
251 static SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
252 loongson_hotkey_suspend, loongson_hotkey_resume);
253 #endif
254
loongson_hotkey_probe(struct platform_device *pdev)255 static int loongson_hotkey_probe(struct platform_device *pdev)
256 {
257 hotkey_handle = ACPI_HANDLE(&pdev->dev);
258
259 if (!hotkey_handle)
260 return -ENODEV;
261
262 return 0;
263 }
264
265 static const struct acpi_device_id loongson_device_ids[] = {
266 {LOONGSON_ACPI_HKEY_HID, 0},
267 {"", 0},
268 };
269 MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
270
271 static struct platform_driver loongson_hotkey_driver = {
272 .probe = loongson_hotkey_probe,
273 .driver = {
274 .name = "loongson-hotkey",
275 .owner = THIS_MODULE,
276 .pm = pm_ptr(&loongson_hotkey_pm),
277 .acpi_match_table = loongson_device_ids,
278 },
279 };
280
hotkey_map(void)281 static int hotkey_map(void)
282 {
283 u32 index;
284 acpi_status status;
285 struct acpi_buffer buf;
286 union acpi_object *pack;
287
288 buf.length = ACPI_ALLOCATE_BUFFER;
289 status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
290 if (status != AE_OK) {
291 pr_err("ACPI exception: %s\n", acpi_format_exception(status));
292 return -1;
293 }
294 pack = buf.pointer;
295 for (index = 0; index < pack->package.count; index++) {
296 union acpi_object *element, *sub_pack;
297
298 sub_pack = &pack->package.elements[index];
299
300 element = &sub_pack->package.elements[0];
301 hotkey_keycode_map[index].type = element->integer.value;
302 element = &sub_pack->package.elements[1];
303 hotkey_keycode_map[index].code = element->integer.value;
304 element = &sub_pack->package.elements[2];
305 hotkey_keycode_map[index].keycode = element->integer.value;
306 }
307
308 return 0;
309 }
310
hotkey_backlight_set(bool enable)311 static int hotkey_backlight_set(bool enable)
312 {
313 if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
314 return -EIO;
315
316 return 0;
317 }
318
ec_get_brightness(void)319 static int ec_get_brightness(void)
320 {
321 int status = 0;
322
323 if (!hotkey_handle)
324 return -ENXIO;
325
326 if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
327 return -EIO;
328
329 return status;
330 }
331
ec_set_brightness(int level)332 static int ec_set_brightness(int level)
333 {
334
335 int ret = 0;
336
337 if (!hotkey_handle)
338 return -ENXIO;
339
340 if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
341 ret = -EIO;
342
343 return ret;
344 }
345
ec_backlight_level(u8 level)346 static int ec_backlight_level(u8 level)
347 {
348 int status = 0;
349
350 if (!hotkey_handle)
351 return -ENXIO;
352
353 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
354 return -EIO;
355
356 if ((status < 0) || (level > status))
357 return status;
358
359 if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
360 return -EIO;
361
362 if ((status < 0) || (level < status))
363 return status;
364
365 return level;
366 }
367
loongson_laptop_backlight_update(struct backlight_device *bd)368 static int loongson_laptop_backlight_update(struct backlight_device *bd)
369 {
370 int lvl = ec_backlight_level(bd->props.brightness);
371 if (lvl < 0)
372 return -EIO;
373 if (ec_set_brightness(lvl))
374 return -EIO;
375
376 return 0;
377 }
378
loongson_laptop_get_brightness(struct backlight_device *bd)379 static int loongson_laptop_get_brightness(struct backlight_device *bd)
380 {
381 int level;
382
383 level = ec_get_brightness();
384 if (level < 0)
385 return -EIO;
386
387 return level;
388 }
389
390 static const struct backlight_ops backlight_laptop_ops = {
391 .update_status = loongson_laptop_backlight_update,
392 .get_brightness = loongson_laptop_get_brightness,
393 };
394
laptop_backlight_register(void)395 static int laptop_backlight_register(void)
396 {
397 int status = 0;
398 struct backlight_properties props;
399
400 memset(&props, 0, sizeof(props));
401
402 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
403 return -EIO;
404
405 props.brightness = 1;
406 props.max_brightness = status;
407 props.type = BACKLIGHT_PLATFORM;
408
409 backlight_device_register("loongson_laptop",
410 NULL, NULL, &backlight_laptop_ops, &props);
411
412 return 0;
413 }
414
loongson_laptop_turn_on_backlight(void)415 int loongson_laptop_turn_on_backlight(void)
416 {
417 int status;
418 union acpi_object arg0 = { ACPI_TYPE_INTEGER };
419 struct acpi_object_list args = { 1, &arg0 };
420
421 arg0.integer.value = 1;
422 status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
423 if (ACPI_FAILURE(status)) {
424 pr_info("Loongson lvds error:0x%x\n", status);
425 return -ENODEV;
426 }
427 return 0;
428 }
429
loongson_laptop_turn_off_backlight(void)430 int loongson_laptop_turn_off_backlight(void)
431 {
432 int status;
433 union acpi_object arg0 = { ACPI_TYPE_INTEGER };
434 struct acpi_object_list args = { 1, &arg0 };
435
436 arg0.integer.value = 0;
437 status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
438 if (ACPI_FAILURE(status)) {
439 pr_info("Loongson lvds error:0x%x\n", status);
440 return -ENODEV;
441 }
442 return 0;
443 }
444
event_init(struct generic_sub_driver *sub_driver)445 static int __init event_init(struct generic_sub_driver *sub_driver)
446 {
447 int ret;
448
449 ret = hotkey_map();
450 if (ret < 0) {
451 pr_err("Failed to parse keymap from DSDT\n");
452 return ret;
453 }
454
455 ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
456 if (ret < 0) {
457 pr_err("Failed to setup input device keymap\n");
458 input_free_device(generic_inputdev);
459 generic_inputdev = NULL;
460
461 return ret;
462 }
463
464 /*
465 * This hotkey driver handle backlight event when
466 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
467 */
468 if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
469 hotkey_backlight_set(true);
470 else
471 hotkey_backlight_set(false);
472
473 pr_info("ACPI: enabling firmware HKEY event interface...\n");
474
475 return ret;
476 }
477
event_notify(struct generic_sub_driver *sub_driver, u32 event)478 static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
479 {
480 int type, scan_code;
481 struct key_entry *ke = NULL;
482
483 scan_code = event & GENERIC_EVENT_CODE_MASK;
484 type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
485 ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
486 if (ke) {
487 if (type == KE_SW) {
488 int status = 0;
489
490 if (hotkey_status_get(&status) < 0)
491 return;
492 ke->sw.value = !!(status & (1 << ke->sw.code));
493 }
494 sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
495 }
496 }
497
498 /* 3. Infrastructure */
499
500 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
501
generic_subdriver_init(struct generic_sub_driver *sub_driver)502 static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
503 {
504 int ret;
505
506 if (!sub_driver || !sub_driver->driver)
507 return -EINVAL;
508
509 ret = platform_driver_register(sub_driver->driver);
510 if (ret)
511 return -EINVAL;
512
513 if (sub_driver->init) {
514 ret = sub_driver->init(sub_driver);
515 if (ret)
516 goto err_out;
517 }
518
519 if (sub_driver->notify) {
520 ret = setup_acpi_notify(sub_driver);
521 if (ret == -ENODEV) {
522 ret = 0;
523 goto err_out;
524 }
525 if (ret < 0)
526 goto err_out;
527 }
528
529 return 0;
530
531 err_out:
532 generic_subdriver_exit(sub_driver);
533 return ret;
534 }
535
generic_subdriver_exit(struct generic_sub_driver *sub_driver)536 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
537 {
538
539 if (sub_driver->acpi_notify_installed) {
540 acpi_remove_notify_handler(*sub_driver->handle,
541 sub_driver->type, dispatch_acpi_notify);
542 sub_driver->acpi_notify_installed = 0;
543 }
544 platform_driver_unregister(sub_driver->driver);
545 }
546
547 static struct generic_sub_driver generic_sub_drivers[] __refdata = {
548 {
549 .name = "hotkey",
550 .init = event_init,
551 .notify = event_notify,
552 .handle = &hotkey_handle,
553 .type = ACPI_DEVICE_NOTIFY,
554 .driver = &loongson_hotkey_driver,
555 },
556 };
557
generic_acpi_laptop_init(void)558 static int __init generic_acpi_laptop_init(void)
559 {
560 bool ec_found;
561 int i, ret, status;
562
563 if (acpi_disabled)
564 return -ENODEV;
565
566 /* The EC device is required */
567 ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
568 if (!ec_found)
569 return -ENODEV;
570
571 /* Enable SCI for EC */
572 acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
573
574 generic_inputdev = input_allocate_device();
575 if (!generic_inputdev) {
576 pr_err("Unable to allocate input device\n");
577 return -ENOMEM;
578 }
579
580 /* Prepare input device, but don't register */
581 generic_inputdev->name =
582 "Loongson Generic Laptop/All-in-One Extra Buttons";
583 generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
584 generic_inputdev->id.bustype = BUS_HOST;
585 generic_inputdev->dev.parent = NULL;
586
587 /* Init subdrivers */
588 for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
589 ret = generic_subdriver_init(&generic_sub_drivers[i]);
590 if (ret < 0) {
591 input_free_device(generic_inputdev);
592 while (--i >= 0)
593 generic_subdriver_exit(&generic_sub_drivers[i]);
594 return ret;
595 }
596 }
597
598 ret = input_register_device(generic_inputdev);
599 if (ret < 0) {
600 input_free_device(generic_inputdev);
601 while (--i >= 0)
602 generic_subdriver_exit(&generic_sub_drivers[i]);
603 pr_err("Unable to register input device\n");
604 return ret;
605 }
606
607 input_device_registered = 1;
608
609 if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
610 pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
611 ret = laptop_backlight_register();
612 if (ret < 0)
613 pr_err("Loongson Laptop: laptop-backlight device register failed\n");
614 }
615
616 return 0;
617 }
618
generic_acpi_laptop_exit(void)619 static void __exit generic_acpi_laptop_exit(void)
620 {
621 if (generic_inputdev) {
622 if (input_device_registered)
623 input_unregister_device(generic_inputdev);
624 else
625 input_free_device(generic_inputdev);
626 }
627 }
628
629 module_init(generic_acpi_laptop_init);
630 module_exit(generic_acpi_laptop_exit);
631
632 MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
633 MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
634 MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
635 MODULE_LICENSE("GPL");
636