linux/drivers/input/misc/soc_button_array.c
<<
>>
Prefs
   1/*
   2 * Supports for the button array on SoC tablets originally running
   3 * Windows 8.
   4 *
   5 * (C) Copyright 2014 Intel Corporation
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License
   9 * as published by the Free Software Foundation; version 2
  10 * of the License.
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/input.h>
  15#include <linux/init.h>
  16#include <linux/kernel.h>
  17#include <linux/acpi.h>
  18#include <linux/gpio/consumer.h>
  19#include <linux/gpio_keys.h>
  20#include <linux/platform_device.h>
  21
  22/*
  23 * Definition of buttons on the tablet. The ACPI index of each button
  24 * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
  25 * Platforms"
  26 */
  27#define MAX_NBUTTONS    5
  28
  29struct soc_button_info {
  30        const char *name;
  31        int acpi_index;
  32        unsigned int event_type;
  33        unsigned int event_code;
  34        bool autorepeat;
  35        bool wakeup;
  36};
  37
  38/*
  39 * Some of the buttons like volume up/down are auto repeat, while others
  40 * are not. To support both, we register two platform devices, and put
  41 * buttons into them based on whether the key should be auto repeat.
  42 */
  43#define BUTTON_TYPES    2
  44
  45struct soc_button_data {
  46        struct platform_device *children[BUTTON_TYPES];
  47};
  48
  49/*
  50 * Get the Nth GPIO number from the ACPI object.
  51 */
  52static int soc_button_lookup_gpio(struct device *dev, int acpi_index)
  53{
  54        struct gpio_desc *desc;
  55        int gpio;
  56
  57        desc = gpiod_get_index(dev, KBUILD_MODNAME, acpi_index, GPIOD_ASIS);
  58        if (IS_ERR(desc))
  59                return PTR_ERR(desc);
  60
  61        gpio = desc_to_gpio(desc);
  62
  63        gpiod_put(desc);
  64
  65        return gpio;
  66}
  67
  68static struct platform_device *
  69soc_button_device_create(struct platform_device *pdev,
  70                         const struct soc_button_info *button_info,
  71                         bool autorepeat)
  72{
  73        const struct soc_button_info *info;
  74        struct platform_device *pd;
  75        struct gpio_keys_button *gpio_keys;
  76        struct gpio_keys_platform_data *gpio_keys_pdata;
  77        int n_buttons = 0;
  78        int gpio;
  79        int error;
  80
  81        gpio_keys_pdata = devm_kzalloc(&pdev->dev,
  82                                       sizeof(*gpio_keys_pdata) +
  83                                        sizeof(*gpio_keys) * MAX_NBUTTONS,
  84                                       GFP_KERNEL);
  85        if (!gpio_keys_pdata)
  86                return ERR_PTR(-ENOMEM);
  87
  88        gpio_keys = (void *)(gpio_keys_pdata + 1);
  89
  90        for (info = button_info; info->name; info++) {
  91                if (info->autorepeat != autorepeat)
  92                        continue;
  93
  94                gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index);
  95                if (gpio < 0)
  96                        continue;
  97
  98                gpio_keys[n_buttons].type = info->event_type;
  99                gpio_keys[n_buttons].code = info->event_code;
 100                gpio_keys[n_buttons].gpio = gpio;
 101                gpio_keys[n_buttons].active_low = 1;
 102                gpio_keys[n_buttons].desc = info->name;
 103                gpio_keys[n_buttons].wakeup = info->wakeup;
 104                n_buttons++;
 105        }
 106
 107        if (n_buttons == 0) {
 108                error = -ENODEV;
 109                goto err_free_mem;
 110        }
 111
 112        gpio_keys_pdata->buttons = gpio_keys;
 113        gpio_keys_pdata->nbuttons = n_buttons;
 114        gpio_keys_pdata->rep = autorepeat;
 115
 116        pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
 117        if (!pd) {
 118                error = -ENOMEM;
 119                goto err_free_mem;
 120        }
 121
 122        error = platform_device_add_data(pd, gpio_keys_pdata,
 123                                         sizeof(*gpio_keys_pdata));
 124        if (error)
 125                goto err_free_pdev;
 126
 127        error = platform_device_add(pd);
 128        if (error)
 129                goto err_free_pdev;
 130
 131        return pd;
 132
 133err_free_pdev:
 134        platform_device_put(pd);
 135err_free_mem:
 136        devm_kfree(&pdev->dev, gpio_keys_pdata);
 137        return ERR_PTR(error);
 138}
 139
 140static int soc_button_remove(struct platform_device *pdev)
 141{
 142        struct soc_button_data *priv = platform_get_drvdata(pdev);
 143
 144        int i;
 145
 146        for (i = 0; i < BUTTON_TYPES; i++)
 147                if (priv->children[i])
 148                        platform_device_unregister(priv->children[i]);
 149
 150        return 0;
 151}
 152
 153static int soc_button_probe(struct platform_device *pdev)
 154{
 155        struct device *dev = &pdev->dev;
 156        const struct acpi_device_id *id;
 157        struct soc_button_info *button_info;
 158        struct soc_button_data *priv;
 159        struct platform_device *pd;
 160        int i;
 161        int error;
 162
 163        id = acpi_match_device(dev->driver->acpi_match_table, dev);
 164        if (!id)
 165                return -ENODEV;
 166
 167        button_info = (struct soc_button_info *)id->driver_data;
 168
 169        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 170        if (!priv)
 171                return -ENOMEM;
 172
 173        platform_set_drvdata(pdev, priv);
 174
 175        for (i = 0; i < BUTTON_TYPES; i++) {
 176                pd = soc_button_device_create(pdev, button_info, i == 0);
 177                if (IS_ERR(pd)) {
 178                        error = PTR_ERR(pd);
 179                        if (error != -ENODEV) {
 180                                soc_button_remove(pdev);
 181                                return error;
 182                        }
 183                        continue;
 184                }
 185
 186                priv->children[i] = pd;
 187        }
 188
 189        if (!priv->children[0] && !priv->children[1])
 190                return -ENODEV;
 191
 192        return 0;
 193}
 194
 195static struct soc_button_info soc_button_PNP0C40[] = {
 196        { "power", 0, EV_KEY, KEY_POWER, false, true },
 197        { "home", 1, EV_KEY, KEY_LEFTMETA, false, true },
 198        { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false },
 199        { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false },
 200        { "rotation_lock", 4, EV_SW, SW_ROTATE_LOCK, false, false },
 201        { }
 202};
 203
 204static const struct acpi_device_id soc_button_acpi_match[] = {
 205        { "PNP0C40", (unsigned long)soc_button_PNP0C40 },
 206        { }
 207};
 208
 209MODULE_DEVICE_TABLE(acpi, soc_button_acpi_match);
 210
 211static struct platform_driver soc_button_driver = {
 212        .probe          = soc_button_probe,
 213        .remove         = soc_button_remove,
 214        .driver         = {
 215                .name = KBUILD_MODNAME,
 216                .acpi_match_table = ACPI_PTR(soc_button_acpi_match),
 217        },
 218};
 219module_platform_driver(soc_button_driver);
 220
 221MODULE_LICENSE("GPL");
 222
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.