brintos

brintos / linux-shallow public Read only

0
0
Text · 14.4 KiB · 9920358 Raw
629 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver4 *5 *  Jianmin Lv <lvjianmin@loongson.cn>6 *  Huacai Chen <chenhuacai@loongson.cn>7 *8 * Copyright (C) 2022 Loongson Technology Corporation Limited9 */10 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt12 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			336#define GENERIC_HOTKEY_MAP_MAX		6437 38#define GENERIC_EVENT_TYPE_OFF		1239#define GENERIC_EVENT_TYPE_MASK		0xF00040#define GENERIC_EVENT_CODE_MASK		0x0FFF41 42struct 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 53static u32 input_device_registered;54static struct input_dev *generic_inputdev;55 56static acpi_handle hotkey_handle;57static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];58 59int loongson_laptop_turn_on_backlight(void);60int loongson_laptop_turn_off_backlight(void);61static int loongson_laptop_backlight_update(struct backlight_device *bd);62 63/* 2. ACPI Helpers and device model */64 65static 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	} else85		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	} else114		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 140static 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 148static 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 157static 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 191static int loongson_hotkey_suspend(struct device *dev)192{193	return 0;194}195 196static 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 the211	 * 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 of220		 * adding SW_LID bit which in sw element can avoid this221		 * case.222		 *223		 * Input system will drop lid event when current lid event224		 * value and last lid status in the same. So laptop driver225		 * doesn't report repeated events.226		 *227		 * Lid status is generally 0, but hardware exception is228		 * 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 241static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,242		loongson_hotkey_suspend, loongson_hotkey_resume);243 244static 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 254static const struct acpi_device_id loongson_device_ids[] = {255	{LOONGSON_ACPI_HKEY_HID, 0},256	{"", 0},257};258MODULE_DEVICE_TABLE(acpi, loongson_device_ids);259 260static 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 270static 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 300static 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 308static 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 321static 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 335static 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 357static 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 369static 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 380static const struct backlight_ops backlight_laptop_ops = {381	.update_status = loongson_laptop_backlight_update,382	.get_brightness = loongson_laptop_get_brightness,383};384 385static 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 405int 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 421int 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 437static 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 when458	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor459	 */460	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)461		hotkey_backlight_set(true);462	else463		hotkey_backlight_set(false);464 465	pr_info("ACPI: enabling firmware HKEY event interface...\n");466 467	return ret;468}469 470static 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 493static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);494 495static 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 524err_out:525	generic_subdriver_exit(sub_driver);526	return ret;527}528 529static 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 540static 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 551static 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 612static void __exit generic_acpi_laptop_exit(void)613{614	if (generic_inputdev) {615		if (input_device_registered)616			input_unregister_device(generic_inputdev);617		else618			input_free_device(generic_inputdev);619	}620}621 622module_init(generic_acpi_laptop_init);623module_exit(generic_acpi_laptop_exit);624 625MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");626MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");627MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");628MODULE_LICENSE("GPL");629