18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Jianmin Lv <lvjianmin@loongson.cn>
68c2ecf20Sopenharmony_ci *  Huacai Chen <chenhuacai@loongson.cn>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci *  This program is free software; you can redistribute it and/or modify
98c2ecf20Sopenharmony_ci *  it under the terms of the GNU General Public License as published by
108c2ecf20Sopenharmony_ci *  the Free Software Foundation; either version 2 of the License, or
118c2ecf20Sopenharmony_ci *  (at your option) any later version.
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci *  This program is distributed in the hope that it will be useful,
148c2ecf20Sopenharmony_ci *  but WITHOUT ANY WARRANTY; without even the implied warranty of
158c2ecf20Sopenharmony_ci *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
168c2ecf20Sopenharmony_ci *  GNU General Public License for more details.
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <linux/init.h>
228c2ecf20Sopenharmony_ci#include <linux/kernel.h>
238c2ecf20Sopenharmony_ci#include <linux/module.h>
248c2ecf20Sopenharmony_ci#include <linux/acpi.h>
258c2ecf20Sopenharmony_ci#include <linux/backlight.h>
268c2ecf20Sopenharmony_ci#include <linux/device.h>
278c2ecf20Sopenharmony_ci#include <linux/input.h>
288c2ecf20Sopenharmony_ci#include <linux/input/sparse-keymap.h>
298c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
308c2ecf20Sopenharmony_ci#include <linux/string.h>
318c2ecf20Sopenharmony_ci#include <linux/types.h>
328c2ecf20Sopenharmony_ci#include <acpi/video.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/* 1. Driver-wide structs and misc. variables */
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci/* ACPI HIDs */
378c2ecf20Sopenharmony_ci#define LOONGSON_ACPI_EC_HID	"PNP0C09"
388c2ecf20Sopenharmony_ci#define LOONGSON_ACPI_HKEY_HID	"LOON0000"
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define ACPI_LAPTOP_NAME "loongson-laptop"
418c2ecf20Sopenharmony_ci#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define MAX_ACPI_ARGS			3
448c2ecf20Sopenharmony_ci#define GENERIC_HOTKEY_MAP_MAX		64
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define GENERIC_EVENT_TYPE_OFF		12
478c2ecf20Sopenharmony_ci#define GENERIC_EVENT_TYPE_MASK		0xF000
488c2ecf20Sopenharmony_ci#define GENERIC_EVENT_CODE_MASK		0x0FFF
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistruct generic_sub_driver {
518c2ecf20Sopenharmony_ci	u32 type;
528c2ecf20Sopenharmony_ci	char *name;
538c2ecf20Sopenharmony_ci	acpi_handle *handle;
548c2ecf20Sopenharmony_ci	struct acpi_device *device;
558c2ecf20Sopenharmony_ci	struct platform_driver *driver;
568c2ecf20Sopenharmony_ci	int (*init)(struct generic_sub_driver *sub_driver);
578c2ecf20Sopenharmony_ci	void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
588c2ecf20Sopenharmony_ci	u8 acpi_notify_installed;
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic u32 input_device_registered;
628c2ecf20Sopenharmony_cistatic struct input_dev *generic_inputdev;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic acpi_handle hotkey_handle;
658c2ecf20Sopenharmony_cistatic struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ciint loongson_laptop_turn_on_backlight(void);
688c2ecf20Sopenharmony_ciint loongson_laptop_turn_off_backlight(void);
698c2ecf20Sopenharmony_cistatic int loongson_laptop_backlight_update(struct backlight_device *bd);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/* 2. ACPI Helpers and device model */
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	char res_type;
768c2ecf20Sopenharmony_ci	char *fmt0 = fmt;
778c2ecf20Sopenharmony_ci	va_list ap;
788c2ecf20Sopenharmony_ci	int success, quiet;
798c2ecf20Sopenharmony_ci	acpi_status status;
808c2ecf20Sopenharmony_ci	struct acpi_object_list params;
818c2ecf20Sopenharmony_ci	struct acpi_buffer result, *resultp;
828c2ecf20Sopenharmony_ci	union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (!*fmt) {
858c2ecf20Sopenharmony_ci		pr_err("acpi_evalf() called with empty format\n");
868c2ecf20Sopenharmony_ci		return 0;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (*fmt == 'q') {
908c2ecf20Sopenharmony_ci		quiet = 1;
918c2ecf20Sopenharmony_ci		fmt++;
928c2ecf20Sopenharmony_ci	} else
938c2ecf20Sopenharmony_ci		quiet = 0;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	res_type = *(fmt++);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	params.count = 0;
988c2ecf20Sopenharmony_ci	params.pointer = &in_objs[0];
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	va_start(ap, fmt);
1018c2ecf20Sopenharmony_ci	while (*fmt) {
1028c2ecf20Sopenharmony_ci		char c = *(fmt++);
1038c2ecf20Sopenharmony_ci		switch (c) {
1048c2ecf20Sopenharmony_ci		case 'd':	/* int */
1058c2ecf20Sopenharmony_ci			in_objs[params.count].integer.value = va_arg(ap, int);
1068c2ecf20Sopenharmony_ci			in_objs[params.count++].type = ACPI_TYPE_INTEGER;
1078c2ecf20Sopenharmony_ci			break;
1088c2ecf20Sopenharmony_ci			/* add more types as needed */
1098c2ecf20Sopenharmony_ci		default:
1108c2ecf20Sopenharmony_ci			pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
1118c2ecf20Sopenharmony_ci			va_end(ap);
1128c2ecf20Sopenharmony_ci			return 0;
1138c2ecf20Sopenharmony_ci		}
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci	va_end(ap);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (res_type != 'v') {
1188c2ecf20Sopenharmony_ci		result.length = sizeof(out_obj);
1198c2ecf20Sopenharmony_ci		result.pointer = &out_obj;
1208c2ecf20Sopenharmony_ci		resultp = &result;
1218c2ecf20Sopenharmony_ci	} else
1228c2ecf20Sopenharmony_ci		resultp = NULL;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	status = acpi_evaluate_object(handle, method, &params, resultp);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	switch (res_type) {
1278c2ecf20Sopenharmony_ci	case 'd':		/* int */
1288c2ecf20Sopenharmony_ci		success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
1298c2ecf20Sopenharmony_ci		if (success && res)
1308c2ecf20Sopenharmony_ci			*res = out_obj.integer.value;
1318c2ecf20Sopenharmony_ci		break;
1328c2ecf20Sopenharmony_ci	case 'v':		/* void */
1338c2ecf20Sopenharmony_ci		success = status == AE_OK;
1348c2ecf20Sopenharmony_ci		break;
1358c2ecf20Sopenharmony_ci		/* add more types as needed */
1368c2ecf20Sopenharmony_ci	default:
1378c2ecf20Sopenharmony_ci		pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
1388c2ecf20Sopenharmony_ci		return 0;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	if (!success && !quiet)
1428c2ecf20Sopenharmony_ci		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
1438c2ecf20Sopenharmony_ci		       method, fmt0, acpi_format_exception(status));
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	return success;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic int hotkey_status_get(int *status)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
1518c2ecf20Sopenharmony_ci		return -EIO;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	return 0;
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct generic_sub_driver *sub_driver = data;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	if (!sub_driver || !sub_driver->notify)
1618c2ecf20Sopenharmony_ci		return;
1628c2ecf20Sopenharmony_ci	sub_driver->notify(sub_driver, event);
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	int rc;
1688c2ecf20Sopenharmony_ci	acpi_status status;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (!*sub_driver->handle)
1718c2ecf20Sopenharmony_ci		return 0;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	rc = acpi_bus_get_device(*sub_driver->handle, &sub_driver->device);
1748c2ecf20Sopenharmony_ci	if (rc < 0) {
1758c2ecf20Sopenharmony_ci		pr_err("acpi_bus_get_device(%s) failed: %d\n", sub_driver->name, rc);
1768c2ecf20Sopenharmony_ci		return -ENODEV;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	sub_driver->device->driver_data = sub_driver;
1808c2ecf20Sopenharmony_ci	sprintf(acpi_device_class(sub_driver->device), "%s/%s",
1818c2ecf20Sopenharmony_ci		ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	status = acpi_install_notify_handler(*sub_driver->handle,
1848c2ecf20Sopenharmony_ci			sub_driver->type, dispatch_acpi_notify, sub_driver);
1858c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
1868c2ecf20Sopenharmony_ci		if (status == AE_ALREADY_EXISTS) {
1878c2ecf20Sopenharmony_ci			pr_notice("Another device driver is already "
1888c2ecf20Sopenharmony_ci				  "handling %s events\n", sub_driver->name);
1898c2ecf20Sopenharmony_ci		} else {
1908c2ecf20Sopenharmony_ci			pr_err("acpi_install_notify_handler(%s) failed: %s\n",
1918c2ecf20Sopenharmony_ci			       sub_driver->name, acpi_format_exception(status));
1928c2ecf20Sopenharmony_ci		}
1938c2ecf20Sopenharmony_ci		return -ENODEV;
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci	sub_driver->acpi_notify_installed = 1;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	return 0;
1988c2ecf20Sopenharmony_ci}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
2018c2ecf20Sopenharmony_cistatic int loongson_hotkey_suspend(struct device *dev)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	return 0;
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic int loongson_hotkey_resume(struct device *dev)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	int status = 0;
2098c2ecf20Sopenharmony_ci	struct key_entry ke;
2108c2ecf20Sopenharmony_ci	struct backlight_device *bd;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
2138c2ecf20Sopenharmony_ci	if (bd) {
2148c2ecf20Sopenharmony_ci		loongson_laptop_backlight_update(bd) ?
2158c2ecf20Sopenharmony_ci		pr_warn("Loongson_backlight: resume brightness failed") :
2168c2ecf20Sopenharmony_ci		pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
2178c2ecf20Sopenharmony_ci	}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	/*
2208c2ecf20Sopenharmony_ci	 * Only if the firmware supports SW_LID event model, we can handle the
2218c2ecf20Sopenharmony_ci	 * event. This is for the consideration of development board without EC.
2228c2ecf20Sopenharmony_ci	 */
2238c2ecf20Sopenharmony_ci	if (test_bit(SW_LID, generic_inputdev->swbit)) {
2248c2ecf20Sopenharmony_ci		if (hotkey_status_get(&status) < 0)
2258c2ecf20Sopenharmony_ci			return -EIO;
2268c2ecf20Sopenharmony_ci		/*
2278c2ecf20Sopenharmony_ci		 * The input device sw element records the last lid status.
2288c2ecf20Sopenharmony_ci		 * When the system is awakened by other wake-up sources,
2298c2ecf20Sopenharmony_ci		 * the lid event will also be reported. The judgment of
2308c2ecf20Sopenharmony_ci		 * adding SW_LID bit which in sw element can avoid this
2318c2ecf20Sopenharmony_ci		 * case.
2328c2ecf20Sopenharmony_ci		 *
2338c2ecf20Sopenharmony_ci		 * Input system will drop lid event when current lid event
2348c2ecf20Sopenharmony_ci		 * value and last lid status in the same. So laptop driver
2358c2ecf20Sopenharmony_ci		 * doesn't report repeated events.
2368c2ecf20Sopenharmony_ci		 *
2378c2ecf20Sopenharmony_ci		 * Lid status is generally 0, but hardware exception is
2388c2ecf20Sopenharmony_ci		 * considered. So add lid status confirmation.
2398c2ecf20Sopenharmony_ci		 */
2408c2ecf20Sopenharmony_ci		if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
2418c2ecf20Sopenharmony_ci			ke.type = KE_SW;
2428c2ecf20Sopenharmony_ci			ke.sw.value = (u8)status;
2438c2ecf20Sopenharmony_ci			ke.sw.code = SW_LID;
2448c2ecf20Sopenharmony_ci			sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
2458c2ecf20Sopenharmony_ci		}
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return 0;
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
2528c2ecf20Sopenharmony_ci		loongson_hotkey_suspend, loongson_hotkey_resume);
2538c2ecf20Sopenharmony_ci#endif
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic int loongson_hotkey_probe(struct platform_device *pdev)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	hotkey_handle = ACPI_HANDLE(&pdev->dev);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (!hotkey_handle)
2608c2ecf20Sopenharmony_ci		return -ENODEV;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return 0;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic const struct acpi_device_id loongson_device_ids[] = {
2668c2ecf20Sopenharmony_ci	{LOONGSON_ACPI_HKEY_HID, 0},
2678c2ecf20Sopenharmony_ci	{"", 0},
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, loongson_device_ids);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_cistatic struct platform_driver loongson_hotkey_driver = {
2728c2ecf20Sopenharmony_ci	.probe		= loongson_hotkey_probe,
2738c2ecf20Sopenharmony_ci	.driver		= {
2748c2ecf20Sopenharmony_ci		.name	= "loongson-hotkey",
2758c2ecf20Sopenharmony_ci		.owner	= THIS_MODULE,
2768c2ecf20Sopenharmony_ci		.pm	= pm_ptr(&loongson_hotkey_pm),
2778c2ecf20Sopenharmony_ci		.acpi_match_table = loongson_device_ids,
2788c2ecf20Sopenharmony_ci	},
2798c2ecf20Sopenharmony_ci};
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_cistatic int hotkey_map(void)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	u32 index;
2848c2ecf20Sopenharmony_ci	acpi_status status;
2858c2ecf20Sopenharmony_ci	struct acpi_buffer buf;
2868c2ecf20Sopenharmony_ci	union acpi_object *pack;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	buf.length = ACPI_ALLOCATE_BUFFER;
2898c2ecf20Sopenharmony_ci	status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
2908c2ecf20Sopenharmony_ci	if (status != AE_OK) {
2918c2ecf20Sopenharmony_ci		pr_err("ACPI exception: %s\n", acpi_format_exception(status));
2928c2ecf20Sopenharmony_ci		return -1;
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci	pack = buf.pointer;
2958c2ecf20Sopenharmony_ci	for (index = 0; index < pack->package.count; index++) {
2968c2ecf20Sopenharmony_ci		union acpi_object *element, *sub_pack;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci		sub_pack = &pack->package.elements[index];
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci		element = &sub_pack->package.elements[0];
3018c2ecf20Sopenharmony_ci		hotkey_keycode_map[index].type = element->integer.value;
3028c2ecf20Sopenharmony_ci		element = &sub_pack->package.elements[1];
3038c2ecf20Sopenharmony_ci		hotkey_keycode_map[index].code = element->integer.value;
3048c2ecf20Sopenharmony_ci		element = &sub_pack->package.elements[2];
3058c2ecf20Sopenharmony_ci		hotkey_keycode_map[index].keycode = element->integer.value;
3068c2ecf20Sopenharmony_ci	}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	return 0;
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_cistatic int hotkey_backlight_set(bool enable)
3128c2ecf20Sopenharmony_ci{
3138c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
3148c2ecf20Sopenharmony_ci		return -EIO;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	return 0;
3178c2ecf20Sopenharmony_ci}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_cistatic int ec_get_brightness(void)
3208c2ecf20Sopenharmony_ci{
3218c2ecf20Sopenharmony_ci	int status = 0;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	if (!hotkey_handle)
3248c2ecf20Sopenharmony_ci		return -ENXIO;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
3278c2ecf20Sopenharmony_ci		return -EIO;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	return status;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_cistatic int ec_set_brightness(int level)
3338c2ecf20Sopenharmony_ci{
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	int ret = 0;
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	if (!hotkey_handle)
3388c2ecf20Sopenharmony_ci		return -ENXIO;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
3418c2ecf20Sopenharmony_ci		ret = -EIO;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	return ret;
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_cistatic int ec_backlight_level(u8 level)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	int status = 0;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	if (!hotkey_handle)
3518c2ecf20Sopenharmony_ci		return -ENXIO;
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
3548c2ecf20Sopenharmony_ci		return -EIO;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	if ((status < 0) || (level > status))
3578c2ecf20Sopenharmony_ci		return status;
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
3608c2ecf20Sopenharmony_ci		return -EIO;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	if ((status < 0) || (level < status))
3638c2ecf20Sopenharmony_ci		return status;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	return level;
3668c2ecf20Sopenharmony_ci}
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_cistatic int loongson_laptop_backlight_update(struct backlight_device *bd)
3698c2ecf20Sopenharmony_ci{
3708c2ecf20Sopenharmony_ci	int lvl = ec_backlight_level(bd->props.brightness);
3718c2ecf20Sopenharmony_ci	if (lvl < 0)
3728c2ecf20Sopenharmony_ci		return -EIO;
3738c2ecf20Sopenharmony_ci	if (ec_set_brightness(lvl))
3748c2ecf20Sopenharmony_ci		return -EIO;
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	return 0;
3778c2ecf20Sopenharmony_ci}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_cistatic int loongson_laptop_get_brightness(struct backlight_device *bd)
3808c2ecf20Sopenharmony_ci{
3818c2ecf20Sopenharmony_ci	int level;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	level = ec_get_brightness();
3848c2ecf20Sopenharmony_ci	if (level < 0)
3858c2ecf20Sopenharmony_ci		return -EIO;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	return level;
3888c2ecf20Sopenharmony_ci}
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_cistatic const struct backlight_ops backlight_laptop_ops = {
3918c2ecf20Sopenharmony_ci	.update_status = loongson_laptop_backlight_update,
3928c2ecf20Sopenharmony_ci	.get_brightness = loongson_laptop_get_brightness,
3938c2ecf20Sopenharmony_ci};
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_cistatic int laptop_backlight_register(void)
3968c2ecf20Sopenharmony_ci{
3978c2ecf20Sopenharmony_ci	int status = 0;
3988c2ecf20Sopenharmony_ci	struct backlight_properties props;
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	memset(&props, 0, sizeof(props));
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
4038c2ecf20Sopenharmony_ci		return -EIO;
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	props.brightness = 1;
4068c2ecf20Sopenharmony_ci	props.max_brightness = status;
4078c2ecf20Sopenharmony_ci	props.type = BACKLIGHT_PLATFORM;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	backlight_device_register("loongson_laptop",
4108c2ecf20Sopenharmony_ci				NULL, NULL, &backlight_laptop_ops, &props);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	return 0;
4138c2ecf20Sopenharmony_ci}
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ciint loongson_laptop_turn_on_backlight(void)
4168c2ecf20Sopenharmony_ci{
4178c2ecf20Sopenharmony_ci	int status;
4188c2ecf20Sopenharmony_ci	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
4198c2ecf20Sopenharmony_ci	struct acpi_object_list args = { 1, &arg0 };
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	arg0.integer.value = 1;
4228c2ecf20Sopenharmony_ci	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
4238c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
4248c2ecf20Sopenharmony_ci		pr_info("Loongson lvds error:0x%x\n", status);
4258c2ecf20Sopenharmony_ci		return -ENODEV;
4268c2ecf20Sopenharmony_ci	}
4278c2ecf20Sopenharmony_ci	return 0;
4288c2ecf20Sopenharmony_ci}
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ciint loongson_laptop_turn_off_backlight(void)
4318c2ecf20Sopenharmony_ci{
4328c2ecf20Sopenharmony_ci	int status;
4338c2ecf20Sopenharmony_ci	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
4348c2ecf20Sopenharmony_ci	struct acpi_object_list args = { 1, &arg0 };
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	arg0.integer.value = 0;
4378c2ecf20Sopenharmony_ci	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
4388c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
4398c2ecf20Sopenharmony_ci		pr_info("Loongson lvds error:0x%x\n", status);
4408c2ecf20Sopenharmony_ci		return -ENODEV;
4418c2ecf20Sopenharmony_ci	}
4428c2ecf20Sopenharmony_ci	return 0;
4438c2ecf20Sopenharmony_ci}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_cistatic int __init event_init(struct generic_sub_driver *sub_driver)
4468c2ecf20Sopenharmony_ci{
4478c2ecf20Sopenharmony_ci	int ret;
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	ret = hotkey_map();
4508c2ecf20Sopenharmony_ci	if (ret < 0) {
4518c2ecf20Sopenharmony_ci		pr_err("Failed to parse keymap from DSDT\n");
4528c2ecf20Sopenharmony_ci		return ret;
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
4568c2ecf20Sopenharmony_ci	if (ret < 0) {
4578c2ecf20Sopenharmony_ci		pr_err("Failed to setup input device keymap\n");
4588c2ecf20Sopenharmony_ci		input_free_device(generic_inputdev);
4598c2ecf20Sopenharmony_ci		generic_inputdev = NULL;
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci		return ret;
4628c2ecf20Sopenharmony_ci	}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	/*
4658c2ecf20Sopenharmony_ci	 * This hotkey driver handle backlight event when
4668c2ecf20Sopenharmony_ci	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
4678c2ecf20Sopenharmony_ci	 */
4688c2ecf20Sopenharmony_ci	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
4698c2ecf20Sopenharmony_ci		hotkey_backlight_set(true);
4708c2ecf20Sopenharmony_ci	else
4718c2ecf20Sopenharmony_ci		hotkey_backlight_set(false);
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	pr_info("ACPI: enabling firmware HKEY event interface...\n");
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	return ret;
4768c2ecf20Sopenharmony_ci}
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_cistatic void event_notify(struct generic_sub_driver *sub_driver, u32 event)
4798c2ecf20Sopenharmony_ci{
4808c2ecf20Sopenharmony_ci	int type, scan_code;
4818c2ecf20Sopenharmony_ci	struct key_entry *ke = NULL;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	scan_code = event & GENERIC_EVENT_CODE_MASK;
4848c2ecf20Sopenharmony_ci	type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
4858c2ecf20Sopenharmony_ci	ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
4868c2ecf20Sopenharmony_ci	if (ke) {
4878c2ecf20Sopenharmony_ci		if (type == KE_SW) {
4888c2ecf20Sopenharmony_ci			int status = 0;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci			if (hotkey_status_get(&status) < 0)
4918c2ecf20Sopenharmony_ci				return;
4928c2ecf20Sopenharmony_ci			ke->sw.value = !!(status & (1 << ke->sw.code));
4938c2ecf20Sopenharmony_ci		}
4948c2ecf20Sopenharmony_ci		sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
4958c2ecf20Sopenharmony_ci	}
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci/* 3. Infrastructure */
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_cistatic int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
5038c2ecf20Sopenharmony_ci{
5048c2ecf20Sopenharmony_ci	int ret;
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	if (!sub_driver || !sub_driver->driver)
5078c2ecf20Sopenharmony_ci		return -EINVAL;
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci	ret = platform_driver_register(sub_driver->driver);
5108c2ecf20Sopenharmony_ci	if (ret)
5118c2ecf20Sopenharmony_ci		return -EINVAL;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	if (sub_driver->init) {
5148c2ecf20Sopenharmony_ci		ret = sub_driver->init(sub_driver);
5158c2ecf20Sopenharmony_ci		if (ret)
5168c2ecf20Sopenharmony_ci			goto err_out;
5178c2ecf20Sopenharmony_ci	}
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	if (sub_driver->notify) {
5208c2ecf20Sopenharmony_ci		ret = setup_acpi_notify(sub_driver);
5218c2ecf20Sopenharmony_ci		if (ret == -ENODEV) {
5228c2ecf20Sopenharmony_ci			ret = 0;
5238c2ecf20Sopenharmony_ci			goto err_out;
5248c2ecf20Sopenharmony_ci		}
5258c2ecf20Sopenharmony_ci		if (ret < 0)
5268c2ecf20Sopenharmony_ci			goto err_out;
5278c2ecf20Sopenharmony_ci	}
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	return 0;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_cierr_out:
5328c2ecf20Sopenharmony_ci	generic_subdriver_exit(sub_driver);
5338c2ecf20Sopenharmony_ci	return ret;
5348c2ecf20Sopenharmony_ci}
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_cistatic void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
5378c2ecf20Sopenharmony_ci{
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	if (sub_driver->acpi_notify_installed) {
5408c2ecf20Sopenharmony_ci		acpi_remove_notify_handler(*sub_driver->handle,
5418c2ecf20Sopenharmony_ci					   sub_driver->type, dispatch_acpi_notify);
5428c2ecf20Sopenharmony_ci		sub_driver->acpi_notify_installed = 0;
5438c2ecf20Sopenharmony_ci	}
5448c2ecf20Sopenharmony_ci	platform_driver_unregister(sub_driver->driver);
5458c2ecf20Sopenharmony_ci}
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_cistatic struct generic_sub_driver generic_sub_drivers[] __refdata = {
5488c2ecf20Sopenharmony_ci	{
5498c2ecf20Sopenharmony_ci		.name = "hotkey",
5508c2ecf20Sopenharmony_ci		.init = event_init,
5518c2ecf20Sopenharmony_ci		.notify = event_notify,
5528c2ecf20Sopenharmony_ci		.handle = &hotkey_handle,
5538c2ecf20Sopenharmony_ci		.type = ACPI_DEVICE_NOTIFY,
5548c2ecf20Sopenharmony_ci		.driver = &loongson_hotkey_driver,
5558c2ecf20Sopenharmony_ci	},
5568c2ecf20Sopenharmony_ci};
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_cistatic int __init generic_acpi_laptop_init(void)
5598c2ecf20Sopenharmony_ci{
5608c2ecf20Sopenharmony_ci	bool ec_found;
5618c2ecf20Sopenharmony_ci	int i, ret, status;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	if (acpi_disabled)
5648c2ecf20Sopenharmony_ci		return -ENODEV;
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	/* The EC device is required */
5678c2ecf20Sopenharmony_ci	ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
5688c2ecf20Sopenharmony_ci	if (!ec_found)
5698c2ecf20Sopenharmony_ci		return -ENODEV;
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	/* Enable SCI for EC */
5728c2ecf20Sopenharmony_ci	acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci	generic_inputdev = input_allocate_device();
5758c2ecf20Sopenharmony_ci	if (!generic_inputdev) {
5768c2ecf20Sopenharmony_ci		pr_err("Unable to allocate input device\n");
5778c2ecf20Sopenharmony_ci		return -ENOMEM;
5788c2ecf20Sopenharmony_ci	}
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci	/* Prepare input device, but don't register */
5818c2ecf20Sopenharmony_ci	generic_inputdev->name =
5828c2ecf20Sopenharmony_ci		"Loongson Generic Laptop/All-in-One Extra Buttons";
5838c2ecf20Sopenharmony_ci	generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
5848c2ecf20Sopenharmony_ci	generic_inputdev->id.bustype = BUS_HOST;
5858c2ecf20Sopenharmony_ci	generic_inputdev->dev.parent = NULL;
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci	/* Init subdrivers */
5888c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
5898c2ecf20Sopenharmony_ci		ret = generic_subdriver_init(&generic_sub_drivers[i]);
5908c2ecf20Sopenharmony_ci		if (ret < 0) {
5918c2ecf20Sopenharmony_ci			input_free_device(generic_inputdev);
5928c2ecf20Sopenharmony_ci			while (--i >= 0)
5938c2ecf20Sopenharmony_ci				generic_subdriver_exit(&generic_sub_drivers[i]);
5948c2ecf20Sopenharmony_ci			return ret;
5958c2ecf20Sopenharmony_ci		}
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	ret = input_register_device(generic_inputdev);
5998c2ecf20Sopenharmony_ci	if (ret < 0) {
6008c2ecf20Sopenharmony_ci		input_free_device(generic_inputdev);
6018c2ecf20Sopenharmony_ci		while (--i >= 0)
6028c2ecf20Sopenharmony_ci			generic_subdriver_exit(&generic_sub_drivers[i]);
6038c2ecf20Sopenharmony_ci		pr_err("Unable to register input device\n");
6048c2ecf20Sopenharmony_ci		return ret;
6058c2ecf20Sopenharmony_ci	}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	input_device_registered = 1;
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
6108c2ecf20Sopenharmony_ci		pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
6118c2ecf20Sopenharmony_ci		ret = laptop_backlight_register();
6128c2ecf20Sopenharmony_ci		if (ret < 0)
6138c2ecf20Sopenharmony_ci			pr_err("Loongson Laptop: laptop-backlight device register failed\n");
6148c2ecf20Sopenharmony_ci	}
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_ci	return 0;
6178c2ecf20Sopenharmony_ci}
6188c2ecf20Sopenharmony_ci
6198c2ecf20Sopenharmony_cistatic void __exit generic_acpi_laptop_exit(void)
6208c2ecf20Sopenharmony_ci{
6218c2ecf20Sopenharmony_ci	if (generic_inputdev) {
6228c2ecf20Sopenharmony_ci		if (input_device_registered)
6238c2ecf20Sopenharmony_ci			input_unregister_device(generic_inputdev);
6248c2ecf20Sopenharmony_ci		else
6258c2ecf20Sopenharmony_ci			input_free_device(generic_inputdev);
6268c2ecf20Sopenharmony_ci	}
6278c2ecf20Sopenharmony_ci}
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_cimodule_init(generic_acpi_laptop_init);
6308c2ecf20Sopenharmony_cimodule_exit(generic_acpi_laptop_exit);
6318c2ecf20Sopenharmony_ci
6328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
6338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
6348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
6358c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
636