linux/drivers/extcon/extcon-gpio.c
<<
>>
Prefs
   1/*
   2 * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
   3 *
   4 * Copyright (C) 2008 Google, Inc.
   5 * Author: Mike Lockwood <lockwood@android.com>
   6 *
   7 * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon
   8 * (originally switch class is supported)
   9 *
  10 * This software is licensed under the terms of the GNU General Public
  11 * License version 2, as published by the Free Software Foundation, and
  12 * may be copied, distributed, and modified under those terms.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 */
  19
  20#include <linux/extcon.h>
  21#include <linux/extcon/extcon-gpio.h>
  22#include <linux/gpio.h>
  23#include <linux/gpio/consumer.h>
  24#include <linux/init.h>
  25#include <linux/interrupt.h>
  26#include <linux/kernel.h>
  27#include <linux/module.h>
  28#include <linux/platform_device.h>
  29#include <linux/slab.h>
  30#include <linux/workqueue.h>
  31
  32struct gpio_extcon_data {
  33        struct extcon_dev *edev;
  34        int irq;
  35        struct delayed_work work;
  36        unsigned long debounce_jiffies;
  37
  38        struct gpio_desc *id_gpiod;
  39        struct gpio_extcon_pdata *pdata;
  40};
  41
  42static void gpio_extcon_work(struct work_struct *work)
  43{
  44        int state;
  45        struct gpio_extcon_data *data =
  46                container_of(to_delayed_work(work), struct gpio_extcon_data,
  47                             work);
  48
  49        state = gpiod_get_value_cansleep(data->id_gpiod);
  50        if (data->pdata->gpio_active_low)
  51                state = !state;
  52        extcon_set_state(data->edev, state);
  53}
  54
  55static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
  56{
  57        struct gpio_extcon_data *data = dev_id;
  58
  59        queue_delayed_work(system_power_efficient_wq, &data->work,
  60                              data->debounce_jiffies);
  61        return IRQ_HANDLED;
  62}
  63
  64static int gpio_extcon_init(struct device *dev, struct gpio_extcon_data *data)
  65{
  66        struct gpio_extcon_pdata *pdata = data->pdata;
  67        int ret;
  68
  69        ret = devm_gpio_request_one(dev, pdata->gpio, GPIOF_DIR_IN,
  70                                dev_name(dev));
  71        if (ret < 0)
  72                return ret;
  73
  74        data->id_gpiod = gpio_to_desc(pdata->gpio);
  75        if (!data->id_gpiod)
  76                return -EINVAL;
  77
  78        if (pdata->debounce) {
  79                ret = gpiod_set_debounce(data->id_gpiod,
  80                                        pdata->debounce * 1000);
  81                if (ret < 0)
  82                        data->debounce_jiffies =
  83                                msecs_to_jiffies(pdata->debounce);
  84        }
  85
  86        data->irq = gpiod_to_irq(data->id_gpiod);
  87        if (data->irq < 0)
  88                return data->irq;
  89
  90        return 0;
  91}
  92
  93static int gpio_extcon_probe(struct platform_device *pdev)
  94{
  95        struct gpio_extcon_pdata *pdata = dev_get_platdata(&pdev->dev);
  96        struct gpio_extcon_data *data;
  97        int ret;
  98
  99        if (!pdata)
 100                return -EBUSY;
 101        if (!pdata->irq_flags || pdata->extcon_id > EXTCON_NONE)
 102                return -EINVAL;
 103
 104        data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data),
 105                                   GFP_KERNEL);
 106        if (!data)
 107                return -ENOMEM;
 108        data->pdata = pdata;
 109
 110        /* Initialize the gpio */
 111        ret = gpio_extcon_init(&pdev->dev, data);
 112        if (ret < 0)
 113                return ret;
 114
 115        /* Allocate the memory of extcon devie and register extcon device */
 116        data->edev = devm_extcon_dev_allocate(&pdev->dev, &pdata->extcon_id);
 117        if (IS_ERR(data->edev)) {
 118                dev_err(&pdev->dev, "failed to allocate extcon device\n");
 119                return -ENOMEM;
 120        }
 121
 122        ret = devm_extcon_dev_register(&pdev->dev, data->edev);
 123        if (ret < 0)
 124                return ret;
 125
 126        INIT_DELAYED_WORK(&data->work, gpio_extcon_work);
 127
 128        /*
 129         * Request the interrupt of gpio to detect whether external connector
 130         * is attached or detached.
 131         */
 132        ret = devm_request_any_context_irq(&pdev->dev, data->irq,
 133                                        gpio_irq_handler, pdata->irq_flags,
 134                                        pdev->name, data);
 135        if (ret < 0)
 136                return ret;
 137
 138        platform_set_drvdata(pdev, data);
 139        /* Perform initial detection */
 140        gpio_extcon_work(&data->work.work);
 141
 142        return 0;
 143}
 144
 145static int gpio_extcon_remove(struct platform_device *pdev)
 146{
 147        struct gpio_extcon_data *data = platform_get_drvdata(pdev);
 148
 149        cancel_delayed_work_sync(&data->work);
 150
 151        return 0;
 152}
 153
 154#ifdef CONFIG_PM_SLEEP
 155static int gpio_extcon_resume(struct device *dev)
 156{
 157        struct gpio_extcon_data *data;
 158
 159        data = dev_get_drvdata(dev);
 160        if (data->pdata->check_on_resume)
 161                queue_delayed_work(system_power_efficient_wq,
 162                        &data->work, data->debounce_jiffies);
 163
 164        return 0;
 165}
 166#endif
 167
 168static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
 169
 170static struct platform_driver gpio_extcon_driver = {
 171        .probe          = gpio_extcon_probe,
 172        .remove         = gpio_extcon_remove,
 173        .driver         = {
 174                .name   = "extcon-gpio",
 175                .pm     = &gpio_extcon_pm_ops,
 176        },
 177};
 178
 179module_platform_driver(gpio_extcon_driver);
 180
 181MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
 182MODULE_DESCRIPTION("GPIO extcon driver");
 183MODULE_LICENSE("GPL");
 184