Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /**
0003  * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
0004  *          and VBUS detection based on extcon-usb-gpio.c.
0005  *
0006  * Copyright (C) 2016 Linaro, Ltd.
0007  * Stephen Boyd <stephen.boyd@linaro.org>
0008  */
0009 
0010 #include <linux/devm-helpers.h>
0011 #include <linux/extcon-provider.h>
0012 #include <linux/init.h>
0013 #include <linux/interrupt.h>
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/mod_devicetable.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/slab.h>
0019 #include <linux/workqueue.h>
0020 
0021 #define USB_ID_DEBOUNCE_MS  5   /* ms */
0022 
0023 struct qcom_usb_extcon_info {
0024     struct extcon_dev *edev;
0025     int id_irq;
0026     int vbus_irq;
0027     struct delayed_work wq_detcable;
0028     unsigned long debounce_jiffies;
0029 };
0030 
0031 static const unsigned int qcom_usb_extcon_cable[] = {
0032     EXTCON_USB,
0033     EXTCON_USB_HOST,
0034     EXTCON_NONE,
0035 };
0036 
0037 static void qcom_usb_extcon_detect_cable(struct work_struct *work)
0038 {
0039     bool state = false;
0040     int ret;
0041     union extcon_property_value val;
0042     struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
0043                             struct qcom_usb_extcon_info,
0044                             wq_detcable);
0045 
0046     if (info->id_irq > 0) {
0047         /* check ID and update cable state */
0048         ret = irq_get_irqchip_state(info->id_irq,
0049                 IRQCHIP_STATE_LINE_LEVEL, &state);
0050         if (ret)
0051             return;
0052 
0053         if (!state) {
0054             val.intval = true;
0055             extcon_set_property(info->edev, EXTCON_USB_HOST,
0056                         EXTCON_PROP_USB_SS, val);
0057         }
0058         extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !state);
0059     }
0060 
0061     if (info->vbus_irq > 0) {
0062         /* check VBUS and update cable state */
0063         ret = irq_get_irqchip_state(info->vbus_irq,
0064                 IRQCHIP_STATE_LINE_LEVEL, &state);
0065         if (ret)
0066             return;
0067 
0068         if (state) {
0069             val.intval = true;
0070             extcon_set_property(info->edev, EXTCON_USB,
0071                         EXTCON_PROP_USB_SS, val);
0072         }
0073         extcon_set_state_sync(info->edev, EXTCON_USB, state);
0074     }
0075 }
0076 
0077 static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
0078 {
0079     struct qcom_usb_extcon_info *info = dev_id;
0080 
0081     queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
0082                info->debounce_jiffies);
0083 
0084     return IRQ_HANDLED;
0085 }
0086 
0087 static int qcom_usb_extcon_probe(struct platform_device *pdev)
0088 {
0089     struct device *dev = &pdev->dev;
0090     struct qcom_usb_extcon_info *info;
0091     int ret;
0092 
0093     info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
0094     if (!info)
0095         return -ENOMEM;
0096 
0097     info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
0098     if (IS_ERR(info->edev)) {
0099         dev_err(dev, "failed to allocate extcon device\n");
0100         return -ENOMEM;
0101     }
0102 
0103     ret = devm_extcon_dev_register(dev, info->edev);
0104     if (ret < 0) {
0105         dev_err(dev, "failed to register extcon device\n");
0106         return ret;
0107     }
0108 
0109     ret = extcon_set_property_capability(info->edev,
0110             EXTCON_USB, EXTCON_PROP_USB_SS);
0111     ret |= extcon_set_property_capability(info->edev,
0112             EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
0113     if (ret) {
0114         dev_err(dev, "failed to register extcon props rc=%d\n",
0115                         ret);
0116         return ret;
0117     }
0118 
0119     info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
0120 
0121     ret = devm_delayed_work_autocancel(dev, &info->wq_detcable,
0122                        qcom_usb_extcon_detect_cable);
0123     if (ret)
0124         return ret;
0125 
0126     info->id_irq = platform_get_irq_byname(pdev, "usb_id");
0127     if (info->id_irq > 0) {
0128         ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
0129                     qcom_usb_irq_handler,
0130                     IRQF_TRIGGER_RISING |
0131                     IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
0132                     pdev->name, info);
0133         if (ret < 0) {
0134             dev_err(dev, "failed to request handler for ID IRQ\n");
0135             return ret;
0136         }
0137     }
0138 
0139     info->vbus_irq = platform_get_irq_byname(pdev, "usb_vbus");
0140     if (info->vbus_irq > 0) {
0141         ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
0142                     qcom_usb_irq_handler,
0143                     IRQF_TRIGGER_RISING |
0144                     IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
0145                     pdev->name, info);
0146         if (ret < 0) {
0147             dev_err(dev, "failed to request handler for VBUS IRQ\n");
0148             return ret;
0149         }
0150     }
0151 
0152     if (info->id_irq < 0 && info->vbus_irq < 0) {
0153         dev_err(dev, "ID and VBUS IRQ not found\n");
0154         return -EINVAL;
0155     }
0156 
0157     platform_set_drvdata(pdev, info);
0158     device_init_wakeup(dev, 1);
0159 
0160     /* Perform initial detection */
0161     qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
0162 
0163     return 0;
0164 }
0165 
0166 #ifdef CONFIG_PM_SLEEP
0167 static int qcom_usb_extcon_suspend(struct device *dev)
0168 {
0169     struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
0170     int ret = 0;
0171 
0172     if (device_may_wakeup(dev)) {
0173         if (info->id_irq > 0)
0174             ret = enable_irq_wake(info->id_irq);
0175         if (info->vbus_irq > 0)
0176             ret = enable_irq_wake(info->vbus_irq);
0177     }
0178 
0179     return ret;
0180 }
0181 
0182 static int qcom_usb_extcon_resume(struct device *dev)
0183 {
0184     struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
0185     int ret = 0;
0186 
0187     if (device_may_wakeup(dev)) {
0188         if (info->id_irq > 0)
0189             ret = disable_irq_wake(info->id_irq);
0190         if (info->vbus_irq > 0)
0191             ret = disable_irq_wake(info->vbus_irq);
0192     }
0193 
0194     return ret;
0195 }
0196 #endif
0197 
0198 static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
0199              qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
0200 
0201 static const struct of_device_id qcom_usb_extcon_dt_match[] = {
0202     { .compatible = "qcom,pm8941-misc", },
0203     { }
0204 };
0205 MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
0206 
0207 static struct platform_driver qcom_usb_extcon_driver = {
0208     .probe      = qcom_usb_extcon_probe,
0209     .driver     = {
0210         .name   = "extcon-pm8941-misc",
0211         .pm = &qcom_usb_extcon_pm_ops,
0212         .of_match_table = qcom_usb_extcon_dt_match,
0213     },
0214 };
0215 module_platform_driver(qcom_usb_extcon_driver);
0216 
0217 MODULE_DESCRIPTION("QCOM USB ID extcon driver");
0218 MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
0219 MODULE_LICENSE("GPL v2");