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