diff options
author | Bjørn Mork <bjorn@mork.no> | 2012-03-09 06:35:05 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-03-09 16:11:01 -0500 |
commit | c3ecb08abef7690c6bc2d22f099cf3ee56881a30 (patch) | |
tree | 51a6720a2483d819e7c0728ca1de6de016ca1b4d | |
parent | 423ce8caab7ea2b13f4a29ce0839369528aafaeb (diff) |
net: qmi_wwan: support devices having a shared QMI/wwan interface
Use the new cdc-wdm subdriver interface to create a device management
device even for USB devices having a single combined QMI/wwan USB
interface with three endpoints (int, bulk in, bulk out) instead of
separate data and control interfaces.
Some Huawei devices can be switched to a single interface mode for
use with other operating systems than Linux. This adds support
for these devices when they run in such non-Linux modes.
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/net/usb/qmi_wwan.c | 168 |
1 files changed, 152 insertions, 16 deletions
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 739e6de7abcb..a61c7a1aff8b 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c | |||
@@ -13,6 +13,7 @@ | |||
13 | #include <linux/usb.h> | 13 | #include <linux/usb.h> |
14 | #include <linux/usb/cdc.h> | 14 | #include <linux/usb/cdc.h> |
15 | #include <linux/usb/usbnet.h> | 15 | #include <linux/usb/usbnet.h> |
16 | #include <linux/usb/cdc-wdm.h> | ||
16 | 17 | ||
17 | /* The name of the CDC Device Management driver */ | 18 | /* The name of the CDC Device Management driver */ |
18 | #define DM_DRIVER "cdc_wdm" | 19 | #define DM_DRIVER "cdc_wdm" |
@@ -64,6 +65,9 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) | |||
64 | struct usb_cdc_ether_desc *cdc_ether = NULL; | 65 | struct usb_cdc_ether_desc *cdc_ether = NULL; |
65 | u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE; | 66 | u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE; |
66 | u32 found = 0; | 67 | u32 found = 0; |
68 | atomic_t *pmcount = (void *)&dev->data[1]; | ||
69 | |||
70 | atomic_set(pmcount, 0); | ||
67 | 71 | ||
68 | /* | 72 | /* |
69 | * assume a data interface has no additional descriptors and | 73 | * assume a data interface has no additional descriptors and |
@@ -170,13 +174,127 @@ err: | |||
170 | return status; | 174 | return status; |
171 | } | 175 | } |
172 | 176 | ||
173 | /* stolen from cdc_ether.c */ | 177 | /* using a counter to merge subdriver requests with our own into a combined state */ |
174 | static int qmi_wwan_manage_power(struct usbnet *dev, int on) | 178 | static int qmi_wwan_manage_power(struct usbnet *dev, int on) |
175 | { | 179 | { |
176 | dev->intf->needs_remote_wakeup = on; | 180 | atomic_t *pmcount = (void *)&dev->data[1]; |
177 | return 0; | 181 | int rv = 0; |
182 | |||
183 | dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(pmcount), on); | ||
184 | |||
185 | if ((on && atomic_add_return(1, pmcount) == 1) || (!on && atomic_dec_and_test(pmcount))) { | ||
186 | /* need autopm_get/put here to ensure the usbcore sees the new value */ | ||
187 | rv = usb_autopm_get_interface(dev->intf); | ||
188 | if (rv < 0) | ||
189 | goto err; | ||
190 | dev->intf->needs_remote_wakeup = on; | ||
191 | usb_autopm_put_interface(dev->intf); | ||
192 | } | ||
193 | err: | ||
194 | return rv; | ||
195 | } | ||
196 | |||
197 | static int qmi_wwan_cdc_wdm_manage_power(struct usb_interface *intf, int on) | ||
198 | { | ||
199 | struct usbnet *dev = usb_get_intfdata(intf); | ||
200 | return qmi_wwan_manage_power(dev, on); | ||
178 | } | 201 | } |
179 | 202 | ||
203 | /* Some devices combine the "control" and "data" functions into a | ||
204 | * single interface with all three endpoints: interrupt + bulk in and | ||
205 | * out | ||
206 | * | ||
207 | * Setting up cdc-wdm as a subdriver owning the interrupt endpoint | ||
208 | * will let it provide userspace access to the encapsulated QMI | ||
209 | * protocol without interfering with the usbnet operations. | ||
210 | */ | ||
211 | static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf) | ||
212 | { | ||
213 | int rv; | ||
214 | struct usb_driver *subdriver = NULL; | ||
215 | atomic_t *pmcount = (void *)&dev->data[1]; | ||
216 | |||
217 | atomic_set(pmcount, 0); | ||
218 | |||
219 | /* collect all three endpoints */ | ||
220 | rv = usbnet_get_endpoints(dev, intf); | ||
221 | if (rv < 0) | ||
222 | goto err; | ||
223 | |||
224 | /* require interrupt endpoint for subdriver */ | ||
225 | if (!dev->status) { | ||
226 | rv = -EINVAL; | ||
227 | goto err; | ||
228 | } | ||
229 | |||
230 | subdriver = usb_cdc_wdm_register(intf, &dev->status->desc, 512, &qmi_wwan_cdc_wdm_manage_power); | ||
231 | if (IS_ERR(subdriver)) { | ||
232 | rv = PTR_ERR(subdriver); | ||
233 | goto err; | ||
234 | } | ||
235 | |||
236 | /* can't let usbnet use the interrupt endpoint */ | ||
237 | dev->status = NULL; | ||
238 | |||
239 | /* save subdriver struct for suspend/resume wrappers */ | ||
240 | dev->data[0] = (unsigned long)subdriver; | ||
241 | |||
242 | err: | ||
243 | return rv; | ||
244 | } | ||
245 | |||
246 | static void qmi_wwan_unbind_shared(struct usbnet *dev, struct usb_interface *intf) | ||
247 | { | ||
248 | struct usb_driver *subdriver = (void *)dev->data[0]; | ||
249 | |||
250 | if (subdriver && subdriver->disconnect) | ||
251 | subdriver->disconnect(intf); | ||
252 | |||
253 | dev->data[0] = (unsigned long)NULL; | ||
254 | } | ||
255 | |||
256 | /* suspend/resume wrappers calling both usbnet and the cdc-wdm | ||
257 | * subdriver if present. | ||
258 | * | ||
259 | * NOTE: cdc-wdm also supports pre/post_reset, but we cannot provide | ||
260 | * wrappers for those without adding usbnet reset support first. | ||
261 | */ | ||
262 | static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message) | ||
263 | { | ||
264 | struct usbnet *dev = usb_get_intfdata(intf); | ||
265 | struct usb_driver *subdriver = (void *)dev->data[0]; | ||
266 | int ret; | ||
267 | |||
268 | ret = usbnet_suspend(intf, message); | ||
269 | if (ret < 0) | ||
270 | goto err; | ||
271 | |||
272 | if (subdriver && subdriver->suspend) | ||
273 | ret = subdriver->suspend(intf, message); | ||
274 | if (ret < 0) | ||
275 | usbnet_resume(intf); | ||
276 | err: | ||
277 | return ret; | ||
278 | } | ||
279 | |||
280 | static int qmi_wwan_resume(struct usb_interface *intf) | ||
281 | { | ||
282 | struct usbnet *dev = usb_get_intfdata(intf); | ||
283 | struct usb_driver *subdriver = (void *)dev->data[0]; | ||
284 | int ret = 0; | ||
285 | |||
286 | if (subdriver && subdriver->resume) | ||
287 | ret = subdriver->resume(intf); | ||
288 | if (ret < 0) | ||
289 | goto err; | ||
290 | ret = usbnet_resume(intf); | ||
291 | if (ret < 0 && subdriver && subdriver->resume && subdriver->suspend) | ||
292 | subdriver->suspend(intf, PMSG_SUSPEND); | ||
293 | err: | ||
294 | return ret; | ||
295 | } | ||
296 | |||
297 | |||
180 | static const struct driver_info qmi_wwan_info = { | 298 | static const struct driver_info qmi_wwan_info = { |
181 | .description = "QMI speaking wwan device", | 299 | .description = "QMI speaking wwan device", |
182 | .flags = FLAG_WWAN, | 300 | .flags = FLAG_WWAN, |
@@ -184,19 +302,37 @@ static const struct driver_info qmi_wwan_info = { | |||
184 | .manage_power = qmi_wwan_manage_power, | 302 | .manage_power = qmi_wwan_manage_power, |
185 | }; | 303 | }; |
186 | 304 | ||
305 | static const struct driver_info qmi_wwan_shared = { | ||
306 | .description = "QMI speaking wwan device with combined interface", | ||
307 | .flags = FLAG_WWAN, | ||
308 | .bind = qmi_wwan_bind_shared, | ||
309 | .unbind = qmi_wwan_unbind_shared, | ||
310 | .manage_power = qmi_wwan_manage_power, | ||
311 | }; | ||
312 | |||
187 | #define HUAWEI_VENDOR_ID 0x12D1 | 313 | #define HUAWEI_VENDOR_ID 0x12D1 |
188 | 314 | ||
189 | static const struct usb_device_id products[] = { | 315 | static const struct usb_device_id products[] = { |
190 | { | 316 | { /* Huawei E392, E398 and possibly others sharing both device id and more... */ |
191 | /* Huawei E392, E398 and possibly others sharing both device id and more... */ | 317 | .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, |
192 | .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, | 318 | .idVendor = HUAWEI_VENDOR_ID, |
193 | .idVendor = HUAWEI_VENDOR_ID, | 319 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, |
194 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, | 320 | .bInterfaceSubClass = 1, |
195 | .bInterfaceSubClass = 1, | 321 | .bInterfaceProtocol = 8, /* NOTE: This is the *slave* interface of the CDC Union! */ |
196 | .bInterfaceProtocol = 8, /* NOTE: This is the *slave* interface of the CDC Union! */ | 322 | .driver_info = (unsigned long)&qmi_wwan_info, |
197 | .driver_info = (unsigned long)&qmi_wwan_info, | 323 | }, |
198 | }, { | 324 | { /* Huawei E392, E398 and possibly others in "Windows mode" |
199 | }, /* END */ | 325 | * using a combined control and data interface without any CDC |
326 | * functional descriptors | ||
327 | */ | ||
328 | .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, | ||
329 | .idVendor = HUAWEI_VENDOR_ID, | ||
330 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, | ||
331 | .bInterfaceSubClass = 1, | ||
332 | .bInterfaceProtocol = 17, | ||
333 | .driver_info = (unsigned long)&qmi_wwan_shared, | ||
334 | }, | ||
335 | { } /* END */ | ||
200 | }; | 336 | }; |
201 | MODULE_DEVICE_TABLE(usb, products); | 337 | MODULE_DEVICE_TABLE(usb, products); |
202 | 338 | ||
@@ -205,9 +341,9 @@ static struct usb_driver qmi_wwan_driver = { | |||
205 | .id_table = products, | 341 | .id_table = products, |
206 | .probe = usbnet_probe, | 342 | .probe = usbnet_probe, |
207 | .disconnect = usbnet_disconnect, | 343 | .disconnect = usbnet_disconnect, |
208 | .suspend = usbnet_suspend, | 344 | .suspend = qmi_wwan_suspend, |
209 | .resume = usbnet_resume, | 345 | .resume = qmi_wwan_resume, |
210 | .reset_resume = usbnet_resume, | 346 | .reset_resume = qmi_wwan_resume, |
211 | .supports_autosuspend = 1, | 347 | .supports_autosuspend = 1, |
212 | }; | 348 | }; |
213 | 349 | ||