Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver
0004  *
0005  * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com
0006  * Author: Roger Quadros <rogerq@ti.com>
0007  */
0008 
0009 #include <linux/extcon-provider.h>
0010 #include <linux/gpio/consumer.h>
0011 #include <linux/init.h>
0012 #include <linux/interrupt.h>
0013 #include <linux/irq.h>
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/slab.h>
0018 #include <linux/workqueue.h>
0019 #include <linux/pinctrl/consumer.h>
0020 #include <linux/mod_devicetable.h>
0021 
0022 #define USB_GPIO_DEBOUNCE_MS    20  /* ms */
0023 
0024 struct usb_extcon_info {
0025     struct device *dev;
0026     struct extcon_dev *edev;
0027 
0028     struct gpio_desc *id_gpiod;
0029     struct gpio_desc *vbus_gpiod;
0030     int id_irq;
0031     int vbus_irq;
0032 
0033     unsigned long debounce_jiffies;
0034     struct delayed_work wq_detcable;
0035 };
0036 
0037 static const unsigned int usb_extcon_cable[] = {
0038     EXTCON_USB,
0039     EXTCON_USB_HOST,
0040     EXTCON_NONE,
0041 };
0042 
0043 /*
0044  * "USB" = VBUS and "USB-HOST" = !ID, so we have:
0045  * Both "USB" and "USB-HOST" can't be set as active at the
0046  * same time so if "USB-HOST" is active (i.e. ID is 0)  we keep "USB" inactive
0047  * even if VBUS is on.
0048  *
0049  *  State              |    ID   |   VBUS
0050  * ----------------------------------------
0051  *  [1] USB            |    H    |    H
0052  *  [2] none           |    H    |    L
0053  *  [3] USB-HOST       |    L    |    H
0054  *  [4] USB-HOST       |    L    |    L
0055  *
0056  * In case we have only one of these signals:
0057  * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1.
0058  * - ID only - we want to distinguish between [1] and [4], so VBUS = ID.
0059 */
0060 static void usb_extcon_detect_cable(struct work_struct *work)
0061 {
0062     int id, vbus;
0063     struct usb_extcon_info *info = container_of(to_delayed_work(work),
0064                             struct usb_extcon_info,
0065                             wq_detcable);
0066 
0067     /* check ID and VBUS and update cable state */
0068     id = info->id_gpiod ?
0069         gpiod_get_value_cansleep(info->id_gpiod) : 1;
0070     vbus = info->vbus_gpiod ?
0071         gpiod_get_value_cansleep(info->vbus_gpiod) : id;
0072 
0073     /* at first we clean states which are no longer active */
0074     if (id)
0075         extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
0076     if (!vbus)
0077         extcon_set_state_sync(info->edev, EXTCON_USB, false);
0078 
0079     if (!id) {
0080         extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
0081     } else {
0082         if (vbus)
0083             extcon_set_state_sync(info->edev, EXTCON_USB, true);
0084     }
0085 }
0086 
0087 static irqreturn_t usb_irq_handler(int irq, void *dev_id)
0088 {
0089     struct usb_extcon_info *info = dev_id;
0090 
0091     queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
0092                info->debounce_jiffies);
0093 
0094     return IRQ_HANDLED;
0095 }
0096 
0097 static int usb_extcon_probe(struct platform_device *pdev)
0098 {
0099     struct device *dev = &pdev->dev;
0100     struct device_node *np = dev->of_node;
0101     struct usb_extcon_info *info;
0102     int ret;
0103 
0104     if (!np)
0105         return -EINVAL;
0106 
0107     info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
0108     if (!info)
0109         return -ENOMEM;
0110 
0111     info->dev = dev;
0112     info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN);
0113     info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
0114                            GPIOD_IN);
0115 
0116     if (!info->id_gpiod && !info->vbus_gpiod) {
0117         dev_err(dev, "failed to get gpios\n");
0118         return -ENODEV;
0119     }
0120 
0121     if (IS_ERR(info->id_gpiod))
0122         return PTR_ERR(info->id_gpiod);
0123 
0124     if (IS_ERR(info->vbus_gpiod))
0125         return PTR_ERR(info->vbus_gpiod);
0126 
0127     info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
0128     if (IS_ERR(info->edev)) {
0129         dev_err(dev, "failed to allocate extcon device\n");
0130         return -ENOMEM;
0131     }
0132 
0133     ret = devm_extcon_dev_register(dev, info->edev);
0134     if (ret < 0) {
0135         dev_err(dev, "failed to register extcon device\n");
0136         return ret;
0137     }
0138 
0139     if (info->id_gpiod)
0140         ret = gpiod_set_debounce(info->id_gpiod,
0141                      USB_GPIO_DEBOUNCE_MS * 1000);
0142     if (!ret && info->vbus_gpiod)
0143         ret = gpiod_set_debounce(info->vbus_gpiod,
0144                      USB_GPIO_DEBOUNCE_MS * 1000);
0145 
0146     if (ret < 0)
0147         info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS);
0148 
0149     INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
0150 
0151     if (info->id_gpiod) {
0152         info->id_irq = gpiod_to_irq(info->id_gpiod);
0153         if (info->id_irq < 0) {
0154             dev_err(dev, "failed to get ID IRQ\n");
0155             return info->id_irq;
0156         }
0157 
0158         ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
0159                         usb_irq_handler,
0160                         IRQF_TRIGGER_RISING |
0161                         IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
0162                         pdev->name, info);
0163         if (ret < 0) {
0164             dev_err(dev, "failed to request handler for ID IRQ\n");
0165             return ret;
0166         }
0167     }
0168 
0169     if (info->vbus_gpiod) {
0170         info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
0171         if (info->vbus_irq < 0) {
0172             dev_err(dev, "failed to get VBUS IRQ\n");
0173             return info->vbus_irq;
0174         }
0175 
0176         ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
0177                         usb_irq_handler,
0178                         IRQF_TRIGGER_RISING |
0179                         IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
0180                         pdev->name, info);
0181         if (ret < 0) {
0182             dev_err(dev, "failed to request handler for VBUS IRQ\n");
0183             return ret;
0184         }
0185     }
0186 
0187     platform_set_drvdata(pdev, info);
0188     device_set_wakeup_capable(&pdev->dev, true);
0189 
0190     /* Perform initial detection */
0191     usb_extcon_detect_cable(&info->wq_detcable.work);
0192 
0193     return 0;
0194 }
0195 
0196 static int usb_extcon_remove(struct platform_device *pdev)
0197 {
0198     struct usb_extcon_info *info = platform_get_drvdata(pdev);
0199 
0200     cancel_delayed_work_sync(&info->wq_detcable);
0201     device_init_wakeup(&pdev->dev, false);
0202 
0203     return 0;
0204 }
0205 
0206 #ifdef CONFIG_PM_SLEEP
0207 static int usb_extcon_suspend(struct device *dev)
0208 {
0209     struct usb_extcon_info *info = dev_get_drvdata(dev);
0210     int ret = 0;
0211 
0212     if (device_may_wakeup(dev)) {
0213         if (info->id_gpiod) {
0214             ret = enable_irq_wake(info->id_irq);
0215             if (ret)
0216                 return ret;
0217         }
0218         if (info->vbus_gpiod) {
0219             ret = enable_irq_wake(info->vbus_irq);
0220             if (ret) {
0221                 if (info->id_gpiod)
0222                     disable_irq_wake(info->id_irq);
0223 
0224                 return ret;
0225             }
0226         }
0227     }
0228 
0229     if (!device_may_wakeup(dev))
0230         pinctrl_pm_select_sleep_state(dev);
0231 
0232     return ret;
0233 }
0234 
0235 static int usb_extcon_resume(struct device *dev)
0236 {
0237     struct usb_extcon_info *info = dev_get_drvdata(dev);
0238     int ret = 0;
0239 
0240     if (!device_may_wakeup(dev))
0241         pinctrl_pm_select_default_state(dev);
0242 
0243     if (device_may_wakeup(dev)) {
0244         if (info->id_gpiod) {
0245             ret = disable_irq_wake(info->id_irq);
0246             if (ret)
0247                 return ret;
0248         }
0249         if (info->vbus_gpiod) {
0250             ret = disable_irq_wake(info->vbus_irq);
0251             if (ret) {
0252                 if (info->id_gpiod)
0253                     enable_irq_wake(info->id_irq);
0254 
0255                 return ret;
0256             }
0257         }
0258     }
0259 
0260     queue_delayed_work(system_power_efficient_wq,
0261                &info->wq_detcable, 0);
0262 
0263     return ret;
0264 }
0265 #endif
0266 
0267 static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops,
0268              usb_extcon_suspend, usb_extcon_resume);
0269 
0270 static const struct of_device_id usb_extcon_dt_match[] = {
0271     { .compatible = "linux,extcon-usb-gpio", },
0272     { /* sentinel */ }
0273 };
0274 MODULE_DEVICE_TABLE(of, usb_extcon_dt_match);
0275 
0276 static const struct platform_device_id usb_extcon_platform_ids[] = {
0277     { .name = "extcon-usb-gpio", },
0278     { /* sentinel */ }
0279 };
0280 MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids);
0281 
0282 static struct platform_driver usb_extcon_driver = {
0283     .probe      = usb_extcon_probe,
0284     .remove     = usb_extcon_remove,
0285     .driver     = {
0286         .name   = "extcon-usb-gpio",
0287         .pm = &usb_extcon_pm_ops,
0288         .of_match_table = usb_extcon_dt_match,
0289     },
0290     .id_table = usb_extcon_platform_ids,
0291 };
0292 
0293 module_platform_driver(usb_extcon_driver);
0294 
0295 MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
0296 MODULE_DESCRIPTION("USB GPIO extcon driver");
0297 MODULE_LICENSE("GPL v2");