123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
- *
- * Jianmin Lv <[email protected]>
- * Huacai Chen <[email protected]>
- *
- * Copyright (C) 2022 Loongson Technology Corporation Limited
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/acpi.h>
- #include <linux/backlight.h>
- #include <linux/device.h>
- #include <linux/input.h>
- #include <linux/input/sparse-keymap.h>
- #include <linux/platform_device.h>
- #include <linux/string.h>
- #include <linux/types.h>
- #include <acpi/video.h>
- /* 1. Driver-wide structs and misc. variables */
- /* ACPI HIDs */
- #define LOONGSON_ACPI_EC_HID "PNP0C09"
- #define LOONGSON_ACPI_HKEY_HID "LOON0000"
- #define ACPI_LAPTOP_NAME "loongson-laptop"
- #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
- #define MAX_ACPI_ARGS 3
- #define GENERIC_HOTKEY_MAP_MAX 64
- #define GENERIC_EVENT_TYPE_OFF 12
- #define GENERIC_EVENT_TYPE_MASK 0xF000
- #define GENERIC_EVENT_CODE_MASK 0x0FFF
- struct generic_sub_driver {
- u32 type;
- char *name;
- acpi_handle *handle;
- struct acpi_device *device;
- struct platform_driver *driver;
- int (*init)(struct generic_sub_driver *sub_driver);
- void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
- u8 acpi_notify_installed;
- };
- static u32 input_device_registered;
- static struct input_dev *generic_inputdev;
- static acpi_handle hotkey_handle;
- static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
- int loongson_laptop_turn_on_backlight(void);
- int loongson_laptop_turn_off_backlight(void);
- static int loongson_laptop_backlight_update(struct backlight_device *bd);
- /* 2. ACPI Helpers and device model */
- static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
- {
- char res_type;
- char *fmt0 = fmt;
- va_list ap;
- int success, quiet;
- acpi_status status;
- struct acpi_object_list params;
- struct acpi_buffer result, *resultp;
- union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
- if (!*fmt) {
- pr_err("acpi_evalf() called with empty format\n");
- return 0;
- }
- if (*fmt == 'q') {
- quiet = 1;
- fmt++;
- } else
- quiet = 0;
- res_type = *(fmt++);
- params.count = 0;
- params.pointer = &in_objs[0];
- va_start(ap, fmt);
- while (*fmt) {
- char c = *(fmt++);
- switch (c) {
- case 'd': /* int */
- in_objs[params.count].integer.value = va_arg(ap, int);
- in_objs[params.count++].type = ACPI_TYPE_INTEGER;
- break;
- /* add more types as needed */
- default:
- pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
- va_end(ap);
- return 0;
- }
- }
- va_end(ap);
- if (res_type != 'v') {
- result.length = sizeof(out_obj);
- result.pointer = &out_obj;
- resultp = &result;
- } else
- resultp = NULL;
- status = acpi_evaluate_object(handle, method, ¶ms, resultp);
- switch (res_type) {
- case 'd': /* int */
- success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
- if (success && res)
- *res = out_obj.integer.value;
- break;
- case 'v': /* void */
- success = status == AE_OK;
- break;
- /* add more types as needed */
- default:
- pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
- return 0;
- }
- if (!success && !quiet)
- pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
- method, fmt0, acpi_format_exception(status));
- return success;
- }
- static int hotkey_status_get(int *status)
- {
- if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
- return -EIO;
- return 0;
- }
- static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
- {
- struct generic_sub_driver *sub_driver = data;
- if (!sub_driver || !sub_driver->notify)
- return;
- sub_driver->notify(sub_driver, event);
- }
- static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
- {
- acpi_status status;
- if (!*sub_driver->handle)
- return 0;
- sub_driver->device = acpi_fetch_acpi_dev(*sub_driver->handle);
- if (!sub_driver->device) {
- pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
- return -ENODEV;
- }
- sub_driver->device->driver_data = sub_driver;
- sprintf(acpi_device_class(sub_driver->device), "%s/%s",
- ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
- status = acpi_install_notify_handler(*sub_driver->handle,
- sub_driver->type, dispatch_acpi_notify, sub_driver);
- if (ACPI_FAILURE(status)) {
- if (status == AE_ALREADY_EXISTS) {
- pr_notice("Another device driver is already "
- "handling %s events\n", sub_driver->name);
- } else {
- pr_err("acpi_install_notify_handler(%s) failed: %s\n",
- sub_driver->name, acpi_format_exception(status));
- }
- return -ENODEV;
- }
- sub_driver->acpi_notify_installed = 1;
- return 0;
- }
- static int loongson_hotkey_suspend(struct device *dev)
- {
- return 0;
- }
- static int loongson_hotkey_resume(struct device *dev)
- {
- int status = 0;
- struct key_entry ke;
- struct backlight_device *bd;
- bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
- if (bd) {
- loongson_laptop_backlight_update(bd) ?
- pr_warn("Loongson_backlight: resume brightness failed") :
- pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
- }
- /*
- * Only if the firmware supports SW_LID event model, we can handle the
- * event. This is for the consideration of development board without EC.
- */
- if (test_bit(SW_LID, generic_inputdev->swbit)) {
- if (hotkey_status_get(&status) < 0)
- return -EIO;
- /*
- * The input device sw element records the last lid status.
- * When the system is awakened by other wake-up sources,
- * the lid event will also be reported. The judgment of
- * adding SW_LID bit which in sw element can avoid this
- * case.
- *
- * Input system will drop lid event when current lid event
- * value and last lid status in the same. So laptop driver
- * doesn't report repeated events.
- *
- * Lid status is generally 0, but hardware exception is
- * considered. So add lid status confirmation.
- */
- if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
- ke.type = KE_SW;
- ke.sw.value = (u8)status;
- ke.sw.code = SW_LID;
- sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
- }
- }
- return 0;
- }
- static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
- loongson_hotkey_suspend, loongson_hotkey_resume);
- static int loongson_hotkey_probe(struct platform_device *pdev)
- {
- hotkey_handle = ACPI_HANDLE(&pdev->dev);
- if (!hotkey_handle)
- return -ENODEV;
- return 0;
- }
- static const struct acpi_device_id loongson_device_ids[] = {
- {LOONGSON_ACPI_HKEY_HID, 0},
- {"", 0},
- };
- MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
- static struct platform_driver loongson_hotkey_driver = {
- .probe = loongson_hotkey_probe,
- .driver = {
- .name = "loongson-hotkey",
- .owner = THIS_MODULE,
- .pm = pm_ptr(&loongson_hotkey_pm),
- .acpi_match_table = loongson_device_ids,
- },
- };
- static int hotkey_map(void)
- {
- u32 index;
- acpi_status status;
- struct acpi_buffer buf;
- union acpi_object *pack;
- buf.length = ACPI_ALLOCATE_BUFFER;
- status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
- if (status != AE_OK) {
- pr_err("ACPI exception: %s\n", acpi_format_exception(status));
- return -1;
- }
- pack = buf.pointer;
- for (index = 0; index < pack->package.count; index++) {
- union acpi_object *element, *sub_pack;
- sub_pack = &pack->package.elements[index];
- element = &sub_pack->package.elements[0];
- hotkey_keycode_map[index].type = element->integer.value;
- element = &sub_pack->package.elements[1];
- hotkey_keycode_map[index].code = element->integer.value;
- element = &sub_pack->package.elements[2];
- hotkey_keycode_map[index].keycode = element->integer.value;
- }
- return 0;
- }
- static int hotkey_backlight_set(bool enable)
- {
- if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
- return -EIO;
- return 0;
- }
- static int ec_get_brightness(void)
- {
- int status = 0;
- if (!hotkey_handle)
- return -ENXIO;
- if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
- return -EIO;
- return status;
- }
- static int ec_set_brightness(int level)
- {
- int ret = 0;
- if (!hotkey_handle)
- return -ENXIO;
- if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
- ret = -EIO;
- return ret;
- }
- static int ec_backlight_level(u8 level)
- {
- int status = 0;
- if (!hotkey_handle)
- return -ENXIO;
- if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
- return -EIO;
- if ((status < 0) || (level > status))
- return status;
- if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
- return -EIO;
- if ((status < 0) || (level < status))
- return status;
- return level;
- }
- static int loongson_laptop_backlight_update(struct backlight_device *bd)
- {
- int lvl = ec_backlight_level(bd->props.brightness);
- if (lvl < 0)
- return -EIO;
- if (ec_set_brightness(lvl))
- return -EIO;
- return 0;
- }
- static int loongson_laptop_get_brightness(struct backlight_device *bd)
- {
- int level;
- level = ec_get_brightness();
- if (level < 0)
- return -EIO;
- return level;
- }
- static const struct backlight_ops backlight_laptop_ops = {
- .update_status = loongson_laptop_backlight_update,
- .get_brightness = loongson_laptop_get_brightness,
- };
- static int laptop_backlight_register(void)
- {
- int status = 0;
- struct backlight_properties props;
- memset(&props, 0, sizeof(props));
- if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
- return -EIO;
- props.brightness = 1;
- props.max_brightness = status;
- props.type = BACKLIGHT_PLATFORM;
- backlight_device_register("loongson_laptop",
- NULL, NULL, &backlight_laptop_ops, &props);
- return 0;
- }
- int loongson_laptop_turn_on_backlight(void)
- {
- int status;
- union acpi_object arg0 = { ACPI_TYPE_INTEGER };
- struct acpi_object_list args = { 1, &arg0 };
- arg0.integer.value = 1;
- status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
- if (ACPI_FAILURE(status)) {
- pr_info("Loongson lvds error: 0x%x\n", status);
- return -ENODEV;
- }
- return 0;
- }
- int loongson_laptop_turn_off_backlight(void)
- {
- int status;
- union acpi_object arg0 = { ACPI_TYPE_INTEGER };
- struct acpi_object_list args = { 1, &arg0 };
- arg0.integer.value = 0;
- status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
- if (ACPI_FAILURE(status)) {
- pr_info("Loongson lvds error: 0x%x\n", status);
- return -ENODEV;
- }
- return 0;
- }
- static int __init event_init(struct generic_sub_driver *sub_driver)
- {
- int ret;
- ret = hotkey_map();
- if (ret < 0) {
- pr_err("Failed to parse keymap from DSDT\n");
- return ret;
- }
- ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
- if (ret < 0) {
- pr_err("Failed to setup input device keymap\n");
- input_free_device(generic_inputdev);
- generic_inputdev = NULL;
- return ret;
- }
- /*
- * This hotkey driver handle backlight event when
- * acpi_video_get_backlight_type() gets acpi_backlight_vendor
- */
- if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
- hotkey_backlight_set(true);
- else
- hotkey_backlight_set(false);
- pr_info("ACPI: enabling firmware HKEY event interface...\n");
- return ret;
- }
- static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
- {
- int type, scan_code;
- struct key_entry *ke = NULL;
- scan_code = event & GENERIC_EVENT_CODE_MASK;
- type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
- ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
- if (ke) {
- if (type == KE_SW) {
- int status = 0;
- if (hotkey_status_get(&status) < 0)
- return;
- ke->sw.value = !!(status & (1 << ke->sw.code));
- }
- sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
- }
- }
- /* 3. Infrastructure */
- static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
- static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
- {
- int ret;
- if (!sub_driver || !sub_driver->driver)
- return -EINVAL;
- ret = platform_driver_register(sub_driver->driver);
- if (ret)
- return -EINVAL;
- if (sub_driver->init) {
- ret = sub_driver->init(sub_driver);
- if (ret)
- goto err_out;
- }
- if (sub_driver->notify) {
- ret = setup_acpi_notify(sub_driver);
- if (ret == -ENODEV) {
- ret = 0;
- goto err_out;
- }
- if (ret < 0)
- goto err_out;
- }
- return 0;
- err_out:
- generic_subdriver_exit(sub_driver);
- return ret;
- }
- static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
- {
- if (sub_driver->acpi_notify_installed) {
- acpi_remove_notify_handler(*sub_driver->handle,
- sub_driver->type, dispatch_acpi_notify);
- sub_driver->acpi_notify_installed = 0;
- }
- platform_driver_unregister(sub_driver->driver);
- }
- static struct generic_sub_driver generic_sub_drivers[] __refdata = {
- {
- .name = "hotkey",
- .init = event_init,
- .notify = event_notify,
- .handle = &hotkey_handle,
- .type = ACPI_DEVICE_NOTIFY,
- .driver = &loongson_hotkey_driver,
- },
- };
- static int __init generic_acpi_laptop_init(void)
- {
- bool ec_found;
- int i, ret, status;
- if (acpi_disabled)
- return -ENODEV;
- /* The EC device is required */
- ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
- if (!ec_found)
- return -ENODEV;
- /* Enable SCI for EC */
- acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
- generic_inputdev = input_allocate_device();
- if (!generic_inputdev) {
- pr_err("Unable to allocate input device\n");
- return -ENOMEM;
- }
- /* Prepare input device, but don't register */
- generic_inputdev->name =
- "Loongson Generic Laptop/All-in-One Extra Buttons";
- generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
- generic_inputdev->id.bustype = BUS_HOST;
- generic_inputdev->dev.parent = NULL;
- /* Init subdrivers */
- for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
- ret = generic_subdriver_init(&generic_sub_drivers[i]);
- if (ret < 0) {
- input_free_device(generic_inputdev);
- while (--i >= 0)
- generic_subdriver_exit(&generic_sub_drivers[i]);
- return ret;
- }
- }
- ret = input_register_device(generic_inputdev);
- if (ret < 0) {
- input_free_device(generic_inputdev);
- while (--i >= 0)
- generic_subdriver_exit(&generic_sub_drivers[i]);
- pr_err("Unable to register input device\n");
- return ret;
- }
- input_device_registered = 1;
- if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
- pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
- ret = laptop_backlight_register();
- if (ret < 0)
- pr_err("Loongson Laptop: laptop-backlight device register failed\n");
- }
- return 0;
- }
- static void __exit generic_acpi_laptop_exit(void)
- {
- if (generic_inputdev) {
- if (input_device_registered)
- input_unregister_device(generic_inputdev);
- else
- input_free_device(generic_inputdev);
- }
- }
- module_init(generic_acpi_laptop_init);
- module_exit(generic_acpi_laptop_exit);
- MODULE_AUTHOR("Jianmin Lv <[email protected]>");
- MODULE_AUTHOR("Huacai Chen <[email protected]>");
- MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
- MODULE_LICENSE("GPL");
|