linux/drivers/extcon/extcon-qcom-spmi-misc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/**
   3 * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
   4 *                      and VBUS detection based on extcon-usb-gpio.c.
   5 *
   6 * Copyright (C) 2016 Linaro, Ltd.
   7 * Stephen Boyd <stephen.boyd@linaro.org>
   8 */
   9
  10#include <linux/devm-helpers.h>
  11#include <linux/extcon-provider.h>
  12#include <linux/init.h>
  13#include <linux/interrupt.h>
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/mod_devicetable.h>
  17#include <linux/platform_device.h>
  18#include <linux/slab.h>
  19#include <linux/workqueue.h>
  20
  21#define USB_ID_DEBOUNCE_MS      5       /* ms */
  22
  23struct qcom_usb_extcon_info {
  24        struct extcon_dev *edev;
  25        int id_irq;
  26        int vbus_irq;
  27        struct delayed_work wq_detcable;
  28        unsigned long debounce_jiffies;
  29};
  30
  31static const unsigned int qcom_usb_extcon_cable[] = {
  32        EXTCON_USB,
  33        EXTCON_USB_HOST,
  34        EXTCON_NONE,
  35};
  36
  37static void qcom_usb_extcon_detect_cable(struct work_struct *work)
  38{
  39        bool state = false;
  40        int ret;
  41        union extcon_property_value val;
  42        struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
  43                                                    struct qcom_usb_extcon_info,
  44                                                    wq_detcable);
  45
  46        if (info->id_irq > 0) {
  47                /* check ID and update cable state */
  48                ret = irq_get_irqchip_state(info->id_irq,
  49                                IRQCHIP_STATE_LINE_LEVEL, &state);
  50                if (ret)
  51                        return;
  52
  53                if (!state) {
  54                        val.intval = true;
  55                        extcon_set_property(info->edev, EXTCON_USB_HOST,
  56                                                EXTCON_PROP_USB_SS, val);
  57                }
  58                extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !state);
  59        }
  60
  61        if (info->vbus_irq > 0) {
  62                /* check VBUS and update cable state */
  63                ret = irq_get_irqchip_state(info->vbus_irq,
  64                                IRQCHIP_STATE_LINE_LEVEL, &state);
  65                if (ret)
  66                        return;
  67
  68                if (state) {
  69                        val.intval = true;
  70                        extcon_set_property(info->edev, EXTCON_USB,
  71                                                EXTCON_PROP_USB_SS, val);
  72                }
  73                extcon_set_state_sync(info->edev, EXTCON_USB, state);
  74        }
  75}
  76
  77static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
  78{
  79        struct qcom_usb_extcon_info *info = dev_id;
  80
  81        queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
  82                           info->debounce_jiffies);
  83
  84        return IRQ_HANDLED;
  85}
  86
  87static int qcom_usb_extcon_probe(struct platform_device *pdev)
  88{
  89        struct device *dev = &pdev->dev;
  90        struct qcom_usb_extcon_info *info;
  91        int ret;
  92
  93        info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
  94        if (!info)
  95                return -ENOMEM;
  96
  97        info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
  98        if (IS_ERR(info->edev)) {
  99                dev_err(dev, "failed to allocate extcon device\n");
 100                return -ENOMEM;
 101        }
 102
 103        ret = devm_extcon_dev_register(dev, info->edev);
 104        if (ret < 0) {
 105                dev_err(dev, "failed to register extcon device\n");
 106                return ret;
 107        }
 108
 109        ret = extcon_set_property_capability(info->edev,
 110                        EXTCON_USB, EXTCON_PROP_USB_SS);
 111        ret |= extcon_set_property_capability(info->edev,
 112                        EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
 113        if (ret) {
 114                dev_err(dev, "failed to register extcon props rc=%d\n",
 115                                                ret);
 116                return ret;
 117        }
 118
 119        info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
 120
 121        ret = devm_delayed_work_autocancel(dev, &info->wq_detcable,
 122                                           qcom_usb_extcon_detect_cable);
 123        if (ret)
 124                return ret;
 125
 126        info->id_irq = platform_get_irq_byname(pdev, "usb_id");
 127        if (info->id_irq > 0) {
 128                ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
 129                                        qcom_usb_irq_handler,
 130                                        IRQF_TRIGGER_RISING |
 131                                        IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
 132                                        pdev->name, info);
 133                if (ret < 0) {
 134                        dev_err(dev, "failed to request handler for ID IRQ\n");
 135                        return ret;
 136                }
 137        }
 138
 139        info->vbus_irq = platform_get_irq_byname(pdev, "usb_vbus");
 140        if (info->vbus_irq > 0) {
 141                ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
 142                                        qcom_usb_irq_handler,
 143                                        IRQF_TRIGGER_RISING |
 144                                        IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
 145                                        pdev->name, info);
 146                if (ret < 0) {
 147                        dev_err(dev, "failed to request handler for VBUS IRQ\n");
 148                        return ret;
 149                }
 150        }
 151
 152        if (info->id_irq < 0 && info->vbus_irq < 0) {
 153                dev_err(dev, "ID and VBUS IRQ not found\n");
 154                return -EINVAL;
 155        }
 156
 157        platform_set_drvdata(pdev, info);
 158        device_init_wakeup(dev, 1);
 159
 160        /* Perform initial detection */
 161        qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
 162
 163        return 0;
 164}
 165
 166#ifdef CONFIG_PM_SLEEP
 167static int qcom_usb_extcon_suspend(struct device *dev)
 168{
 169        struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
 170        int ret = 0;
 171
 172        if (device_may_wakeup(dev)) {
 173                if (info->id_irq > 0)
 174                        ret = enable_irq_wake(info->id_irq);
 175                if (info->vbus_irq > 0)
 176                        ret = enable_irq_wake(info->vbus_irq);
 177        }
 178
 179        return ret;
 180}
 181
 182static int qcom_usb_extcon_resume(struct device *dev)
 183{
 184        struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
 185        int ret = 0;
 186
 187        if (device_may_wakeup(dev)) {
 188                if (info->id_irq > 0)
 189                        ret = disable_irq_wake(info->id_irq);
 190                if (info->vbus_irq > 0)
 191                        ret = disable_irq_wake(info->vbus_irq);
 192        }
 193
 194        return ret;
 195}
 196#endif
 197
 198static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
 199                         qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
 200
 201static const struct of_device_id qcom_usb_extcon_dt_match[] = {
 202        { .compatible = "qcom,pm8941-misc", },
 203        { }
 204};
 205MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
 206
 207static struct platform_driver qcom_usb_extcon_driver = {
 208        .probe          = qcom_usb_extcon_probe,
 209        .driver         = {
 210                .name   = "extcon-pm8941-misc",
 211                .pm     = &qcom_usb_extcon_pm_ops,
 212                .of_match_table = qcom_usb_extcon_dt_match,
 213        },
 214};
 215module_platform_driver(qcom_usb_extcon_driver);
 216
 217MODULE_DESCRIPTION("QCOM USB ID extcon driver");
 218MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
 219MODULE_LICENSE("GPL v2");
 220