linux/drivers/platform/x86/dell-rbtn.c
<<
>>
Prefs
   1/*
   2    Dell Airplane Mode Switch driver
   3    Copyright (C) 2014-2015  Pali Rohár <pali.rohar@gmail.com>
   4
   5    This program is free software; you can redistribute it and/or modify
   6    it under the terms of the GNU General Public License as published by
   7    the Free Software Foundation; either version 2 of the License, or
   8    (at your option) any later version.
   9
  10    This program is distributed in the hope that it will be useful,
  11    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13    GNU General Public License for more details.
  14*/
  15
  16#include <linux/module.h>
  17#include <linux/acpi.h>
  18#include <linux/rfkill.h>
  19#include <linux/input.h>
  20
  21enum rbtn_type {
  22        RBTN_UNKNOWN,
  23        RBTN_TOGGLE,
  24        RBTN_SLIDER,
  25};
  26
  27struct rbtn_data {
  28        enum rbtn_type type;
  29        struct rfkill *rfkill;
  30        struct input_dev *input_dev;
  31        bool suspended;
  32};
  33
  34
  35/*
  36 * acpi functions
  37 */
  38
  39static enum rbtn_type rbtn_check(struct acpi_device *device)
  40{
  41        unsigned long long output;
  42        acpi_status status;
  43
  44        status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
  45        if (ACPI_FAILURE(status))
  46                return RBTN_UNKNOWN;
  47
  48        switch (output) {
  49        case 0:
  50        case 1:
  51                return RBTN_TOGGLE;
  52        case 2:
  53        case 3:
  54                return RBTN_SLIDER;
  55        default:
  56                return RBTN_UNKNOWN;
  57        }
  58}
  59
  60static int rbtn_get(struct acpi_device *device)
  61{
  62        unsigned long long output;
  63        acpi_status status;
  64
  65        status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
  66        if (ACPI_FAILURE(status))
  67                return -EINVAL;
  68
  69        return !output;
  70}
  71
  72static int rbtn_acquire(struct acpi_device *device, bool enable)
  73{
  74        struct acpi_object_list input;
  75        union acpi_object param;
  76        acpi_status status;
  77
  78        param.type = ACPI_TYPE_INTEGER;
  79        param.integer.value = enable;
  80        input.count = 1;
  81        input.pointer = &param;
  82
  83        status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
  84        if (ACPI_FAILURE(status))
  85                return -EINVAL;
  86
  87        return 0;
  88}
  89
  90
  91/*
  92 * rfkill device
  93 */
  94
  95static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
  96{
  97        struct acpi_device *device = data;
  98        int state;
  99
 100        state = rbtn_get(device);
 101        if (state < 0)
 102                return;
 103
 104        rfkill_set_states(rfkill, state, state);
 105}
 106
 107static int rbtn_rfkill_set_block(void *data, bool blocked)
 108{
 109        /* NOTE: setting soft rfkill state is not supported */
 110        return -EINVAL;
 111}
 112
 113static const struct rfkill_ops rbtn_ops = {
 114        .query = rbtn_rfkill_query,
 115        .set_block = rbtn_rfkill_set_block,
 116};
 117
 118static int rbtn_rfkill_init(struct acpi_device *device)
 119{
 120        struct rbtn_data *rbtn_data = device->driver_data;
 121        int ret;
 122
 123        if (rbtn_data->rfkill)
 124                return 0;
 125
 126        /*
 127         * NOTE: rbtn controls all radio devices, not only WLAN
 128         *       but rfkill interface does not support "ANY" type
 129         *       so "WLAN" type is used
 130         */
 131        rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
 132                                         RFKILL_TYPE_WLAN, &rbtn_ops, device);
 133        if (!rbtn_data->rfkill)
 134                return -ENOMEM;
 135
 136        ret = rfkill_register(rbtn_data->rfkill);
 137        if (ret) {
 138                rfkill_destroy(rbtn_data->rfkill);
 139                rbtn_data->rfkill = NULL;
 140                return ret;
 141        }
 142
 143        return 0;
 144}
 145
 146static void rbtn_rfkill_exit(struct acpi_device *device)
 147{
 148        struct rbtn_data *rbtn_data = device->driver_data;
 149
 150        if (!rbtn_data->rfkill)
 151                return;
 152
 153        rfkill_unregister(rbtn_data->rfkill);
 154        rfkill_destroy(rbtn_data->rfkill);
 155        rbtn_data->rfkill = NULL;
 156}
 157
 158static void rbtn_rfkill_event(struct acpi_device *device)
 159{
 160        struct rbtn_data *rbtn_data = device->driver_data;
 161
 162        if (rbtn_data->rfkill)
 163                rbtn_rfkill_query(rbtn_data->rfkill, device);
 164}
 165
 166
 167/*
 168 * input device
 169 */
 170
 171static int rbtn_input_init(struct rbtn_data *rbtn_data)
 172{
 173        int ret;
 174
 175        rbtn_data->input_dev = input_allocate_device();
 176        if (!rbtn_data->input_dev)
 177                return -ENOMEM;
 178
 179        rbtn_data->input_dev->name = "DELL Wireless hotkeys";
 180        rbtn_data->input_dev->phys = "dellabce/input0";
 181        rbtn_data->input_dev->id.bustype = BUS_HOST;
 182        rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
 183        set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
 184
 185        ret = input_register_device(rbtn_data->input_dev);
 186        if (ret) {
 187                input_free_device(rbtn_data->input_dev);
 188                rbtn_data->input_dev = NULL;
 189                return ret;
 190        }
 191
 192        return 0;
 193}
 194
 195static void rbtn_input_exit(struct rbtn_data *rbtn_data)
 196{
 197        input_unregister_device(rbtn_data->input_dev);
 198        rbtn_data->input_dev = NULL;
 199}
 200
 201static void rbtn_input_event(struct rbtn_data *rbtn_data)
 202{
 203        input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
 204        input_sync(rbtn_data->input_dev);
 205        input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
 206        input_sync(rbtn_data->input_dev);
 207}
 208
 209
 210/*
 211 * acpi driver
 212 */
 213
 214static int rbtn_add(struct acpi_device *device);
 215static int rbtn_remove(struct acpi_device *device);
 216static void rbtn_notify(struct acpi_device *device, u32 event);
 217
 218static const struct acpi_device_id rbtn_ids[] = {
 219        { "DELRBTN", 0 },
 220        { "DELLABCE", 0 },
 221
 222        /*
 223         * This driver can also handle the "DELLABC6" device that
 224         * appears on the XPS 13 9350, but that device is disabled by
 225         * the DSDT unless booted with acpi_osi="!Windows 2012"
 226         * acpi_osi="!Windows 2013".
 227         *
 228         * According to Mario at Dell:
 229         *
 230         *  DELLABC6 is a custom interface that was created solely to
 231         *  have airplane mode support for Windows 7.  For Windows 10
 232         *  the proper interface is to use that which is handled by
 233         *  intel-hid. A OEM airplane mode driver is not used.
 234         *
 235         *  Since the kernel doesn't identify as Windows 7 it would be
 236         *  incorrect to do attempt to use that interface.
 237         *
 238         * Even if we override _OSI and bind to DELLABC6, we end up with
 239         * inconsistent behavior in which userspace can get out of sync
 240         * with the rfkill state as it conflicts with events from
 241         * intel-hid.
 242         *
 243         * The upshot is that it is better to just ignore DELLABC6
 244         * devices.
 245         */
 246
 247        { "", 0 },
 248};
 249
 250#ifdef CONFIG_PM_SLEEP
 251static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)
 252{
 253        struct rbtn_data *rbtn_data = context;
 254
 255        rbtn_data->suspended = false;
 256}
 257
 258static int rbtn_suspend(struct device *dev)
 259{
 260        struct acpi_device *device = to_acpi_device(dev);
 261        struct rbtn_data *rbtn_data = acpi_driver_data(device);
 262
 263        rbtn_data->suspended = true;
 264
 265        return 0;
 266}
 267
 268static int rbtn_resume(struct device *dev)
 269{
 270        struct acpi_device *device = to_acpi_device(dev);
 271        struct rbtn_data *rbtn_data = acpi_driver_data(device);
 272        acpi_status status;
 273
 274        /*
 275         * Upon resume, some BIOSes send an ACPI notification thet triggers
 276         * an unwanted input event. In order to ignore it, we use a flag
 277         * that we set at suspend and clear once we have received the extra
 278         * ACPI notification. Since ACPI notifications are delivered
 279         * asynchronously to drivers, we clear the flag from the workqueue
 280         * used to deliver the notifications. This should be enough
 281         * to have the flag cleared only after we received the extra
 282         * notification, if any.
 283         */
 284        status = acpi_os_execute(OSL_NOTIFY_HANDLER,
 285                         rbtn_clear_suspended_flag, rbtn_data);
 286        if (ACPI_FAILURE(status))
 287                rbtn_clear_suspended_flag(rbtn_data);
 288
 289        return 0;
 290}
 291#endif
 292
 293static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);
 294
 295static struct acpi_driver rbtn_driver = {
 296        .name = "dell-rbtn",
 297        .ids = rbtn_ids,
 298        .drv.pm = &rbtn_pm_ops,
 299        .ops = {
 300                .add = rbtn_add,
 301                .remove = rbtn_remove,
 302                .notify = rbtn_notify,
 303        },
 304        .owner = THIS_MODULE,
 305};
 306
 307
 308/*
 309 * notifier export functions
 310 */
 311
 312static bool auto_remove_rfkill = true;
 313
 314static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
 315
 316static int rbtn_inc_count(struct device *dev, void *data)
 317{
 318        struct acpi_device *device = to_acpi_device(dev);
 319        struct rbtn_data *rbtn_data = device->driver_data;
 320        int *count = data;
 321
 322        if (rbtn_data->type == RBTN_SLIDER)
 323                (*count)++;
 324
 325        return 0;
 326}
 327
 328static int rbtn_switch_dev(struct device *dev, void *data)
 329{
 330        struct acpi_device *device = to_acpi_device(dev);
 331        struct rbtn_data *rbtn_data = device->driver_data;
 332        bool enable = data;
 333
 334        if (rbtn_data->type != RBTN_SLIDER)
 335                return 0;
 336
 337        if (enable)
 338                rbtn_rfkill_init(device);
 339        else
 340                rbtn_rfkill_exit(device);
 341
 342        return 0;
 343}
 344
 345int dell_rbtn_notifier_register(struct notifier_block *nb)
 346{
 347        bool first;
 348        int count;
 349        int ret;
 350
 351        count = 0;
 352        ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
 353                                     rbtn_inc_count);
 354        if (ret || count == 0)
 355                return -ENODEV;
 356
 357        first = !rbtn_chain_head.head;
 358
 359        ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);
 360        if (ret != 0)
 361                return ret;
 362
 363        if (auto_remove_rfkill && first)
 364                ret = driver_for_each_device(&rbtn_driver.drv, NULL,
 365                                             (void *)false, rbtn_switch_dev);
 366
 367        return ret;
 368}
 369EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);
 370
 371int dell_rbtn_notifier_unregister(struct notifier_block *nb)
 372{
 373        int ret;
 374
 375        ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);
 376        if (ret != 0)
 377                return ret;
 378
 379        if (auto_remove_rfkill && !rbtn_chain_head.head)
 380                ret = driver_for_each_device(&rbtn_driver.drv, NULL,
 381                                             (void *)true, rbtn_switch_dev);
 382
 383        return ret;
 384}
 385EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
 386
 387
 388/*
 389 * acpi driver functions
 390 */
 391
 392static int rbtn_add(struct acpi_device *device)
 393{
 394        struct rbtn_data *rbtn_data;
 395        enum rbtn_type type;
 396        int ret = 0;
 397
 398        type = rbtn_check(device);
 399        if (type == RBTN_UNKNOWN) {
 400                dev_info(&device->dev, "Unknown device type\n");
 401                return -EINVAL;
 402        }
 403
 404        ret = rbtn_acquire(device, true);
 405        if (ret < 0) {
 406                dev_err(&device->dev, "Cannot enable device\n");
 407                return ret;
 408        }
 409
 410        rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
 411        if (!rbtn_data)
 412                return -ENOMEM;
 413
 414        rbtn_data->type = type;
 415        device->driver_data = rbtn_data;
 416
 417        switch (rbtn_data->type) {
 418        case RBTN_TOGGLE:
 419                ret = rbtn_input_init(rbtn_data);
 420                break;
 421        case RBTN_SLIDER:
 422                if (auto_remove_rfkill && rbtn_chain_head.head)
 423                        ret = 0;
 424                else
 425                        ret = rbtn_rfkill_init(device);
 426                break;
 427        default:
 428                ret = -EINVAL;
 429        }
 430
 431        return ret;
 432
 433}
 434
 435static int rbtn_remove(struct acpi_device *device)
 436{
 437        struct rbtn_data *rbtn_data = device->driver_data;
 438
 439        switch (rbtn_data->type) {
 440        case RBTN_TOGGLE:
 441                rbtn_input_exit(rbtn_data);
 442                break;
 443        case RBTN_SLIDER:
 444                rbtn_rfkill_exit(device);
 445                break;
 446        default:
 447                break;
 448        }
 449
 450        rbtn_acquire(device, false);
 451        device->driver_data = NULL;
 452
 453        return 0;
 454}
 455
 456static void rbtn_notify(struct acpi_device *device, u32 event)
 457{
 458        struct rbtn_data *rbtn_data = device->driver_data;
 459
 460        /*
 461         * Some BIOSes send a notification at resume.
 462         * Ignore it to prevent unwanted input events.
 463         */
 464        if (rbtn_data->suspended) {
 465                dev_dbg(&device->dev, "ACPI notification ignored\n");
 466                return;
 467        }
 468
 469        if (event != 0x80) {
 470                dev_info(&device->dev, "Received unknown event (0x%x)\n",
 471                         event);
 472                return;
 473        }
 474
 475        switch (rbtn_data->type) {
 476        case RBTN_TOGGLE:
 477                rbtn_input_event(rbtn_data);
 478                break;
 479        case RBTN_SLIDER:
 480                rbtn_rfkill_event(device);
 481                atomic_notifier_call_chain(&rbtn_chain_head, event, device);
 482                break;
 483        default:
 484                break;
 485        }
 486}
 487
 488
 489/*
 490 * module functions
 491 */
 492
 493module_acpi_driver(rbtn_driver);
 494
 495module_param(auto_remove_rfkill, bool, 0444);
 496
 497MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "
 498                                     "other modules start receiving events "
 499                                     "from this module and re-add them when "
 500                                     "the last module stops receiving events "
 501                                     "(default true)");
 502MODULE_DEVICE_TABLE(acpi, rbtn_ids);
 503MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
 504MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
 505MODULE_LICENSE("GPL");
 506