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
42struct 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
53static u32 input_device_registered;
54static struct input_dev *generic_inputdev;
55
56static acpi_handle hotkey_handle;
57static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
58
59int loongson_laptop_turn_on_backlight(void);
60int loongson_laptop_turn_off_backlight(void);
61static int loongson_laptop_backlight_update(struct backlight_device *bd);
62
63/* 2. ACPI Helpers and device model */
64
65static 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(object: handle, pathname: method, parameter_objects: &params, return_object_buffer: 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
140static int hotkey_status_get(int *status)
141{
142 if (!acpi_evalf(handle: hotkey_handle, res: status, method: "GSWS", fmt: "d"))
143 return -EIO;
144
145 return 0;
146}
147
148static 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
157static 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(handle: *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), fmt: "%s/%s",
172 ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
173
174 status = acpi_install_notify_handler(device: *sub_driver->handle,
175 handler_type: sub_driver->type, handler: dispatch_acpi_notify, context: 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
191static int loongson_hotkey_suspend(struct device *dev)
192{
193 return 0;
194}
195
196static 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(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: &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(dev: generic_inputdev, ke: &ke, value: 1, autorelease: true);
235 }
236 }
237
238 return 0;
239}
240
241static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
242 loongson_hotkey_suspend, loongson_hotkey_resume);
243
244static 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
254static const struct acpi_device_id loongson_device_ids[] = {
255 {LOONGSON_ACPI_HKEY_HID, 0},
256 {"", 0},
257};
258MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
259
260static 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
270static 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(object: hotkey_handle, pathname: "KMAP", NULL, return_buffer: &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
300static int hotkey_backlight_set(bool enable)
301{
302 if (!acpi_evalf(handle: hotkey_handle, NULL, method: "VCBL", fmt: "vd", enable ? 1 : 0))
303 return -EIO;
304
305 return 0;
306}
307
308static int ec_get_brightness(void)
309{
310 int status = 0;
311
312 if (!hotkey_handle)
313 return -ENXIO;
314
315 if (!acpi_evalf(handle: hotkey_handle, res: &status, method: "ECBG", fmt: "d"))
316 return -EIO;
317
318 return status;
319}
320
321static 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(handle: hotkey_handle, NULL, method: "ECBS", fmt: "vd", level))
330 ret = -EIO;
331
332 return ret;
333}
334
335static int ec_backlight_level(u8 level)
336{
337 int status = 0;
338
339 if (!hotkey_handle)
340 return -ENXIO;
341
342 if (!acpi_evalf(handle: hotkey_handle, res: &status, method: "ECLL", fmt: "d"))
343 return -EIO;
344
345 if ((status < 0) || (level > status))
346 return status;
347
348 if (!acpi_evalf(handle: hotkey_handle, res: &status, method: "ECSL", fmt: "d"))
349 return -EIO;
350
351 if ((status < 0) || (level < status))
352 return status;
353
354 return level;
355}
356
357static int loongson_laptop_backlight_update(struct backlight_device *bd)
358{
359 int lvl = ec_backlight_level(level: bd->props.brightness);
360
361 if (lvl < 0)
362 return -EIO;
363 if (ec_set_brightness(level: lvl))
364 return -EIO;
365
366 return 0;
367}
368
369static 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
380static const struct backlight_ops backlight_laptop_ops = {
381 .update_status = loongson_laptop_backlight_update,
382 .get_brightness = loongson_laptop_get_brightness,
383};
384
385static 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(handle: hotkey_handle, res: &status, method: "ECLL", fmt: "d"))
393 return -EIO;
394
395 props.brightness = 1;
396 props.max_brightness = status;
397 props.type = BACKLIGHT_PLATFORM;
398
399 backlight_device_register(name: "loongson_laptop",
400 NULL, NULL, ops: &backlight_laptop_ops, props: &props);
401
402 return 0;
403}
404
405int 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, pathname: "\\BLSW", parameter_objects: &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
421int 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, pathname: "\\BLSW", parameter_objects: &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
437static 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(dev: generic_inputdev, keymap: hotkey_keycode_map, NULL);
448 if (ret < 0) {
449 pr_err("Failed to setup input device keymap\n");
450 input_free_device(dev: 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(enable: true);
462 else
463 hotkey_backlight_set(enable: false);
464
465 pr_info("ACPI: enabling firmware HKEY event interface...\n");
466
467 return ret;
468}
469
470static 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(dev: generic_inputdev, code: scan_code);
478 if (ke) {
479 if (type == KE_SW) {
480 int status = 0;
481
482 if (hotkey_status_get(status: &status) < 0)
483 return;
484
485 ke->sw.value = !!(status & (1 << ke->sw.code));
486 }
487 sparse_keymap_report_entry(dev: generic_inputdev, ke, value: 1, autorelease: true);
488 }
489}
490
491/* 3. Infrastructure */
492
493static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
494
495static 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
524err_out:
525 generic_subdriver_exit(sub_driver);
526 return ret;
527}
528
529static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
530{
531
532 if (sub_driver->acpi_notify_installed) {
533 acpi_remove_notify_handler(device: *sub_driver->handle,
534 handler_type: sub_driver->type, handler: dispatch_acpi_notify);
535 sub_driver->acpi_notify_installed = 0;
536 }
537 platform_driver_unregister(sub_driver->driver);
538}
539
540static 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
551static 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, value: 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(sub_driver: &generic_sub_drivers[i]);
583 if (ret < 0) {
584 input_free_device(dev: generic_inputdev);
585 while (--i >= 0)
586 generic_subdriver_exit(sub_driver: &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(dev: generic_inputdev);
594 while (--i >= 0)
595 generic_subdriver_exit(sub_driver: &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(handle: hotkey_handle, res: &status, method: "ECBG", fmt: "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
612static 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(dev: generic_inputdev);
619 }
620}
621
622module_init(generic_acpi_laptop_init);
623module_exit(generic_acpi_laptop_exit);
624
625MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
626MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
627MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
628MODULE_LICENSE("GPL");
629

source code of linux/drivers/platform/loongarch/loongson-laptop.c