File Index
Files and directories
Sign in
FILE

loongson-laptop.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/loongarch/loongson-laptop.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
  4 *
  5 *  Jianmin Lv <lvjianmin@loongson.cn>
  6 *  Huacai Chen <chenhuacai@loongson.cn>
  7 *
  8 * Copyright (C) 2022 Loongson Technology Corporation Limited
  9 */
 10
 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 12
 13#include <linux/init.h>
 14#include <linux/kernel.h>
 15#include <linux/module.h>
 16#include <linux/acpi.h>
 17#include <linux/backlight.h>
 18#include <linux/device.h>
 19#include <linux/input.h>
 20#include <linux/input/sparse-keymap.h>
 21#include <linux/platform_device.h>
 22#include <linux/string.h>
 23#include <linux/types.h>
 24#include <acpi/video.h>
 25
 26/* 1. Driver-wide structs and misc. variables */
 27
 28/* ACPI HIDs */
 29#define LOONGSON_ACPI_EC_HID	"PNP0C09"
 30#define LOONGSON_ACPI_HKEY_HID	"LOON0000"
 31
 32#define ACPI_LAPTOP_NAME "loongson-laptop"
 33#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
 34
 35#define MAX_ACPI_ARGS			3
 36#define GENERIC_HOTKEY_MAP_MAX		64
 37
 38#define GENERIC_EVENT_TYPE_OFF		12
 39#define GENERIC_EVENT_TYPE_MASK		0xF000
 40#define GENERIC_EVENT_CODE_MASK		0x0FFF
 41
 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
 59static bool bl_powered;
 60static int loongson_laptop_backlight_update(struct backlight_device *bd);
 61
 62/* 2. ACPI Helpers and device model */
 63
 64static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
 65{
 66	char res_type;
 67	char *fmt0 = fmt;
 68	va_list ap;
 69	int success, quiet;
 70	acpi_status status;
 71	struct acpi_object_list params;
 72	struct acpi_buffer result, *resultp;
 73	union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
 74
 75	if (!*fmt) {
 76		pr_err("acpi_evalf() called with empty format\n");
 77		return 0;
 78	}
 79
 80	if (*fmt == 'q') {
 81		quiet = 1;
 82		fmt++;
 83	} else
 84		quiet = 0;
 85
 86	res_type = *(fmt++);
 87
 88	params.count = 0;
 89	params.pointer = &in_objs[0];
 90
 91	va_start(ap, fmt);
 92	while (*fmt) {
 93		char c = *(fmt++);
 94		switch (c) {
 95		case 'd':	/* int */
 96			in_objs[params.count].integer.value = va_arg(ap, int);
 97			in_objs[params.count++].type = ACPI_TYPE_INTEGER;
 98			break;
 99			/* add more types as needed */
100		default:
101			pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
102			va_end(ap);
103			return 0;
104		}
105	}
106	va_end(ap);
107
108	if (res_type != 'v') {
109		result.length = sizeof(out_obj);
110		result.pointer = &out_obj;
111		resultp = &result;
112	} else
113		resultp = NULL;
114
115	status = acpi_evaluate_object(handle, method, &params, resultp);
116
117	switch (res_type) {
118	case 'd':		/* int */
119		success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
120		if (success && res)
121			*res = out_obj.integer.value;
122		break;
123	case 'v':		/* void */
124		success = status == AE_OK;
125		break;
126		/* add more types as needed */
127	default:
128		pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
129		return 0;
130	}
131
132	if (!success && !quiet)
133		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
134		       method, fmt0, acpi_format_exception(status));
135
136	return success;
137}
138
139static int hotkey_status_get(int *status)
140{
141	if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
142		return -EIO;
143
144	return 0;
145}
146
147static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
148{
149	struct generic_sub_driver *sub_driver = data;
150
151	if (!sub_driver || !sub_driver->notify)
152		return;
153	sub_driver->notify(sub_driver, event);
154}
155
156static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
157{
158	acpi_status status;
159
160	if (!*sub_driver->handle)
161		return 0;
162
163	sub_driver->device = acpi_fetch_acpi_dev(*sub_driver->handle);
164	if (!sub_driver->device) {
165		pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
166		return -ENODEV;
167	}
168
169	sub_driver->device->driver_data = sub_driver;
170	sprintf(acpi_device_class(sub_driver->device), "%s/%s",
171		ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
172
173	status = acpi_install_notify_handler(*sub_driver->handle,
174			sub_driver->type, dispatch_acpi_notify, sub_driver);
175	if (ACPI_FAILURE(status)) {
176		if (status == AE_ALREADY_EXISTS) {
177			pr_notice("Another device driver is already "
178				  "handling %s events\n", sub_driver->name);
179		} else {
180			pr_err("acpi_install_notify_handler(%s) failed: %s\n",
181			       sub_driver->name, acpi_format_exception(status));
182		}
183		return -ENODEV;
184	}
185	sub_driver->acpi_notify_installed = 1;
186
187	return 0;
188}
189
190static int loongson_hotkey_suspend(struct device *dev)
191{
192	return 0;
193}
194
195static int loongson_hotkey_resume(struct device *dev)
196{
197	int status = 0;
198	struct key_entry ke;
199	struct backlight_device *bd;
200
201	bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
202	if (bd) {
203		loongson_laptop_backlight_update(bd) ?
204		pr_warn("Loongson_backlight: resume brightness failed") :
205		pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
206	}
207
208	/*
209	 * Only if the firmware supports SW_LID event model, we can handle the
210	 * event. This is for the consideration of development board without EC.
211	 */
212	if (test_bit(SW_LID, generic_inputdev->swbit)) {
213		if (hotkey_status_get(&status) < 0)
214			return -EIO;
215		/*
216		 * The input device sw element records the last lid status.
217		 * When the system is awakened by other wake-up sources,
218		 * the lid event will also be reported. The judgment of
219		 * adding SW_LID bit which in sw element can avoid this
220		 * case.
221		 *
222		 * Input system will drop lid event when current lid event
223		 * value and last lid status in the same. So laptop driver
224		 * doesn't report repeated events.
225		 *
226		 * Lid status is generally 0, but hardware exception is
227		 * considered. So add lid status confirmation.
228		 */
229		if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
230			ke.type = KE_SW;
231			ke.sw.value = (u8)status;
232			ke.sw.code = SW_LID;
233			sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
234		}
235	}
236
237	return 0;
238}
239
240static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
241		loongson_hotkey_suspend, loongson_hotkey_resume);
242
243static int loongson_hotkey_probe(struct platform_device *pdev)
244{
245	hotkey_handle = ACPI_HANDLE(&pdev->dev);
246
247	if (!hotkey_handle)
248		return -ENODEV;
249
250	return 0;
251}
252
253static const struct acpi_device_id loongson_device_ids[] = {
254	{LOONGSON_ACPI_HKEY_HID, 0},
255	{"", 0},
256};
257MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
258
259static struct platform_driver loongson_hotkey_driver = {
260	.probe		= loongson_hotkey_probe,
261	.driver		= {
262		.name	= "loongson-hotkey",
263		.owner	= THIS_MODULE,
264		.pm	= pm_ptr(&loongson_hotkey_pm),
265		.acpi_match_table = loongson_device_ids,
266	},
267};
268
269static int hotkey_map(void)
270{
271	u32 index;
272	acpi_status status;
273	struct acpi_buffer buf;
274	union acpi_object *pack;
275
276	buf.length = ACPI_ALLOCATE_BUFFER;
277	status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
278	if (status != AE_OK) {
279		pr_err("ACPI exception: %s\n", acpi_format_exception(status));
280		return -1;
281	}
282	pack = buf.pointer;
283	for (index = 0; index < pack->package.count; index++) {
284		union acpi_object *element, *sub_pack;
285
286		sub_pack = &pack->package.elements[index];
287
288		element = &sub_pack->package.elements[0];
289		hotkey_keycode_map[index].type = element->integer.value;
290		element = &sub_pack->package.elements[1];
291		hotkey_keycode_map[index].code = element->integer.value;
292		element = &sub_pack->package.elements[2];
293		hotkey_keycode_map[index].keycode = element->integer.value;
294	}
295
296	return 0;
297}
298
299static int hotkey_backlight_set(bool enable)
300{
301	if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
302		return -EIO;
303
304	return 0;
305}
306
307static int ec_get_brightness(void)
308{
309	int status = 0;
310
311	if (!hotkey_handle)
312		return -ENXIO;
313
314	if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
315		return -EIO;
316
317	return status;
318}
319
320static int ec_set_brightness(int level)
321{
322
323	int ret = 0;
324
325	if (!hotkey_handle)
326		return -ENXIO;
327
328	if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
329		ret = -EIO;
330
331	return ret;
332}
333
334static int ec_backlight_level(u8 level)
335{
336	int status = 0;
337
338	if (!hotkey_handle)
339		return -ENXIO;
340
341	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
342		return -EIO;
343
344	if ((status < 0) || (level > status))
345		return status;
346
347	if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
348		return -EIO;
349
350	if ((status < 0) || (level < status))
351		return status;
352
353	return level;
354}
355
356static int ec_backlight_set_power(bool state)
357{
358	int status;
359	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
360	struct acpi_object_list args = { 1, &arg0 };
361
362	arg0.integer.value = state;
363	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
364	if (ACPI_FAILURE(status)) {
365		pr_info("Loongson lvds error: 0x%x\n", status);
366		return -EIO;
367	}
368
369	return 0;
370}
371
372static int loongson_laptop_backlight_update(struct backlight_device *bd)
373{
374	bool target_powered = !backlight_is_blank(bd);
375	int ret = 0, lvl = ec_backlight_level(bd->props.brightness);
376
377	if (lvl < 0)
378		return -EIO;
379
380	if (ec_set_brightness(lvl))
381		return -EIO;
382
383	if (target_powered != bl_powered) {
384		ret = ec_backlight_set_power(target_powered);
385		if (ret < 0)
386			return ret;
387
388		bl_powered = target_powered;
389	}
390
391	return ret;
392}
393
394static int loongson_laptop_get_brightness(struct backlight_device *bd)
395{
396	int level;
397
398	level = ec_get_brightness();
399	if (level < 0)
400		return -EIO;
401
402	return level;
403}
404
405static const struct backlight_ops backlight_laptop_ops = {
406	.update_status = loongson_laptop_backlight_update,
407	.get_brightness = loongson_laptop_get_brightness,
408};
409
410static int laptop_backlight_register(void)
411{
412	int status = 0, ret;
413	struct backlight_properties props;
414
415	memset(&props, 0, sizeof(props));
416
417	if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
418		return -EIO;
419
420	ret = ec_backlight_set_power(true);
421	if (ret)
422		return ret;
423
424	bl_powered = true;
425
426	props.max_brightness = status;
427	props.brightness = ec_get_brightness();
428	props.power = BACKLIGHT_POWER_ON;
429	props.type = BACKLIGHT_PLATFORM;
430
431	backlight_device_register("loongson_laptop",
432				NULL, NULL, &backlight_laptop_ops, &props);
433
434
435	return 0;
436}
437
438static int __init event_init(struct generic_sub_driver *sub_driver)
439{
440	int ret;
441
442	ret = hotkey_map();
443	if (ret < 0) {
444		pr_err("Failed to parse keymap from DSDT\n");
445		return ret;
446	}
447
448	ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
449	if (ret < 0) {
450		pr_err("Failed to setup input device keymap\n");
451		input_free_device(generic_inputdev);
452		generic_inputdev = NULL;
453
454		return ret;
455	}
456
457	/*
458	 * This hotkey driver handle backlight event when
459	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
460	 */
461	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
462		hotkey_backlight_set(true);
463	else
464		hotkey_backlight_set(false);
465
466	pr_info("ACPI: enabling firmware HKEY event interface...\n");
467
468	return ret;
469}
470
471static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
472{
473	int type, scan_code;
474	struct key_entry *ke = NULL;
475
476	scan_code = event & GENERIC_EVENT_CODE_MASK;
477	type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
478	ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
479	if (ke) {
480		if (type == KE_SW) {
481			int status = 0;
482
483			if (hotkey_status_get(&status) < 0)
484				return;
485
486			ke->sw.value = !!(status & (1 << ke->sw.code));
487		}
488		sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
489	}
490}
491
492/* 3. Infrastructure */
493
494static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
495
496static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
497{
498	int ret;
499
500	if (!sub_driver || !sub_driver->driver)
501		return -EINVAL;
502
503	ret = platform_driver_register(sub_driver->driver);
504	if (ret)
505		return -EINVAL;
506
507	if (sub_driver->init) {
508		ret = sub_driver->init(sub_driver);
509		if (ret)
510			goto err_out;
511	}
512
513	if (sub_driver->notify) {
514		ret = setup_acpi_notify(sub_driver);
515		if (ret == -ENODEV) {
516			ret = 0;
517			goto err_out;
518		}
519		if (ret < 0)
520			goto err_out;
521	}
522
523	return 0;
524
525err_out:
526	generic_subdriver_exit(sub_driver);
527	return ret;
528}
529
530static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
531{
532
533	if (sub_driver->acpi_notify_installed) {
534		acpi_remove_notify_handler(*sub_driver->handle,
535					   sub_driver->type, dispatch_acpi_notify);
536		sub_driver->acpi_notify_installed = 0;
537	}
538	platform_driver_unregister(sub_driver->driver);
539}
540
541static struct generic_sub_driver generic_sub_drivers[] __refdata = {
542	{
543		.name = "hotkey",
544		.init = event_init,
545		.notify = event_notify,
546		.handle = &hotkey_handle,
547		.type = ACPI_DEVICE_NOTIFY,
548		.driver = &loongson_hotkey_driver,
549	},
550};
551
552static int __init generic_acpi_laptop_init(void)
553{
554	bool ec_found;
555	int i, ret, status;
556
557	if (acpi_disabled)
558		return -ENODEV;
559
560	/* The EC device is required */
561	ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
562	if (!ec_found)
563		return -ENODEV;
564
565	/* Enable SCI for EC */
566	acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
567
568	generic_inputdev = input_allocate_device();
569	if (!generic_inputdev) {
570		pr_err("Unable to allocate input device\n");
571		return -ENOMEM;
572	}
573
574	/* Prepare input device, but don't register */
575	generic_inputdev->name =
576		"Loongson Generic Laptop/All-in-One Extra Buttons";
577	generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
578	generic_inputdev->id.bustype = BUS_HOST;
579	generic_inputdev->dev.parent = NULL;
580
581	/* Init subdrivers */
582	for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
583		ret = generic_subdriver_init(&generic_sub_drivers[i]);
584		if (ret < 0) {
585			input_free_device(generic_inputdev);
586			while (--i >= 0)
587				generic_subdriver_exit(&generic_sub_drivers[i]);
588			return ret;
589		}
590	}
591
592	ret = input_register_device(generic_inputdev);
593	if (ret < 0) {
594		input_free_device(generic_inputdev);
595		while (--i >= 0)
596			generic_subdriver_exit(&generic_sub_drivers[i]);
597		pr_err("Unable to register input device\n");
598		return ret;
599	}
600
601	input_device_registered = 1;
602
603	if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
604		pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
605		ret = laptop_backlight_register();
606		if (ret < 0)
607			pr_err("Loongson Laptop: laptop-backlight device register failed\n");
608	}
609
610	return 0;
611}
612
613static void __exit generic_acpi_laptop_exit(void)
614{
615	int i;
616
617	if (generic_inputdev) {
618		if (!input_device_registered) {
619			input_free_device(generic_inputdev);
620		} else {
621			input_unregister_device(generic_inputdev);
622
623			for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++)
624				generic_subdriver_exit(&generic_sub_drivers[i]);
625		}
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");
~/ Read-only file preview.