Path: blob/master/drivers/extcon/extcon-qcom-spmi-misc.c
26378 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID3* and VBUS detection based on extcon-usb-gpio.c.4*5* Copyright (C) 2016 Linaro, Ltd.6* Stephen Boyd <[email protected]>7*/89#include <linux/devm-helpers.h>10#include <linux/extcon-provider.h>11#include <linux/init.h>12#include <linux/interrupt.h>13#include <linux/kernel.h>14#include <linux/module.h>15#include <linux/mod_devicetable.h>16#include <linux/platform_device.h>17#include <linux/slab.h>18#include <linux/workqueue.h>1920#define USB_ID_DEBOUNCE_MS 5 /* ms */2122struct qcom_usb_extcon_info {23struct extcon_dev *edev;24int id_irq;25int vbus_irq;26struct delayed_work wq_detcable;27unsigned long debounce_jiffies;28};2930static const unsigned int qcom_usb_extcon_cable[] = {31EXTCON_USB,32EXTCON_USB_HOST,33EXTCON_NONE,34};3536static void qcom_usb_extcon_detect_cable(struct work_struct *work)37{38bool state = false;39int ret;40union extcon_property_value val;41struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),42struct qcom_usb_extcon_info,43wq_detcable);4445if (info->id_irq > 0) {46/* check ID and update cable state */47ret = irq_get_irqchip_state(info->id_irq,48IRQCHIP_STATE_LINE_LEVEL, &state);49if (ret)50return;5152if (!state) {53val.intval = true;54extcon_set_property(info->edev, EXTCON_USB_HOST,55EXTCON_PROP_USB_SS, val);56}57extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !state);58}5960if (info->vbus_irq > 0) {61/* check VBUS and update cable state */62ret = irq_get_irqchip_state(info->vbus_irq,63IRQCHIP_STATE_LINE_LEVEL, &state);64if (ret)65return;6667if (state) {68val.intval = true;69extcon_set_property(info->edev, EXTCON_USB,70EXTCON_PROP_USB_SS, val);71}72extcon_set_state_sync(info->edev, EXTCON_USB, state);73}74}7576static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)77{78struct qcom_usb_extcon_info *info = dev_id;7980queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,81info->debounce_jiffies);8283return IRQ_HANDLED;84}8586static int qcom_usb_extcon_probe(struct platform_device *pdev)87{88struct device *dev = &pdev->dev;89struct qcom_usb_extcon_info *info;90int ret;9192info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);93if (!info)94return -ENOMEM;9596info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);97if (IS_ERR(info->edev)) {98dev_err(dev, "failed to allocate extcon device\n");99return -ENOMEM;100}101102ret = devm_extcon_dev_register(dev, info->edev);103if (ret < 0) {104dev_err(dev, "failed to register extcon device\n");105return ret;106}107108ret = extcon_set_property_capability(info->edev,109EXTCON_USB, EXTCON_PROP_USB_SS);110ret |= extcon_set_property_capability(info->edev,111EXTCON_USB_HOST, EXTCON_PROP_USB_SS);112if (ret) {113dev_err(dev, "failed to register extcon props rc=%d\n",114ret);115return ret;116}117118info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);119120ret = devm_delayed_work_autocancel(dev, &info->wq_detcable,121qcom_usb_extcon_detect_cable);122if (ret)123return ret;124125info->id_irq = platform_get_irq_byname_optional(pdev, "usb_id");126if (info->id_irq > 0) {127ret = devm_request_threaded_irq(dev, info->id_irq, NULL,128qcom_usb_irq_handler,129IRQF_TRIGGER_RISING |130IRQF_TRIGGER_FALLING | IRQF_ONESHOT,131pdev->name, info);132if (ret < 0) {133dev_err(dev, "failed to request handler for ID IRQ\n");134return ret;135}136}137138info->vbus_irq = platform_get_irq_byname_optional(pdev, "usb_vbus");139if (info->vbus_irq > 0) {140ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,141qcom_usb_irq_handler,142IRQF_TRIGGER_RISING |143IRQF_TRIGGER_FALLING | IRQF_ONESHOT,144pdev->name, info);145if (ret < 0) {146dev_err(dev, "failed to request handler for VBUS IRQ\n");147return ret;148}149}150151if (info->id_irq < 0 && info->vbus_irq < 0) {152dev_err(dev, "ID and VBUS IRQ not found\n");153return -EINVAL;154}155156platform_set_drvdata(pdev, info);157device_init_wakeup(dev, 1);158159/* Perform initial detection */160qcom_usb_extcon_detect_cable(&info->wq_detcable.work);161162return 0;163}164165#ifdef CONFIG_PM_SLEEP166static int qcom_usb_extcon_suspend(struct device *dev)167{168struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);169int ret = 0;170171if (device_may_wakeup(dev)) {172if (info->id_irq > 0)173ret = enable_irq_wake(info->id_irq);174if (info->vbus_irq > 0)175ret = enable_irq_wake(info->vbus_irq);176}177178return ret;179}180181static int qcom_usb_extcon_resume(struct device *dev)182{183struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);184int ret = 0;185186if (device_may_wakeup(dev)) {187if (info->id_irq > 0)188ret = disable_irq_wake(info->id_irq);189if (info->vbus_irq > 0)190ret = disable_irq_wake(info->vbus_irq);191}192193return ret;194}195#endif196197static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,198qcom_usb_extcon_suspend, qcom_usb_extcon_resume);199200static const struct of_device_id qcom_usb_extcon_dt_match[] = {201{ .compatible = "qcom,pm8941-misc", },202{ }203};204MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);205206static struct platform_driver qcom_usb_extcon_driver = {207.probe = qcom_usb_extcon_probe,208.driver = {209.name = "extcon-pm8941-misc",210.pm = &qcom_usb_extcon_pm_ops,211.of_match_table = qcom_usb_extcon_dt_match,212},213};214module_platform_driver(qcom_usb_extcon_driver);215216MODULE_DESCRIPTION("QCOM USB ID extcon driver");217MODULE_AUTHOR("Stephen Boyd <[email protected]>");218MODULE_LICENSE("GPL v2");219220221