drm/tests: hdmi: Fix memory leaks in drm_display_mode_from_cea_vic()
[drm/drm-misc.git] / drivers / platform / loongarch / loongson-laptop.c
blob99203584949daa2af9e5013c7a8ed7d64b60bf0f
1 // SPDX-License-Identifier: GPL-2.0
2 /*
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
9 */
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 */
28 /* ACPI HIDs */
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 {
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;
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, ...)
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;
76 if (!*fmt) {
77 pr_err("acpi_evalf() called with empty format\n");
78 return 0;
81 if (*fmt == 'q') {
82 quiet = 1;
83 fmt++;
84 } else
85 quiet = 0;
87 res_type = *(fmt++);
89 params.count = 0;
90 params.pointer = &in_objs[0];
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;
107 va_end(ap);
109 if (res_type != 'v') {
110 result.length = sizeof(out_obj);
111 result.pointer = &out_obj;
112 resultp = &result;
113 } else
114 resultp = NULL;
116 status = acpi_evaluate_object(handle, method, &params, resultp);
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;
133 if (!success && !quiet)
134 pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
135 method, fmt0, acpi_format_exception(status));
137 return success;
140 static int hotkey_status_get(int *status)
142 if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
143 return -EIO;
145 return 0;
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)
153 return;
154 sub_driver->notify(sub_driver, event);
157 static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
159 acpi_status status;
161 if (!*sub_driver->handle)
162 return 0;
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;
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);
180 } else {
181 pr_err("acpi_install_notify_handler(%s) failed: %s\n",
182 sub_driver->name, acpi_format_exception(status));
184 return -ENODEV;
186 sub_driver->acpi_notify_installed = 1;
188 return 0;
191 static int loongson_hotkey_suspend(struct device *dev)
193 return 0;
196 static int loongson_hotkey_resume(struct device *dev)
198 int status = 0;
199 struct key_entry ke;
200 struct backlight_device *bd;
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);
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)
215 return -EIO;
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.
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))) {
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);
238 return 0;
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);
248 if (!hotkey_handle)
249 return -ENODEV;
251 return 0;
254 static const struct acpi_device_id loongson_device_ids[] = {
255 {LOONGSON_ACPI_HKEY_HID, 0},
256 {"", 0},
258 MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
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,
270 static int hotkey_map(void)
272 u32 index;
273 acpi_status status;
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));
281 return -1;
283 pack = buf.pointer;
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;
297 return 0;
300 static int hotkey_backlight_set(bool enable)
302 if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
303 return -EIO;
305 return 0;
308 static int ec_get_brightness(void)
310 int status = 0;
312 if (!hotkey_handle)
313 return -ENXIO;
315 if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
316 return -EIO;
318 return status;
321 static int ec_set_brightness(int level)
324 int ret = 0;
326 if (!hotkey_handle)
327 return -ENXIO;
329 if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
330 ret = -EIO;
332 return ret;
335 static int ec_backlight_level(u8 level)
337 int status = 0;
339 if (!hotkey_handle)
340 return -ENXIO;
342 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
343 return -EIO;
345 if ((status < 0) || (level > status))
346 return status;
348 if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
349 return -EIO;
351 if ((status < 0) || (level < status))
352 return status;
354 return level;
357 static int loongson_laptop_backlight_update(struct backlight_device *bd)
359 int lvl = ec_backlight_level(bd->props.brightness);
361 if (lvl < 0)
362 return -EIO;
363 if (ec_set_brightness(lvl))
364 return -EIO;
366 return 0;
369 static int loongson_laptop_get_brightness(struct backlight_device *bd)
371 int level;
373 level = ec_get_brightness();
374 if (level < 0)
375 return -EIO;
377 return level;
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)
387 int status = 0;
388 struct backlight_properties props;
390 memset(&props, 0, sizeof(props));
392 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
393 return -EIO;
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);
402 return 0;
405 int loongson_laptop_turn_on_backlight(void)
407 int status;
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);
415 return -ENODEV;
418 return 0;
421 int loongson_laptop_turn_off_backlight(void)
423 int status;
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);
431 return -ENODEV;
434 return 0;
437 static int __init event_init(struct generic_sub_driver *sub_driver)
439 int ret;
441 ret = hotkey_map();
442 if (ret < 0) {
443 pr_err("Failed to parse keymap from DSDT\n");
444 return ret;
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;
453 return ret;
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);
462 else
463 hotkey_backlight_set(false);
465 pr_info("ACPI: enabling firmware HKEY event interface...\n");
467 return ret;
470 static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
472 int type, scan_code;
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);
478 if (ke) {
479 if (type == KE_SW) {
480 int status = 0;
482 if (hotkey_status_get(&status) < 0)
483 return;
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)
497 int ret;
499 if (!sub_driver || !sub_driver->driver)
500 return -EINVAL;
502 ret = platform_driver_register(sub_driver->driver);
503 if (ret)
504 return -EINVAL;
506 if (sub_driver->init) {
507 ret = sub_driver->init(sub_driver);
508 if (ret)
509 goto err_out;
512 if (sub_driver->notify) {
513 ret = setup_acpi_notify(sub_driver);
514 if (ret == -ENODEV) {
515 ret = 0;
516 goto err_out;
518 if (ret < 0)
519 goto err_out;
522 return 0;
524 err_out:
525 generic_subdriver_exit(sub_driver);
526 return ret;
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 = {
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,
551 static int __init generic_acpi_laptop_init(void)
553 bool ec_found;
554 int i, ret, status;
556 if (acpi_disabled)
557 return -ENODEV;
559 /* The EC device is required */
560 ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
561 if (!ec_found)
562 return -ENODEV;
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");
570 return -ENOMEM;
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]);
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;
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;
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();
605 if (ret < 0)
606 pr_err("Loongson Laptop: laptop-backlight device register failed\n");
609 return 0;
612 static void __exit generic_acpi_laptop_exit(void)
614 if (generic_inputdev) {
615 if (input_device_registered)
616 input_unregister_device(generic_inputdev);
617 else
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");