xref: /linux/drivers/platform/loongarch/loongson-laptop.c (revision 0ce096db719ebaf46d4faf93e1ed1341c1853919)
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 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);
62 
63 /* 2. ACPI Helpers and device model */
64 
65 static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
66 {
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;
75 
76 	if (!*fmt) {
77 		pr_err("acpi_evalf() called with empty format\n");
78 		return 0;
79 	}
80 
81 	if (*fmt == 'q') {
82 		quiet = 1;
83 		fmt++;
84 	} else
85 		quiet = 0;
86 
87 	res_type = *(fmt++);
88 
89 	params.count = 0;
90 	params.pointer = &in_objs[0];
91 
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;
105 		}
106 	}
107 	va_end(ap);
108 
109 	if (res_type != 'v') {
110 		result.length = sizeof(out_obj);
111 		result.pointer = &out_obj;
112 		resultp = &result;
113 	} else
114 		resultp = NULL;
115 
116 	status = acpi_evaluate_object(handle, method, &params, resultp);
117 
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;
131 	}
132 
133 	if (!success && !quiet)
134 		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
135 		       method, fmt0, acpi_format_exception(status));
136 
137 	return success;
138 }
139 
140 static int hotkey_status_get(int *status)
141 {
142 	if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
143 		return -EIO;
144 
145 	return 0;
146 }
147 
148 static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
149 {
150 	struct generic_sub_driver *sub_driver = data;
151 
152 	if (!sub_driver || !sub_driver->notify)
153 		return;
154 	sub_driver->notify(sub_driver, event);
155 }
156 
157 static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
158 {
159 	acpi_status status;
160 
161 	if (!*sub_driver->handle)
162 		return 0;
163 
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;
168 	}
169 
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);
173 
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));
183 		}
184 		return -ENODEV;
185 	}
186 	sub_driver->acpi_notify_installed = 1;
187 
188 	return 0;
189 }
190 
191 static int loongson_hotkey_suspend(struct device *dev)
192 {
193 	return 0;
194 }
195 
196 static int loongson_hotkey_resume(struct device *dev)
197 {
198 	int status = 0;
199 	struct key_entry ke;
200 	struct backlight_device *bd;
201 
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);
207 	}
208 
209 	/*
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.
212 	 */
213 	if (test_bit(SW_LID, generic_inputdev->swbit)) {
214 		if (hotkey_status_get(&status) < 0)
215 			return -EIO;
216 		/*
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.
222 		 *
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.
226 		 *
227 		 * Lid status is generally 0, but hardware exception is
228 		 * considered. So add lid status confirmation.
229 		 */
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);
235 		}
236 	}
237 
238 	return 0;
239 }
240 
241 static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
242 		loongson_hotkey_suspend, loongson_hotkey_resume);
243 
244 static int loongson_hotkey_probe(struct platform_device *pdev)
245 {
246 	hotkey_handle = ACPI_HANDLE(&pdev->dev);
247 
248 	if (!hotkey_handle)
249 		return -ENODEV;
250 
251 	return 0;
252 }
253 
254 static const struct acpi_device_id loongson_device_ids[] = {
255 	{LOONGSON_ACPI_HKEY_HID, 0},
256 	{"", 0},
257 };
258 MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
259 
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,
267 	},
268 };
269 
270 static int hotkey_map(void)
271 {
272 	u32 index;
273 	acpi_status status;
274 	struct acpi_buffer buf;
275 	union acpi_object *pack;
276 
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;
282 	}
283 	pack = buf.pointer;
284 	for (index = 0; index < pack->package.count; index++) {
285 		union acpi_object *element, *sub_pack;
286 
287 		sub_pack = &pack->package.elements[index];
288 
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;
295 	}
296 
297 	return 0;
298 }
299 
300 static int hotkey_backlight_set(bool enable)
301 {
302 	if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
303 		return -EIO;
304 
305 	return 0;
306 }
307 
308 static int ec_get_brightness(void)
309 {
310 	int status = 0;
311 
312 	if (!hotkey_handle)
313 		return -ENXIO;
314 
315 	if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
316 		return -EIO;
317 
318 	return status;
319 }
320 
321 static int ec_set_brightness(int level)
322 {
323 
324 	int ret = 0;
325 
326 	if (!hotkey_handle)
327 		return -ENXIO;
328 
329 	if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
330 		ret = -EIO;
331 
332 	return ret;
333 }
334 
335 static int ec_backlight_level(u8 level)
336 {
337 	int status = 0;
338 
339 	if (!hotkey_handle)
340 		return -ENXIO;
341 
342 	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
343 		return -EIO;
344 
345 	if ((status < 0) || (level > status))
346 		return status;
347 
348 	if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
349 		return -EIO;
350 
351 	if ((status < 0) || (level < status))
352 		return status;
353 
354 	return level;
355 }
356 
357 static int loongson_laptop_backlight_update(struct backlight_device *bd)
358 {
359 	int lvl = ec_backlight_level(bd->props.brightness);
360 
361 	if (lvl < 0)
362 		return -EIO;
363 	if (ec_set_brightness(lvl))
364 		return -EIO;
365 
366 	return 0;
367 }
368 
369 static int loongson_laptop_get_brightness(struct backlight_device *bd)
370 {
371 	int level;
372 
373 	level = ec_get_brightness();
374 	if (level < 0)
375 		return -EIO;
376 
377 	return level;
378 }
379 
380 static const struct backlight_ops backlight_laptop_ops = {
381 	.update_status = loongson_laptop_backlight_update,
382 	.get_brightness = loongson_laptop_get_brightness,
383 };
384 
385 static int laptop_backlight_register(void)
386 {
387 	int status = 0;
388 	struct backlight_properties props;
389 
390 	memset(&props, 0, sizeof(props));
391 
392 	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
393 		return -EIO;
394 
395 	props.brightness = 1;
396 	props.max_brightness = status;
397 	props.type = BACKLIGHT_PLATFORM;
398 
399 	backlight_device_register("loongson_laptop",
400 				NULL, NULL, &backlight_laptop_ops, &props);
401 
402 	return 0;
403 }
404 
405 int loongson_laptop_turn_on_backlight(void)
406 {
407 	int status;
408 	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
409 	struct acpi_object_list args = { 1, &arg0 };
410 
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;
416 	}
417 
418 	return 0;
419 }
420 
421 int loongson_laptop_turn_off_backlight(void)
422 {
423 	int status;
424 	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
425 	struct acpi_object_list args = { 1, &arg0 };
426 
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;
432 	}
433 
434 	return 0;
435 }
436 
437 static int __init event_init(struct generic_sub_driver *sub_driver)
438 {
439 	int ret;
440 
441 	ret = hotkey_map();
442 	if (ret < 0) {
443 		pr_err("Failed to parse keymap from DSDT\n");
444 		return ret;
445 	}
446 
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;
452 
453 		return ret;
454 	}
455 
456 	/*
457 	 * This hotkey driver handle backlight event when
458 	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
459 	 */
460 	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
461 		hotkey_backlight_set(true);
462 	else
463 		hotkey_backlight_set(false);
464 
465 	pr_info("ACPI: enabling firmware HKEY event interface...\n");
466 
467 	return ret;
468 }
469 
470 static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
471 {
472 	int type, scan_code;
473 	struct key_entry *ke = NULL;
474 
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;
481 
482 			if (hotkey_status_get(&status) < 0)
483 				return;
484 
485 			ke->sw.value = !!(status & (1 << ke->sw.code));
486 		}
487 		sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
488 	}
489 }
490 
491 /* 3. Infrastructure */
492 
493 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
494 
495 static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
496 {
497 	int ret;
498 
499 	if (!sub_driver || !sub_driver->driver)
500 		return -EINVAL;
501 
502 	ret = platform_driver_register(sub_driver->driver);
503 	if (ret)
504 		return -EINVAL;
505 
506 	if (sub_driver->init) {
507 		ret = sub_driver->init(sub_driver);
508 		if (ret)
509 			goto err_out;
510 	}
511 
512 	if (sub_driver->notify) {
513 		ret = setup_acpi_notify(sub_driver);
514 		if (ret == -ENODEV) {
515 			ret = 0;
516 			goto err_out;
517 		}
518 		if (ret < 0)
519 			goto err_out;
520 	}
521 
522 	return 0;
523 
524 err_out:
525 	generic_subdriver_exit(sub_driver);
526 	return ret;
527 }
528 
529 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
530 {
531 
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;
536 	}
537 	platform_driver_unregister(sub_driver->driver);
538 }
539 
540 static struct generic_sub_driver generic_sub_drivers[] __refdata = {
541 	{
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,
548 	},
549 };
550 
551 static int __init generic_acpi_laptop_init(void)
552 {
553 	bool ec_found;
554 	int i, ret, status;
555 
556 	if (acpi_disabled)
557 		return -ENODEV;
558 
559 	/* The EC device is required */
560 	ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
561 	if (!ec_found)
562 		return -ENODEV;
563 
564 	/* Enable SCI for EC */
565 	acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
566 
567 	generic_inputdev = input_allocate_device();
568 	if (!generic_inputdev) {
569 		pr_err("Unable to allocate input device\n");
570 		return -ENOMEM;
571 	}
572 
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;
579 
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;
588 		}
589 	}
590 
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;
598 	}
599 
600 	input_device_registered = 1;
601 
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");
607 	}
608 
609 	return 0;
610 }
611 
612 static void __exit generic_acpi_laptop_exit(void)
613 {
614 	if (generic_inputdev) {
615 		if (input_device_registered)
616 			input_unregister_device(generic_inputdev);
617 		else
618 			input_free_device(generic_inputdev);
619 	}
620 }
621 
622 module_init(generic_acpi_laptop_init);
623 module_exit(generic_acpi_laptop_exit);
624 
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");
629