diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-11-28 02:46:47 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-11-28 02:46:47 -0500 |
commit | 75e9ebecfedb88a22eee0d2b3b63aeaeab1bfdee (patch) | |
tree | 1644b2981f64465828d0be9df7f478236c3cf09a | |
parent | cdefb95bfc0de9a60b91b748f15e65eddb4f116e (diff) | |
parent | 541332a13b1ded42097ba96c52c7bc70931e528c (diff) |
Merge tag 'extcon-next-for-4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/extcon into usb-nextx
Chanwoo writes:
Update extcon for 4.10
Detailed description for this pull request:
- The extcon-usb-gpio driver supports the VBUS detection with USB ID and VBUS pin.
-rw-r--r-- | Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt | 3 | ||||
-rw-r--r-- | drivers/extcon/extcon-usb-gpio.c | 169 |
2 files changed, 132 insertions, 40 deletions
diff --git a/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt index af0b903de293..dfc14f71e81f 100644 --- a/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt +++ b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt | |||
@@ -5,7 +5,10 @@ connected to a GPIO pin. | |||
5 | 5 | ||
6 | Required properties: | 6 | Required properties: |
7 | - compatible: Should be "linux,extcon-usb-gpio" | 7 | - compatible: Should be "linux,extcon-usb-gpio" |
8 | |||
9 | Either one of id-gpio or vbus-gpio must be present. Both can be present as well. | ||
8 | - id-gpio: gpio for USB ID pin. See gpio binding. | 10 | - id-gpio: gpio for USB ID pin. See gpio binding. |
11 | - vbus-gpio: gpio for USB VBUS pin. | ||
9 | 12 | ||
10 | Example: Examples of extcon-usb-gpio node in dra7-evm.dts as listed below: | 13 | Example: Examples of extcon-usb-gpio node in dra7-evm.dts as listed below: |
11 | extcon_usb1 { | 14 | extcon_usb1 { |
diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index a27d350f69e3..d589c5feff3d 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c | |||
@@ -24,7 +24,6 @@ | |||
24 | #include <linux/module.h> | 24 | #include <linux/module.h> |
25 | #include <linux/of_gpio.h> | 25 | #include <linux/of_gpio.h> |
26 | #include <linux/platform_device.h> | 26 | #include <linux/platform_device.h> |
27 | #include <linux/pm_wakeirq.h> | ||
28 | #include <linux/slab.h> | 27 | #include <linux/slab.h> |
29 | #include <linux/workqueue.h> | 28 | #include <linux/workqueue.h> |
30 | #include <linux/acpi.h> | 29 | #include <linux/acpi.h> |
@@ -36,7 +35,9 @@ struct usb_extcon_info { | |||
36 | struct extcon_dev *edev; | 35 | struct extcon_dev *edev; |
37 | 36 | ||
38 | struct gpio_desc *id_gpiod; | 37 | struct gpio_desc *id_gpiod; |
38 | struct gpio_desc *vbus_gpiod; | ||
39 | int id_irq; | 39 | int id_irq; |
40 | int vbus_irq; | ||
40 | 41 | ||
41 | unsigned long debounce_jiffies; | 42 | unsigned long debounce_jiffies; |
42 | struct delayed_work wq_detcable; | 43 | struct delayed_work wq_detcable; |
@@ -48,31 +49,47 @@ static const unsigned int usb_extcon_cable[] = { | |||
48 | EXTCON_NONE, | 49 | EXTCON_NONE, |
49 | }; | 50 | }; |
50 | 51 | ||
52 | /* | ||
53 | * "USB" = VBUS and "USB-HOST" = !ID, so we have: | ||
54 | * Both "USB" and "USB-HOST" can't be set as active at the | ||
55 | * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive | ||
56 | * even if VBUS is on. | ||
57 | * | ||
58 | * State | ID | VBUS | ||
59 | * ---------------------------------------- | ||
60 | * [1] USB | H | H | ||
61 | * [2] none | H | L | ||
62 | * [3] USB-HOST | L | H | ||
63 | * [4] USB-HOST | L | L | ||
64 | * | ||
65 | * In case we have only one of these signals: | ||
66 | * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. | ||
67 | * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. | ||
68 | */ | ||
51 | static void usb_extcon_detect_cable(struct work_struct *work) | 69 | static void usb_extcon_detect_cable(struct work_struct *work) |
52 | { | 70 | { |
53 | int id; | 71 | int id, vbus; |
54 | struct usb_extcon_info *info = container_of(to_delayed_work(work), | 72 | struct usb_extcon_info *info = container_of(to_delayed_work(work), |
55 | struct usb_extcon_info, | 73 | struct usb_extcon_info, |
56 | wq_detcable); | 74 | wq_detcable); |
57 | 75 | ||
58 | /* check ID and update cable state */ | 76 | /* check ID and VBUS and update cable state */ |
59 | id = gpiod_get_value_cansleep(info->id_gpiod); | 77 | id = info->id_gpiod ? |
60 | if (id) { | 78 | gpiod_get_value_cansleep(info->id_gpiod) : 1; |
61 | /* | 79 | vbus = info->vbus_gpiod ? |
62 | * ID = 1 means USB HOST cable detached. | 80 | gpiod_get_value_cansleep(info->vbus_gpiod) : id; |
63 | * As we don't have event for USB peripheral cable attached, | 81 | |
64 | * we simulate USB peripheral attach here. | 82 | /* at first we clean states which are no longer active */ |
65 | */ | 83 | if (id) |
66 | extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); | 84 | extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); |
67 | extcon_set_state_sync(info->edev, EXTCON_USB, true); | 85 | if (!vbus) |
68 | } else { | ||
69 | /* | ||
70 | * ID = 0 means USB HOST cable attached. | ||
71 | * As we don't have event for USB peripheral cable detached, | ||
72 | * we simulate USB peripheral detach here. | ||
73 | */ | ||
74 | extcon_set_state_sync(info->edev, EXTCON_USB, false); | 86 | extcon_set_state_sync(info->edev, EXTCON_USB, false); |
87 | |||
88 | if (!id) { | ||
75 | extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); | 89 | extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); |
90 | } else { | ||
91 | if (vbus) | ||
92 | extcon_set_state_sync(info->edev, EXTCON_USB, true); | ||
76 | } | 93 | } |
77 | } | 94 | } |
78 | 95 | ||
@@ -101,12 +118,21 @@ static int usb_extcon_probe(struct platform_device *pdev) | |||
101 | return -ENOMEM; | 118 | return -ENOMEM; |
102 | 119 | ||
103 | info->dev = dev; | 120 | info->dev = dev; |
104 | info->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); | 121 | info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); |
105 | if (IS_ERR(info->id_gpiod)) { | 122 | info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", |
106 | dev_err(dev, "failed to get ID GPIO\n"); | 123 | GPIOD_IN); |
107 | return PTR_ERR(info->id_gpiod); | 124 | |
125 | if (!info->id_gpiod && !info->vbus_gpiod) { | ||
126 | dev_err(dev, "failed to get gpios\n"); | ||
127 | return -ENODEV; | ||
108 | } | 128 | } |
109 | 129 | ||
130 | if (IS_ERR(info->id_gpiod)) | ||
131 | return PTR_ERR(info->id_gpiod); | ||
132 | |||
133 | if (IS_ERR(info->vbus_gpiod)) | ||
134 | return PTR_ERR(info->vbus_gpiod); | ||
135 | |||
110 | info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); | 136 | info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); |
111 | if (IS_ERR(info->edev)) { | 137 | if (IS_ERR(info->edev)) { |
112 | dev_err(dev, "failed to allocate extcon device\n"); | 138 | dev_err(dev, "failed to allocate extcon device\n"); |
@@ -119,32 +145,56 @@ static int usb_extcon_probe(struct platform_device *pdev) | |||
119 | return ret; | 145 | return ret; |
120 | } | 146 | } |
121 | 147 | ||
122 | ret = gpiod_set_debounce(info->id_gpiod, | 148 | if (info->id_gpiod) |
123 | USB_GPIO_DEBOUNCE_MS * 1000); | 149 | ret = gpiod_set_debounce(info->id_gpiod, |
150 | USB_GPIO_DEBOUNCE_MS * 1000); | ||
151 | if (!ret && info->vbus_gpiod) | ||
152 | ret = gpiod_set_debounce(info->vbus_gpiod, | ||
153 | USB_GPIO_DEBOUNCE_MS * 1000); | ||
154 | |||
124 | if (ret < 0) | 155 | if (ret < 0) |
125 | info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); | 156 | info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); |
126 | 157 | ||
127 | INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); | 158 | INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); |
128 | 159 | ||
129 | info->id_irq = gpiod_to_irq(info->id_gpiod); | 160 | if (info->id_gpiod) { |
130 | if (info->id_irq < 0) { | 161 | info->id_irq = gpiod_to_irq(info->id_gpiod); |
131 | dev_err(dev, "failed to get ID IRQ\n"); | 162 | if (info->id_irq < 0) { |
132 | return info->id_irq; | 163 | dev_err(dev, "failed to get ID IRQ\n"); |
164 | return info->id_irq; | ||
165 | } | ||
166 | |||
167 | ret = devm_request_threaded_irq(dev, info->id_irq, NULL, | ||
168 | usb_irq_handler, | ||
169 | IRQF_TRIGGER_RISING | | ||
170 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | ||
171 | pdev->name, info); | ||
172 | if (ret < 0) { | ||
173 | dev_err(dev, "failed to request handler for ID IRQ\n"); | ||
174 | return ret; | ||
175 | } | ||
133 | } | 176 | } |
134 | 177 | ||
135 | ret = devm_request_threaded_irq(dev, info->id_irq, NULL, | 178 | if (info->vbus_gpiod) { |
136 | usb_irq_handler, | 179 | info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); |
137 | IRQF_TRIGGER_RISING | | 180 | if (info->vbus_irq < 0) { |
138 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | 181 | dev_err(dev, "failed to get VBUS IRQ\n"); |
139 | pdev->name, info); | 182 | return info->vbus_irq; |
140 | if (ret < 0) { | 183 | } |
141 | dev_err(dev, "failed to request handler for ID IRQ\n"); | 184 | |
142 | return ret; | 185 | ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, |
186 | usb_irq_handler, | ||
187 | IRQF_TRIGGER_RISING | | ||
188 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | ||
189 | pdev->name, info); | ||
190 | if (ret < 0) { | ||
191 | dev_err(dev, "failed to request handler for VBUS IRQ\n"); | ||
192 | return ret; | ||
193 | } | ||
143 | } | 194 | } |
144 | 195 | ||
145 | platform_set_drvdata(pdev, info); | 196 | platform_set_drvdata(pdev, info); |
146 | device_init_wakeup(dev, true); | 197 | device_init_wakeup(dev, true); |
147 | dev_pm_set_wake_irq(dev, info->id_irq); | ||
148 | 198 | ||
149 | /* Perform initial detection */ | 199 | /* Perform initial detection */ |
150 | usb_extcon_detect_cable(&info->wq_detcable.work); | 200 | usb_extcon_detect_cable(&info->wq_detcable.work); |
@@ -157,8 +207,6 @@ static int usb_extcon_remove(struct platform_device *pdev) | |||
157 | struct usb_extcon_info *info = platform_get_drvdata(pdev); | 207 | struct usb_extcon_info *info = platform_get_drvdata(pdev); |
158 | 208 | ||
159 | cancel_delayed_work_sync(&info->wq_detcable); | 209 | cancel_delayed_work_sync(&info->wq_detcable); |
160 | |||
161 | dev_pm_clear_wake_irq(&pdev->dev); | ||
162 | device_init_wakeup(&pdev->dev, false); | 210 | device_init_wakeup(&pdev->dev, false); |
163 | 211 | ||
164 | return 0; | 212 | return 0; |
@@ -170,12 +218,32 @@ static int usb_extcon_suspend(struct device *dev) | |||
170 | struct usb_extcon_info *info = dev_get_drvdata(dev); | 218 | struct usb_extcon_info *info = dev_get_drvdata(dev); |
171 | int ret = 0; | 219 | int ret = 0; |
172 | 220 | ||
221 | if (device_may_wakeup(dev)) { | ||
222 | if (info->id_gpiod) { | ||
223 | ret = enable_irq_wake(info->id_irq); | ||
224 | if (ret) | ||
225 | return ret; | ||
226 | } | ||
227 | if (info->vbus_gpiod) { | ||
228 | ret = enable_irq_wake(info->vbus_irq); | ||
229 | if (ret) { | ||
230 | if (info->id_gpiod) | ||
231 | disable_irq_wake(info->id_irq); | ||
232 | |||
233 | return ret; | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | |||
173 | /* | 238 | /* |
174 | * We don't want to process any IRQs after this point | 239 | * We don't want to process any IRQs after this point |
175 | * as GPIOs used behind I2C subsystem might not be | 240 | * as GPIOs used behind I2C subsystem might not be |
176 | * accessible until resume completes. So disable IRQ. | 241 | * accessible until resume completes. So disable IRQ. |
177 | */ | 242 | */ |
178 | disable_irq(info->id_irq); | 243 | if (info->id_gpiod) |
244 | disable_irq(info->id_irq); | ||
245 | if (info->vbus_gpiod) | ||
246 | disable_irq(info->vbus_irq); | ||
179 | 247 | ||
180 | return ret; | 248 | return ret; |
181 | } | 249 | } |
@@ -185,7 +253,28 @@ static int usb_extcon_resume(struct device *dev) | |||
185 | struct usb_extcon_info *info = dev_get_drvdata(dev); | 253 | struct usb_extcon_info *info = dev_get_drvdata(dev); |
186 | int ret = 0; | 254 | int ret = 0; |
187 | 255 | ||
188 | enable_irq(info->id_irq); | 256 | if (device_may_wakeup(dev)) { |
257 | if (info->id_gpiod) { | ||
258 | ret = disable_irq_wake(info->id_irq); | ||
259 | if (ret) | ||
260 | return ret; | ||
261 | } | ||
262 | if (info->vbus_gpiod) { | ||
263 | ret = disable_irq_wake(info->vbus_irq); | ||
264 | if (ret) { | ||
265 | if (info->id_gpiod) | ||
266 | enable_irq_wake(info->id_irq); | ||
267 | |||
268 | return ret; | ||
269 | } | ||
270 | } | ||
271 | } | ||
272 | |||
273 | if (info->id_gpiod) | ||
274 | enable_irq(info->id_irq); | ||
275 | if (info->vbus_gpiod) | ||
276 | enable_irq(info->vbus_irq); | ||
277 | |||
189 | if (!device_may_wakeup(dev)) | 278 | if (!device_may_wakeup(dev)) |
190 | queue_delayed_work(system_power_efficient_wq, | 279 | queue_delayed_work(system_power_efficient_wq, |
191 | &info->wq_detcable, 0); | 280 | &info->wq_detcable, 0); |