extcon-qcom-spmi-misc.c 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /**
  3. * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
  4. * detection based on extcon-usb-gpio.c.
  5. *
  6. * Copyright (C) 2016 Linaro, Ltd.
  7. * Stephen Boyd <stephen.boyd@linaro.org>
  8. */
  9. #include <linux/extcon-provider.h>
  10. #include <linux/init.h>
  11. #include <linux/interrupt.h>
  12. #include <linux/kernel.h>
  13. #include <linux/module.h>
  14. #include <linux/mod_devicetable.h>
  15. #include <linux/platform_device.h>
  16. #include <linux/slab.h>
  17. #include <linux/workqueue.h>
  18. #define USB_ID_DEBOUNCE_MS 5 /* ms */
  19. struct qcom_usb_extcon_info {
  20. struct extcon_dev *edev;
  21. int irq;
  22. struct delayed_work wq_detcable;
  23. unsigned long debounce_jiffies;
  24. };
  25. static const unsigned int qcom_usb_extcon_cable[] = {
  26. EXTCON_USB_HOST,
  27. EXTCON_NONE,
  28. };
  29. static void qcom_usb_extcon_detect_cable(struct work_struct *work)
  30. {
  31. bool id;
  32. int ret;
  33. struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
  34. struct qcom_usb_extcon_info,
  35. wq_detcable);
  36. /* check ID and update cable state */
  37. ret = irq_get_irqchip_state(info->irq, IRQCHIP_STATE_LINE_LEVEL, &id);
  38. if (ret)
  39. return;
  40. extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !id);
  41. }
  42. static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
  43. {
  44. struct qcom_usb_extcon_info *info = dev_id;
  45. queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
  46. info->debounce_jiffies);
  47. return IRQ_HANDLED;
  48. }
  49. static int qcom_usb_extcon_probe(struct platform_device *pdev)
  50. {
  51. struct device *dev = &pdev->dev;
  52. struct qcom_usb_extcon_info *info;
  53. int ret;
  54. info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
  55. if (!info)
  56. return -ENOMEM;
  57. info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
  58. if (IS_ERR(info->edev)) {
  59. dev_err(dev, "failed to allocate extcon device\n");
  60. return -ENOMEM;
  61. }
  62. ret = devm_extcon_dev_register(dev, info->edev);
  63. if (ret < 0) {
  64. dev_err(dev, "failed to register extcon device\n");
  65. return ret;
  66. }
  67. info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
  68. INIT_DELAYED_WORK(&info->wq_detcable, qcom_usb_extcon_detect_cable);
  69. info->irq = platform_get_irq_byname(pdev, "usb_id");
  70. if (info->irq < 0)
  71. return info->irq;
  72. ret = devm_request_threaded_irq(dev, info->irq, NULL,
  73. qcom_usb_irq_handler,
  74. IRQF_TRIGGER_RISING |
  75. IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
  76. pdev->name, info);
  77. if (ret < 0) {
  78. dev_err(dev, "failed to request handler for ID IRQ\n");
  79. return ret;
  80. }
  81. platform_set_drvdata(pdev, info);
  82. device_init_wakeup(dev, 1);
  83. /* Perform initial detection */
  84. qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
  85. return 0;
  86. }
  87. static int qcom_usb_extcon_remove(struct platform_device *pdev)
  88. {
  89. struct qcom_usb_extcon_info *info = platform_get_drvdata(pdev);
  90. cancel_delayed_work_sync(&info->wq_detcable);
  91. return 0;
  92. }
  93. #ifdef CONFIG_PM_SLEEP
  94. static int qcom_usb_extcon_suspend(struct device *dev)
  95. {
  96. struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
  97. int ret = 0;
  98. if (device_may_wakeup(dev))
  99. ret = enable_irq_wake(info->irq);
  100. return ret;
  101. }
  102. static int qcom_usb_extcon_resume(struct device *dev)
  103. {
  104. struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
  105. int ret = 0;
  106. if (device_may_wakeup(dev))
  107. ret = disable_irq_wake(info->irq);
  108. return ret;
  109. }
  110. #endif
  111. static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
  112. qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
  113. static const struct of_device_id qcom_usb_extcon_dt_match[] = {
  114. { .compatible = "qcom,pm8941-misc", },
  115. { }
  116. };
  117. MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
  118. static struct platform_driver qcom_usb_extcon_driver = {
  119. .probe = qcom_usb_extcon_probe,
  120. .remove = qcom_usb_extcon_remove,
  121. .driver = {
  122. .name = "extcon-pm8941-misc",
  123. .pm = &qcom_usb_extcon_pm_ops,
  124. .of_match_table = qcom_usb_extcon_dt_match,
  125. },
  126. };
  127. module_platform_driver(qcom_usb_extcon_driver);
  128. MODULE_DESCRIPTION("QCOM USB ID extcon driver");
  129. MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
  130. MODULE_LICENSE("GPL v2");