^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0-only
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * Asus Wireless Radio Control Driver
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * Copyright (C) 2015-2016 Endless Mobile, Inc.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) #include <linux/kernel.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) #include <linux/init.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) #include <linux/types.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #include <linux/acpi.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) #include <linux/input.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) #include <linux/pci_ids.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) #include <linux/leds.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) struct hswc_params {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) u8 on;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) u8 off;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) u8 status;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) struct asus_wireless_data {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) struct input_dev *idev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) struct acpi_device *adev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) const struct hswc_params *hswc_params;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) struct workqueue_struct *wq;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) struct work_struct led_work;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) struct led_classdev led;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) int led_state;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) static const struct hswc_params atk4001_id_params = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) .on = 0x0,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) .off = 0x1,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) .status = 0x2,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) static const struct hswc_params atk4002_id_params = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) .on = 0x5,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) .off = 0x4,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) .status = 0x2,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) static const struct acpi_device_id device_ids[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) {"ATK4001", (kernel_ulong_t)&atk4001_id_params},
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) {"ATK4002", (kernel_ulong_t)&atk4002_id_params},
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) {"", 0},
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) MODULE_DEVICE_TABLE(acpi, device_ids);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) static acpi_status asus_wireless_method(acpi_handle handle, const char *method,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) int param, u64 *ret)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) struct acpi_object_list p;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) union acpi_object obj;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) acpi_status s;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) method, param);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) obj.type = ACPI_TYPE_INTEGER;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) obj.integer.value = param;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) p.count = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) p.pointer = &obj;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) s = acpi_evaluate_integer(handle, (acpi_string) method, &p, ret);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) if (ACPI_FAILURE(s))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) acpi_handle_err(handle,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) "Failed to eval method %s, param %#x (%d)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) method, param, s);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) acpi_handle_debug(handle, "%s returned %#llx\n", method, *ret);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) return s;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) static enum led_brightness led_state_get(struct led_classdev *led)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) struct asus_wireless_data *data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) acpi_status s;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) u64 ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) data = container_of(led, struct asus_wireless_data, led);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) data->hswc_params->status, &ret);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) if (ACPI_SUCCESS(s) && ret == data->hswc_params->on)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) return LED_FULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) return LED_OFF;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) static void led_state_update(struct work_struct *work)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) struct asus_wireless_data *data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) u64 ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) data = container_of(work, struct asus_wireless_data, led_work);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) data->led_state, &ret);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) static void led_state_set(struct led_classdev *led, enum led_brightness value)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) struct asus_wireless_data *data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) data = container_of(led, struct asus_wireless_data, led);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) data->led_state = value == LED_OFF ? data->hswc_params->off :
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) data->hswc_params->on;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) queue_work(data->wq, &data->led_work);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) static void asus_wireless_notify(struct acpi_device *adev, u32 event)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) struct asus_wireless_data *data = acpi_driver_data(adev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) dev_dbg(&adev->dev, "event=%#x\n", event);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) if (event != 0x88) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) input_report_key(data->idev, KEY_RFKILL, 1);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) input_sync(data->idev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) input_report_key(data->idev, KEY_RFKILL, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) input_sync(data->idev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) static int asus_wireless_add(struct acpi_device *adev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) struct asus_wireless_data *data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) const struct acpi_device_id *id;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) int err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) if (!data)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) adev->driver_data = data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) data->adev = adev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) data->idev = devm_input_allocate_device(&adev->dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) if (!data->idev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) data->idev->name = "Asus Wireless Radio Control";
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) data->idev->phys = "asus-wireless/input0";
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) data->idev->id.bustype = BUS_HOST;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) set_bit(EV_KEY, data->idev->evbit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) set_bit(KEY_RFKILL, data->idev->keybit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) err = input_register_device(data->idev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) if (err)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) return err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) for (id = device_ids; id->id[0]; id++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) if (!strcmp((char *) id->id, acpi_device_hid(adev))) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) data->hswc_params =
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) (const struct hswc_params *)id->driver_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) if (!data->hswc_params)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) data->wq = create_singlethread_workqueue("asus_wireless_workqueue");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) if (!data->wq)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) INIT_WORK(&data->led_work, led_state_update);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) data->led.name = "asus-wireless::airplane";
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) data->led.brightness_set = led_state_set;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) data->led.brightness_get = led_state_get;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) data->led.flags = LED_CORE_SUSPENDRESUME;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) data->led.max_brightness = 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) data->led.default_trigger = "rfkill-none";
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) err = devm_led_classdev_register(&adev->dev, &data->led);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) if (err)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) destroy_workqueue(data->wq);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) return err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) static int asus_wireless_remove(struct acpi_device *adev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) struct asus_wireless_data *data = acpi_driver_data(adev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) if (data->wq) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) devm_led_classdev_unregister(&adev->dev, &data->led);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) destroy_workqueue(data->wq);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) static struct acpi_driver asus_wireless_driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) .name = "Asus Wireless Radio Control Driver",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) .class = "hotkey",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) .ids = device_ids,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) .ops = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) .add = asus_wireless_add,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) .remove = asus_wireless_remove,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) .notify = asus_wireless_notify,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) module_acpi_driver(asus_wireless_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) MODULE_LICENSE("GPL");