blob: 6730d4ae8a32472499c177a08516e11ec58711f8 [file] [log] [blame]
Anirudh Ghayalbce18922018-01-10 10:02:47 +05301/* Copyright (c) 2013-2015, 2017-2018, The Linux Foundation. All rights reserved.
Anirudh Ghayal52c77062018-01-10 08:45:26 +05302 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/module.h>
14#include <linux/init.h>
15#include <linux/kernel.h>
16#include <linux/err.h>
17#include <linux/errno.h>
18#include <linux/slab.h>
19#include <linux/of_platform.h>
20#include <linux/interrupt.h>
21#include <linux/of_gpio.h>
22#include <linux/gpio.h>
23#include <linux/extcon.h>
24#include <linux/regulator/consumer.h>
25
26struct gpio_usbdetect {
27 struct platform_device *pdev;
28 struct regulator *vin;
29 int vbus_det_irq;
30 int id_det_irq;
31 int gpio;
32 struct extcon_dev *extcon_dev;
33 int vbus_state;
34 bool id_state;
35};
36
37static const unsigned int gpio_usb_extcon_table[] = {
38 EXTCON_USB,
39 EXTCON_USB_HOST,
Anirudh Ghayal52c77062018-01-10 08:45:26 +053040 EXTCON_NONE,
41};
42
43static irqreturn_t gpio_usbdetect_vbus_irq(int irq, void *data)
44{
45 struct gpio_usbdetect *usb = data;
Anirudh Ghayalbce18922018-01-10 10:02:47 +053046 union extcon_property_value val;
Anirudh Ghayal52c77062018-01-10 08:45:26 +053047
48 usb->vbus_state = gpio_get_value(usb->gpio);
49 if (usb->vbus_state) {
50 dev_dbg(&usb->pdev->dev, "setting vbus notification\n");
Anirudh Ghayalbce18922018-01-10 10:02:47 +053051 val.intval = true;
52 extcon_set_property(usb->extcon_dev, EXTCON_USB,
53 EXTCON_PROP_USB_SS, val);
Anirudh Ghayal52c77062018-01-10 08:45:26 +053054 extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 1);
55 } else {
56 dev_dbg(&usb->pdev->dev, "setting vbus removed notification\n");
57 extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 0);
58 }
59
60 return IRQ_HANDLED;
61}
62
63static irqreturn_t gpio_usbdetect_id_irq(int irq, void *data)
64{
65 struct gpio_usbdetect *usb = data;
66 int ret;
67
68 ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL,
69 &usb->id_state);
70 if (ret < 0) {
71 dev_err(&usb->pdev->dev, "unable to read ID IRQ LINE\n");
72 return IRQ_HANDLED;
73 }
74
75 return IRQ_WAKE_THREAD;
76}
77
78static irqreturn_t gpio_usbdetect_id_irq_thread(int irq, void *data)
79{
80 struct gpio_usbdetect *usb = data;
81 bool curr_id_state;
82 static int prev_id_state = -EINVAL;
Anirudh Ghayalbce18922018-01-10 10:02:47 +053083 union extcon_property_value val;
Anirudh Ghayal52c77062018-01-10 08:45:26 +053084
85 curr_id_state = usb->id_state;
86 if (curr_id_state == prev_id_state) {
87 dev_dbg(&usb->pdev->dev, "no change in ID state\n");
88 return IRQ_HANDLED;
89 }
90
91 if (curr_id_state) {
92 dev_dbg(&usb->pdev->dev, "stopping usb host\n");
93 extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 0);
94 enable_irq(usb->vbus_det_irq);
95 } else {
96 dev_dbg(&usb->pdev->dev, "starting usb HOST\n");
97 disable_irq(usb->vbus_det_irq);
Anirudh Ghayalbce18922018-01-10 10:02:47 +053098 val.intval = true;
99 extcon_set_property(usb->extcon_dev, EXTCON_USB_HOST,
100 EXTCON_PROP_USB_SS, val);
Anirudh Ghayal52c77062018-01-10 08:45:26 +0530101 extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 1);
102 }
103
104 prev_id_state = curr_id_state;
105 return IRQ_HANDLED;
106}
107
108static const u32 gpio_usb_extcon_exclusive[] = {0x3, 0};
109
110static int gpio_usbdetect_probe(struct platform_device *pdev)
111{
112 struct gpio_usbdetect *usb;
113 int rc;
114
115 usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL);
116 if (!usb)
117 return -ENOMEM;
118
119 usb->pdev = pdev;
120
121 usb->extcon_dev = devm_extcon_dev_allocate(&pdev->dev,
122 gpio_usb_extcon_table);
123 if (IS_ERR(usb->extcon_dev)) {
124 dev_err(&pdev->dev, "failed to allocate a extcon device\n");
125 return PTR_ERR(usb->extcon_dev);
126 }
127
128 usb->extcon_dev->mutually_exclusive = gpio_usb_extcon_exclusive;
129 rc = devm_extcon_dev_register(&pdev->dev, usb->extcon_dev);
130 if (rc) {
131 dev_err(&pdev->dev, "failed to register extcon device\n");
132 return rc;
133 }
134
Anirudh Ghayalbce18922018-01-10 10:02:47 +0530135 rc = extcon_set_property_capability(usb->extcon_dev,
136 EXTCON_USB, EXTCON_PROP_USB_SS);
137 rc |= extcon_set_property_capability(usb->extcon_dev,
138 EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
139 if (rc) {
140 dev_err(&pdev->dev, "failed to register extcon props rc=%d\n",
141 rc);
142 return rc;
143 }
144
Anirudh Ghayal52c77062018-01-10 08:45:26 +0530145 if (of_get_property(pdev->dev.of_node, "vin-supply", NULL)) {
146 usb->vin = devm_regulator_get(&pdev->dev, "vin");
147 if (IS_ERR(usb->vin)) {
148 dev_err(&pdev->dev, "Failed to get VIN regulator: %ld\n",
149 PTR_ERR(usb->vin));
150 return PTR_ERR(usb->vin);
151 }
152 }
153
154 if (usb->vin) {
155 rc = regulator_enable(usb->vin);
156 if (rc) {
157 dev_err(&pdev->dev, "Failed to enable VIN regulator: %d\n",
158 rc);
159 return rc;
160 }
161 }
162
163 usb->gpio = of_get_named_gpio(pdev->dev.of_node,
164 "qcom,vbus-det-gpio", 0);
165 if (usb->gpio < 0) {
166 dev_err(&pdev->dev, "Failed to get gpio: %d\n", usb->gpio);
167 rc = usb->gpio;
168 goto error;
169 }
170
171 rc = gpio_request(usb->gpio, "vbus-det-gpio");
172 if (rc < 0) {
173 dev_err(&pdev->dev, "Failed to request gpio: %d\n", rc);
174 goto error;
175 }
176
177 usb->vbus_det_irq = gpio_to_irq(usb->gpio);
178 if (usb->vbus_det_irq < 0) {
179 dev_err(&pdev->dev, "get vbus_det_irq failed\n");
180 rc = usb->vbus_det_irq;
181 goto error;
182 }
183
184 rc = devm_request_threaded_irq(&pdev->dev, usb->vbus_det_irq,
185 NULL, gpio_usbdetect_vbus_irq,
186 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
187 IRQF_ONESHOT, "vbus_det_irq", usb);
188 if (rc) {
189 dev_err(&pdev->dev, "request for vbus_det_irq failed: %d\n",
190 rc);
191 goto error;
192 }
193
194 usb->id_det_irq = platform_get_irq_byname(pdev, "pmic_id_irq");
195 if (usb->id_det_irq < 0) {
196 dev_err(&pdev->dev, "get id_det_irq failed\n");
197 rc = usb->id_det_irq;
198 goto error;
199 }
200
201 rc = devm_request_threaded_irq(&pdev->dev, usb->id_det_irq,
202 gpio_usbdetect_id_irq,
203 gpio_usbdetect_id_irq_thread,
204 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
205 IRQF_ONESHOT, "id_det_irq", usb);
206 if (rc) {
207 dev_err(&pdev->dev, "request for id_det_irq failed: %d\n", rc);
208 goto error;
209 }
210
211 enable_irq_wake(usb->vbus_det_irq);
212 enable_irq_wake(usb->id_det_irq);
213 dev_set_drvdata(&pdev->dev, usb);
214
215 if (usb->id_det_irq) {
216 gpio_usbdetect_id_irq(usb->id_det_irq, usb);
217 if (!usb->id_state) {
218 gpio_usbdetect_id_irq_thread(usb->id_det_irq, usb);
219 return 0;
220 }
221 }
222
223 /* Read and report initial VBUS state */
224 gpio_usbdetect_vbus_irq(usb->vbus_det_irq, usb);
225
226 return 0;
227
228error:
229 if (usb->vin)
230 regulator_disable(usb->vin);
231 return rc;
232}
233
234static int gpio_usbdetect_remove(struct platform_device *pdev)
235{
236 struct gpio_usbdetect *usb = dev_get_drvdata(&pdev->dev);
237
238 disable_irq_wake(usb->vbus_det_irq);
239 disable_irq(usb->vbus_det_irq);
240 disable_irq_wake(usb->id_det_irq);
241 disable_irq(usb->id_det_irq);
242 if (usb->vin)
243 regulator_disable(usb->vin);
244
245 return 0;
246}
247
248static const struct of_device_id of_match_table[] = {
249 { .compatible = "qcom,gpio-usbdetect", },
250 {}
251};
252
253static struct platform_driver gpio_usbdetect_driver = {
254 .driver = {
255 .name = "qcom,gpio-usbdetect",
256 .of_match_table = of_match_table,
257 },
258 .probe = gpio_usbdetect_probe,
259 .remove = gpio_usbdetect_remove,
260};
261
262module_driver(gpio_usbdetect_driver, platform_driver_register,
263 platform_driver_unregister);
264
265MODULE_DESCRIPTION("GPIO USB VBUS Detection driver");
266MODULE_LICENSE("GPL v2");