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 
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, &params, 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 
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 
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 
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 
190 static int loongson_hotkey_suspend(struct device *dev)
191 {
192 	return 0;
193 }
194 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 = BACKLIGHT_POWER_ON;
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 
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 
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 
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 
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 
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 
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