linux/drivers/extcon/extcon-qcom-spmi-misc.c
<<
>>
Prefs
   1/**
   2 * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
   3 *                              detection based on extcon-usb-gpio.c.
   4 *
   5 * Copyright (C) 2016 Linaro, Ltd.
   6 * Stephen Boyd <stephen.boyd@linaro.org>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 */
  17
  18#include <linux/extcon-provider.h>
  19#include <linux/init.h>
  20#include <linux/interrupt.h>
  21#include <linux/kernel.h>
  22#include <linux/module.h>
  23#include <linux/platform_device.h>
  24#include <linux/slab.h>
  25#include <linux/workqueue.h>
  26
  27#define USB_ID_DEBOUNCE_MS      5       /* ms */
  28
  29struct qcom_usb_extcon_info {
  30        struct extcon_dev *edev;
  31        int irq;
  32        struct delayed_work wq_detcable;
  33        unsigned long debounce_jiffies;
  34};
  35
  36static const unsigned int qcom_usb_extcon_cable[] = {
  37        EXTCON_USB_HOST,
  38        EXTCON_NONE,
  39};
  40
  41static void qcom_usb_extcon_detect_cable(struct work_struct *work)
  42{
  43        bool id;
  44        int ret;
  45        struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
  46                                                    struct qcom_usb_extcon_info,
  47                                                    wq_detcable);
  48
  49        /* check ID and update cable state */
  50        ret = irq_get_irqchip_state(info->irq, IRQCHIP_STATE_LINE_LEVEL, &id);
  51        if (ret)
  52                return;
  53
  54        extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !id);
  55}
  56
  57static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
  58{
  59        struct qcom_usb_extcon_info *info = dev_id;
  60
  61        queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
  62                           info->debounce_jiffies);
  63
  64        return IRQ_HANDLED;
  65}
  66
  67static int qcom_usb_extcon_probe(struct platform_device *pdev)
  68{
  69        struct device *dev = &pdev->dev;
  70        struct qcom_usb_extcon_info *info;
  71        int ret;
  72
  73        info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
  74        if (!info)
  75                return -ENOMEM;
  76
  77        info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
  78        if (IS_ERR(info->edev)) {
  79                dev_err(dev, "failed to allocate extcon device\n");
  80                return -ENOMEM;
  81        }
  82
  83        ret = devm_extcon_dev_register(dev, info->edev);
  84        if (ret < 0) {
  85                dev_err(dev, "failed to register extcon device\n");
  86                return ret;
  87        }
  88
  89        info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
  90        INIT_DELAYED_WORK(&info->wq_detcable, qcom_usb_extcon_detect_cable);
  91
  92        info->irq = platform_get_irq_byname(pdev, "usb_id");
  93        if (info->irq < 0)
  94                return info->irq;
  95
  96        ret = devm_request_threaded_irq(dev, info->irq, NULL,
  97                                        qcom_usb_irq_handler,
  98                                        IRQF_TRIGGER_RISING |
  99                                        IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
 100                                        pdev->name, info);
 101        if (ret < 0) {
 102                dev_err(dev, "failed to request handler for ID IRQ\n");
 103                return ret;
 104        }
 105
 106        platform_set_drvdata(pdev, info);
 107        device_init_wakeup(dev, 1);
 108
 109        /* Perform initial detection */
 110        qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
 111
 112        return 0;
 113}
 114
 115static int qcom_usb_extcon_remove(struct platform_device *pdev)
 116{
 117        struct qcom_usb_extcon_info *info = platform_get_drvdata(pdev);
 118
 119        cancel_delayed_work_sync(&info->wq_detcable);
 120
 121        return 0;
 122}
 123
 124#ifdef CONFIG_PM_SLEEP
 125static int qcom_usb_extcon_suspend(struct device *dev)
 126{
 127        struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
 128        int ret = 0;
 129
 130        if (device_may_wakeup(dev))
 131                ret = enable_irq_wake(info->irq);
 132
 133        return ret;
 134}
 135
 136static int qcom_usb_extcon_resume(struct device *dev)
 137{
 138        struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
 139        int ret = 0;
 140
 141        if (device_may_wakeup(dev))
 142                ret = disable_irq_wake(info->irq);
 143
 144        return ret;
 145}
 146#endif
 147
 148static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
 149                         qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
 150
 151static const struct of_device_id qcom_usb_extcon_dt_match[] = {
 152        { .compatible = "qcom,pm8941-misc", },
 153        { }
 154};
 155MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
 156
 157static struct platform_driver qcom_usb_extcon_driver = {
 158        .probe          = qcom_usb_extcon_probe,
 159        .remove         = qcom_usb_extcon_remove,
 160        .driver         = {
 161                .name   = "extcon-pm8941-misc",
 162                .pm     = &qcom_usb_extcon_pm_ops,
 163                .of_match_table = qcom_usb_extcon_dt_match,
 164        },
 165};
 166module_platform_driver(qcom_usb_extcon_driver);
 167
 168MODULE_DESCRIPTION("QCOM USB ID extcon driver");
 169MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
 170MODULE_LICENSE("GPL v2");
 171