xref: /openbmc/linux/drivers/platform/loongarch/loongson-laptop.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
16246ed09SJianmin Lv // SPDX-License-Identifier: GPL-2.0
26246ed09SJianmin Lv /*
36246ed09SJianmin Lv  *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
46246ed09SJianmin Lv  *
56246ed09SJianmin Lv  *  Jianmin Lv <lvjianmin@loongson.cn>
66246ed09SJianmin Lv  *  Huacai Chen <chenhuacai@loongson.cn>
76246ed09SJianmin Lv  *
86246ed09SJianmin Lv  * Copyright (C) 2022 Loongson Technology Corporation Limited
96246ed09SJianmin Lv  */
106246ed09SJianmin Lv 
116246ed09SJianmin Lv #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
126246ed09SJianmin Lv 
136246ed09SJianmin Lv #include <linux/init.h>
146246ed09SJianmin Lv #include <linux/kernel.h>
156246ed09SJianmin Lv #include <linux/module.h>
166246ed09SJianmin Lv #include <linux/acpi.h>
176246ed09SJianmin Lv #include <linux/backlight.h>
186246ed09SJianmin Lv #include <linux/device.h>
196246ed09SJianmin Lv #include <linux/input.h>
206246ed09SJianmin Lv #include <linux/input/sparse-keymap.h>
216246ed09SJianmin Lv #include <linux/platform_device.h>
226246ed09SJianmin Lv #include <linux/string.h>
236246ed09SJianmin Lv #include <linux/types.h>
246246ed09SJianmin Lv #include <acpi/video.h>
256246ed09SJianmin Lv 
266246ed09SJianmin Lv /* 1. Driver-wide structs and misc. variables */
276246ed09SJianmin Lv 
286246ed09SJianmin Lv /* ACPI HIDs */
296246ed09SJianmin Lv #define LOONGSON_ACPI_EC_HID	"PNP0C09"
306246ed09SJianmin Lv #define LOONGSON_ACPI_HKEY_HID	"LOON0000"
316246ed09SJianmin Lv 
326246ed09SJianmin Lv #define ACPI_LAPTOP_NAME "loongson-laptop"
336246ed09SJianmin Lv #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
346246ed09SJianmin Lv 
356246ed09SJianmin Lv #define MAX_ACPI_ARGS			3
366246ed09SJianmin Lv #define GENERIC_HOTKEY_MAP_MAX		64
376246ed09SJianmin Lv 
386246ed09SJianmin Lv #define GENERIC_EVENT_TYPE_OFF		12
396246ed09SJianmin Lv #define GENERIC_EVENT_TYPE_MASK		0xF000
406246ed09SJianmin Lv #define GENERIC_EVENT_CODE_MASK		0x0FFF
416246ed09SJianmin Lv 
426246ed09SJianmin Lv struct generic_sub_driver {
436246ed09SJianmin Lv 	u32 type;
446246ed09SJianmin Lv 	char *name;
456246ed09SJianmin Lv 	acpi_handle *handle;
466246ed09SJianmin Lv 	struct acpi_device *device;
476246ed09SJianmin Lv 	struct platform_driver *driver;
486246ed09SJianmin Lv 	int (*init)(struct generic_sub_driver *sub_driver);
496246ed09SJianmin Lv 	void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
506246ed09SJianmin Lv 	u8 acpi_notify_installed;
516246ed09SJianmin Lv };
526246ed09SJianmin Lv 
536246ed09SJianmin Lv static u32 input_device_registered;
546246ed09SJianmin Lv static struct input_dev *generic_inputdev;
556246ed09SJianmin Lv 
566246ed09SJianmin Lv static acpi_handle hotkey_handle;
576246ed09SJianmin Lv static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
586246ed09SJianmin Lv 
596246ed09SJianmin Lv int loongson_laptop_turn_on_backlight(void);
606246ed09SJianmin Lv int loongson_laptop_turn_off_backlight(void);
616246ed09SJianmin Lv static int loongson_laptop_backlight_update(struct backlight_device *bd);
626246ed09SJianmin Lv 
636246ed09SJianmin Lv /* 2. ACPI Helpers and device model */
646246ed09SJianmin Lv 
acpi_evalf(acpi_handle handle,int * res,char * method,char * fmt,...)656246ed09SJianmin Lv static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
666246ed09SJianmin Lv {
676246ed09SJianmin Lv 	char res_type;
686246ed09SJianmin Lv 	char *fmt0 = fmt;
696246ed09SJianmin Lv 	va_list ap;
706246ed09SJianmin Lv 	int success, quiet;
716246ed09SJianmin Lv 	acpi_status status;
726246ed09SJianmin Lv 	struct acpi_object_list params;
736246ed09SJianmin Lv 	struct acpi_buffer result, *resultp;
746246ed09SJianmin Lv 	union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
756246ed09SJianmin Lv 
766246ed09SJianmin Lv 	if (!*fmt) {
776246ed09SJianmin Lv 		pr_err("acpi_evalf() called with empty format\n");
786246ed09SJianmin Lv 		return 0;
796246ed09SJianmin Lv 	}
806246ed09SJianmin Lv 
816246ed09SJianmin Lv 	if (*fmt == 'q') {
826246ed09SJianmin Lv 		quiet = 1;
836246ed09SJianmin Lv 		fmt++;
846246ed09SJianmin Lv 	} else
856246ed09SJianmin Lv 		quiet = 0;
866246ed09SJianmin Lv 
876246ed09SJianmin Lv 	res_type = *(fmt++);
886246ed09SJianmin Lv 
896246ed09SJianmin Lv 	params.count = 0;
906246ed09SJianmin Lv 	params.pointer = &in_objs[0];
916246ed09SJianmin Lv 
926246ed09SJianmin Lv 	va_start(ap, fmt);
936246ed09SJianmin Lv 	while (*fmt) {
946246ed09SJianmin Lv 		char c = *(fmt++);
956246ed09SJianmin Lv 		switch (c) {
966246ed09SJianmin Lv 		case 'd':	/* int */
976246ed09SJianmin Lv 			in_objs[params.count].integer.value = va_arg(ap, int);
986246ed09SJianmin Lv 			in_objs[params.count++].type = ACPI_TYPE_INTEGER;
996246ed09SJianmin Lv 			break;
1006246ed09SJianmin Lv 			/* add more types as needed */
1016246ed09SJianmin Lv 		default:
1026246ed09SJianmin Lv 			pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
1036246ed09SJianmin Lv 			va_end(ap);
1046246ed09SJianmin Lv 			return 0;
1056246ed09SJianmin Lv 		}
1066246ed09SJianmin Lv 	}
1076246ed09SJianmin Lv 	va_end(ap);
1086246ed09SJianmin Lv 
1096246ed09SJianmin Lv 	if (res_type != 'v') {
1106246ed09SJianmin Lv 		result.length = sizeof(out_obj);
1116246ed09SJianmin Lv 		result.pointer = &out_obj;
1126246ed09SJianmin Lv 		resultp = &result;
1136246ed09SJianmin Lv 	} else
1146246ed09SJianmin Lv 		resultp = NULL;
1156246ed09SJianmin Lv 
1166246ed09SJianmin Lv 	status = acpi_evaluate_object(handle, method, &params, resultp);
1176246ed09SJianmin Lv 
1186246ed09SJianmin Lv 	switch (res_type) {
1196246ed09SJianmin Lv 	case 'd':		/* int */
1206246ed09SJianmin Lv 		success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
1216246ed09SJianmin Lv 		if (success && res)
1226246ed09SJianmin Lv 			*res = out_obj.integer.value;
1236246ed09SJianmin Lv 		break;
1246246ed09SJianmin Lv 	case 'v':		/* void */
1256246ed09SJianmin Lv 		success = status == AE_OK;
1266246ed09SJianmin Lv 		break;
1276246ed09SJianmin Lv 		/* add more types as needed */
1286246ed09SJianmin Lv 	default:
1296246ed09SJianmin Lv 		pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
1306246ed09SJianmin Lv 		return 0;
1316246ed09SJianmin Lv 	}
1326246ed09SJianmin Lv 
1336246ed09SJianmin Lv 	if (!success && !quiet)
1346246ed09SJianmin Lv 		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
1356246ed09SJianmin Lv 		       method, fmt0, acpi_format_exception(status));
1366246ed09SJianmin Lv 
1376246ed09SJianmin Lv 	return success;
1386246ed09SJianmin Lv }
1396246ed09SJianmin Lv 
hotkey_status_get(int * status)1406246ed09SJianmin Lv static int hotkey_status_get(int *status)
1416246ed09SJianmin Lv {
1426246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
1436246ed09SJianmin Lv 		return -EIO;
1446246ed09SJianmin Lv 
1456246ed09SJianmin Lv 	return 0;
1466246ed09SJianmin Lv }
1476246ed09SJianmin Lv 
dispatch_acpi_notify(acpi_handle handle,u32 event,void * data)1486246ed09SJianmin Lv static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
1496246ed09SJianmin Lv {
1506246ed09SJianmin Lv 	struct generic_sub_driver *sub_driver = data;
1516246ed09SJianmin Lv 
1526246ed09SJianmin Lv 	if (!sub_driver || !sub_driver->notify)
1536246ed09SJianmin Lv 		return;
1546246ed09SJianmin Lv 	sub_driver->notify(sub_driver, event);
1556246ed09SJianmin Lv }
1566246ed09SJianmin Lv 
setup_acpi_notify(struct generic_sub_driver * sub_driver)1576246ed09SJianmin Lv static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
1586246ed09SJianmin Lv {
1596246ed09SJianmin Lv 	acpi_status status;
1606246ed09SJianmin Lv 
1616246ed09SJianmin Lv 	if (!*sub_driver->handle)
1626246ed09SJianmin Lv 		return 0;
1636246ed09SJianmin Lv 
1646246ed09SJianmin Lv 	sub_driver->device = acpi_fetch_acpi_dev(*sub_driver->handle);
1656246ed09SJianmin Lv 	if (!sub_driver->device) {
1666246ed09SJianmin Lv 		pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
1676246ed09SJianmin Lv 		return -ENODEV;
1686246ed09SJianmin Lv 	}
1696246ed09SJianmin Lv 
1706246ed09SJianmin Lv 	sub_driver->device->driver_data = sub_driver;
1716246ed09SJianmin Lv 	sprintf(acpi_device_class(sub_driver->device), "%s/%s",
1726246ed09SJianmin Lv 		ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
1736246ed09SJianmin Lv 
1746246ed09SJianmin Lv 	status = acpi_install_notify_handler(*sub_driver->handle,
1756246ed09SJianmin Lv 			sub_driver->type, dispatch_acpi_notify, sub_driver);
1766246ed09SJianmin Lv 	if (ACPI_FAILURE(status)) {
1776246ed09SJianmin Lv 		if (status == AE_ALREADY_EXISTS) {
1786246ed09SJianmin Lv 			pr_notice("Another device driver is already "
1796246ed09SJianmin Lv 				  "handling %s events\n", sub_driver->name);
1806246ed09SJianmin Lv 		} else {
1816246ed09SJianmin Lv 			pr_err("acpi_install_notify_handler(%s) failed: %s\n",
1826246ed09SJianmin Lv 			       sub_driver->name, acpi_format_exception(status));
1836246ed09SJianmin Lv 		}
1846246ed09SJianmin Lv 		return -ENODEV;
1856246ed09SJianmin Lv 	}
1866246ed09SJianmin Lv 	sub_driver->acpi_notify_installed = 1;
1876246ed09SJianmin Lv 
1886246ed09SJianmin Lv 	return 0;
1896246ed09SJianmin Lv }
1906246ed09SJianmin Lv 
loongson_hotkey_suspend(struct device * dev)1916246ed09SJianmin Lv static int loongson_hotkey_suspend(struct device *dev)
1926246ed09SJianmin Lv {
1936246ed09SJianmin Lv 	return 0;
1946246ed09SJianmin Lv }
1956246ed09SJianmin Lv 
loongson_hotkey_resume(struct device * dev)1966246ed09SJianmin Lv static int loongson_hotkey_resume(struct device *dev)
1976246ed09SJianmin Lv {
1986246ed09SJianmin Lv 	int status = 0;
1996246ed09SJianmin Lv 	struct key_entry ke;
2006246ed09SJianmin Lv 	struct backlight_device *bd;
2016246ed09SJianmin Lv 
202fbe605abSHuacai Chen 	bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
203fbe605abSHuacai Chen 	if (bd) {
204fbe605abSHuacai Chen 		loongson_laptop_backlight_update(bd) ?
205fbe605abSHuacai Chen 		pr_warn("Loongson_backlight: resume brightness failed") :
206fbe605abSHuacai Chen 		pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
207fbe605abSHuacai Chen 	}
208fbe605abSHuacai Chen 
2096246ed09SJianmin Lv 	/*
2106246ed09SJianmin Lv 	 * Only if the firmware supports SW_LID event model, we can handle the
2116246ed09SJianmin Lv 	 * event. This is for the consideration of development board without EC.
2126246ed09SJianmin Lv 	 */
2136246ed09SJianmin Lv 	if (test_bit(SW_LID, generic_inputdev->swbit)) {
2146246ed09SJianmin Lv 		if (hotkey_status_get(&status) < 0)
2156246ed09SJianmin Lv 			return -EIO;
2166246ed09SJianmin Lv 		/*
2176246ed09SJianmin Lv 		 * The input device sw element records the last lid status.
2186246ed09SJianmin Lv 		 * When the system is awakened by other wake-up sources,
2196246ed09SJianmin Lv 		 * the lid event will also be reported. The judgment of
2206246ed09SJianmin Lv 		 * adding SW_LID bit which in sw element can avoid this
2216246ed09SJianmin Lv 		 * case.
2226246ed09SJianmin Lv 		 *
2236246ed09SJianmin Lv 		 * Input system will drop lid event when current lid event
2246246ed09SJianmin Lv 		 * value and last lid status in the same. So laptop driver
2256246ed09SJianmin Lv 		 * doesn't report repeated events.
2266246ed09SJianmin Lv 		 *
2276246ed09SJianmin Lv 		 * Lid status is generally 0, but hardware exception is
2286246ed09SJianmin Lv 		 * considered. So add lid status confirmation.
2296246ed09SJianmin Lv 		 */
2306246ed09SJianmin Lv 		if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
2316246ed09SJianmin Lv 			ke.type = KE_SW;
2326246ed09SJianmin Lv 			ke.sw.value = (u8)status;
2336246ed09SJianmin Lv 			ke.sw.code = SW_LID;
2346246ed09SJianmin Lv 			sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
2356246ed09SJianmin Lv 		}
2366246ed09SJianmin Lv 	}
2376246ed09SJianmin Lv 
2386246ed09SJianmin Lv 	return 0;
2396246ed09SJianmin Lv }
2406246ed09SJianmin Lv 
2416246ed09SJianmin Lv static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
2426246ed09SJianmin Lv 		loongson_hotkey_suspend, loongson_hotkey_resume);
2436246ed09SJianmin Lv 
loongson_hotkey_probe(struct platform_device * pdev)2446246ed09SJianmin Lv static int loongson_hotkey_probe(struct platform_device *pdev)
2456246ed09SJianmin Lv {
2466246ed09SJianmin Lv 	hotkey_handle = ACPI_HANDLE(&pdev->dev);
2476246ed09SJianmin Lv 
2486246ed09SJianmin Lv 	if (!hotkey_handle)
2496246ed09SJianmin Lv 		return -ENODEV;
2506246ed09SJianmin Lv 
2516246ed09SJianmin Lv 	return 0;
2526246ed09SJianmin Lv }
2536246ed09SJianmin Lv 
2546246ed09SJianmin Lv static const struct acpi_device_id loongson_device_ids[] = {
2556246ed09SJianmin Lv 	{LOONGSON_ACPI_HKEY_HID, 0},
2566246ed09SJianmin Lv 	{"", 0},
2576246ed09SJianmin Lv };
2586246ed09SJianmin Lv MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
2596246ed09SJianmin Lv 
2606246ed09SJianmin Lv static struct platform_driver loongson_hotkey_driver = {
2616246ed09SJianmin Lv 	.probe		= loongson_hotkey_probe,
2626246ed09SJianmin Lv 	.driver		= {
2636246ed09SJianmin Lv 		.name	= "loongson-hotkey",
2646246ed09SJianmin Lv 		.owner	= THIS_MODULE,
2656246ed09SJianmin Lv 		.pm	= pm_ptr(&loongson_hotkey_pm),
2666246ed09SJianmin Lv 		.acpi_match_table = loongson_device_ids,
2676246ed09SJianmin Lv 	},
2686246ed09SJianmin Lv };
2696246ed09SJianmin Lv 
hotkey_map(void)2706246ed09SJianmin Lv static int hotkey_map(void)
2716246ed09SJianmin Lv {
2726246ed09SJianmin Lv 	u32 index;
2736246ed09SJianmin Lv 	acpi_status status;
2746246ed09SJianmin Lv 	struct acpi_buffer buf;
2756246ed09SJianmin Lv 	union acpi_object *pack;
2766246ed09SJianmin Lv 
2776246ed09SJianmin Lv 	buf.length = ACPI_ALLOCATE_BUFFER;
2786246ed09SJianmin Lv 	status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
2796246ed09SJianmin Lv 	if (status != AE_OK) {
2806246ed09SJianmin Lv 		pr_err("ACPI exception: %s\n", acpi_format_exception(status));
2816246ed09SJianmin Lv 		return -1;
2826246ed09SJianmin Lv 	}
2836246ed09SJianmin Lv 	pack = buf.pointer;
2846246ed09SJianmin Lv 	for (index = 0; index < pack->package.count; index++) {
2856246ed09SJianmin Lv 		union acpi_object *element, *sub_pack;
2866246ed09SJianmin Lv 
2876246ed09SJianmin Lv 		sub_pack = &pack->package.elements[index];
2886246ed09SJianmin Lv 
2896246ed09SJianmin Lv 		element = &sub_pack->package.elements[0];
2906246ed09SJianmin Lv 		hotkey_keycode_map[index].type = element->integer.value;
2916246ed09SJianmin Lv 		element = &sub_pack->package.elements[1];
2926246ed09SJianmin Lv 		hotkey_keycode_map[index].code = element->integer.value;
2936246ed09SJianmin Lv 		element = &sub_pack->package.elements[2];
2946246ed09SJianmin Lv 		hotkey_keycode_map[index].keycode = element->integer.value;
2956246ed09SJianmin Lv 	}
2966246ed09SJianmin Lv 
2976246ed09SJianmin Lv 	return 0;
2986246ed09SJianmin Lv }
2996246ed09SJianmin Lv 
hotkey_backlight_set(bool enable)3006246ed09SJianmin Lv static int hotkey_backlight_set(bool enable)
3016246ed09SJianmin Lv {
3026246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
3036246ed09SJianmin Lv 		return -EIO;
3046246ed09SJianmin Lv 
3056246ed09SJianmin Lv 	return 0;
3066246ed09SJianmin Lv }
3076246ed09SJianmin Lv 
ec_get_brightness(void)3086246ed09SJianmin Lv static int ec_get_brightness(void)
3096246ed09SJianmin Lv {
3106246ed09SJianmin Lv 	int status = 0;
3116246ed09SJianmin Lv 
3126246ed09SJianmin Lv 	if (!hotkey_handle)
3136246ed09SJianmin Lv 		return -ENXIO;
3146246ed09SJianmin Lv 
3156246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
3166246ed09SJianmin Lv 		return -EIO;
3176246ed09SJianmin Lv 
3186246ed09SJianmin Lv 	return status;
3196246ed09SJianmin Lv }
3206246ed09SJianmin Lv 
ec_set_brightness(int level)3216246ed09SJianmin Lv static int ec_set_brightness(int level)
3226246ed09SJianmin Lv {
3236246ed09SJianmin Lv 
3246246ed09SJianmin Lv 	int ret = 0;
3256246ed09SJianmin Lv 
3266246ed09SJianmin Lv 	if (!hotkey_handle)
3276246ed09SJianmin Lv 		return -ENXIO;
3286246ed09SJianmin Lv 
3296246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
3306246ed09SJianmin Lv 		ret = -EIO;
3316246ed09SJianmin Lv 
3326246ed09SJianmin Lv 	return ret;
3336246ed09SJianmin Lv }
3346246ed09SJianmin Lv 
ec_backlight_level(u8 level)3356246ed09SJianmin Lv static int ec_backlight_level(u8 level)
3366246ed09SJianmin Lv {
3376246ed09SJianmin Lv 	int status = 0;
3386246ed09SJianmin Lv 
3396246ed09SJianmin Lv 	if (!hotkey_handle)
3406246ed09SJianmin Lv 		return -ENXIO;
3416246ed09SJianmin Lv 
3426246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
3436246ed09SJianmin Lv 		return -EIO;
3446246ed09SJianmin Lv 
3456246ed09SJianmin Lv 	if ((status < 0) || (level > status))
3466246ed09SJianmin Lv 		return status;
3476246ed09SJianmin Lv 
3486246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
3496246ed09SJianmin Lv 		return -EIO;
3506246ed09SJianmin Lv 
3516246ed09SJianmin Lv 	if ((status < 0) || (level < status))
3526246ed09SJianmin Lv 		return status;
3536246ed09SJianmin Lv 
3546246ed09SJianmin Lv 	return level;
3556246ed09SJianmin Lv }
3566246ed09SJianmin Lv 
loongson_laptop_backlight_update(struct backlight_device * bd)3576246ed09SJianmin Lv static int loongson_laptop_backlight_update(struct backlight_device *bd)
3586246ed09SJianmin Lv {
3596246ed09SJianmin Lv 	int lvl = ec_backlight_level(bd->props.brightness);
3606246ed09SJianmin Lv 
3616246ed09SJianmin Lv 	if (lvl < 0)
3626246ed09SJianmin Lv 		return -EIO;
3636246ed09SJianmin Lv 	if (ec_set_brightness(lvl))
3646246ed09SJianmin Lv 		return -EIO;
3656246ed09SJianmin Lv 
3666246ed09SJianmin Lv 	return 0;
3676246ed09SJianmin Lv }
3686246ed09SJianmin Lv 
loongson_laptop_get_brightness(struct backlight_device * bd)3696246ed09SJianmin Lv static int loongson_laptop_get_brightness(struct backlight_device *bd)
3706246ed09SJianmin Lv {
3716246ed09SJianmin Lv 	int level;
3726246ed09SJianmin Lv 
3736246ed09SJianmin Lv 	level = ec_get_brightness();
3746246ed09SJianmin Lv 	if (level < 0)
3756246ed09SJianmin Lv 		return -EIO;
3766246ed09SJianmin Lv 
3776246ed09SJianmin Lv 	return level;
3786246ed09SJianmin Lv }
3796246ed09SJianmin Lv 
3806246ed09SJianmin Lv static const struct backlight_ops backlight_laptop_ops = {
3816246ed09SJianmin Lv 	.update_status = loongson_laptop_backlight_update,
3826246ed09SJianmin Lv 	.get_brightness = loongson_laptop_get_brightness,
3836246ed09SJianmin Lv };
3846246ed09SJianmin Lv 
laptop_backlight_register(void)3856246ed09SJianmin Lv static int laptop_backlight_register(void)
3866246ed09SJianmin Lv {
3876246ed09SJianmin Lv 	int status = 0;
3886246ed09SJianmin Lv 	struct backlight_properties props;
3896246ed09SJianmin Lv 
3906246ed09SJianmin Lv 	memset(&props, 0, sizeof(props));
3916246ed09SJianmin Lv 
3926246ed09SJianmin Lv 	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
3936246ed09SJianmin Lv 		return -EIO;
3946246ed09SJianmin Lv 
3956246ed09SJianmin Lv 	props.brightness = 1;
3966246ed09SJianmin Lv 	props.max_brightness = status;
3976246ed09SJianmin Lv 	props.type = BACKLIGHT_PLATFORM;
3986246ed09SJianmin Lv 
3996246ed09SJianmin Lv 	backlight_device_register("loongson_laptop",
4006246ed09SJianmin Lv 				NULL, NULL, &backlight_laptop_ops, &props);
4016246ed09SJianmin Lv 
4026246ed09SJianmin Lv 	return 0;
4036246ed09SJianmin Lv }
4046246ed09SJianmin Lv 
loongson_laptop_turn_on_backlight(void)4056246ed09SJianmin Lv int loongson_laptop_turn_on_backlight(void)
4066246ed09SJianmin Lv {
4076246ed09SJianmin Lv 	int status;
4086246ed09SJianmin Lv 	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
4096246ed09SJianmin Lv 	struct acpi_object_list args = { 1, &arg0 };
4106246ed09SJianmin Lv 
4116246ed09SJianmin Lv 	arg0.integer.value = 1;
4126246ed09SJianmin Lv 	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
4136246ed09SJianmin Lv 	if (ACPI_FAILURE(status)) {
4146246ed09SJianmin Lv 		pr_info("Loongson lvds error: 0x%x\n", status);
4156246ed09SJianmin Lv 		return -ENODEV;
4166246ed09SJianmin Lv 	}
4176246ed09SJianmin Lv 
4186246ed09SJianmin Lv 	return 0;
4196246ed09SJianmin Lv }
4206246ed09SJianmin Lv 
loongson_laptop_turn_off_backlight(void)4216246ed09SJianmin Lv int loongson_laptop_turn_off_backlight(void)
4226246ed09SJianmin Lv {
4236246ed09SJianmin Lv 	int status;
4246246ed09SJianmin Lv 	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
4256246ed09SJianmin Lv 	struct acpi_object_list args = { 1, &arg0 };
4266246ed09SJianmin Lv 
4276246ed09SJianmin Lv 	arg0.integer.value = 0;
4286246ed09SJianmin Lv 	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
4296246ed09SJianmin Lv 	if (ACPI_FAILURE(status)) {
4306246ed09SJianmin Lv 		pr_info("Loongson lvds error: 0x%x\n", status);
4316246ed09SJianmin Lv 		return -ENODEV;
4326246ed09SJianmin Lv 	}
4336246ed09SJianmin Lv 
4346246ed09SJianmin Lv 	return 0;
4356246ed09SJianmin Lv }
4366246ed09SJianmin Lv 
event_init(struct generic_sub_driver * sub_driver)4376246ed09SJianmin Lv static int __init event_init(struct generic_sub_driver *sub_driver)
4386246ed09SJianmin Lv {
4396246ed09SJianmin Lv 	int ret;
4406246ed09SJianmin Lv 
4416246ed09SJianmin Lv 	ret = hotkey_map();
4426246ed09SJianmin Lv 	if (ret < 0) {
4436246ed09SJianmin Lv 		pr_err("Failed to parse keymap from DSDT\n");
4446246ed09SJianmin Lv 		return ret;
4456246ed09SJianmin Lv 	}
4466246ed09SJianmin Lv 
4476246ed09SJianmin Lv 	ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
4486246ed09SJianmin Lv 	if (ret < 0) {
4496246ed09SJianmin Lv 		pr_err("Failed to setup input device keymap\n");
4506246ed09SJianmin Lv 		input_free_device(generic_inputdev);
451*d8191691SYang Yingliang 		generic_inputdev = NULL;
4526246ed09SJianmin Lv 
4536246ed09SJianmin Lv 		return ret;
4546246ed09SJianmin Lv 	}
4556246ed09SJianmin Lv 
4566246ed09SJianmin Lv 	/*
4576246ed09SJianmin Lv 	 * This hotkey driver handle backlight event when
4586246ed09SJianmin Lv 	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
4596246ed09SJianmin Lv 	 */
4606246ed09SJianmin Lv 	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
4616246ed09SJianmin Lv 		hotkey_backlight_set(true);
4626246ed09SJianmin Lv 	else
4636246ed09SJianmin Lv 		hotkey_backlight_set(false);
4646246ed09SJianmin Lv 
4656246ed09SJianmin Lv 	pr_info("ACPI: enabling firmware HKEY event interface...\n");
4666246ed09SJianmin Lv 
4676246ed09SJianmin Lv 	return ret;
4686246ed09SJianmin Lv }
4696246ed09SJianmin Lv 
event_notify(struct generic_sub_driver * sub_driver,u32 event)4706246ed09SJianmin Lv static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
4716246ed09SJianmin Lv {
4726246ed09SJianmin Lv 	int type, scan_code;
4736246ed09SJianmin Lv 	struct key_entry *ke = NULL;
4746246ed09SJianmin Lv 
4756246ed09SJianmin Lv 	scan_code = event & GENERIC_EVENT_CODE_MASK;
4766246ed09SJianmin Lv 	type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
4776246ed09SJianmin Lv 	ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
4786246ed09SJianmin Lv 	if (ke) {
4796246ed09SJianmin Lv 		if (type == KE_SW) {
4806246ed09SJianmin Lv 			int status = 0;
4816246ed09SJianmin Lv 
4826246ed09SJianmin Lv 			if (hotkey_status_get(&status) < 0)
4836246ed09SJianmin Lv 				return;
4846246ed09SJianmin Lv 
4856246ed09SJianmin Lv 			ke->sw.value = !!(status & (1 << ke->sw.code));
4866246ed09SJianmin Lv 		}
4876246ed09SJianmin Lv 		sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
4886246ed09SJianmin Lv 	}
4896246ed09SJianmin Lv }
4906246ed09SJianmin Lv 
4916246ed09SJianmin Lv /* 3. Infrastructure */
4926246ed09SJianmin Lv 
4936246ed09SJianmin Lv static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
4946246ed09SJianmin Lv 
generic_subdriver_init(struct generic_sub_driver * sub_driver)4956246ed09SJianmin Lv static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
4966246ed09SJianmin Lv {
4976246ed09SJianmin Lv 	int ret;
4986246ed09SJianmin Lv 
4996246ed09SJianmin Lv 	if (!sub_driver || !sub_driver->driver)
5006246ed09SJianmin Lv 		return -EINVAL;
5016246ed09SJianmin Lv 
5026246ed09SJianmin Lv 	ret = platform_driver_register(sub_driver->driver);
5036246ed09SJianmin Lv 	if (ret)
5046246ed09SJianmin Lv 		return -EINVAL;
5056246ed09SJianmin Lv 
506*d8191691SYang Yingliang 	if (sub_driver->init) {
507*d8191691SYang Yingliang 		ret = sub_driver->init(sub_driver);
508*d8191691SYang Yingliang 		if (ret)
509*d8191691SYang Yingliang 			goto err_out;
510*d8191691SYang Yingliang 	}
5116246ed09SJianmin Lv 
5126246ed09SJianmin Lv 	if (sub_driver->notify) {
5136246ed09SJianmin Lv 		ret = setup_acpi_notify(sub_driver);
5146246ed09SJianmin Lv 		if (ret == -ENODEV) {
5156246ed09SJianmin Lv 			ret = 0;
5166246ed09SJianmin Lv 			goto err_out;
5176246ed09SJianmin Lv 		}
5186246ed09SJianmin Lv 		if (ret < 0)
5196246ed09SJianmin Lv 			goto err_out;
5206246ed09SJianmin Lv 	}
5216246ed09SJianmin Lv 
5226246ed09SJianmin Lv 	return 0;
5236246ed09SJianmin Lv 
5246246ed09SJianmin Lv err_out:
5256246ed09SJianmin Lv 	generic_subdriver_exit(sub_driver);
526*d8191691SYang Yingliang 	return ret;
5276246ed09SJianmin Lv }
5286246ed09SJianmin Lv 
generic_subdriver_exit(struct generic_sub_driver * sub_driver)5296246ed09SJianmin Lv static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
5306246ed09SJianmin Lv {
5316246ed09SJianmin Lv 
5326246ed09SJianmin Lv 	if (sub_driver->acpi_notify_installed) {
5336246ed09SJianmin Lv 		acpi_remove_notify_handler(*sub_driver->handle,
5346246ed09SJianmin Lv 					   sub_driver->type, dispatch_acpi_notify);
5356246ed09SJianmin Lv 		sub_driver->acpi_notify_installed = 0;
5366246ed09SJianmin Lv 	}
5376246ed09SJianmin Lv 	platform_driver_unregister(sub_driver->driver);
5386246ed09SJianmin Lv }
5396246ed09SJianmin Lv 
5406246ed09SJianmin Lv static struct generic_sub_driver generic_sub_drivers[] __refdata = {
5416246ed09SJianmin Lv 	{
5426246ed09SJianmin Lv 		.name = "hotkey",
5436246ed09SJianmin Lv 		.init = event_init,
5446246ed09SJianmin Lv 		.notify = event_notify,
5456246ed09SJianmin Lv 		.handle = &hotkey_handle,
5466246ed09SJianmin Lv 		.type = ACPI_DEVICE_NOTIFY,
5476246ed09SJianmin Lv 		.driver = &loongson_hotkey_driver,
5486246ed09SJianmin Lv 	},
5496246ed09SJianmin Lv };
5506246ed09SJianmin Lv 
generic_acpi_laptop_init(void)5516246ed09SJianmin Lv static int __init generic_acpi_laptop_init(void)
5526246ed09SJianmin Lv {
5536246ed09SJianmin Lv 	bool ec_found;
5546246ed09SJianmin Lv 	int i, ret, status;
5556246ed09SJianmin Lv 
5566246ed09SJianmin Lv 	if (acpi_disabled)
5576246ed09SJianmin Lv 		return -ENODEV;
5586246ed09SJianmin Lv 
5596246ed09SJianmin Lv 	/* The EC device is required */
5606246ed09SJianmin Lv 	ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
5616246ed09SJianmin Lv 	if (!ec_found)
5626246ed09SJianmin Lv 		return -ENODEV;
5636246ed09SJianmin Lv 
5646246ed09SJianmin Lv 	/* Enable SCI for EC */
5656246ed09SJianmin Lv 	acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
5666246ed09SJianmin Lv 
5676246ed09SJianmin Lv 	generic_inputdev = input_allocate_device();
5686246ed09SJianmin Lv 	if (!generic_inputdev) {
5696246ed09SJianmin Lv 		pr_err("Unable to allocate input device\n");
5706246ed09SJianmin Lv 		return -ENOMEM;
5716246ed09SJianmin Lv 	}
5726246ed09SJianmin Lv 
5736246ed09SJianmin Lv 	/* Prepare input device, but don't register */
5746246ed09SJianmin Lv 	generic_inputdev->name =
5756246ed09SJianmin Lv 		"Loongson Generic Laptop/All-in-One Extra Buttons";
5766246ed09SJianmin Lv 	generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
5776246ed09SJianmin Lv 	generic_inputdev->id.bustype = BUS_HOST;
5786246ed09SJianmin Lv 	generic_inputdev->dev.parent = NULL;
5796246ed09SJianmin Lv 
5806246ed09SJianmin Lv 	/* Init subdrivers */
5816246ed09SJianmin Lv 	for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
5826246ed09SJianmin Lv 		ret = generic_subdriver_init(&generic_sub_drivers[i]);
5836246ed09SJianmin Lv 		if (ret < 0) {
5846246ed09SJianmin Lv 			input_free_device(generic_inputdev);
5856246ed09SJianmin Lv 			while (--i >= 0)
5866246ed09SJianmin Lv 				generic_subdriver_exit(&generic_sub_drivers[i]);
5876246ed09SJianmin Lv 			return ret;
5886246ed09SJianmin Lv 		}
5896246ed09SJianmin Lv 	}
5906246ed09SJianmin Lv 
5916246ed09SJianmin Lv 	ret = input_register_device(generic_inputdev);
5926246ed09SJianmin Lv 	if (ret < 0) {
5936246ed09SJianmin Lv 		input_free_device(generic_inputdev);
5946246ed09SJianmin Lv 		while (--i >= 0)
5956246ed09SJianmin Lv 			generic_subdriver_exit(&generic_sub_drivers[i]);
5966246ed09SJianmin Lv 		pr_err("Unable to register input device\n");
5976246ed09SJianmin Lv 		return ret;
5986246ed09SJianmin Lv 	}
5996246ed09SJianmin Lv 
6006246ed09SJianmin Lv 	input_device_registered = 1;
6016246ed09SJianmin Lv 
6026246ed09SJianmin Lv 	if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
6036246ed09SJianmin Lv 		pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
6046246ed09SJianmin Lv 		ret = laptop_backlight_register();
6056246ed09SJianmin Lv 		if (ret < 0)
6066246ed09SJianmin Lv 			pr_err("Loongson Laptop: laptop-backlight device register failed\n");
6076246ed09SJianmin Lv 	}
6086246ed09SJianmin Lv 
6096246ed09SJianmin Lv 	return 0;
6106246ed09SJianmin Lv }
6116246ed09SJianmin Lv 
generic_acpi_laptop_exit(void)6126246ed09SJianmin Lv static void __exit generic_acpi_laptop_exit(void)
6136246ed09SJianmin Lv {
6146246ed09SJianmin Lv 	if (generic_inputdev) {
6156246ed09SJianmin Lv 		if (input_device_registered)
6166246ed09SJianmin Lv 			input_unregister_device(generic_inputdev);
6176246ed09SJianmin Lv 		else
6186246ed09SJianmin Lv 			input_free_device(generic_inputdev);
6196246ed09SJianmin Lv 	}
6206246ed09SJianmin Lv }
6216246ed09SJianmin Lv 
6226246ed09SJianmin Lv module_init(generic_acpi_laptop_init);
6236246ed09SJianmin Lv module_exit(generic_acpi_laptop_exit);
6246246ed09SJianmin Lv 
6256246ed09SJianmin Lv MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
6266246ed09SJianmin Lv MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
6276246ed09SJianmin Lv MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
6286246ed09SJianmin Lv MODULE_LICENSE("GPL");
629