linux/drivers/extcon/extcon-gpio.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
   4 *
   5 * Copyright (C) 2008 Google, Inc.
   6 * Author: Mike Lockwood <lockwood@android.com>
   7 *
   8 * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon
   9 * (originally switch class is supported)
  10 */
  11
  12#include <linux/devm-helpers.h>
  13#include <linux/extcon-provider.h>
  14#include <linux/gpio/consumer.h>
  15#include <linux/init.h>
  16#include <linux/interrupt.h>
  17#include <linux/kernel.h>
  18#include <linux/module.h>
  19#include <linux/platform_device.h>
  20#include <linux/slab.h>
  21#include <linux/workqueue.h>
  22
  23/**
  24 * struct gpio_extcon_data - A simple GPIO-controlled extcon device state container.
  25 * @edev:               Extcon device.
  26 * @work:               Work fired by the interrupt.
  27 * @debounce_jiffies:   Number of jiffies to wait for the GPIO to stabilize, from the debounce
  28 *                      value.
  29 * @gpiod:              GPIO descriptor for this external connector.
  30 * @extcon_id:          The unique id of specific external connector.
  31 * @debounce:           Debounce time for GPIO IRQ in ms.
  32 * @check_on_resume:    Boolean describing whether to check the state of gpio
  33 *                      while resuming from sleep.
  34 */
  35struct gpio_extcon_data {
  36        struct extcon_dev *edev;
  37        struct delayed_work work;
  38        unsigned long debounce_jiffies;
  39        struct gpio_desc *gpiod;
  40        unsigned int extcon_id;
  41        unsigned long debounce;
  42        bool check_on_resume;
  43};
  44
  45static void gpio_extcon_work(struct work_struct *work)
  46{
  47        int state;
  48        struct gpio_extcon_data *data =
  49                container_of(to_delayed_work(work), struct gpio_extcon_data,
  50                             work);
  51
  52        state = gpiod_get_value_cansleep(data->gpiod);
  53        extcon_set_state_sync(data->edev, data->extcon_id, state);
  54}
  55
  56static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
  57{
  58        struct gpio_extcon_data *data = dev_id;
  59
  60        queue_delayed_work(system_power_efficient_wq, &data->work,
  61                              data->debounce_jiffies);
  62        return IRQ_HANDLED;
  63}
  64
  65static int gpio_extcon_probe(struct platform_device *pdev)
  66{
  67        struct gpio_extcon_data *data;
  68        struct device *dev = &pdev->dev;
  69        unsigned long irq_flags;
  70        int irq;
  71        int ret;
  72
  73        data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL);
  74        if (!data)
  75                return -ENOMEM;
  76
  77        /*
  78         * FIXME: extcon_id represents the unique identifier of external
  79         * connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id
  80         * is necessary to register the extcon device. But, it's not yet
  81         * developed to get the extcon id from device-tree or others.
  82         * On later, it have to be solved.
  83         */
  84        if (data->extcon_id > EXTCON_NONE)
  85                return -EINVAL;
  86
  87        data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN);
  88        if (IS_ERR(data->gpiod))
  89                return PTR_ERR(data->gpiod);
  90        irq = gpiod_to_irq(data->gpiod);
  91        if (irq <= 0)
  92                return irq;
  93
  94        /*
  95         * It is unlikely that this is an acknowledged interrupt that goes
  96         * away after handling, what we are looking for are falling edges
  97         * if the signal is active low, and rising edges if the signal is
  98         * active high.
  99         */
 100        if (gpiod_is_active_low(data->gpiod))
 101                irq_flags = IRQF_TRIGGER_FALLING;
 102        else
 103                irq_flags = IRQF_TRIGGER_RISING;
 104
 105        /* Allocate the memory of extcon devie and register extcon device */
 106        data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id);
 107        if (IS_ERR(data->edev)) {
 108                dev_err(dev, "failed to allocate extcon device\n");
 109                return -ENOMEM;
 110        }
 111
 112        ret = devm_extcon_dev_register(dev, data->edev);
 113        if (ret < 0)
 114                return ret;
 115
 116        ret = devm_delayed_work_autocancel(dev, &data->work, gpio_extcon_work);
 117        if (ret)
 118                return ret;
 119
 120        /*
 121         * Request the interrupt of gpio to detect whether external connector
 122         * is attached or detached.
 123         */
 124        ret = devm_request_any_context_irq(dev, irq,
 125                                        gpio_irq_handler, irq_flags,
 126                                        pdev->name, data);
 127        if (ret < 0)
 128                return ret;
 129
 130        platform_set_drvdata(pdev, data);
 131        /* Perform initial detection */
 132        gpio_extcon_work(&data->work.work);
 133
 134        return 0;
 135}
 136
 137#ifdef CONFIG_PM_SLEEP
 138static int gpio_extcon_resume(struct device *dev)
 139{
 140        struct gpio_extcon_data *data;
 141
 142        data = dev_get_drvdata(dev);
 143        if (data->check_on_resume)
 144                queue_delayed_work(system_power_efficient_wq,
 145                        &data->work, data->debounce_jiffies);
 146
 147        return 0;
 148}
 149#endif
 150
 151static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
 152
 153static struct platform_driver gpio_extcon_driver = {
 154        .probe          = gpio_extcon_probe,
 155        .driver         = {
 156                .name   = "extcon-gpio",
 157                .pm     = &gpio_extcon_pm_ops,
 158        },
 159};
 160
 161module_platform_driver(gpio_extcon_driver);
 162
 163MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
 164MODULE_DESCRIPTION("GPIO extcon driver");
 165MODULE_LICENSE("GPL");
 166