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