Merge ath-next from git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git
[sfrench/cifs-2.6.git] / drivers / extcon / extcon-qcom-spmi-misc.c
1 /**
2  * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
3  *                              detection based on extcon-usb-gpio.c.
4  *
5  * Copyright (C) 2016 Linaro, Ltd.
6  * Stephen Boyd <stephen.boyd@linaro.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17
18 #include <linux/extcon-provider.h>
19 #include <linux/init.h>
20 #include <linux/interrupt.h>
21 #include <linux/kernel.h>
22 #include <linux/module.h>
23 #include <linux/mod_devicetable.h>
24 #include <linux/platform_device.h>
25 #include <linux/slab.h>
26 #include <linux/workqueue.h>
27
28 #define USB_ID_DEBOUNCE_MS      5       /* ms */
29
30 struct qcom_usb_extcon_info {
31         struct extcon_dev *edev;
32         int irq;
33         struct delayed_work wq_detcable;
34         unsigned long debounce_jiffies;
35 };
36
37 static const unsigned int qcom_usb_extcon_cable[] = {
38         EXTCON_USB_HOST,
39         EXTCON_NONE,
40 };
41
42 static void qcom_usb_extcon_detect_cable(struct work_struct *work)
43 {
44         bool id;
45         int ret;
46         struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
47                                                     struct qcom_usb_extcon_info,
48                                                     wq_detcable);
49
50         /* check ID and update cable state */
51         ret = irq_get_irqchip_state(info->irq, IRQCHIP_STATE_LINE_LEVEL, &id);
52         if (ret)
53                 return;
54
55         extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !id);
56 }
57
58 static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
59 {
60         struct qcom_usb_extcon_info *info = dev_id;
61
62         queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
63                            info->debounce_jiffies);
64
65         return IRQ_HANDLED;
66 }
67
68 static int qcom_usb_extcon_probe(struct platform_device *pdev)
69 {
70         struct device *dev = &pdev->dev;
71         struct qcom_usb_extcon_info *info;
72         int ret;
73
74         info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
75         if (!info)
76                 return -ENOMEM;
77
78         info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
79         if (IS_ERR(info->edev)) {
80                 dev_err(dev, "failed to allocate extcon device\n");
81                 return -ENOMEM;
82         }
83
84         ret = devm_extcon_dev_register(dev, info->edev);
85         if (ret < 0) {
86                 dev_err(dev, "failed to register extcon device\n");
87                 return ret;
88         }
89
90         info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
91         INIT_DELAYED_WORK(&info->wq_detcable, qcom_usb_extcon_detect_cable);
92
93         info->irq = platform_get_irq_byname(pdev, "usb_id");
94         if (info->irq < 0)
95                 return info->irq;
96
97         ret = devm_request_threaded_irq(dev, info->irq, NULL,
98                                         qcom_usb_irq_handler,
99                                         IRQF_TRIGGER_RISING |
100                                         IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
101                                         pdev->name, info);
102         if (ret < 0) {
103                 dev_err(dev, "failed to request handler for ID IRQ\n");
104                 return ret;
105         }
106
107         platform_set_drvdata(pdev, info);
108         device_init_wakeup(dev, 1);
109
110         /* Perform initial detection */
111         qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
112
113         return 0;
114 }
115
116 static int qcom_usb_extcon_remove(struct platform_device *pdev)
117 {
118         struct qcom_usb_extcon_info *info = platform_get_drvdata(pdev);
119
120         cancel_delayed_work_sync(&info->wq_detcable);
121
122         return 0;
123 }
124
125 #ifdef CONFIG_PM_SLEEP
126 static int qcom_usb_extcon_suspend(struct device *dev)
127 {
128         struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
129         int ret = 0;
130
131         if (device_may_wakeup(dev))
132                 ret = enable_irq_wake(info->irq);
133
134         return ret;
135 }
136
137 static int qcom_usb_extcon_resume(struct device *dev)
138 {
139         struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
140         int ret = 0;
141
142         if (device_may_wakeup(dev))
143                 ret = disable_irq_wake(info->irq);
144
145         return ret;
146 }
147 #endif
148
149 static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
150                          qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
151
152 static const struct of_device_id qcom_usb_extcon_dt_match[] = {
153         { .compatible = "qcom,pm8941-misc", },
154         { }
155 };
156 MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
157
158 static struct platform_driver qcom_usb_extcon_driver = {
159         .probe          = qcom_usb_extcon_probe,
160         .remove         = qcom_usb_extcon_remove,
161         .driver         = {
162                 .name   = "extcon-pm8941-misc",
163                 .pm     = &qcom_usb_extcon_pm_ops,
164                 .of_match_table = qcom_usb_extcon_dt_match,
165         },
166 };
167 module_platform_driver(qcom_usb_extcon_driver);
168
169 MODULE_DESCRIPTION("QCOM USB ID extcon driver");
170 MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
171 MODULE_LICENSE("GPL v2");