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