1 // SPDX-License-Identifier: GPL-2.0
3 * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
5 * Jianmin Lv <lvjianmin@loongson.cn>
6 * Huacai Chen <chenhuacai@loongson.cn>
8 * Copyright (C) 2022 Loongson Technology Corporation Limited
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
26 /* 1. Driver-wide structs and misc. variables */
29 #define LOONGSON_ACPI_EC_HID "PNP0C09"
30 #define LOONGSON_ACPI_HKEY_HID "LOON0000"
32 #define ACPI_LAPTOP_NAME "loongson-laptop"
33 #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
35 #define MAX_ACPI_ARGS 3
36 #define GENERIC_HOTKEY_MAP_MAX 64
38 #define GENERIC_EVENT_TYPE_OFF 12
39 #define GENERIC_EVENT_TYPE_MASK 0xF000
40 #define GENERIC_EVENT_CODE_MASK 0x0FFF
42 struct generic_sub_driver
{
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
;
53 static u32 input_device_registered
;
54 static struct input_dev
*generic_inputdev
;
56 static acpi_handle hotkey_handle
;
57 static struct key_entry hotkey_keycode_map
[GENERIC_HOTKEY_MAP_MAX
];
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
);
63 /* 2. ACPI Helpers and device model */
65 static int acpi_evalf(acpi_handle handle
, int *res
, char *method
, char *fmt
, ...)
72 struct acpi_object_list params
;
73 struct acpi_buffer result
, *resultp
;
74 union acpi_object in_objs
[MAX_ACPI_ARGS
], out_obj
;
77 pr_err("acpi_evalf() called with empty format\n");
90 params
.pointer
= &in_objs
[0];
97 in_objs
[params
.count
].integer
.value
= va_arg(ap
, int);
98 in_objs
[params
.count
++].type
= ACPI_TYPE_INTEGER
;
100 /* add more types as needed */
102 pr_err("acpi_evalf() called with invalid format character '%c'\n", c
);
109 if (res_type
!= 'v') {
110 result
.length
= sizeof(out_obj
);
111 result
.pointer
= &out_obj
;
116 status
= acpi_evaluate_object(handle
, method
, ¶ms
, resultp
);
120 success
= (status
== AE_OK
&& out_obj
.type
== ACPI_TYPE_INTEGER
);
122 *res
= out_obj
.integer
.value
;
125 success
= status
== AE_OK
;
127 /* add more types as needed */
129 pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type
);
133 if (!success
&& !quiet
)
134 pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
135 method
, fmt0
, acpi_format_exception(status
));
140 static int hotkey_status_get(int *status
)
142 if (!acpi_evalf(hotkey_handle
, status
, "GSWS", "d"))
148 static void dispatch_acpi_notify(acpi_handle handle
, u32 event
, void *data
)
150 struct generic_sub_driver
*sub_driver
= data
;
152 if (!sub_driver
|| !sub_driver
->notify
)
154 sub_driver
->notify(sub_driver
, event
);
157 static int __init
setup_acpi_notify(struct generic_sub_driver
*sub_driver
)
161 if (!*sub_driver
->handle
)
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
);
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
);
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
);
181 pr_err("acpi_install_notify_handler(%s) failed: %s\n",
182 sub_driver
->name
, acpi_format_exception(status
));
186 sub_driver
->acpi_notify_installed
= 1;
191 static int loongson_hotkey_suspend(struct device
*dev
)
196 static int loongson_hotkey_resume(struct device
*dev
)
200 struct backlight_device
*bd
;
202 bd
= backlight_device_get_by_type(BACKLIGHT_PLATFORM
);
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
);
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.
213 if (test_bit(SW_LID
, generic_inputdev
->swbit
)) {
214 if (hotkey_status_get(&status
) < 0)
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
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.
227 * Lid status is generally 0, but hardware exception is
228 * considered. So add lid status confirmation.
230 if (test_bit(SW_LID
, generic_inputdev
->sw
) && !(status
& (1 << SW_LID
))) {
232 ke
.sw
.value
= (u8
)status
;
234 sparse_keymap_report_entry(generic_inputdev
, &ke
, 1, true);
241 static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm
,
242 loongson_hotkey_suspend
, loongson_hotkey_resume
);
244 static int loongson_hotkey_probe(struct platform_device
*pdev
)
246 hotkey_handle
= ACPI_HANDLE(&pdev
->dev
);
254 static const struct acpi_device_id loongson_device_ids
[] = {
255 {LOONGSON_ACPI_HKEY_HID
, 0},
258 MODULE_DEVICE_TABLE(acpi
, loongson_device_ids
);
260 static struct platform_driver loongson_hotkey_driver
= {
261 .probe
= loongson_hotkey_probe
,
263 .name
= "loongson-hotkey",
264 .owner
= THIS_MODULE
,
265 .pm
= pm_ptr(&loongson_hotkey_pm
),
266 .acpi_match_table
= loongson_device_ids
,
270 static int hotkey_map(void)
274 struct acpi_buffer buf
;
275 union acpi_object
*pack
;
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
));
284 for (index
= 0; index
< pack
->package
.count
; index
++) {
285 union acpi_object
*element
, *sub_pack
;
287 sub_pack
= &pack
->package
.elements
[index
];
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
;
300 static int hotkey_backlight_set(bool enable
)
302 if (!acpi_evalf(hotkey_handle
, NULL
, "VCBL", "vd", enable
? 1 : 0))
308 static int ec_get_brightness(void)
315 if (!acpi_evalf(hotkey_handle
, &status
, "ECBG", "d"))
321 static int ec_set_brightness(int level
)
329 if (!acpi_evalf(hotkey_handle
, NULL
, "ECBS", "vd", level
))
335 static int ec_backlight_level(u8 level
)
342 if (!acpi_evalf(hotkey_handle
, &status
, "ECLL", "d"))
345 if ((status
< 0) || (level
> status
))
348 if (!acpi_evalf(hotkey_handle
, &status
, "ECSL", "d"))
351 if ((status
< 0) || (level
< status
))
357 static int loongson_laptop_backlight_update(struct backlight_device
*bd
)
359 int lvl
= ec_backlight_level(bd
->props
.brightness
);
363 if (ec_set_brightness(lvl
))
369 static int loongson_laptop_get_brightness(struct backlight_device
*bd
)
373 level
= ec_get_brightness();
380 static const struct backlight_ops backlight_laptop_ops
= {
381 .update_status
= loongson_laptop_backlight_update
,
382 .get_brightness
= loongson_laptop_get_brightness
,
385 static int laptop_backlight_register(void)
388 struct backlight_properties props
;
390 memset(&props
, 0, sizeof(props
));
392 if (!acpi_evalf(hotkey_handle
, &status
, "ECLL", "d"))
395 props
.brightness
= 1;
396 props
.max_brightness
= status
;
397 props
.type
= BACKLIGHT_PLATFORM
;
399 backlight_device_register("loongson_laptop",
400 NULL
, NULL
, &backlight_laptop_ops
, &props
);
405 int loongson_laptop_turn_on_backlight(void)
408 union acpi_object arg0
= { ACPI_TYPE_INTEGER
};
409 struct acpi_object_list args
= { 1, &arg0
};
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
);
421 int loongson_laptop_turn_off_backlight(void)
424 union acpi_object arg0
= { ACPI_TYPE_INTEGER
};
425 struct acpi_object_list args
= { 1, &arg0
};
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
);
437 static int __init
event_init(struct generic_sub_driver
*sub_driver
)
443 pr_err("Failed to parse keymap from DSDT\n");
447 ret
= sparse_keymap_setup(generic_inputdev
, hotkey_keycode_map
, NULL
);
449 pr_err("Failed to setup input device keymap\n");
450 input_free_device(generic_inputdev
);
451 generic_inputdev
= NULL
;
457 * This hotkey driver handle backlight event when
458 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
460 if (acpi_video_get_backlight_type() == acpi_backlight_vendor
)
461 hotkey_backlight_set(true);
463 hotkey_backlight_set(false);
465 pr_info("ACPI: enabling firmware HKEY event interface...\n");
470 static void event_notify(struct generic_sub_driver
*sub_driver
, u32 event
)
473 struct key_entry
*ke
= NULL
;
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
);
482 if (hotkey_status_get(&status
) < 0)
485 ke
->sw
.value
= !!(status
& (1 << ke
->sw
.code
));
487 sparse_keymap_report_entry(generic_inputdev
, ke
, 1, true);
491 /* 3. Infrastructure */
493 static void generic_subdriver_exit(struct generic_sub_driver
*sub_driver
);
495 static int __init
generic_subdriver_init(struct generic_sub_driver
*sub_driver
)
499 if (!sub_driver
|| !sub_driver
->driver
)
502 ret
= platform_driver_register(sub_driver
->driver
);
506 if (sub_driver
->init
) {
507 ret
= sub_driver
->init(sub_driver
);
512 if (sub_driver
->notify
) {
513 ret
= setup_acpi_notify(sub_driver
);
514 if (ret
== -ENODEV
) {
525 generic_subdriver_exit(sub_driver
);
529 static void generic_subdriver_exit(struct generic_sub_driver
*sub_driver
)
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;
537 platform_driver_unregister(sub_driver
->driver
);
540 static struct generic_sub_driver generic_sub_drivers
[] __refdata
= {
544 .notify
= event_notify
,
545 .handle
= &hotkey_handle
,
546 .type
= ACPI_DEVICE_NOTIFY
,
547 .driver
= &loongson_hotkey_driver
,
551 static int __init
generic_acpi_laptop_init(void)
559 /* The EC device is required */
560 ec_found
= acpi_dev_found(LOONGSON_ACPI_EC_HID
);
564 /* Enable SCI for EC */
565 acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE
, 1);
567 generic_inputdev
= input_allocate_device();
568 if (!generic_inputdev
) {
569 pr_err("Unable to allocate input device\n");
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
;
580 /* Init subdrivers */
581 for (i
= 0; i
< ARRAY_SIZE(generic_sub_drivers
); i
++) {
582 ret
= generic_subdriver_init(&generic_sub_drivers
[i
]);
584 input_free_device(generic_inputdev
);
586 generic_subdriver_exit(&generic_sub_drivers
[i
]);
591 ret
= input_register_device(generic_inputdev
);
593 input_free_device(generic_inputdev
);
595 generic_subdriver_exit(&generic_sub_drivers
[i
]);
596 pr_err("Unable to register input device\n");
600 input_device_registered
= 1;
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();
606 pr_err("Loongson Laptop: laptop-backlight device register failed\n");
612 static void __exit
generic_acpi_laptop_exit(void)
614 if (generic_inputdev
) {
615 if (input_device_registered
)
616 input_unregister_device(generic_inputdev
);
618 input_free_device(generic_inputdev
);
622 module_init(generic_acpi_laptop_init
);
623 module_exit(generic_acpi_laptop_exit
);
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");