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