0001
0002
0003
0004
0005
0006
0007
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
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
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
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
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");