2008-06-20 05:18:50 +04:00
/*
* 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/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 ,
2009-02-12 01:11:36 +03:00
. wMaxPacketSize = cpu_to_le16 ( 512 ) ,
2008-06-20 05:18:50 +04:00
} ;
static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = {
. bLength = USB_DT_ENDPOINT_SIZE ,
. bDescriptorType = USB_DT_ENDPOINT ,
. bmAttributes = USB_ENDPOINT_XFER_BULK ,
2009-02-12 01:11:36 +03:00
. wMaxPacketSize = cpu_to_le16 ( 512 ) ,
2008-06-20 05:18:50 +04:00
} ;
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 ;
}