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
50struct 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
61static u32 input_device_registered;
62static struct input_dev *generic_inputdev;
63
64static acpi_handle hotkey_handle;
65static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
66
67int loongson_laptop_turn_on_backlight(void);
68int loongson_laptop_turn_off_backlight(void);
69static int loongson_laptop_backlight_update(struct backlight_device *bd);
70
71/* 2. ACPI Helpers and device model */
72
73static 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
148static 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
156static 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
165static 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
201static int loongson_hotkey_suspend(struct device *dev)
202{
203	return 0;
204}
205
206static 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
251static SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
252		loongson_hotkey_suspend, loongson_hotkey_resume);
253#endif
254
255static 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
265static const struct acpi_device_id loongson_device_ids[] = {
266	{LOONGSON_ACPI_HKEY_HID, 0},
267	{"", 0},
268};
269MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
270
271static 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
281static 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
311static 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
319static 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
332static 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
346static 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
368static 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
379static 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
390static const struct backlight_ops backlight_laptop_ops = {
391	.update_status = loongson_laptop_backlight_update,
392	.get_brightness = loongson_laptop_get_brightness,
393};
394
395static 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
415int 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
430int 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
445static 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
478static 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
500static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
501
502static 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
531err_out:
532	generic_subdriver_exit(sub_driver);
533	return ret;
534}
535
536static 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
547static 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
558static 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
619static 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
629module_init(generic_acpi_laptop_init);
630module_exit(generic_acpi_laptop_exit);
631
632MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
633MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
634MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
635MODULE_LICENSE("GPL");
636