| 1 | |
| 2 | |
| 3 | * ACPI Direct App Launch driver
|
| 4 | *
|
| 5 | * Copyright (C) 2024 Armin Wolf <[email protected]>
|
| 6 | * Copyright (C) 2022 Arvid Norlander <[email protected]>
|
| 7 | * Copyright (C) 2007-2010 Angelo Arrifano <[email protected]>
|
| 8 | *
|
| 9 | * Information gathered from disassembled dsdt and from here:
|
| 10 | * <https://archive.org/details/microsoft-acpi-dirapplaunch>
|
| 11 |
|
| 12 |
|
| 13 | #include <linux/acpi.h>
|
| 14 | #include <linux/device.h>
|
| 15 | #include <linux/errno.h>
|
| 16 | #include <linux/init.h>
|
| 17 | #include <linux/input.h>
|
| 18 | #include <linux/input/sparse-keymap.h>
|
| 19 | #include <linux/module.h>
|
| 20 | #include <linux/mutex.h>
|
| 21 | #include <linux/platform_device.h>
|
| 22 | #include <linux/printk.h>
|
| 23 | #include <linux/slab.h>
|
| 24 | #include <linux/sysfs.h>
|
| 25 | #include <linux/types.h>
|
| 26 |
|
| 27 | #include <linux/unaligned.h>
|
| 28 |
|
| 29 | #define DRIVER_NAME "quickstart"
|
| 30 |
|
| 31 | |
| 32 | * There will be two events:
|
| 33 | * 0x02 - Button was pressed while device was off/sleeping.
|
| 34 | * 0x80 - Button was pressed while device was up.
|
| 35 |
|
| 36 | #define QUICKSTART_EVENT_RUNTIME 0x80
|
| 37 |
|
| 38 | struct quickstart_data {
|
| 39 | struct device *dev;
|
| 40 | struct mutex input_lock;
|
| 41 | struct input_dev *input_device;
|
| 42 | char input_name[32];
|
| 43 | char phys[32];
|
| 44 | u32 id;
|
| 45 | };
|
| 46 |
|
| 47 | |
| 48 | * Knowing what these buttons do require system specific knowledge.
|
| 49 | * This could be done by matching on DMI data in a long quirk table.
|
| 50 | * However, it is easier to leave it up to user space to figure this out.
|
| 51 | *
|
| 52 | * Using for example udev hwdb the scancode 0x1 can be remapped suitably.
|
| 53 |
|
| 54 | static const struct key_entry quickstart_keymap[] = {
|
| 55 | { KE_KEY, 0x1, { KEY_UNKNOWN } },
|
| 56 | { KE_END, 0 },
|
| 57 | };
|
| 58 |
|
| 59 | static ssize_t button_id_show(struct device *dev, struct device_attribute *attr, char *buf)
|
| 60 | {
|
| 61 | struct quickstart_data *data = dev_get_drvdata(dev);
|
| 62 |
|
| 63 | return sysfs_emit(buf, "%u\n", data->id);
|
| 64 | }
|
| 65 | static DEVICE_ATTR_RO(button_id);
|
| 66 |
|
| 67 | static struct attribute *quickstart_attrs[] = {
|
| 68 | &dev_attr_button_id.attr,
|
| 69 | NULL
|
| 70 | };
|
| 71 | ATTRIBUTE_GROUPS(quickstart);
|
| 72 |
|
| 73 | static void quickstart_notify(acpi_handle handle, u32 event, void *context)
|
| 74 | {
|
| 75 | struct quickstart_data *data = context;
|
| 76 |
|
| 77 | switch (event) {
|
| 78 | case QUICKSTART_EVENT_RUNTIME:
|
| 79 | mutex_lock(&data->input_lock);
|
| 80 | sparse_keymap_report_event(data->input_device, 0x1, 1, true);
|
| 81 | mutex_unlock(&data->input_lock);
|
| 82 |
|
| 83 | acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0);
|
| 84 | break;
|
| 85 | default:
|
| 86 | dev_err(data->dev, FW_INFO "Unexpected ACPI notify event (%u)\n", event);
|
| 87 | break;
|
| 88 | }
|
| 89 | }
|
| 90 |
|
| 91 | |
| 92 | * The GHID ACPI method is used to indicate the "role" of the button.
|
| 93 | * However, all the meanings of these values are vendor defined.
|
| 94 | *
|
| 95 | * We do however expose this value to user space.
|
| 96 |
|
| 97 | static int quickstart_get_ghid(struct quickstart_data *data)
|
| 98 | {
|
| 99 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
| 100 | acpi_handle handle = ACPI_HANDLE(data->dev);
|
| 101 | union acpi_object *obj;
|
| 102 | acpi_status status;
|
| 103 | int ret = 0;
|
| 104 |
|
| 105 | |
| 106 | * This returns a buffer telling the button usage ID,
|
| 107 | * and triggers pending notify events (The ones before booting).
|
| 108 |
|
| 109 | status = acpi_evaluate_object_typed(handle, "GHID", NULL, &buffer, ACPI_TYPE_BUFFER);
|
| 110 | if (ACPI_FAILURE(status))
|
| 111 | return -EIO;
|
| 112 |
|
| 113 | obj = buffer.pointer;
|
| 114 | if (!obj)
|
| 115 | return -ENODATA;
|
| 116 |
|
| 117 | |
| 118 | * Quoting the specification:
|
| 119 | * "The GHID method can return a BYTE, WORD, or DWORD.
|
| 120 | * The value must be encoded in little-endian byte
|
| 121 | * order (least significant byte first)."
|
| 122 |
|
| 123 | switch (obj->buffer.length) {
|
| 124 | case 1:
|
| 125 | data->id = obj->buffer.pointer[0];
|
| 126 | break;
|
| 127 | case 2:
|
| 128 | data->id = get_unaligned_le16(obj->buffer.pointer);
|
| 129 | break;
|
| 130 | case 4:
|
| 131 | data->id = get_unaligned_le32(obj->buffer.pointer);
|
| 132 | break;
|
| 133 | default:
|
| 134 | dev_err(data->dev,
|
| 135 | FW_BUG "GHID method returned buffer of unexpected length %u\n",
|
| 136 | obj->buffer.length);
|
| 137 | ret = -EIO;
|
| 138 | break;
|
| 139 | }
|
| 140 |
|
| 141 | kfree(obj);
|
| 142 |
|
| 143 | return ret;
|
| 144 | }
|
| 145 |
|
| 146 | static void quickstart_notify_remove(void *context)
|
| 147 | {
|
| 148 | struct quickstart_data *data = context;
|
| 149 | acpi_handle handle;
|
| 150 |
|
| 151 | handle = ACPI_HANDLE(data->dev);
|
| 152 |
|
| 153 | acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify);
|
| 154 | }
|
| 155 |
|
| 156 | static int quickstart_probe(struct platform_device *pdev)
|
| 157 | {
|
| 158 | struct quickstart_data *data;
|
| 159 | acpi_handle handle;
|
| 160 | acpi_status status;
|
| 161 | int ret;
|
| 162 |
|
| 163 | handle = ACPI_HANDLE(&pdev->dev);
|
| 164 | if (!handle)
|
| 165 | return -ENODEV;
|
| 166 |
|
| 167 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
| 168 | if (!data)
|
| 169 | return -ENOMEM;
|
| 170 |
|
| 171 | data->dev = &pdev->dev;
|
| 172 | dev_set_drvdata(&pdev->dev, data);
|
| 173 |
|
| 174 | ret = devm_mutex_init(&pdev->dev, &data->input_lock);
|
| 175 | if (ret < 0)
|
| 176 | return ret;
|
| 177 |
|
| 178 | |
| 179 | * We have to initialize the device wakeup before evaluating GHID because
|
| 180 | * doing so will notify the device if the button was used to wake the machine
|
| 181 | * from S5.
|
| 182 |
|
| 183 | device_init_wakeup(&pdev->dev, true);
|
| 184 |
|
| 185 | ret = quickstart_get_ghid(data);
|
| 186 | if (ret < 0)
|
| 187 | return ret;
|
| 188 |
|
| 189 | data->input_device = devm_input_allocate_device(&pdev->dev);
|
| 190 | if (!data->input_device)
|
| 191 | return -ENOMEM;
|
| 192 |
|
| 193 | ret = sparse_keymap_setup(data->input_device, quickstart_keymap, NULL);
|
| 194 | if (ret < 0)
|
| 195 | return ret;
|
| 196 |
|
| 197 | snprintf(data->input_name, sizeof(data->input_name), "Quickstart Button %u", data->id);
|
| 198 | snprintf(data->phys, sizeof(data->phys), DRIVER_NAME "/input%u", data->id);
|
| 199 |
|
| 200 | data->input_device->name = data->input_name;
|
| 201 | data->input_device->phys = data->phys;
|
| 202 | data->input_device->id.bustype = BUS_HOST;
|
| 203 |
|
| 204 | ret = input_register_device(data->input_device);
|
| 205 | if (ret < 0)
|
| 206 | return ret;
|
| 207 |
|
| 208 | status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify, data);
|
| 209 | if (ACPI_FAILURE(status))
|
| 210 | return -EIO;
|
| 211 |
|
| 212 | return devm_add_action_or_reset(&pdev->dev, quickstart_notify_remove, data);
|
| 213 | }
|
| 214 |
|
| 215 | static const struct acpi_device_id quickstart_device_ids[] = {
|
| 216 | { "PNP0C32" },
|
| 217 | { }
|
| 218 | };
|
| 219 | MODULE_DEVICE_TABLE(acpi, quickstart_device_ids);
|
| 220 |
|
| 221 | static struct platform_driver quickstart_platform_driver = {
|
| 222 | .driver = {
|
| 223 | .name = DRIVER_NAME,
|
| 224 | .dev_groups = quickstart_groups,
|
| 225 | .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
| 226 | .acpi_match_table = quickstart_device_ids,
|
| 227 | },
|
| 228 | .probe = quickstart_probe,
|
| 229 | };
|
| 230 | module_platform_driver(quickstart_platform_driver);
|
| 231 |
|
| 232 | MODULE_AUTHOR("Armin Wolf <[email protected]>");
|
| 233 | MODULE_AUTHOR("Arvid Norlander <[email protected]>");
|
| 234 | MODULE_AUTHOR("Angelo Arrifano");
|
| 235 | MODULE_DESCRIPTION("ACPI Direct App Launch driver");
|
| 236 | MODULE_LICENSE("GPL");
|