Commit | Line | Data |
---|---|---|
f358f5b4 FB |
1 | /* |
2 | * nokia.c -- Nokia Composite Gadget Driver | |
3 | * | |
4 | * Copyright (C) 2008-2010 Nokia Corporation | |
5 | * Contact: Felipe Balbi <felipe.balbi@nokia.com> | |
6 | * | |
7 | * This gadget driver borrows from serial.c which is: | |
8 | * | |
9 | * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) | |
10 | * Copyright (C) 2008 by David Brownell | |
11 | * Copyright (C) 2008 by Nokia Corporation | |
12 | * | |
13 | * This software is distributed under the terms of the GNU General | |
14 | * Public License ("GPL") as published by the Free Software Foundation, | |
15 | * version 2 of that License. | |
16 | */ | |
17 | ||
18 | #include <linux/kernel.h> | |
3a343449 | 19 | #include <linux/module.h> |
f358f5b4 FB |
20 | #include <linux/device.h> |
21 | ||
22 | #include "u_serial.h" | |
23 | #include "u_ether.h" | |
24 | #include "u_phonet.h" | |
b904d081 | 25 | #include "u_ecm.h" |
f358f5b4 FB |
26 | #include "gadget_chips.h" |
27 | ||
28 | /* Defines */ | |
29 | ||
30 | #define NOKIA_VERSION_NUM 0x0211 | |
31 | #define NOKIA_LONG_NAME "N900 (PC-Suite Mode)" | |
32 | ||
7d16e8d3 | 33 | USB_GADGET_COMPOSITE_OPTIONS(); |
f358f5b4 | 34 | |
f1a1823f AP |
35 | USB_ETHERNET_MODULE_PARAMETERS(); |
36 | ||
f358f5b4 FB |
37 | #define NOKIA_VENDOR_ID 0x0421 /* Nokia */ |
38 | #define NOKIA_PRODUCT_ID 0x01c8 /* Nokia Gadget */ | |
39 | ||
40 | /* string IDs are assigned dynamically */ | |
41 | ||
276e2e4f | 42 | #define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX |
f358f5b4 FB |
43 | |
44 | static char manufacturer_nokia[] = "Nokia"; | |
45 | static const char product_nokia[] = NOKIA_LONG_NAME; | |
46 | static const char description_nokia[] = "PC-Suite Configuration"; | |
47 | ||
48 | static struct usb_string strings_dev[] = { | |
276e2e4f SAS |
49 | [USB_GADGET_MANUFACTURER_IDX].s = manufacturer_nokia, |
50 | [USB_GADGET_PRODUCT_IDX].s = NOKIA_LONG_NAME, | |
51 | [USB_GADGET_SERIAL_IDX].s = "", | |
f358f5b4 FB |
52 | [STRING_DESCRIPTION_IDX].s = description_nokia, |
53 | { } /* end of list */ | |
54 | }; | |
55 | ||
56 | static struct usb_gadget_strings stringtab_dev = { | |
57 | .language = 0x0409, /* en-us */ | |
58 | .strings = strings_dev, | |
59 | }; | |
60 | ||
61 | static struct usb_gadget_strings *dev_strings[] = { | |
62 | &stringtab_dev, | |
63 | NULL, | |
64 | }; | |
65 | ||
66 | static struct usb_device_descriptor device_desc = { | |
67 | .bLength = USB_DT_DEVICE_SIZE, | |
68 | .bDescriptorType = USB_DT_DEVICE, | |
69 | .bcdUSB = __constant_cpu_to_le16(0x0200), | |
70 | .bDeviceClass = USB_CLASS_COMM, | |
71 | .idVendor = __constant_cpu_to_le16(NOKIA_VENDOR_ID), | |
72 | .idProduct = __constant_cpu_to_le16(NOKIA_PRODUCT_ID), | |
ed9cbda6 | 73 | .bcdDevice = cpu_to_le16(NOKIA_VERSION_NUM), |
f358f5b4 FB |
74 | /* .iManufacturer = DYNAMIC */ |
75 | /* .iProduct = DYNAMIC */ | |
76 | .bNumConfigurations = 1, | |
77 | }; | |
78 | ||
79 | /*-------------------------------------------------------------------------*/ | |
80 | ||
81 | /* Module */ | |
82 | MODULE_DESCRIPTION("Nokia composite gadget driver for N900"); | |
83 | MODULE_AUTHOR("Felipe Balbi"); | |
84 | MODULE_LICENSE("GPL"); | |
85 | ||
86 | /*-------------------------------------------------------------------------*/ | |
15761826 SAS |
87 | static struct usb_function *f_acm_cfg1; |
88 | static struct usb_function *f_acm_cfg2; | |
b904d081 AP |
89 | static struct usb_function *f_ecm_cfg1; |
90 | static struct usb_function *f_ecm_cfg2; | |
3a343449 AP |
91 | static struct usb_function *f_obex1_cfg1; |
92 | static struct usb_function *f_obex2_cfg1; | |
93 | static struct usb_function *f_obex1_cfg2; | |
94 | static struct usb_function *f_obex2_cfg2; | |
83167f12 AP |
95 | static struct usb_function *f_phonet_cfg1; |
96 | static struct usb_function *f_phonet_cfg2; | |
0189e63a | 97 | |
f358f5b4 | 98 | |
15761826 SAS |
99 | static struct usb_configuration nokia_config_500ma_driver = { |
100 | .label = "Bus Powered", | |
101 | .bConfigurationValue = 1, | |
102 | /* .iConfiguration = DYNAMIC */ | |
103 | .bmAttributes = USB_CONFIG_ATT_ONE, | |
104 | .MaxPower = 500, | |
105 | }; | |
106 | ||
107 | static struct usb_configuration nokia_config_100ma_driver = { | |
108 | .label = "Self Powered", | |
109 | .bConfigurationValue = 2, | |
110 | /* .iConfiguration = DYNAMIC */ | |
111 | .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, | |
112 | .MaxPower = 100, | |
113 | }; | |
114 | ||
115 | static struct usb_function_instance *fi_acm; | |
b904d081 | 116 | static struct usb_function_instance *fi_ecm; |
3a343449 AP |
117 | static struct usb_function_instance *fi_obex1; |
118 | static struct usb_function_instance *fi_obex2; | |
83167f12 | 119 | static struct usb_function_instance *fi_phonet; |
15761826 | 120 | |
f358f5b4 FB |
121 | static int __init nokia_bind_config(struct usb_configuration *c) |
122 | { | |
15761826 | 123 | struct usb_function *f_acm; |
83167f12 | 124 | struct usb_function *f_phonet = NULL; |
3a343449 | 125 | struct usb_function *f_obex1 = NULL; |
b904d081 | 126 | struct usb_function *f_ecm; |
3a343449 | 127 | struct usb_function *f_obex2 = NULL; |
f358f5b4 | 128 | int status = 0; |
45ab4609 AP |
129 | int obex1_stat = -1; |
130 | int obex2_stat = -1; | |
131 | int phonet_stat = -1; | |
f358f5b4 | 132 | |
83167f12 AP |
133 | if (!IS_ERR(fi_phonet)) { |
134 | f_phonet = usb_get_function(fi_phonet); | |
135 | if (IS_ERR(f_phonet)) | |
136 | pr_debug("could not get phonet function\n"); | |
137 | } | |
f358f5b4 | 138 | |
3a343449 AP |
139 | if (!IS_ERR(fi_obex1)) { |
140 | f_obex1 = usb_get_function(fi_obex1); | |
141 | if (IS_ERR(f_obex1)) | |
142 | pr_debug("could not get obex function 0\n"); | |
143 | } | |
f358f5b4 | 144 | |
3a343449 AP |
145 | if (!IS_ERR(fi_obex2)) { |
146 | f_obex2 = usb_get_function(fi_obex2); | |
147 | if (IS_ERR(f_obex2)) | |
148 | pr_debug("could not get obex function 1\n"); | |
149 | } | |
f358f5b4 | 150 | |
15761826 | 151 | f_acm = usb_get_function(fi_acm); |
3a343449 AP |
152 | if (IS_ERR(f_acm)) { |
153 | status = PTR_ERR(f_acm); | |
154 | goto err_get_acm; | |
155 | } | |
156 | ||
b904d081 AP |
157 | f_ecm = usb_get_function(fi_ecm); |
158 | if (IS_ERR(f_ecm)) { | |
159 | status = PTR_ERR(f_ecm); | |
160 | goto err_get_ecm; | |
161 | } | |
162 | ||
83167f12 AP |
163 | if (!IS_ERR_OR_NULL(f_phonet)) { |
164 | phonet_stat = usb_add_function(c, f_phonet); | |
165 | if (phonet_stat) | |
166 | pr_debug("could not add phonet function\n"); | |
167 | } | |
168 | ||
3a343449 AP |
169 | if (!IS_ERR_OR_NULL(f_obex1)) { |
170 | obex1_stat = usb_add_function(c, f_obex1); | |
171 | if (obex1_stat) | |
172 | pr_debug("could not add obex function 0\n"); | |
173 | } | |
174 | ||
175 | if (!IS_ERR_OR_NULL(f_obex2)) { | |
176 | obex2_stat = usb_add_function(c, f_obex2); | |
177 | if (obex2_stat) | |
178 | pr_debug("could not add obex function 1\n"); | |
179 | } | |
15761826 SAS |
180 | |
181 | status = usb_add_function(c, f_acm); | |
f358f5b4 | 182 | if (status) |
15761826 | 183 | goto err_conf; |
f358f5b4 | 184 | |
b904d081 | 185 | status = usb_add_function(c, f_ecm); |
15761826 SAS |
186 | if (status) { |
187 | pr_debug("could not bind ecm config %d\n", status); | |
188 | goto err_ecm; | |
189 | } | |
3a343449 | 190 | if (c == &nokia_config_500ma_driver) { |
15761826 | 191 | f_acm_cfg1 = f_acm; |
b904d081 | 192 | f_ecm_cfg1 = f_ecm; |
83167f12 | 193 | f_phonet_cfg1 = f_phonet; |
3a343449 AP |
194 | f_obex1_cfg1 = f_obex1; |
195 | f_obex2_cfg1 = f_obex2; | |
196 | } else { | |
15761826 | 197 | f_acm_cfg2 = f_acm; |
b904d081 | 198 | f_ecm_cfg2 = f_ecm; |
83167f12 | 199 | f_phonet_cfg2 = f_phonet; |
3a343449 AP |
200 | f_obex1_cfg2 = f_obex1; |
201 | f_obex2_cfg2 = f_obex2; | |
202 | } | |
f358f5b4 FB |
203 | |
204 | return status; | |
15761826 SAS |
205 | err_ecm: |
206 | usb_remove_function(c, f_acm); | |
207 | err_conf: | |
3a343449 AP |
208 | if (!obex2_stat) |
209 | usb_remove_function(c, f_obex2); | |
210 | if (!obex1_stat) | |
211 | usb_remove_function(c, f_obex1); | |
83167f12 AP |
212 | if (!phonet_stat) |
213 | usb_remove_function(c, f_phonet); | |
b904d081 AP |
214 | usb_put_function(f_ecm); |
215 | err_get_ecm: | |
15761826 | 216 | usb_put_function(f_acm); |
3a343449 AP |
217 | err_get_acm: |
218 | if (!IS_ERR_OR_NULL(f_obex2)) | |
219 | usb_put_function(f_obex2); | |
220 | if (!IS_ERR_OR_NULL(f_obex1)) | |
221 | usb_put_function(f_obex1); | |
83167f12 AP |
222 | if (!IS_ERR_OR_NULL(f_phonet)) |
223 | usb_put_function(f_phonet); | |
15761826 | 224 | return status; |
f358f5b4 FB |
225 | } |
226 | ||
f358f5b4 FB |
227 | static int __init nokia_bind(struct usb_composite_dev *cdev) |
228 | { | |
f358f5b4 FB |
229 | struct usb_gadget *gadget = cdev->gadget; |
230 | int status; | |
231 | ||
e1f15ccb | 232 | status = usb_string_ids_tab(cdev, strings_dev); |
f358f5b4 FB |
233 | if (status < 0) |
234 | goto err_usb; | |
276e2e4f SAS |
235 | device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; |
236 | device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; | |
e1f15ccb | 237 | status = strings_dev[STRING_DESCRIPTION_IDX].id; |
f358f5b4 FB |
238 | nokia_config_500ma_driver.iConfiguration = status; |
239 | nokia_config_100ma_driver.iConfiguration = status; | |
240 | ||
b904d081 AP |
241 | if (!gadget_supports_altsettings(gadget)) { |
242 | status = -ENODEV; | |
f358f5b4 | 243 | goto err_usb; |
b904d081 | 244 | } |
f358f5b4 | 245 | |
83167f12 AP |
246 | fi_phonet = usb_get_function_instance("phonet"); |
247 | if (IS_ERR(fi_phonet)) | |
248 | pr_debug("could not find phonet function\n"); | |
249 | ||
3a343449 AP |
250 | fi_obex1 = usb_get_function_instance("obex"); |
251 | if (IS_ERR(fi_obex1)) | |
252 | pr_debug("could not find obex function 1\n"); | |
253 | ||
254 | fi_obex2 = usb_get_function_instance("obex"); | |
255 | if (IS_ERR(fi_obex2)) | |
256 | pr_debug("could not find obex function 2\n"); | |
257 | ||
15761826 | 258 | fi_acm = usb_get_function_instance("acm"); |
b904d081 AP |
259 | if (IS_ERR(fi_acm)) { |
260 | status = PTR_ERR(fi_acm); | |
3a343449 | 261 | goto err_obex2_inst; |
b904d081 AP |
262 | } |
263 | ||
264 | fi_ecm = usb_get_function_instance("ecm"); | |
265 | if (IS_ERR(fi_ecm)) { | |
266 | status = PTR_ERR(fi_ecm); | |
267 | goto err_acm_inst; | |
268 | } | |
15761826 | 269 | |
25985edc | 270 | /* finally register the configuration */ |
c9bfff9c UKK |
271 | status = usb_add_config(cdev, &nokia_config_500ma_driver, |
272 | nokia_bind_config); | |
f358f5b4 | 273 | if (status < 0) |
b904d081 | 274 | goto err_ecm_inst; |
f358f5b4 | 275 | |
c9bfff9c UKK |
276 | status = usb_add_config(cdev, &nokia_config_100ma_driver, |
277 | nokia_bind_config); | |
f358f5b4 | 278 | if (status < 0) |
15761826 | 279 | goto err_put_cfg1; |
f358f5b4 | 280 | |
7d16e8d3 | 281 | usb_composite_overwrite_options(cdev, &coverwrite); |
f358f5b4 FB |
282 | dev_info(&gadget->dev, "%s\n", NOKIA_LONG_NAME); |
283 | ||
284 | return 0; | |
285 | ||
15761826 SAS |
286 | err_put_cfg1: |
287 | usb_put_function(f_acm_cfg1); | |
3a343449 AP |
288 | if (!IS_ERR_OR_NULL(f_obex1_cfg1)) |
289 | usb_put_function(f_obex1_cfg1); | |
290 | if (!IS_ERR_OR_NULL(f_obex2_cfg1)) | |
291 | usb_put_function(f_obex2_cfg1); | |
83167f12 AP |
292 | if (!IS_ERR_OR_NULL(f_phonet_cfg1)) |
293 | usb_put_function(f_phonet_cfg1); | |
b904d081 AP |
294 | usb_put_function(f_ecm_cfg1); |
295 | err_ecm_inst: | |
296 | usb_put_function_instance(fi_ecm); | |
15761826 SAS |
297 | err_acm_inst: |
298 | usb_put_function_instance(fi_acm); | |
3a343449 AP |
299 | err_obex2_inst: |
300 | if (!IS_ERR(fi_obex2)) | |
301 | usb_put_function_instance(fi_obex2); | |
302 | if (!IS_ERR(fi_obex1)) | |
303 | usb_put_function_instance(fi_obex1); | |
83167f12 AP |
304 | if (!IS_ERR(fi_phonet)) |
305 | usb_put_function_instance(fi_phonet); | |
f358f5b4 | 306 | err_usb: |
f358f5b4 FB |
307 | return status; |
308 | } | |
309 | ||
310 | static int __exit nokia_unbind(struct usb_composite_dev *cdev) | |
311 | { | |
3a343449 AP |
312 | if (!IS_ERR_OR_NULL(f_obex1_cfg2)) |
313 | usb_put_function(f_obex1_cfg2); | |
314 | if (!IS_ERR_OR_NULL(f_obex2_cfg2)) | |
315 | usb_put_function(f_obex2_cfg2); | |
316 | if (!IS_ERR_OR_NULL(f_obex1_cfg1)) | |
317 | usb_put_function(f_obex1_cfg1); | |
318 | if (!IS_ERR_OR_NULL(f_obex2_cfg1)) | |
319 | usb_put_function(f_obex2_cfg1); | |
83167f12 AP |
320 | if (!IS_ERR_OR_NULL(f_phonet_cfg1)) |
321 | usb_put_function(f_phonet_cfg1); | |
322 | if (!IS_ERR_OR_NULL(f_phonet_cfg2)) | |
323 | usb_put_function(f_phonet_cfg2); | |
15761826 SAS |
324 | usb_put_function(f_acm_cfg1); |
325 | usb_put_function(f_acm_cfg2); | |
b904d081 AP |
326 | usb_put_function(f_ecm_cfg1); |
327 | usb_put_function(f_ecm_cfg2); | |
83167f12 | 328 | |
b904d081 | 329 | usb_put_function_instance(fi_ecm); |
3a343449 AP |
330 | if (!IS_ERR(fi_obex2)) |
331 | usb_put_function_instance(fi_obex2); | |
83167f12 AP |
332 | if (!IS_ERR(fi_obex1)) |
333 | usb_put_function_instance(fi_obex1); | |
334 | if (!IS_ERR(fi_phonet)) | |
335 | usb_put_function_instance(fi_phonet); | |
15761826 | 336 | usb_put_function_instance(fi_acm); |
19b10a88 | 337 | |
f358f5b4 FB |
338 | return 0; |
339 | } | |
340 | ||
c2ec75c2 | 341 | static __refdata struct usb_composite_driver nokia_driver = { |
f358f5b4 FB |
342 | .name = "g_nokia", |
343 | .dev = &device_desc, | |
344 | .strings = dev_strings, | |
35a0e0bf | 345 | .max_speed = USB_SPEED_HIGH, |
03e42bd5 | 346 | .bind = nokia_bind, |
f358f5b4 FB |
347 | .unbind = __exit_p(nokia_unbind), |
348 | }; | |
349 | ||
350 | static int __init nokia_init(void) | |
351 | { | |
03e42bd5 | 352 | return usb_composite_probe(&nokia_driver); |
f358f5b4 FB |
353 | } |
354 | module_init(nokia_init); | |
355 | ||
356 | static void __exit nokia_cleanup(void) | |
357 | { | |
358 | usb_composite_unregister(&nokia_driver); | |
359 | } | |
360 | module_exit(nokia_cleanup); |