mirror of
https://github.com/torvalds/linux.git
synced 2024-11-07 04:32:03 +00:00
72c973dd2b
Change usb_ep_enable() prototype to use endpoint descriptor from usb_ep. This optimization spares the FDs from saving the endpoint chosen descriptor. This optimization is not full though. To fully exploit this change, one needs to update all the UDCs as well since in the current implementation each of them saves the endpoint descriptor in it's internal (and extended) endpoint structure. Signed-off-by: Tatyana Brokhman <tlinder@codeaurora.org> Signed-off-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
296 lines
8.1 KiB
C
296 lines
8.1 KiB
C
/*
|
|
* f_serial.c - generic USB serial function driver
|
|
*
|
|
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
|
|
* Copyright (C) 2008 by David Brownell
|
|
* Copyright (C) 2008 by Nokia Corporation
|
|
*
|
|
* This software is distributed under the terms of the GNU General
|
|
* Public License ("GPL") as published by the Free Software Foundation,
|
|
* either version 2 of that License or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
|
|
#include "u_serial.h"
|
|
#include "gadget_chips.h"
|
|
|
|
|
|
/*
|
|
* This function packages a simple "generic serial" port with no real
|
|
* control mechanisms, just raw data transfer over two bulk endpoints.
|
|
*
|
|
* Because it's not standardized, this isn't as interoperable as the
|
|
* CDC ACM driver. However, for many purposes it's just as functional
|
|
* if you can arrange appropriate host side drivers.
|
|
*/
|
|
|
|
struct gser_descs {
|
|
struct usb_endpoint_descriptor *in;
|
|
struct usb_endpoint_descriptor *out;
|
|
};
|
|
|
|
struct f_gser {
|
|
struct gserial port;
|
|
u8 data_id;
|
|
u8 port_num;
|
|
|
|
struct gser_descs fs;
|
|
struct gser_descs hs;
|
|
};
|
|
|
|
static inline struct f_gser *func_to_gser(struct usb_function *f)
|
|
{
|
|
return container_of(f, struct f_gser, port.func);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* interface descriptor: */
|
|
|
|
static struct usb_interface_descriptor gser_interface_desc __initdata = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
/* .bInterfaceNumber = DYNAMIC */
|
|
.bNumEndpoints = 2,
|
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
|
.bInterfaceSubClass = 0,
|
|
.bInterfaceProtocol = 0,
|
|
/* .iInterface = DYNAMIC */
|
|
};
|
|
|
|
/* full speed support: */
|
|
|
|
static struct usb_endpoint_descriptor gser_fs_in_desc __initdata = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_OUT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_descriptor_header *gser_fs_function[] __initdata = {
|
|
(struct usb_descriptor_header *) &gser_interface_desc,
|
|
(struct usb_descriptor_header *) &gser_fs_in_desc,
|
|
(struct usb_descriptor_header *) &gser_fs_out_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* high speed support: */
|
|
|
|
static struct usb_endpoint_descriptor gser_hs_in_desc __initdata = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
};
|
|
|
|
static struct usb_descriptor_header *gser_hs_function[] __initdata = {
|
|
(struct usb_descriptor_header *) &gser_interface_desc,
|
|
(struct usb_descriptor_header *) &gser_hs_in_desc,
|
|
(struct usb_descriptor_header *) &gser_hs_out_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* string descriptors: */
|
|
|
|
static struct usb_string gser_string_defs[] = {
|
|
[0].s = "Generic Serial",
|
|
{ } /* end of list */
|
|
};
|
|
|
|
static struct usb_gadget_strings gser_string_table = {
|
|
.language = 0x0409, /* en-us */
|
|
.strings = gser_string_defs,
|
|
};
|
|
|
|
static struct usb_gadget_strings *gser_strings[] = {
|
|
&gser_string_table,
|
|
NULL,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
|
{
|
|
struct f_gser *gser = func_to_gser(f);
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
|
|
/* we know alt == 0, so this is an activation or a reset */
|
|
|
|
if (gser->port.in->driver_data) {
|
|
DBG(cdev, "reset generic ttyGS%d\n", gser->port_num);
|
|
gserial_disconnect(&gser->port);
|
|
} else {
|
|
DBG(cdev, "activate generic ttyGS%d\n", gser->port_num);
|
|
gser->port.in->desc = ep_choose(cdev->gadget,
|
|
gser->hs.in, gser->fs.in);
|
|
gser->port.out->desc = ep_choose(cdev->gadget,
|
|
gser->hs.out, gser->fs.out);
|
|
}
|
|
gserial_connect(&gser->port, gser->port_num);
|
|
return 0;
|
|
}
|
|
|
|
static void gser_disable(struct usb_function *f)
|
|
{
|
|
struct f_gser *gser = func_to_gser(f);
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
|
|
DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num);
|
|
gserial_disconnect(&gser->port);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* serial function driver setup/binding */
|
|
|
|
static int __init
|
|
gser_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct usb_composite_dev *cdev = c->cdev;
|
|
struct f_gser *gser = func_to_gser(f);
|
|
int status;
|
|
struct usb_ep *ep;
|
|
|
|
/* allocate instance-specific interface IDs */
|
|
status = usb_interface_id(c, f);
|
|
if (status < 0)
|
|
goto fail;
|
|
gser->data_id = status;
|
|
gser_interface_desc.bInterfaceNumber = status;
|
|
|
|
status = -ENODEV;
|
|
|
|
/* allocate instance-specific endpoints */
|
|
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc);
|
|
if (!ep)
|
|
goto fail;
|
|
gser->port.in = ep;
|
|
ep->driver_data = cdev; /* claim */
|
|
|
|
ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
|
|
if (!ep)
|
|
goto fail;
|
|
gser->port.out = ep;
|
|
ep->driver_data = cdev; /* claim */
|
|
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->descriptors = usb_copy_descriptors(gser_fs_function);
|
|
|
|
gser->fs.in = usb_find_endpoint(gser_fs_function,
|
|
f->descriptors, &gser_fs_in_desc);
|
|
gser->fs.out = usb_find_endpoint(gser_fs_function,
|
|
f->descriptors, &gser_fs_out_desc);
|
|
|
|
|
|
/* support all relevant hardware speeds... we expect that when
|
|
* hardware is dual speed, all bulk-capable endpoints work at
|
|
* both speeds
|
|
*/
|
|
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
|
gser_hs_in_desc.bEndpointAddress =
|
|
gser_fs_in_desc.bEndpointAddress;
|
|
gser_hs_out_desc.bEndpointAddress =
|
|
gser_fs_out_desc.bEndpointAddress;
|
|
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->hs_descriptors = usb_copy_descriptors(gser_hs_function);
|
|
|
|
gser->hs.in = usb_find_endpoint(gser_hs_function,
|
|
f->hs_descriptors, &gser_hs_in_desc);
|
|
gser->hs.out = usb_find_endpoint(gser_hs_function,
|
|
f->hs_descriptors, &gser_hs_out_desc);
|
|
}
|
|
|
|
DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
|
|
gser->port_num,
|
|
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
|
gser->port.in->name, gser->port.out->name);
|
|
return 0;
|
|
|
|
fail:
|
|
/* we might as well release our claims on endpoints */
|
|
if (gser->port.out)
|
|
gser->port.out->driver_data = NULL;
|
|
if (gser->port.in)
|
|
gser->port.in->driver_data = NULL;
|
|
|
|
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
gser_unbind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
if (gadget_is_dualspeed(c->cdev->gadget))
|
|
usb_free_descriptors(f->hs_descriptors);
|
|
usb_free_descriptors(f->descriptors);
|
|
kfree(func_to_gser(f));
|
|
}
|
|
|
|
/**
|
|
* gser_bind_config - add a generic serial function to a configuration
|
|
* @c: the configuration to support the serial instance
|
|
* @port_num: /dev/ttyGS* port this interface will use
|
|
* Context: single threaded during gadget setup
|
|
*
|
|
* Returns zero on success, else negative errno.
|
|
*
|
|
* Caller must have called @gserial_setup() with enough ports to
|
|
* handle all the ones it binds. Caller is also responsible
|
|
* for calling @gserial_cleanup() before module unload.
|
|
*/
|
|
int __init gser_bind_config(struct usb_configuration *c, u8 port_num)
|
|
{
|
|
struct f_gser *gser;
|
|
int status;
|
|
|
|
/* REVISIT might want instance-specific strings to help
|
|
* distinguish instances ...
|
|
*/
|
|
|
|
/* maybe allocate device-global string ID */
|
|
if (gser_string_defs[0].id == 0) {
|
|
status = usb_string_id(c->cdev);
|
|
if (status < 0)
|
|
return status;
|
|
gser_string_defs[0].id = status;
|
|
}
|
|
|
|
/* allocate and initialize one new instance */
|
|
gser = kzalloc(sizeof *gser, GFP_KERNEL);
|
|
if (!gser)
|
|
return -ENOMEM;
|
|
|
|
gser->port_num = port_num;
|
|
|
|
gser->port.func.name = "gser";
|
|
gser->port.func.strings = gser_strings;
|
|
gser->port.func.bind = gser_bind;
|
|
gser->port.func.unbind = gser_unbind;
|
|
gser->port.func.set_alt = gser_set_alt;
|
|
gser->port.func.disable = gser_disable;
|
|
|
|
status = usb_add_function(c, &gser->port.func);
|
|
if (status)
|
|
kfree(gser);
|
|
return status;
|
|
}
|