// SPDX-License-Identifier: GPL-2.0-or-later /* * Airplane mode button for AMD, HP & Xiaomi laptops * * Copyright (C) 2014-2017 Alex Hung * Copyright (C) 2021 Advanced Micro Devices */ #include #include #include #include #include #include #include MODULE_DESCRIPTION("Airplane mode button for AMD, HP & Xiaomi laptops"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex Hung"); MODULE_ALIAS("acpi*:HPQ6001:*"); MODULE_ALIAS("acpi*:WSTADEF:*"); MODULE_ALIAS("acpi*:AMDI0051:*"); MODULE_ALIAS("acpi*:LGEX0815:*"); struct wl_button { struct input_dev *input_dev; char phys[32]; }; static const struct acpi_device_id wl_ids[] = { {"HPQ6001", 0}, {"WSTADEF", 0}, {"AMDI0051", 0}, {"LGEX0815", 0}, {"", 0}, }; static int wireless_input_setup(struct device *dev) { struct wl_button *button = dev_get_drvdata(dev); int err; button->input_dev = input_allocate_device(); if (!button->input_dev) return -ENOMEM; snprintf(button->phys, sizeof(button->phys), "%s/input0", acpi_device_hid(ACPI_COMPANION(dev))); button->input_dev->name = "Wireless hotkeys"; button->input_dev->phys = button->phys; button->input_dev->id.bustype = BUS_HOST; button->input_dev->evbit[0] = BIT(EV_KEY); set_bit(KEY_RFKILL, button->input_dev->keybit); err = input_register_device(button->input_dev); if (err) goto err_free_dev; return 0; err_free_dev: input_free_device(button->input_dev); return err; } static void wireless_input_destroy(struct device *dev) { struct wl_button *button = dev_get_drvdata(dev); input_unregister_device(button->input_dev); kfree(button); } static void wl_notify(acpi_handle handle, u32 event, void *data) { struct wl_button *button = data; if (event != 0x80) { pr_info("Received unknown event (0x%x)\n", event); return; } input_report_key(button->input_dev, KEY_RFKILL, 1); input_sync(button->input_dev); input_report_key(button->input_dev, KEY_RFKILL, 0); input_sync(button->input_dev); } static int wl_probe(struct platform_device *pdev) { struct acpi_device *adev; struct wl_button *button; int err; adev = ACPI_COMPANION(&pdev->dev); if (!adev) return -ENODEV; button = kzalloc_obj(struct wl_button); if (!button) return -ENOMEM; platform_set_drvdata(pdev, button); err = wireless_input_setup(&pdev->dev); if (err) { pr_err("Failed to setup wireless hotkeys\n"); kfree(button); return err; } err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, wl_notify, button); if (err) { pr_err("Failed to install ACPI notify handler\n"); wireless_input_destroy(&pdev->dev); } return err; } static void wl_remove(struct platform_device *pdev) { acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), ACPI_DEVICE_NOTIFY, wl_notify); wireless_input_destroy(&pdev->dev); } static struct platform_driver wl_driver = { .probe = wl_probe, .remove = wl_remove, .driver = { .name = "wireless-hotkey", .acpi_match_table = wl_ids, }, }; module_platform_driver(wl_driver);