18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Asus Wireless Radio Control Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015-2016 Endless Mobile, Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/types.h> 128c2ecf20Sopenharmony_ci#include <linux/acpi.h> 138c2ecf20Sopenharmony_ci#include <linux/input.h> 148c2ecf20Sopenharmony_ci#include <linux/pci_ids.h> 158c2ecf20Sopenharmony_ci#include <linux/leds.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistruct hswc_params { 188c2ecf20Sopenharmony_ci u8 on; 198c2ecf20Sopenharmony_ci u8 off; 208c2ecf20Sopenharmony_ci u8 status; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct asus_wireless_data { 248c2ecf20Sopenharmony_ci struct input_dev *idev; 258c2ecf20Sopenharmony_ci struct acpi_device *adev; 268c2ecf20Sopenharmony_ci const struct hswc_params *hswc_params; 278c2ecf20Sopenharmony_ci struct workqueue_struct *wq; 288c2ecf20Sopenharmony_ci struct work_struct led_work; 298c2ecf20Sopenharmony_ci struct led_classdev led; 308c2ecf20Sopenharmony_ci int led_state; 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic const struct hswc_params atk4001_id_params = { 348c2ecf20Sopenharmony_ci .on = 0x0, 358c2ecf20Sopenharmony_ci .off = 0x1, 368c2ecf20Sopenharmony_ci .status = 0x2, 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic const struct hswc_params atk4002_id_params = { 408c2ecf20Sopenharmony_ci .on = 0x5, 418c2ecf20Sopenharmony_ci .off = 0x4, 428c2ecf20Sopenharmony_ci .status = 0x2, 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic const struct acpi_device_id device_ids[] = { 468c2ecf20Sopenharmony_ci {"ATK4001", (kernel_ulong_t)&atk4001_id_params}, 478c2ecf20Sopenharmony_ci {"ATK4002", (kernel_ulong_t)&atk4002_id_params}, 488c2ecf20Sopenharmony_ci {"", 0}, 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, device_ids); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic acpi_status asus_wireless_method(acpi_handle handle, const char *method, 538c2ecf20Sopenharmony_ci int param, u64 *ret) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct acpi_object_list p; 568c2ecf20Sopenharmony_ci union acpi_object obj; 578c2ecf20Sopenharmony_ci acpi_status s; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n", 608c2ecf20Sopenharmony_ci method, param); 618c2ecf20Sopenharmony_ci obj.type = ACPI_TYPE_INTEGER; 628c2ecf20Sopenharmony_ci obj.integer.value = param; 638c2ecf20Sopenharmony_ci p.count = 1; 648c2ecf20Sopenharmony_ci p.pointer = &obj; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci s = acpi_evaluate_integer(handle, (acpi_string) method, &p, ret); 678c2ecf20Sopenharmony_ci if (ACPI_FAILURE(s)) 688c2ecf20Sopenharmony_ci acpi_handle_err(handle, 698c2ecf20Sopenharmony_ci "Failed to eval method %s, param %#x (%d)\n", 708c2ecf20Sopenharmony_ci method, param, s); 718c2ecf20Sopenharmony_ci else 728c2ecf20Sopenharmony_ci acpi_handle_debug(handle, "%s returned %#llx\n", method, *ret); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return s; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic enum led_brightness led_state_get(struct led_classdev *led) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct asus_wireless_data *data; 808c2ecf20Sopenharmony_ci acpi_status s; 818c2ecf20Sopenharmony_ci u64 ret; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci data = container_of(led, struct asus_wireless_data, led); 848c2ecf20Sopenharmony_ci s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC", 858c2ecf20Sopenharmony_ci data->hswc_params->status, &ret); 868c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(s) && ret == data->hswc_params->on) 878c2ecf20Sopenharmony_ci return LED_FULL; 888c2ecf20Sopenharmony_ci return LED_OFF; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic void led_state_update(struct work_struct *work) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct asus_wireless_data *data; 948c2ecf20Sopenharmony_ci u64 ret; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci data = container_of(work, struct asus_wireless_data, led_work); 978c2ecf20Sopenharmony_ci asus_wireless_method(acpi_device_handle(data->adev), "HSWC", 988c2ecf20Sopenharmony_ci data->led_state, &ret); 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic void led_state_set(struct led_classdev *led, enum led_brightness value) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct asus_wireless_data *data; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci data = container_of(led, struct asus_wireless_data, led); 1068c2ecf20Sopenharmony_ci data->led_state = value == LED_OFF ? data->hswc_params->off : 1078c2ecf20Sopenharmony_ci data->hswc_params->on; 1088c2ecf20Sopenharmony_ci queue_work(data->wq, &data->led_work); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic void asus_wireless_notify(struct acpi_device *adev, u32 event) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci struct asus_wireless_data *data = acpi_driver_data(adev); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci dev_dbg(&adev->dev, "event=%#x\n", event); 1168c2ecf20Sopenharmony_ci if (event != 0x88) { 1178c2ecf20Sopenharmony_ci dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event); 1188c2ecf20Sopenharmony_ci return; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci input_report_key(data->idev, KEY_RFKILL, 1); 1218c2ecf20Sopenharmony_ci input_sync(data->idev); 1228c2ecf20Sopenharmony_ci input_report_key(data->idev, KEY_RFKILL, 0); 1238c2ecf20Sopenharmony_ci input_sync(data->idev); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic int asus_wireless_add(struct acpi_device *adev) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct asus_wireless_data *data; 1298c2ecf20Sopenharmony_ci const struct acpi_device_id *id; 1308c2ecf20Sopenharmony_ci int err; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL); 1338c2ecf20Sopenharmony_ci if (!data) 1348c2ecf20Sopenharmony_ci return -ENOMEM; 1358c2ecf20Sopenharmony_ci adev->driver_data = data; 1368c2ecf20Sopenharmony_ci data->adev = adev; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci data->idev = devm_input_allocate_device(&adev->dev); 1398c2ecf20Sopenharmony_ci if (!data->idev) 1408c2ecf20Sopenharmony_ci return -ENOMEM; 1418c2ecf20Sopenharmony_ci data->idev->name = "Asus Wireless Radio Control"; 1428c2ecf20Sopenharmony_ci data->idev->phys = "asus-wireless/input0"; 1438c2ecf20Sopenharmony_ci data->idev->id.bustype = BUS_HOST; 1448c2ecf20Sopenharmony_ci data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK; 1458c2ecf20Sopenharmony_ci set_bit(EV_KEY, data->idev->evbit); 1468c2ecf20Sopenharmony_ci set_bit(KEY_RFKILL, data->idev->keybit); 1478c2ecf20Sopenharmony_ci err = input_register_device(data->idev); 1488c2ecf20Sopenharmony_ci if (err) 1498c2ecf20Sopenharmony_ci return err; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci for (id = device_ids; id->id[0]; id++) { 1528c2ecf20Sopenharmony_ci if (!strcmp((char *) id->id, acpi_device_hid(adev))) { 1538c2ecf20Sopenharmony_ci data->hswc_params = 1548c2ecf20Sopenharmony_ci (const struct hswc_params *)id->driver_data; 1558c2ecf20Sopenharmony_ci break; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci if (!data->hswc_params) 1598c2ecf20Sopenharmony_ci return 0; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); 1628c2ecf20Sopenharmony_ci if (!data->wq) 1638c2ecf20Sopenharmony_ci return -ENOMEM; 1648c2ecf20Sopenharmony_ci INIT_WORK(&data->led_work, led_state_update); 1658c2ecf20Sopenharmony_ci data->led.name = "asus-wireless::airplane"; 1668c2ecf20Sopenharmony_ci data->led.brightness_set = led_state_set; 1678c2ecf20Sopenharmony_ci data->led.brightness_get = led_state_get; 1688c2ecf20Sopenharmony_ci data->led.flags = LED_CORE_SUSPENDRESUME; 1698c2ecf20Sopenharmony_ci data->led.max_brightness = 1; 1708c2ecf20Sopenharmony_ci data->led.default_trigger = "rfkill-none"; 1718c2ecf20Sopenharmony_ci err = devm_led_classdev_register(&adev->dev, &data->led); 1728c2ecf20Sopenharmony_ci if (err) 1738c2ecf20Sopenharmony_ci destroy_workqueue(data->wq); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci return err; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic int asus_wireless_remove(struct acpi_device *adev) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci struct asus_wireless_data *data = acpi_driver_data(adev); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (data->wq) { 1838c2ecf20Sopenharmony_ci devm_led_classdev_unregister(&adev->dev, &data->led); 1848c2ecf20Sopenharmony_ci destroy_workqueue(data->wq); 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci return 0; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic struct acpi_driver asus_wireless_driver = { 1908c2ecf20Sopenharmony_ci .name = "Asus Wireless Radio Control Driver", 1918c2ecf20Sopenharmony_ci .class = "hotkey", 1928c2ecf20Sopenharmony_ci .ids = device_ids, 1938c2ecf20Sopenharmony_ci .ops = { 1948c2ecf20Sopenharmony_ci .add = asus_wireless_add, 1958c2ecf20Sopenharmony_ci .remove = asus_wireless_remove, 1968c2ecf20Sopenharmony_ci .notify = asus_wireless_notify, 1978c2ecf20Sopenharmony_ci }, 1988c2ecf20Sopenharmony_ci}; 1998c2ecf20Sopenharmony_cimodule_acpi_driver(asus_wireless_driver); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Asus Wireless Radio Control Driver"); 2028c2ecf20Sopenharmony_ciMODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>"); 2038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 204