summaryrefslogtreecommitdiff
path: root/patches.tizen/0874-usb-gadget-udc-core-Add-extcon-hanling-to-reduce-pow.patch
blob: 0f57a72d4c47625905858a80c802bac54d3b7a23 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
From 695a5c99e399cf67ae9f9ab724f135af1fb0279d Mon Sep 17 00:00:00 2001
From: Kamil Debski <k.debski@samsung.com>
Date: Tue, 15 Oct 2013 13:55:18 +0200
Subject: [PATCH 0874/1302] usb: gadget: udc-core: Add extcon hanling to reduce
 power use

Extcon driver provides information about what kind of cable has been
inserted in the device's USB port. Power use can be reduced if UDC is
switched off while no suitable cable is present.

Signed-off-by: Kamil Debski <k.debski@samsung.com>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
---
 drivers/usb/gadget/udc-core.c | 90 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c
index 5514822..dcaa5bf 100644
--- a/drivers/usb/gadget/udc-core.c
+++ b/drivers/usb/gadget/udc-core.c
@@ -27,6 +27,11 @@
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 
+#include <linux/of.h>
+#include <linux/extcon.h>
+#include <linux/extcon/of_extcon.h>
+#include <linux/workqueue.h>
+
 /**
  * struct usb_udc - describes one usb device controller
  * @driver - the gadget driver pointer. For use by the class code
@@ -42,6 +47,12 @@ struct usb_udc {
 	struct usb_gadget		*gadget;
 	struct device			dev;
 	struct list_head		list;
+	struct extcon_specific_cable_nb extcon_usb_dev;
+	struct notifier_block		extcon_nb;
+	struct workqueue_struct		*pwr_workqueue;
+	struct work_struct		pwr_work;
+	char				cable_state;
+	char				enabled;
 };
 
 static struct class *udc_class;
@@ -266,6 +277,10 @@ static void usb_gadget_remove_driver(struct usb_udc *udc)
 	dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n",
 			udc->gadget->name);
 
+	if (udc->extcon_usb_dev.edev)
+		extcon_unregister_notifier(udc->extcon_usb_dev.edev,
+							&udc->extcon_nb);
+
 	kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
 
 	usb_gadget_disconnect(udc->gadget);
@@ -314,10 +329,44 @@ found:
 }
 EXPORT_SYMBOL_GPL(usb_del_gadget_udc);
 
+static void udc_pwr_worker(struct work_struct * work)
+{
+	struct usb_udc *udc = container_of(work, struct usb_udc, pwr_work);
+
+	dev_dbg(&udc->dev, "UDC power worker, cable_state=%d, enabled=%d\n",
+						udc->cable_state, udc->enabled);
+
+	if (udc->cable_state && !udc->enabled) {
+		usb_gadget_udc_start(udc->gadget, udc->driver);
+		usb_gadget_connect(udc->gadget);
+		udc->enabled = 1;
+	} else if (!udc->cable_state && udc->enabled) {
+		usb_gadget_disconnect(udc->gadget);
+		usb_gadget_udc_stop(udc->gadget, udc->driver);
+		udc->enabled = 0;
+	}
+}
+
+static int udc_extcon_notifier(struct notifier_block *nb, unsigned long event,
+								void *ptr)
+{
+	struct usb_udc *udc = container_of(nb, struct usb_udc, extcon_nb);
+
+	dev_dbg(&udc->dev, "extcon notifier, cable state=%lu\n", event);
+	udc->cable_state = event;
+
+	queue_work(udc->pwr_workqueue, &udc->pwr_work);
+
+	return NOTIFY_OK;
+}
+
+
 /* ------------------------------------------------------------------------- */
 
 static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
 {
+	const struct device_node *node;
+	struct extcon_dev *edev = 0;
 	int ret;
 
 	dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
@@ -327,18 +376,54 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri
 	udc->dev.driver = &driver->driver;
 	udc->gadget->dev.driver = &driver->driver;
 
+	node = udc->gadget->dev.of_node;
+	/* Check if we have an extcon associated with the UDC driver */
+	if (node && of_property_read_bool(node, "extcon")) {
+		edev = of_extcon_get_extcon_dev(&udc->gadget->dev, 0);
+
+		if(IS_ERR(edev)) {
+			dev_dbg(&udc->dev, "couldn't get extcon device\n");
+			ret = -EINVAL;
+			goto err1;
+		}
+
+		udc->pwr_workqueue = create_singlethread_workqueue("udc");
+		INIT_WORK(&udc->pwr_work, udc_pwr_worker);
+		udc->extcon_nb.notifier_call = udc_extcon_notifier;
+		ret = extcon_register_interest(&udc->extcon_usb_dev, edev->name,
+							"USB", &udc->extcon_nb);
+
+		if (ret) {
+			dev_err(&udc->dev, "failed to register notifier for USB\n");
+			goto err1;
+		}
+
+	}
+
 	ret = driver->bind(udc->gadget, driver);
 	if (ret)
-		goto err1;
+		goto err2;
 	ret = usb_gadget_udc_start(udc->gadget, driver);
 	if (ret) {
 		driver->unbind(udc->gadget);
-		goto err1;
+		goto err2;
 	}
+
 	usb_gadget_connect(udc->gadget);
 
+	if (udc->extcon_usb_dev.edev) {
+		udc->enabled = 1;
+		udc->cable_state = extcon_get_cable_state_(
+					udc->extcon_usb_dev.edev,
+					udc->extcon_usb_dev.cable_index);
+		queue_work(udc->pwr_workqueue, &udc->pwr_work);
+	}
+
 	kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
 	return 0;
+err2:
+	if (udc->extcon_usb_dev.edev)
+		extcon_unregister_notifier(edev, &udc->extcon_nb);
 err1:
 	dev_err(&udc->dev, "failed to start %s: %d\n",
 			udc->driver->function, ret);
@@ -551,6 +636,7 @@ static int __init usb_udc_init(void)
 	}
 
 	udc_class->dev_uevent = usb_udc_uevent;
+
 	return 0;
 }
 subsys_initcall(usb_udc_init);
-- 
1.8.3.2