blob: 41245d8906201b656c3312ab3105ee6a8123704f [file] [log] [blame]
Stephen Boyd38085c92016-09-09 14:48:47 -07001/**
2 * extcon-qcom-spmi-misc.c - Qualcomm USB extcon driver to support USB ID
Anirudh Ghayal25065322018-02-14 19:05:48 +05303 * and VBUS detection based on extcon-usb-gpio.c.
Stephen Boyd38085c92016-09-09 14:48:47 -07004 *
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.h>
19#include <linux/init.h>
20#include <linux/interrupt.h>
21#include <linux/kernel.h>
22#include <linux/module.h>
23#include <linux/platform_device.h>
24#include <linux/slab.h>
25#include <linux/workqueue.h>
26
27#define USB_ID_DEBOUNCE_MS 5 /* ms */
28
29struct qcom_usb_extcon_info {
30 struct extcon_dev *edev;
Anirudh Ghayal25065322018-02-14 19:05:48 +053031 int id_irq;
32 int vbus_irq;
Stephen Boyd38085c92016-09-09 14:48:47 -070033 struct delayed_work wq_detcable;
34 unsigned long debounce_jiffies;
35};
36
37static const unsigned int qcom_usb_extcon_cable[] = {
Anirudh Ghayal25065322018-02-14 19:05:48 +053038 EXTCON_USB,
Stephen Boyd38085c92016-09-09 14:48:47 -070039 EXTCON_USB_HOST,
40 EXTCON_NONE,
41};
42
43static void qcom_usb_extcon_detect_cable(struct work_struct *work)
44{
Anirudh Ghayal25065322018-02-14 19:05:48 +053045 bool state = 0;
Stephen Boyd38085c92016-09-09 14:48:47 -070046 int ret;
Anirudh Ghayal25065322018-02-14 19:05:48 +053047 union extcon_property_value val;
Stephen Boyd38085c92016-09-09 14:48:47 -070048 struct qcom_usb_extcon_info *info = container_of(to_delayed_work(work),
49 struct qcom_usb_extcon_info,
50 wq_detcable);
51
Anirudh Ghayal25065322018-02-14 19:05:48 +053052 if (info->id_irq > 0) {
53 /* check ID and update cable state */
54 ret = irq_get_irqchip_state(info->id_irq,
55 IRQCHIP_STATE_LINE_LEVEL, &state);
56 if (ret)
57 return;
Stephen Boyd38085c92016-09-09 14:48:47 -070058
Anirudh Ghayal25065322018-02-14 19:05:48 +053059 if (!state) {
60 val.intval = true;
61 extcon_set_property(info->edev, EXTCON_USB_HOST,
62 EXTCON_PROP_USB_SS, val);
63 }
64 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, !state);
65 }
66
67 if (info->vbus_irq > 0) {
68 /* check VBUS and update cable state */
69 ret = irq_get_irqchip_state(info->vbus_irq,
70 IRQCHIP_STATE_LINE_LEVEL, &state);
71 if (ret)
72 return;
73
74 if (state) {
75 val.intval = true;
76 extcon_set_property(info->edev, EXTCON_USB,
77 EXTCON_PROP_USB_SS, val);
78 }
79 extcon_set_cable_state_(info->edev, EXTCON_USB, state);
80 }
Stephen Boyd38085c92016-09-09 14:48:47 -070081}
82
83static irqreturn_t qcom_usb_irq_handler(int irq, void *dev_id)
84{
85 struct qcom_usb_extcon_info *info = dev_id;
86
87 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
88 info->debounce_jiffies);
89
90 return IRQ_HANDLED;
91}
92
93static int qcom_usb_extcon_probe(struct platform_device *pdev)
94{
95 struct device *dev = &pdev->dev;
96 struct qcom_usb_extcon_info *info;
97 int ret;
98
99 info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
100 if (!info)
101 return -ENOMEM;
102
103 info->edev = devm_extcon_dev_allocate(dev, qcom_usb_extcon_cable);
104 if (IS_ERR(info->edev)) {
105 dev_err(dev, "failed to allocate extcon device\n");
106 return -ENOMEM;
107 }
108
109 ret = devm_extcon_dev_register(dev, info->edev);
110 if (ret < 0) {
111 dev_err(dev, "failed to register extcon device\n");
112 return ret;
113 }
114
Anirudh Ghayal25065322018-02-14 19:05:48 +0530115 ret = extcon_set_property_capability(info->edev,
116 EXTCON_USB, EXTCON_PROP_USB_SS);
117 ret |= extcon_set_property_capability(info->edev,
118 EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
119 if (ret) {
120 dev_err(dev, "failed to register extcon props rc=%d\n",
121 ret);
122 return ret;
123 }
124
Stephen Boyd38085c92016-09-09 14:48:47 -0700125 info->debounce_jiffies = msecs_to_jiffies(USB_ID_DEBOUNCE_MS);
126 INIT_DELAYED_WORK(&info->wq_detcable, qcom_usb_extcon_detect_cable);
127
Anirudh Ghayal25065322018-02-14 19:05:48 +0530128 info->id_irq = platform_get_irq_byname(pdev, "usb_id");
129 if (info->id_irq > 0) {
130 ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
Stephen Boyd38085c92016-09-09 14:48:47 -0700131 qcom_usb_irq_handler,
132 IRQF_TRIGGER_RISING |
133 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
134 pdev->name, info);
Anirudh Ghayal25065322018-02-14 19:05:48 +0530135 if (ret < 0) {
136 dev_err(dev, "failed to request handler for ID IRQ\n");
137 return ret;
138 }
139 }
140
141 info->vbus_irq = platform_get_irq_byname(pdev, "usb_vbus");
142 if (info->vbus_irq > 0) {
143 ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
144 qcom_usb_irq_handler,
145 IRQF_TRIGGER_RISING |
146 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
147 pdev->name, info);
148 if (ret < 0) {
149 dev_err(dev, "failed to request handler for VBUS IRQ\n");
150 return ret;
151 }
152 }
153
154 if (info->id_irq < 0 && info->vbus_irq < 0) {
155 dev_err(dev, "ID and VBUS IRQ not found\n");
156 return -EINVAL;
Stephen Boyd38085c92016-09-09 14:48:47 -0700157 }
158
159 platform_set_drvdata(pdev, info);
160 device_init_wakeup(dev, 1);
161
162 /* Perform initial detection */
163 qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
164
165 return 0;
166}
167
168static int qcom_usb_extcon_remove(struct platform_device *pdev)
169{
170 struct qcom_usb_extcon_info *info = platform_get_drvdata(pdev);
171
172 cancel_delayed_work_sync(&info->wq_detcable);
173
174 return 0;
175}
176
177#ifdef CONFIG_PM_SLEEP
178static int qcom_usb_extcon_suspend(struct device *dev)
179{
180 struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
181 int ret = 0;
182
Anirudh Ghayal25065322018-02-14 19:05:48 +0530183 if (device_may_wakeup(dev)) {
184 if (info->id_irq > 0)
185 ret = enable_irq_wake(info->id_irq);
186 if (info->vbus_irq > 0)
187 ret = enable_irq_wake(info->vbus_irq);
188 }
Stephen Boyd38085c92016-09-09 14:48:47 -0700189
190 return ret;
191}
192
193static int qcom_usb_extcon_resume(struct device *dev)
194{
195 struct qcom_usb_extcon_info *info = dev_get_drvdata(dev);
196 int ret = 0;
197
Anirudh Ghayal25065322018-02-14 19:05:48 +0530198 if (device_may_wakeup(dev)) {
199 if (info->id_irq > 0)
200 ret = disable_irq_wake(info->id_irq);
201 if (info->vbus_irq > 0)
202 ret = disable_irq_wake(info->vbus_irq);
203 }
Stephen Boyd38085c92016-09-09 14:48:47 -0700204
205 return ret;
206}
207#endif
208
209static SIMPLE_DEV_PM_OPS(qcom_usb_extcon_pm_ops,
210 qcom_usb_extcon_suspend, qcom_usb_extcon_resume);
211
212static const struct of_device_id qcom_usb_extcon_dt_match[] = {
213 { .compatible = "qcom,pm8941-misc", },
Anirudh Ghayal25065322018-02-14 19:05:48 +0530214 { .compatible = "qcom,pmd-vbus-det", },
Stephen Boyd38085c92016-09-09 14:48:47 -0700215 { }
216};
217MODULE_DEVICE_TABLE(of, qcom_usb_extcon_dt_match);
218
219static struct platform_driver qcom_usb_extcon_driver = {
220 .probe = qcom_usb_extcon_probe,
221 .remove = qcom_usb_extcon_remove,
222 .driver = {
223 .name = "extcon-pm8941-misc",
224 .pm = &qcom_usb_extcon_pm_ops,
225 .of_match_table = qcom_usb_extcon_dt_match,
226 },
227};
228module_platform_driver(qcom_usb_extcon_driver);
229
230MODULE_DESCRIPTION("QCOM USB ID extcon driver");
231MODULE_AUTHOR("Stephen Boyd <stephen.boyd@linaro.org>");
232MODULE_LICENSE("GPL v2");