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