Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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
 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	} else
 85		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	} else
114		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 the
211	 * 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 of
220		 * adding SW_LID bit which in sw element can avoid this
221		 * case.
222		 *
223		 * Input system will drop lid event when current lid event
224		 * value and last lid status in the same. So laptop driver
225		 * doesn't report repeated events.
226		 *
227		 * Lid status is generally 0, but hardware exception is
228		 * 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 when
458	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
459	 */
460	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
461		hotkey_backlight_set(true);
462	else
463		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		else
618			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");