Name

Peripheral Controller Drivers — Structure and Interface

Description

This section is mainly of interest to developers who want to write a new peripheral controller driver. It describes the interface used by the USB stack to initiate PCD operations and the API that a PCD can use to interact with the USB stack.

PCD Objects

The main interface between the USB stack and each type of PCD is the usb_pcd object:

struct usb_pcd
{
    const char                  *name;                          // Driver name

    int pad;

    // Initialization etc.
    void (*init)( void );                                       // Initialize controller(s)
    int  (*attach)( usb_pcdi *pcdi, usb_target *tgt );          // Attach to hardware
    int  (*detach)( usb_pcdi *pcdi, usb_target *tgt );          // Detach from hardware

    // Endpoint attach/detach
    int (*endpoint_attach)( usb_pcdi *pcdi, usb_target_endpoint *tep );
    int (*endpoint_detach)( usb_pcdi *pcdi, usb_target_endpoint *tep );

    // Set/clear endpoint stall
    int (*endpoint_stall)( usb_pcdi *pcdi, usb_target_endpoint *tep, int stall );

    // Transfer handling
    int  (*submit)( usb_target *tgt, usb_tfr *tfr );            // Submit transfer (chain)
    int  (*cancel)( usb_target *tgt, usb_tfr *tfr );            // Cancel transfer

    // Controller operation
    void (*poll)( usb_pcdi *pcdi );                             // Poll controller for events

    int (*set_address)( usb_pcdi *pcdi, usb_uint8 addr );       // Set new target address

};

The fields are as follows:

name
This is a pointer to a string that names this device. It is mainly used for debugging.
init

This is called once by the USB stack to initialize all PCDs of this type. In combination with platform code this function should enumerate all the PCDs of the supported type and eventually call usb_pcdi_register() to make the controller available to the USB stack.

The call to usb_pcdi_register() is passed a usb_pcdi object that the PCD should allocate in its private data structures.

While this function should locate the devices and initialize the PCD data structures it should not access the Peripheral Controller hardware at this point.

attach
This is called to attach the PCD to the hardware. This is when the hardware should be initialized, interrupt handlers registered and everything made ready for transfers to occur.
detach

This is called to detach the PCD from the hardware. It should undo the initialization done by the attach function, leaving the device free for other software to take control.

The main reason for this attach/detach mechanism is to allow OTG devices to be shared between host and peripheral drivers.

endpoint_attach

This is called to create an endpoint in the peripheral controller. The PCD should use the endpoint descriptor in the usb_target_endpoint object to create an endpoint of the correct type and direction for the device.

The PCD will typically allocate controller and driver data structures to represent this endpoint. If the underlying controller only supports a limited number of endpoints, then the driver should either fail excess endpoint attachments, or arrange to share the physical endpoints between a larger number of virtual endpoints. If the PCD endpoint is created successfully the it should assign a pointer to it to the pcd field in the usb_target_endpoint object.

endpoint_detach
This is called when the target is detached, or changes its active interface. It undoes the resource allocation made in endpoint_attach. Additionally, this function must cancel any transfers that are pending on the endpoint. Depending on the nature of the controller, these transfer cancellations and the eventual deallocation of the endpoint may happen after this function returns.
submit
This is called to submit a transfer to the controller. Internally, this function should extract the PCD private data from the target pcdi field and the endpoint from the target's usb_target_endpoint object for the transfer's endpoint address. The PCD is free to use the hcd_endpoint and hcd_list fields in the usb_tfr object; the latter should be initialized before use.
cancel
This is called to cancel a pending transfer. The transfer will not necessarily be available for reuse once this function returns. This is only guaranteed once the transfer's callback is invoked, either with a USB_TFR_CANCELLED status, some other error, or even USB_OK.
poll

This is called from the main USB handling loop to give the PCD the chance to service the hardware. Controller operations may be handled either in this function or in the ISR or DSR; however, callbacks must be made from this function. The PCD should test the hardware for transfer completion, device attach/detach and errors and handle them here.

If a transfer completes in this polling routine its callback may either be invoked directly by calling usb_tfr_callback_pop() or may be deferred for later processing by calling usb_tfr_complete_async(). The latter is preferable since it avoids any problems of recursion if the callback submits another transfer.

The simplest way to write an PCD is to do all device event handling in the poll() routine. If the controller supports interrupts then the PCD can call usb_signal_poll() to cause the poll routine to be called. If it makes sense to handle device events in the ISR or DSR, callbacks, such as returning transfers, should still happen in the poll routine.

set_address
This is called to set the address of the target in the peripheral controller. This is called after the host has sent a SET_ADDRESS command.

There is just one instance of the usb_pcd object for each type of peripheral controller. However, there may be more than one physical device of each type on the board. Each of these is represented by a usb_pcdi object:

struct usb_pcdi
{
    usb_node                    node;                   // Link in PDCI list

    char                        *name;                  // Instance name

    usb_uint8                   state;                  // Controller state

    usb_pcd                     *pcd;                   // Pointer to PCD
    usb_target                  *tgt;                   // Current attached target
    void                        *pcdi;                  // Driver private data
};
node
Node in list of active PCD instances. This need not be initialized by the PCD, it is initialize by usb_pcdi_register().
name
Name of this interface. This should distinguish it from all other PCDIs, and is used by usb_pcdi_find_by_name() to locate this PCDI. This field must be initialized by the PCD before calling usb_pcdi_register().
pcd
A pointer to the usb_pcd object for the controlling driver. This field must be initialized by the PCD before calling usb_pcdi_register().
tgt
When a target is attached to a PCDI by calling usb_target_attach(), a pointer to the target is placed here. This field should be initialized to NULL by the PCD before calling usb_pcdi_register().
pcdi
A pointer to a per-instance data structure in the PCD. This field must be initialized by the PCD before calling usb_pcdi_register().