diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-06-19 21:18:50 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-07-21 18:16:07 -0400 |
commit | 61d8baea5d02f0f00fb789ce5551cbd8f8b77087 (patch) | |
tree | 90e52cbef05d698cb6b5a361930f19ea9b6fba17 /drivers/usb/gadget/f_serial.c | |
parent | 4d5a73dc39c1e1d8ba5feec5c6234ae920c59161 (diff) |
usb gadget serial: split out generic serial function
Split out the generic serial support into a "function driver". This
closely mimics the ACM support, but with a MUCH simpler control model.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget/f_serial.c')
-rw-r--r-- | drivers/usb/gadget/f_serial.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c new file mode 100644 index 000000000000..1b6bde9aaed5 --- /dev/null +++ b/drivers/usb/gadget/f_serial.c | |||
@@ -0,0 +1,296 @@ | |||
1 | /* | ||
2 | * f_serial.c - generic USB serial function driver | ||
3 | * | ||
4 | * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) | ||
5 | * Copyright (C) 2008 by David Brownell | ||
6 | * Copyright (C) 2008 by Nokia Corporation | ||
7 | * | ||
8 | * This software is distributed under the terms of the GNU General | ||
9 | * Public License ("GPL") as published by the Free Software Foundation, | ||
10 | * either version 2 of that License or (at your option) any later version. | ||
11 | */ | ||
12 | |||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/device.h> | ||
15 | |||
16 | #include "u_serial.h" | ||
17 | #include "gadget_chips.h" | ||
18 | |||
19 | |||
20 | /* | ||
21 | * This function packages a simple "generic serial" port with no real | ||
22 | * control mechanisms, just raw data transfer over two bulk endpoints. | ||
23 | * | ||
24 | * Because it's not standardized, this isn't as interoperable as the | ||
25 | * CDC ACM driver. However, for many purposes it's just as functional | ||
26 | * if you can arrange appropriate host side drivers. | ||
27 | */ | ||
28 | |||
29 | struct gser_descs { | ||
30 | struct usb_endpoint_descriptor *in; | ||
31 | struct usb_endpoint_descriptor *out; | ||
32 | }; | ||
33 | |||
34 | struct f_gser { | ||
35 | struct gserial port; | ||
36 | u8 data_id; | ||
37 | u8 port_num; | ||
38 | |||
39 | struct usb_descriptor_header **fs_function; | ||
40 | struct gser_descs fs; | ||
41 | struct usb_descriptor_header **hs_function; | ||
42 | struct gser_descs hs; | ||
43 | }; | ||
44 | |||
45 | static inline struct f_gser *func_to_gser(struct usb_function *f) | ||
46 | { | ||
47 | return container_of(f, struct f_gser, port.func); | ||
48 | } | ||
49 | |||
50 | /*-------------------------------------------------------------------------*/ | ||
51 | |||
52 | /* interface descriptor: */ | ||
53 | |||
54 | static struct usb_interface_descriptor gser_interface_desc __initdata = { | ||
55 | .bLength = USB_DT_INTERFACE_SIZE, | ||
56 | .bDescriptorType = USB_DT_INTERFACE, | ||
57 | /* .bInterfaceNumber = DYNAMIC */ | ||
58 | .bNumEndpoints = 2, | ||
59 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, | ||
60 | .bInterfaceSubClass = 0, | ||
61 | .bInterfaceProtocol = 0, | ||
62 | /* .iInterface = DYNAMIC */ | ||
63 | }; | ||
64 | |||
65 | /* full speed support: */ | ||
66 | |||
67 | static struct usb_endpoint_descriptor gser_fs_in_desc __initdata = { | ||
68 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
69 | .bDescriptorType = USB_DT_ENDPOINT, | ||
70 | .bEndpointAddress = USB_DIR_IN, | ||
71 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
72 | }; | ||
73 | |||
74 | static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = { | ||
75 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
76 | .bDescriptorType = USB_DT_ENDPOINT, | ||
77 | .bEndpointAddress = USB_DIR_OUT, | ||
78 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
79 | }; | ||
80 | |||
81 | static struct usb_descriptor_header *gser_fs_function[] __initdata = { | ||
82 | (struct usb_descriptor_header *) &gser_interface_desc, | ||
83 | (struct usb_descriptor_header *) &gser_fs_in_desc, | ||
84 | (struct usb_descriptor_header *) &gser_fs_out_desc, | ||
85 | NULL, | ||
86 | }; | ||
87 | |||
88 | /* high speed support: */ | ||
89 | |||
90 | static struct usb_endpoint_descriptor gser_hs_in_desc __initdata = { | ||
91 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
92 | .bDescriptorType = USB_DT_ENDPOINT, | ||
93 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
94 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
95 | }; | ||
96 | |||
97 | static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = { | ||
98 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
99 | .bDescriptorType = USB_DT_ENDPOINT, | ||
100 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
101 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
102 | }; | ||
103 | |||
104 | static struct usb_descriptor_header *gser_hs_function[] __initdata = { | ||
105 | (struct usb_descriptor_header *) &gser_interface_desc, | ||
106 | (struct usb_descriptor_header *) &gser_hs_in_desc, | ||
107 | (struct usb_descriptor_header *) &gser_hs_out_desc, | ||
108 | NULL, | ||
109 | }; | ||
110 | |||
111 | /* string descriptors: */ | ||
112 | |||
113 | static struct usb_string gser_string_defs[] = { | ||
114 | [0].s = "Generic Serial", | ||
115 | { } /* end of list */ | ||
116 | }; | ||
117 | |||
118 | static struct usb_gadget_strings gser_string_table = { | ||
119 | .language = 0x0409, /* en-us */ | ||
120 | .strings = gser_string_defs, | ||
121 | }; | ||
122 | |||
123 | static struct usb_gadget_strings *gser_strings[] = { | ||
124 | &gser_string_table, | ||
125 | NULL, | ||
126 | }; | ||
127 | |||
128 | /*-------------------------------------------------------------------------*/ | ||
129 | |||
130 | static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) | ||
131 | { | ||
132 | struct f_gser *gser = func_to_gser(f); | ||
133 | struct usb_composite_dev *cdev = f->config->cdev; | ||
134 | |||
135 | /* we know alt == 0, so this is an activation or a reset */ | ||
136 | |||
137 | if (gser->port.in->driver_data) { | ||
138 | DBG(cdev, "reset generic ttyGS%d\n", gser->port_num); | ||
139 | gserial_disconnect(&gser->port); | ||
140 | } else { | ||
141 | DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); | ||
142 | gser->port.in_desc = ep_choose(cdev->gadget, | ||
143 | gser->hs.in, gser->fs.in); | ||
144 | gser->port.out_desc = ep_choose(cdev->gadget, | ||
145 | gser->hs.out, gser->fs.out); | ||
146 | } | ||
147 | gserial_connect(&gser->port, gser->port_num); | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static void gser_disable(struct usb_function *f) | ||
152 | { | ||
153 | struct f_gser *gser = func_to_gser(f); | ||
154 | struct usb_composite_dev *cdev = f->config->cdev; | ||
155 | |||
156 | DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num); | ||
157 | gserial_disconnect(&gser->port); | ||
158 | } | ||
159 | |||
160 | /*-------------------------------------------------------------------------*/ | ||
161 | |||
162 | /* serial function driver setup/binding */ | ||
163 | |||
164 | static int __init | ||
165 | gser_bind(struct usb_configuration *c, struct usb_function *f) | ||
166 | { | ||
167 | struct usb_composite_dev *cdev = c->cdev; | ||
168 | struct f_gser *gser = func_to_gser(f); | ||
169 | int status; | ||
170 | struct usb_ep *ep; | ||
171 | |||
172 | /* allocate instance-specific interface IDs */ | ||
173 | status = usb_interface_id(c, f); | ||
174 | if (status < 0) | ||
175 | goto fail; | ||
176 | gser->data_id = status; | ||
177 | gser_interface_desc.bInterfaceNumber = status; | ||
178 | |||
179 | status = -ENODEV; | ||
180 | |||
181 | /* allocate instance-specific endpoints */ | ||
182 | ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc); | ||
183 | if (!ep) | ||
184 | goto fail; | ||
185 | gser->port.in = ep; | ||
186 | ep->driver_data = cdev; /* claim */ | ||
187 | |||
188 | ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc); | ||
189 | if (!ep) | ||
190 | goto fail; | ||
191 | gser->port.out = ep; | ||
192 | ep->driver_data = cdev; /* claim */ | ||
193 | |||
194 | /* copy descriptors, and track endpoint copies */ | ||
195 | f->descriptors = usb_copy_descriptors(gser_fs_function); | ||
196 | |||
197 | gser->fs.in = usb_find_endpoint(gser_fs_function, | ||
198 | f->descriptors, &gser_fs_in_desc); | ||
199 | gser->fs.out = usb_find_endpoint(gser_fs_function, | ||
200 | f->descriptors, &gser_fs_out_desc); | ||
201 | |||
202 | |||
203 | /* support all relevant hardware speeds... we expect that when | ||
204 | * hardware is dual speed, all bulk-capable endpoints work at | ||
205 | * both speeds | ||
206 | */ | ||
207 | if (gadget_is_dualspeed(c->cdev->gadget)) { | ||
208 | gser_hs_in_desc.bEndpointAddress = | ||
209 | gser_fs_in_desc.bEndpointAddress; | ||
210 | gser_hs_out_desc.bEndpointAddress = | ||
211 | gser_fs_out_desc.bEndpointAddress; | ||
212 | |||
213 | /* copy descriptors, and track endpoint copies */ | ||
214 | f->hs_descriptors = usb_copy_descriptors(gser_hs_function); | ||
215 | |||
216 | gser->hs.in = usb_find_endpoint(gser_hs_function, | ||
217 | f->hs_descriptors, &gser_hs_in_desc); | ||
218 | gser->hs.out = usb_find_endpoint(gser_hs_function, | ||
219 | f->hs_descriptors, &gser_hs_out_desc); | ||
220 | } | ||
221 | |||
222 | DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", | ||
223 | gser->port_num, | ||
224 | gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", | ||
225 | gser->port.in->name, gser->port.out->name); | ||
226 | return 0; | ||
227 | |||
228 | fail: | ||
229 | /* we might as well release our claims on endpoints */ | ||
230 | if (gser->port.out) | ||
231 | gser->port.out->driver_data = NULL; | ||
232 | if (gser->port.in) | ||
233 | gser->port.in->driver_data = NULL; | ||
234 | |||
235 | ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); | ||
236 | |||
237 | return status; | ||
238 | } | ||
239 | |||
240 | static void | ||
241 | gser_unbind(struct usb_configuration *c, struct usb_function *f) | ||
242 | { | ||
243 | if (gadget_is_dualspeed(c->cdev->gadget)) | ||
244 | usb_free_descriptors(f->hs_descriptors); | ||
245 | usb_free_descriptors(f->descriptors); | ||
246 | kfree(func_to_gser(f)); | ||
247 | } | ||
248 | |||
249 | /** | ||
250 | * gser_bind_config - add a generic serial function to a configuration | ||
251 | * @c: the configuration to support the serial instance | ||
252 | * @port_num: /dev/ttyGS* port this interface will use | ||
253 | * Context: single threaded during gadget setup | ||
254 | * | ||
255 | * Returns zero on success, else negative errno. | ||
256 | * | ||
257 | * Caller must have called @gserial_setup() with enough ports to | ||
258 | * handle all the ones it binds. Caller is also responsible | ||
259 | * for calling @gserial_cleanup() before module unload. | ||
260 | */ | ||
261 | int __init gser_bind_config(struct usb_configuration *c, u8 port_num) | ||
262 | { | ||
263 | struct f_gser *gser; | ||
264 | int status; | ||
265 | |||
266 | /* REVISIT might want instance-specific strings to help | ||
267 | * distinguish instances ... | ||
268 | */ | ||
269 | |||
270 | /* maybe allocate device-global string ID */ | ||
271 | if (gser_string_defs[0].id == 0) { | ||
272 | status = usb_string_id(c->cdev); | ||
273 | if (status < 0) | ||
274 | return status; | ||
275 | gser_string_defs[0].id = status; | ||
276 | } | ||
277 | |||
278 | /* allocate and initialize one new instance */ | ||
279 | gser = kzalloc(sizeof *gser, GFP_KERNEL); | ||
280 | if (!gser) | ||
281 | return -ENOMEM; | ||
282 | |||
283 | gser->port_num = port_num; | ||
284 | |||
285 | gser->port.func.name = "gser"; | ||
286 | gser->port.func.strings = gser_strings; | ||
287 | gser->port.func.bind = gser_bind; | ||
288 | gser->port.func.unbind = gser_unbind; | ||
289 | gser->port.func.set_alt = gser_set_alt; | ||
290 | gser->port.func.disable = gser_disable; | ||
291 | |||
292 | status = usb_add_function(c, &gser->port.func); | ||
293 | if (status) | ||
294 | kfree(gser); | ||
295 | return status; | ||
296 | } | ||