diff options
Diffstat (limited to 'ecos/packages/devs/usb')
34 files changed, 16452 insertions, 0 deletions
diff --git a/ecos/packages/devs/usb/at91/current/ChangeLog b/ecos/packages/devs/usb/at91/current/ChangeLog new file mode 100644 index 0000000..0f38086 --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/ChangeLog @@ -0,0 +1,131 @@ +2010-11-12 Christophe Coutand <ecos@hotmail.co.uk> + + * src/usbs_at91.c : Complete support for halting endpoints + * cdl/usbs_at91.cdl : Add option + CYGSEM_DEVS_USB_AT91_ZERO_LENGTH_PACKET_TERMINATION. When data + transfer length is known from both side, sending an empty packet + at the end of the transfer is not required. + Add devtab entry for endpoints 4 to 7. + +2008-11-17 Gabor Toeroek <tgabor84@gmail.com> + + * src/usbs_at91.c (usbs_at91_set_pullup): Fix typo introduced by + Andrew Lunn in the last patch. + +2008-11-03 Gabor Toeroek <tgabor84@gmail.com> + + * cdl/usbs_at91.cdl: + * include/usbs_at91.h: + * src/usbs_at91.c: + * src/usbs_at91_data.c: Support for SAM7SE which has an internal + pullup. Added extra endpoints for those chips that have them. + +2007-11-20 Andrew Lunn <andrew.lunn@ascom.ch> + + * cdl/usbs_at91.cdl: Fixed typos in + CYGNUM_DEVS_USB_AT91_GPIO_PULLUP_INVERTED and + CYGNUM_DEVS_USB_AT91_GPIO_READ_POWER_INVERTED. Fixing the typo. + + * src/usbs_at91.c (usbs_at91_set_pullup): Change the logic so that + CYGNUM_DEVS_USB_AT91_GPIO_PULLUP_INVERTED does actually cause an + invert when set true. + +2006-09-07 John Eigelaar <jeigelaar@mweb.co.za> + + * cdl/usbs_at91.c: Read actual EP addresses from the EP configuartion + rather than relying on the order in the configuration list. + +2006-06-06 Andrew Lunn <andrew.lunn@ascom.ch> + + * cdl/usbs_at91.cdl: Allow EP0 to be enabled when there are no + slave clients. + +2006-05-25 Andrew Lunn <andrew.lunn@ascom.ch> + + * src/usbs_at91_data.cxx: Change the initialization priority. The + USB tty driver is initialized at priority CYG_INIT_IO, so the USB + device has to be initialized before that. + +2006-05-19 Andrew Lunn <andrew.lunn@ascom.ch> + + * src/usbs_at91.c: Rework pullup and power dectect to use the AT91 + GPIO macros. + +2006-05-07 Andrew Lunn <andrew.lunn@ascom.ch> + + * Included into eCos anonymous CVS. + + Note: There appears to be a hardware bug with OUT transfers. It + appears that after resetting the endpoint, the first OUT transfer + does not trigger a receive interrupt. The BK0 bit is not set, + however the number of bytes in the receiver FIFO is correct. The + second OUT transfer causes both BK0 and BK1 bits to be set and an + interrupt generated. Currently no workaround is used to correct + this behavoiour. + +2006-04-23 Andrew Lunn <andrew.lunn@ascom.ch> + + * src/usbs_at91.c: Endpoint 3 can send upto 64 bytes at a time, + not 8. + +2006-04-20 Andrew Lunn <andrew.lunn@ascom.ch> + + * src/usbs_at91.c (usbs_at91_control_data_sent): Send a zero byte + packet when the transfer is an exact multiple of the endpoint + buffer size. + +2006-04-16 Andrew Lunn <andrew.lunn@ascom.ch> + + * src/usbs_at91.c (usbs_testing_endpoints): Added support for the + USB testcase framework by exporting what endpoint we have. + * src/usbs_at91.c (usbs_at91_control_setup): Make requests other + than standard requests work, eg CLASS, VENDOR etc. + * src/usbs_at91.c (usbs_at91_handle_reset): Configure the endpoint + hardware using the configuration information. + * src/usbs_at91.c (usbs_at91_control_data_sent): Transaction is + complete when the buffer is empty _and_ there is no refill + function defined. + +2006-03-10 Oliver Munz <oli@snr.ch> + + * CDL-Update for PLL-Freuenzy. + +2006-03-06 Oliver Munz <oli@snr.ch> + + * USB device driver work finished - haha. + + What's missing: + The set_... and get_freature-host-commands are not implemented yet. + The dynamical configuration of the data-endpoints is not reflectet in + the "usbs.h" API. + Isochronus transfer is not implemented. + Polling routines are not implemented. + USB-Tests are not done. + The PowerDetectPin is not used for the state. + AT91SAM7X's are not supported at the moment. + + But it works for me... + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2010 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/at91/current/cdl/usbs_at91.cdl b/ecos/packages/devs/usb/at91/current/cdl/usbs_at91.cdl new file mode 100644 index 0000000..91c3994 --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/cdl/usbs_at91.cdl @@ -0,0 +1,286 @@ +# ==================================================================== +# +# usbs_at91.cdl +# +# USB device driver for the ATMEL AT91 family of processors. +# +# ==================================================================== +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 2006, 2010 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +# ==================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): Oliver Munz, Andrew Lunn +# Original data: bartv +# Contributors: ccoutand +# Date: 2006-02-25 +# +#####DESCRIPTIONEND#### +# ==================================================================== + +cdl_package CYGPKG_DEVS_USB_AT91 { + display "Atmel AT91 USB Device Driver" + include_dir "cyg/io/usb" + parent CYGPKG_IO_USB + implements CYGHWR_IO_USB_SLAVE + + cdl_interface CYGINT_DEVS_USB_AT91_HAS_USB { + description " + This interface is implemented by HALs for devices which have + the USB hardware." + } + + description " + This package provides a suitable eCos device driver + for AT91 USB. + In this version the driver will support the AT91SAM7S. + Other AT92 devices may work, but have not been tested. + The Driver needs 48, 96 or 192MHz plus minus 0.25%. + Buffers are allocated only in the higher level. There + is no need to configure the endpoints in this CDL, because + they will be configured dynamical at the set_configuration + call from the host... + The endpoints 1..3 can be configured as bulk or interrupt + IN or OUT endpoint. Isochronous transfer is not supported." + + cdl_component CYGFUN_DEVS_USB_AT91_EP0 { + display "Support the control endpoint 0" + flavor bool + default_value CYGINT_IO_USB_SLAVE_CLIENTS + requires CYGPKG_IO_USB CYGPKG_IO_USB_SLAVE + requires { + ((CYGNUM_HAL_ARM_AT91_CLOCK_SPEED < 48120000 && + CYGNUM_HAL_ARM_AT91_CLOCK_SPEED > 47880000) || + (CYGNUM_HAL_ARM_AT91_CLOCK_SPEED < 96240000 && + CYGNUM_HAL_ARM_AT91_CLOCK_SPEED > 95760000)) + } + + active_if CYGINT_DEVS_USB_AT91_HAS_USB + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + + compile usbs_at91.c + compile -library=libextras.a usbs_at91_data.cxx + description " + Enable support for endpoint 0. If this support is disabled + then the entire USB port is unusable." + + cdl_option CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_INTERNAL { + display "The chip has internal pullup" + flavor bool + calculated { CYGHWR_HAL_ARM_AT91SAM7SE } + requires { CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN == "NONE" } + description " + The chip has an internal pullup resistor; + the use of this pullup is mandatory (?), so there + should be no external pullup resistor." + } + + cdl_option CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN { + display "PIO-Pin who controls the pullup resistor" + flavor data + default_value { "AT91_GPIO_PA16" } + description " + Every GPIO pin is able to do it. If you don't need + a pin because your HW has the pullup fixed wired + then select NONE" + } + + cdl_option CYGNUM_DEVS_USB_AT91_GPIO_SET_PULLUP_INVERTED { + display "Has the signal to be inverted?" + active_if {CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN != "NONE"} + flavor bool + default_value 1 + description " + This option indicates that the pullup pin should + be inverted. ie VDD is active, VCC is inactive. For the + AT91SAM7SEK it needs to be inverted, hence this default." + } + + cdl_option CYGDAT_DEVS_USB_AT91_GPIO_READ_POWER_PIN { + display "PIO-Pin who see the USB-Power" + flavor data + default_value { "AT91_GPIO_PA13"} + description " + Every GPIO pin is able to do it. If you don't need + a pin then select NONE" + } + + cdl_option CYGNUM_DEVS_USB_AT91_GPIO_READ_POWER_INVERTED { + display "Has the signal to be inverted?" + flavor bool + default_value 0 + description " + This option indicates that the power detect pin should + be inverted. ie VDD is active, VCC is inactive." + } + + cdl_option CYGSEM_DEVS_USB_AT91_ZERO_LENGTH_PACKET_TERMINATION { + display "Allow USB driver to end data transfer with an empty data packet." + flavor bool + default_value 1 + description " + This option allows the USB driver to automatically signal the + end of a data transmission with an empty packet, if the last packet + fragment is equal to the endpoint buffer size." + } + + } + + cdl_component CYGPKG_DEVS_USB_AT91_DEVTAB_ENTRIES { + display "Provide a devtab entry for endpoints" + active_if CYGFUN_DEVS_USB_AT91_EP0 + default_value 0 + description " + This component controls if /dev/usb entries will be created." + + cdl_option CYGVAR_DEVS_USB_AT91_EP0_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 0" + flavor bool + default_value 0 + requires CYGPKG_IO + description " + If endpoint 0 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and ioctl calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP1_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 1" + flavor bool + default_value 1 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP2_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 2" + flavor bool + default_value 1 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP3_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 3" + flavor bool + default_value 1 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP4_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 4" + flavor bool + default_value 0 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP5_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 5" + flavor bool + default_value 0 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP6_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 6" + flavor bool + default_value 0 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGVAR_DEVS_USB_AT91_EP7_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 7" + flavor bool + default_value 0 + requires CYGPKG_IO + description " + If this endpoint will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed." + } + + cdl_option CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME { + display "Base name for devtab entries" + flavor data + default_value { "\"/dev/usbs\"" } + description " + If the uAT91 USB device driver package provides devtab + entries for any of the endpoints then this option gives + control over the names of these entries. By default the + endpoints will be called \"/dev/usbs0c\", \"/dev/usbs3w\" + and \"/dev/usbs4r\" (assuming all three endpoints are + enabled. The common part \"/dev/usbs\" is determined + by this configuration option. It may be necessary to + change this if there are multiple USB slave-side + devices on the target hardware to prevent a name clash." + } + } +} diff --git a/ecos/packages/devs/usb/at91/current/include/usbs_at91.h b/ecos/packages/devs/usb/at91/current/include/usbs_at91.h new file mode 100644 index 0000000..f3316ce --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/include/usbs_at91.h @@ -0,0 +1,82 @@ +#ifndef CYGONCE_USBS_AT91_H +#define CYGONCE_USBS_AT91_H +//========================================================================== +// +// include/usbs_at91.h +// +// The interface exported by the AT91 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2006, 2007 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Oliver Munz +// Contributors: bartv +// Date: 2006-02-25 +// Purpose: +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/usb/usbs.h> +#include <pkgconf/devs_usb_at91.h> +#include <pkgconf/system.h> +#ifdef CYGPKG_HAL_ARM_AT91SAM7 +#include <pkgconf/hal_arm_at91sam7.h> +#endif + +#if defined(CYGHWR_HAL_ARM_AT91SAM7SE) +#define AT91_USB_ENDPOINTS 8 +#elif defined(CYGHWR_HAL_ARM_AT91SAM7X) +#define AT91_USB_ENDPOINTS 6 +#else +#define AT91_USB_ENDPOINTS 4 +#endif + +extern usbs_control_endpoint usbs_at91_ep0; +extern usbs_rx_endpoint usbs_at91_ep1; +extern usbs_rx_endpoint usbs_at91_ep2; +extern usbs_rx_endpoint usbs_at91_ep3; +#if (AT91_USB_ENDPOINTS > 4) +extern usbs_rx_endpoint usbs_at91_ep4; +extern usbs_rx_endpoint usbs_at91_ep5; +#if (AT91_USB_ENDPOINTS > 6) +extern usbs_rx_endpoint usbs_at91_ep6; +extern usbs_rx_endpoint usbs_at91_ep7; +#endif +#endif + +extern void usbs_at91_endpoint_init(usbs_rx_endpoint * pep, + cyg_uint8 endpoint_type, cyg_bool enable); +#endif /* CYGONCE_USBS_AT91_H */ diff --git a/ecos/packages/devs/usb/at91/current/src/bitops.h b/ecos/packages/devs/usb/at91/current/src/bitops.h new file mode 100644 index 0000000..78ed1eb --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/src/bitops.h @@ -0,0 +1,98 @@ +#ifndef CYGONCE_USBS_AT91_BITOPS_H +#define CYGONCE_USBS_AT91_BITOPS_H + +//========================================================================== +// +// bitops.h +// +// Hardware Bit manipulation macros for the AT91 USB device +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Oliver Munz +// Contributors: bartv +// Date: 2006-02-22 +// +//####DESCRIPTIONEND#### +//========================================================================== + +// Set the given bits in a device register +#define SET_BITS(_register_, _bits_) \ + CYG_MACRO_START \ + cyg_uint32 _value_; \ + HAL_READ_UINT32(_register_, _value_); \ + _value_ |= _bits_; \ + HAL_WRITE_UINT32(_register_, _value_); \ + CYG_MACRO_END + +// Clear the given bits in a device register +#define CLEAR_BITS(_register_, _bits_) \ + CYG_MACRO_START \ + cyg_uint32 _value_; \ + HAL_READ_UINT32(_register_, _value_); \ + _value_ &= ~_bits_; \ + HAL_WRITE_UINT32(_register_, _value_); \ + CYG_MACRO_END + +#define BITS_ARE_SET(_register_, _bits) \ + bits_are_set(_register_, _bits) + +#define BITS_ARE_CLEARED(_register_, _bits) \ + bits_are_cleared(_register_, _bits) + +static inline cyg_bool +bits_are_set (cyg_addrword_t addr, cyg_uint32 bits) +{ + cyg_uint32 read; + + HAL_READ_UINT32 (addr, read); + + return (read & bits) == bits; +} + +static inline cyg_bool +bits_are_cleared (cyg_addrword_t addr, cyg_uint32 bits) +{ + cyg_uint32 read; + + HAL_READ_UINT32 (addr, read); + + return (read | ~bits) == ~bits; +} + + +#endif // CYGONCE_USBS_AT91_BITOPS_H + diff --git a/ecos/packages/devs/usb/at91/current/src/usbs_at91.c b/ecos/packages/devs/usb/at91/current/src/usbs_at91.c new file mode 100644 index 0000000..03db043 --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/src/usbs_at91.c @@ -0,0 +1,1633 @@ +//========================================================================== +// +// usbs_at91.c +// +// Driver for the AT91 USB device +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2006, 2010 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Oliver Munz, +// Contributors: Andrew Lunn, bartv, ccoutand +// Date: 2006-02-22 +// +// This code implements support for the on-chip USB port on the AT91 +// family of processors. The code has been developed on the AT91SAM7S +// and may or may not work on other members of the AT91 family. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <pkgconf/system.h> +#include <pkgconf/devs_usb_at91.h> +#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> +#include <cyg/io/usb/usbs_at91.h> + +#include CYGBLD_HAL_PLATFORM_H +#include <cyg/hal/hal_io.h> +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_platform_ints.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/diag.h> + +#include "bitops.h" + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define AT91_UDP_CSR0 (AT91_UDP_CSR) +#define AT91_UDP_FDR0 (AT91_UDP_FDR) + +#define pIER (AT91_UDP + AT91_UDP_IER) +#define pIDR (AT91_UDP + AT91_UDP_IDR) +#define pISR (AT91_UDP + AT91_UDP_ISR) +#define pIMR (AT91_UDP + AT91_UDP_IMR) +#define pICR (AT91_UDP + AT91_UDP_ICR) + +#define pCSR0 (AT91_UDP + AT91_UDP_CSR0) +#define pFDR0 (AT91_UDP + AT91_UDP_FDR0) + +#define pCSRn(N) (pCSR0 + (N * 4)) +#define pFDRn(N) (pFDR0 + (N * 4)) + +#if (AT91_USB_ENDPOINTS == 8) +#define AT91_UDP_ALLOWED_IRQs \ + (AT91_UDP_WAKEUP | AT91_UDP_ENDBUSRES | AT91_UDP_EXTRSM | \ + AT91_UDP_RXRSM | AT91_UDP_RXSUSP | AT91_UDP_EPINT0 | \ + AT91_UDP_EPINT1 | AT91_UDP_EPINT2 | AT91_UDP_EPINT3 | \ + AT91_UDP_EPINT4 | AT91_UDP_EPINT5 | \ + AT91_UDP_EPINT6 | AT91_UDP_EPINT7 ) +#elif (AT91_USB_ENDPOINTS == 6) +#define AT91_UDP_ALLOWED_IRQs \ + (AT91_UDP_WAKEUP | AT91_UDP_ENDBUSRES | AT91_UDP_EXTRSM | \ + AT91_UDP_RXRSM | AT91_UDP_RXSUSP | AT91_UDP_EPINT0 | \ + AT91_UDP_EPINT1 | AT91_UDP_EPINT2 | AT91_UDP_EPINT3 | \ + AT91_UDP_EPINT4 | AT91_UDP_EPINT5 ) +#elif (AT91_USB_ENDPOINTS == 4) +#define AT91_UDP_ALLOWED_IRQs \ + (AT91_UDP_WAKEUP | AT91_UDP_ENDBUSRES | AT91_UDP_EXTRSM | \ + AT91_UDP_RXRSM | AT91_UDP_RXSUSP | AT91_UDP_EPINT0 | \ + AT91_UDP_EPINT1 | AT91_UDP_EPINT2 | AT91_UDP_EPINT3) +#endif + +#define THERE_IS_A_NEW_PACKET_IN_THE_UDP 0xffff + +// Fifo size for each end point. +#if defined(CYGHWR_HAL_ARM_AT91SAM7SE) +static const cyg_uint16 usbs_at91_endpoint_fifo_size[AT91_USB_ENDPOINTS] = { + 8, + 64, + 64, + 64, + 512, + 512, + 64, + 64 +}; +#elif defined(CYGHWR_HAL_ARM_AT91SAM7X) +static const cyg_uint16 usbs_at91_endpoint_fifo_size[AT91_USB_ENDPOINTS] = { + 8, + 64, + 64, + 64, + 256, + 256 +}; +#else +static const cyg_uint16 usbs_at91_endpoint_fifo_size[AT91_USB_ENDPOINTS] = { + 8, + 64, + 64, + 64, +}; +#endif + +// Does an endpoint support ping pong buffering? +#if defined(CYGHWR_HAL_ARM_AT91SAM7SE) +static const bool usbs_at91_endpoint_pingpong[AT91_USB_ENDPOINTS] = { + false, + true, + true, + false, + true, + true, + true, + true +}; +#elif defined(CYGHWR_HAL_ARM_AT91SAM7X) +static const bool usbs_at91_endpoint_pingpong[AT91_USB_ENDPOINTS] = { + false, + true, + true, + false, + true, + true +}; +#else +static const bool usbs_at91_endpoint_pingpong[AT91_USB_ENDPOINTS] = { + false, + true, + true, + false +}; +#endif + +static cyg_uint8 *usbs_at91_endpoint_pbegin[AT91_USB_ENDPOINTS] = +#if (AT91_USB_ENDPOINTS == 8) + { 0, 0, 0, 0, 0, 0, 0, 0 }; +#elif (AT91_USB_ENDPOINTS == 6) + { 0, 0, 0, 0, 0, 0 }; +#else + { 0, 0, 0, 0 }; +#endif + +static cyg_uint8 *usbs_at91_endpoint_pend[AT91_USB_ENDPOINTS] = +#if (AT91_USB_ENDPOINTS == 8) + { 0, 0, 0, 0, 0, 0, 0, 0 }; +#elif (AT91_USB_ENDPOINTS == 6) + { 0, 0, 0, 0, 0, 0 }; +#else + { 0, 0 ,0, 0 }; +#endif + +static bool usbs_at91_endpoint_bank1[AT91_USB_ENDPOINTS] = +#if (AT91_USB_ENDPOINTS == 8) + { false, false, false, false, false, false, false, false }; +#elif (AT91_USB_ENDPOINTS == 6) + { false, false, false, false, false, false }; +#else + { false, false, false, false }; +#endif + +static cyg_uint16 usbs_at91_endpoint_bytes_in_fifo[AT91_USB_ENDPOINTS] = +#if (AT91_USB_ENDPOINTS == 8) + { 0, 0, 0, 0, 0, 0, 0, 0 }; +#elif (AT91_USB_ENDPOINTS == 6) + { 0, 0, 0, 0, 0, 0 }; +#else + { 0, 0, 0, 0 }; +#endif + +static cyg_uint16 usbs_at91_endpoint_bytes_received[AT91_USB_ENDPOINTS] = + { THERE_IS_A_NEW_PACKET_IN_THE_UDP, THERE_IS_A_NEW_PACKET_IN_THE_UDP, +#if (AT91_USB_ENDPOINTS > 4) + THERE_IS_A_NEW_PACKET_IN_THE_UDP, THERE_IS_A_NEW_PACKET_IN_THE_UDP, +#if (AT91_USB_ENDPOINTS > 6) + THERE_IS_A_NEW_PACKET_IN_THE_UDP, THERE_IS_A_NEW_PACKET_IN_THE_UDP, +#endif +#endif + THERE_IS_A_NEW_PACKET_IN_THE_UDP, THERE_IS_A_NEW_PACKET_IN_THE_UDP}; + +static cyg_interrupt usbs_at91_intr_data; +static cyg_handle_t usbs_at91_intr_handle; + +static void usbs_at91_ep0_start(usbs_control_endpoint *); +static void usbs_at91_poll(usbs_control_endpoint *); + +static void usbs_at91_endpoint_start(usbs_rx_endpoint * pep); +static void usbs_at91_endpoint_set_halted(usbs_rx_endpoint * pep, + cyg_bool new_value); +void usbs_at91_endpoint_init(usbs_rx_endpoint * pep, + cyg_uint8 endpoint_type, + cyg_bool enable); + +// Endpoint 0, the control endpoint, structure. +usbs_control_endpoint usbs_at91_ep0 = { + // The hardware does not distinguish between detached, attached and powered. + state: USBS_STATE_POWERED, + enumeration_data: (usbs_enumeration_data *) 0, + start_fn: usbs_at91_ep0_start, + poll_fn: usbs_at91_poll, + interrupt_vector: CYGNUM_HAL_INTERRUPT_UDP, + control_buffer: {0, 0, 0, 0, 0, 0, 0, 0}, + state_change_fn: (void (*) (usbs_control_endpoint *, + void *, usbs_state_change, int)) 0, + state_change_data: (void *) 0, + standard_control_fn: (usbs_control_return (*) + (usbs_control_endpoint *, void *)) 0, + standard_control_data: (void *) 0, + class_control_fn: (usbs_control_return (*) + (usbs_control_endpoint *, void *)) 0, + class_control_data: (void *) 0, + vendor_control_fn: (usbs_control_return (*) + (usbs_control_endpoint *, void *)) 0, + vendor_control_data: (void *) 0, + reserved_control_fn: (usbs_control_return (*) + (usbs_control_endpoint *, void *)) 0, + reserved_control_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + fill_buffer_fn: (void (*)(usbs_control_endpoint *)) 0, + fill_data: (void *) 0, + fill_index: 0, + complete_fn: (usbs_control_return (*)(usbs_control_endpoint *, + int)) 0 +}; + +// Endpoint 1 receive control structure +usbs_rx_endpoint usbs_at91_ep1 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; + +// Endpoint 2 Receive control structure +usbs_rx_endpoint usbs_at91_ep2 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; + +// Endpoint 3 Receive control structure +usbs_rx_endpoint usbs_at91_ep3 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; + +#if (AT91_USB_ENDPOINTS > 4) +// Endpoint 4 Receive control structure +usbs_rx_endpoint usbs_at91_ep4 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; +#endif + +#if (AT91_USB_ENDPOINTS > 5) +// Endpoint 5 Receive control structure +usbs_rx_endpoint usbs_at91_ep5 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; +#endif + +#if (AT91_USB_ENDPOINTS > 6) +// Endpoint 6 Receive control structure +usbs_rx_endpoint usbs_at91_ep6 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; +#endif + +#if (AT91_USB_ENDPOINTS > 7) +// Endpoint 7 Receive control structure +usbs_rx_endpoint usbs_at91_ep7 = { + start_rx_fn: usbs_at91_endpoint_start, + set_halted_fn: usbs_at91_endpoint_set_halted, + complete_fn: (void (*)(void *, int)) 0, + complete_data: (void *) 0, + buffer: (unsigned char *) 0, + buffer_size: 0, + halted: 0, +}; +#endif + +// Array of end points. Used for translating end point pointer to an +// end point number +static const void *usbs_at91_endpoints[AT91_USB_ENDPOINTS] = { + (void *) &usbs_at91_ep0, + (void *) &usbs_at91_ep1, + (void *) &usbs_at91_ep2, + (void *) &usbs_at91_ep3 +#if (AT91_USB_ENDPOINTS > 4) + ,(void *) &usbs_at91_ep4, (void *) &usbs_at91_ep5 +#if (AT91_USB_ENDPOINTS > 6) + ,(void *) &usbs_at91_ep6, (void *) &usbs_at91_ep7 +#endif +#endif +}; + +// Convert an endpoint pointer to an endpoint number, using the array +// of endpoint structures +static int +usbs_at91_pep_to_number(const usbs_rx_endpoint * pep) +{ + int epn; + + for(epn=0; epn < AT91_USB_ENDPOINTS; epn++) { + if (pep == usbs_at91_endpoints[epn]) + return epn; + } + CYG_FAIL("Unknown endpoint"); + return 0; +} + +typedef enum ep0_low_level_status_t { + EP0_LL_IDLE = 0, + EP0_LL_REQUEST, + EP0_LL_SEND_READY, + EP0_LL_ACK, + EP0_LL_RECEIVE_READY, + EP0_LL_ISOERROR, + EP0_LL_STALL, + EP0_LL_SET_ADDRESS, +} ep0_low_level_status_t; + +// Enable/Disable interrupts for a specific endpoint. +static void +usbs_at91_endpoint_interrupt_enable (cyg_uint8 epn, bool enable) +{ + CYG_ASSERT (epn < AT91_USB_ENDPOINTS, "Invalid endpoint"); + + if (enable) { + HAL_WRITE_UINT32 (pIER, 1 << epn); + } else { + HAL_WRITE_UINT32 (pIDR, 1 << epn); + } +} + +static cyg_uint8 * +read_fifo_uint8 (cyg_uint8 * pdest, cyg_addrword_t psource, cyg_uint32 size) +{ + cyg_uint8 *preqbyte = pdest; + cyg_uint8 reqbyte; + + while (size--) { + HAL_READ_UINT8 (psource, reqbyte); + *preqbyte = reqbyte; + preqbyte++; + } + + return preqbyte; +} + +static cyg_uint8 * +write_fifo_uint8 (cyg_addrword_t pdest, cyg_uint8 * psource, + cyg_uint8 * psource_end) +{ + cyg_uint8 *preqbyte; + + for (preqbyte = psource; preqbyte < psource_end; preqbyte++) { + HAL_WRITE_UINT8 (pdest, (*preqbyte)); + } + + return preqbyte; +} + +/* Tell the host that the device is ready to start communication */ +static void +usbs_at91_set_pullup (bool set) +{ +#ifdef CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_INTERNAL + cyg_uint32 txvc; + HAL_READ_UINT32(AT91_UDP + AT91_UDP_TXVC, txvc); + if (set) { + txvc |= AT91_UDP_TXVC_PUON; + } else { + txvc &= ~AT91_UDP_TXVC_PUON; + } + HAL_WRITE_UINT32(AT91_UDP + AT91_UDP_TXVC, txvc); +#endif // CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_INTERNAL + +#ifndef CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN_NONE + if ( +#ifdef CYGNUM_DEVS_USB_AT91_GPIO_SET_PULLUP_INVERTED + !set +#else + set +#endif + ) { + HAL_ARM_AT91_GPIO_SET(CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN); + } else { + HAL_ARM_AT91_GPIO_RESET(CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN); + } +#endif +} + +/* Is the USB powered? */ +bool +usbs_at91_read_power (void) +{ +#ifndef CYGDAT_DEVS_USB_AT91_GPIO_READ_POWER_PIN_NONE + cyg_bool state; + + HAL_ARM_AT91_GPIO_GET(CYGDAT_DEVS_USB_AT91_GPIO_READ_POWER_PIN, state); +#ifdef CYGNUM_DEVS_USB_AT91_GPIO_READ_POWER_INVERTED + return !state; +#else + return state; +#endif +#endif + return true; +} + +// Stop all transfers that are currently active. +static void +usbs_end_all_transfers (usbs_control_return returncode) +{ + cyg_uint32 epn; + usbs_rx_endpoint *pep; + + for (epn = 1; epn < AT91_USB_ENDPOINTS; epn++) { + if (BITS_ARE_SET (pIMR, 1 << epn)) { + // If the end point is transmitting, call the complete function + // to terminate to transfer + pep = (usbs_rx_endpoint *) usbs_at91_endpoints[epn]; + + if (pep->complete_fn) { + (*pep->complete_fn) (pep->complete_data, returncode); + } + // Disable interrupts from the endpoint + usbs_at91_endpoint_interrupt_enable (epn, false); + } + } +} + +// There has been a change in state. Update the end point. +static void +usbs_state_notify (usbs_control_endpoint * pcep) +{ + static int old_state = USBS_STATE_CHANGE_POWERED; + int state = pcep->state & USBS_STATE_MASK; + + if (pcep->state != old_state) { + usbs_end_all_transfers (-EPIPE); + switch (state) { + case USBS_STATE_DETACHED: + case USBS_STATE_ATTACHED: + case USBS_STATE_POWERED: + // Nothing to do + break; + case USBS_STATE_DEFAULT: + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_GLB_STATE, 0); + break; + case USBS_STATE_ADDRESSED: + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_GLB_STATE, AT91_UDP_GLB_FADDEN); + break; + case USBS_STATE_CONFIGURED: + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_GLB_STATE, AT91_UDP_GLB_CONFG); + break; + default: + CYG_FAIL("Unknown endpoint state"); + } + + if (pcep->state_change_fn) { + (*pcep->state_change_fn) (pcep, pcep->state_change_data, pcep->state, old_state); + } + + old_state = pcep->state; + } +} + +static usbs_control_return +usbs_parse_host_get_command (usbs_control_endpoint * pcep) +{ + usbs_control_return retcode; + cyg_uint8 dev_req_type = + (((usb_devreq *) pcep->control_buffer)->type) & USB_DEVREQ_TYPE_MASK; + + switch (dev_req_type) { + case USB_DEVREQ_TYPE_STANDARD: + if (!pcep->standard_control_fn) { + return usbs_handle_standard_control (pcep); + } + + retcode = + (*pcep->standard_control_fn) (pcep, pcep->standard_control_data); + + if (retcode == USBS_CONTROL_RETURN_UNKNOWN) { + return usbs_handle_standard_control (pcep); + } + return retcode; + + case USB_DEVREQ_TYPE_CLASS: + if (!pcep->class_control_fn) { + return USBS_CONTROL_RETURN_STALL; + } + return (*pcep->class_control_fn) (pcep, pcep->class_control_data); + + case USB_DEVREQ_TYPE_VENDOR: + if (!pcep->class_control_fn) { + return USBS_CONTROL_RETURN_STALL; + } + return (*pcep->class_control_fn) (pcep, pcep->vendor_control_data); + + case USB_DEVREQ_TYPE_RESERVED: + if (!pcep->reserved_control_fn) { + return USBS_CONTROL_RETURN_STALL; + } + return (*pcep->reserved_control_fn) (pcep, pcep->reserved_control_data); + default: + return USBS_CONTROL_RETURN_STALL; + } +} + +static void +usbs_at91_endpoint_set_halted (usbs_rx_endpoint * pep, cyg_bool new_value) +{ + int epn = usbs_at91_pep_to_number(pep); + cyg_addrword_t pCSR = pCSRn(epn); + cyg_uint32 reg; + + cyg_drv_dsr_lock (); + + if (pep->halted != new_value) { + /* There is something is to do */ + pep->halted = new_value; + + if ( new_value ) { + + /* Halt endpoint */ + SET_BITS (pCSR, AT91_UDP_CSR_FORCESTALL); + + } else { + /* Restart endpoint */ + CLEAR_BITS (pCSR, AT91_UDP_CSR_FORCESTALL); + + // Reset Fifo + HAL_READ_UINT32 (AT91_UDP + AT91_UDP_RST_EP, reg); + reg |= (1 << epn); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, reg); + reg &= ~(1 << epn); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, reg); + + // Ready to use + if (pep->complete_fn) { + (*pep->complete_fn) (pep->complete_data, ENOERR); + } + + } + } + cyg_drv_dsr_unlock (); +} + +void +usbs_at91_endpoint_init (usbs_rx_endpoint * pep, cyg_uint8 endpoint_type, + cyg_bool enable) +{ + int epn = usbs_at91_pep_to_number(pep); + cyg_addrword_t pCSR = pCSRn(epn); + + CYG_ASSERT (AT91_USB_ENDPOINTS > epn, "Invalid end point"); + + usbs_at91_endpoint_interrupt_enable (epn, false); + /* Reset endpoint */ + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, 1 << epn); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, 0); + + pep->halted = false; + + /* Type | In */ + HAL_WRITE_UINT32 (pCSR, (((((cyg_uint32) endpoint_type) & 0x03) << 8) | + ((((cyg_uint32) endpoint_type) & 0x80) << 3))); + + usbs_at91_endpoint_bytes_in_fifo[epn] = 0; + usbs_at91_endpoint_bytes_received[epn] = THERE_IS_A_NEW_PACKET_IN_THE_UDP; + usbs_at91_endpoint_bank1[epn] = false; + + if (enable) { + SET_BITS (pCSR, AT91_UDP_CSR_EPEDS); + } +} + +static void +usbs_at91_reset_device (void) +{ + int epn; + + usbs_end_all_transfers (-EPIPE); + + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_IDR, 0xffffffff); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_ICR, 0xffffffff); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, 0xffffffff); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_RST_EP, 0x00000000); + + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_FADDR, AT91_UDP_FADDR_FEN); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_CSR0, + AT91_UDP_CSR_EPEDS | AT91_UDP_CSR_EPTYPE_CTRL); + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_IER, AT91_UDP_ALLOWED_IRQs); + + for (epn=1; epn < AT91_USB_ENDPOINTS; epn++) { + usbs_at91_endpoint_init ((usbs_rx_endpoint *)usbs_at91_endpoints[epn], + 0, false); + } +} + +static void +usbs_at91_handle_reset (void) +{ + int epn; + const usb_endpoint_descriptor *usb_endpoints; + cyg_uint8 endpoint_type; + + cyg_uint8 endpoint_number; + + usbs_at91_reset_device (); + + // Now walk the endpoints configuring them correctly. This only + // works if there is one interface. + usb_endpoints = usbs_at91_ep0.enumeration_data->endpoints; + + for (epn = 1; + epn <= usbs_at91_ep0.enumeration_data->total_number_endpoints; + epn++) { + + endpoint_type = (usb_endpoints[epn-1].attributes | + (usb_endpoints[epn-1].endpoint & + USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN ? + USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN : + USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT)); + endpoint_number = usb_endpoints[epn-1].endpoint & ~(USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN); + + if ( endpoint_number < AT91_USB_ENDPOINTS ) { + usbs_at91_endpoint_init((usbs_rx_endpoint *)usbs_at91_endpoints[endpoint_number], + endpoint_type, + true); + } + } +} + +static void +usbs_at91_ep0_start (usbs_control_endpoint * endpoint) +{ + usbs_at91_handle_reset (); + + // If there is additional platform-specific initialization to + // perform, do it now. This macro can come from the platform HAL, + // but may not be available on all platforms. +#ifdef AT91_USB_PLATFORM_INIT + AT91_USB_PLATFORM_INIT (); +#endif + + usbs_at91_set_pullup (true); + CLEAR_BITS(AT91_UDP + AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS); +} + +static void +usbs_at91_endpoint_start (usbs_rx_endpoint * pep) +{ + int epn = usbs_at91_pep_to_number(pep); + cyg_addrword_t pCSR = pCSRn(epn); + cyg_addrword_t pFDR = pFDRn(epn); + cyg_uint16 space = 0; + cyg_uint16 endpoint_size = usbs_at91_endpoint_fifo_size[epn]; + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[epn]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[epn]; + + CYG_ASSERT (pep->complete_fn, "No complete_fn()"); + + cyg_drv_dsr_lock (); + if (usbs_at91_ep0.state != USBS_STATE_CONFIGURED) { + /* If not configured it means there is nothing to do */ + cyg_drv_dsr_unlock (); + + if (pep->complete_fn) { + (*pep->complete_fn) (pep->complete_data, -EPIPE); + } + return; + } + + // Is this endpoint currently stalled? If so then a size of 0 can + // be used to block until the stall condition is clear, anything + // else should result in an immediate callback. + if (pep->halted) { + /* Halted means nothing to do */ + cyg_drv_dsr_unlock (); + + if (pep->complete_fn && pep->buffer_size != 0) { + (*pep->complete_fn) (pep->complete_data, -EAGAIN); + } + return; + } + + if (BITS_ARE_SET (pIMR, 1 << epn)) { + cyg_drv_dsr_unlock (); + + if (pep->complete_fn) { + (*pep->complete_fn) (pep->complete_data, -EIO); + } + + return; + } + + CYG_ASSERT (BITS_ARE_SET (pCSR, 1 << 9), "Wrong endpoint type"); + + *ppbegin = pep->buffer; /* Set the working pointers */ + *ppend = (cyg_uint8 *) ((cyg_uint32) pep->buffer + pep->buffer_size); + + if (BITS_ARE_SET (pCSR, 0x400)) { /* IN: tx_endpoint */ + space = (cyg_uint32) * ppend - (cyg_uint32) * ppbegin; + +#ifdef CYGSEM_DEVS_USB_AT91_ZERO_LENGTH_PACKET_TERMINATION + if (space == endpoint_size) { + *ppend = *ppbegin; /* Send zero-packet */ + } +#endif + + *ppbegin = + write_fifo_uint8 (pFDR, *ppbegin, + (cyg_uint8 *) ((cyg_uint32) * ppbegin + + MIN (space, endpoint_size))); + SET_BITS (pCSR, AT91_UDP_CSR_TXPKTRDY); + + if (*ppend == *ppbegin) { /* Last packet ? */ + *ppend = *ppbegin - 1; /* The packet hasn't been sent yet */ + } + } + + usbs_at91_endpoint_interrupt_enable (epn, true); + + cyg_drv_dsr_unlock (); +} + +// Perform transmit handling on an endpoint +static bool +usbs_at91_endpoint_isr_tx(cyg_uint8 epn) +{ + cyg_addrword_t pCSR = pCSRn(epn); + cyg_addrword_t pFDR = pFDRn(epn); + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[epn]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[epn]; + cyg_uint32 space = 0; + cyg_uint16 endpoint_size = usbs_at91_endpoint_fifo_size[epn]; + + CLEAR_BITS (pCSR, AT91_UDP_CSR_TXCOMP); + + if (BITS_ARE_CLEARED (pCSR, AT91_UDP_CSR_TXPKTRDY)) { + /* Ready to transmit ? */ + if (*ppend > *ppbegin) { + /* Something to send */ + + space = (cyg_uint32) * ppend - (cyg_uint32) * ppbegin; + +#ifdef CYGSEM_DEVS_USB_AT91_ZERO_LENGTH_PACKET_TERMINATION + if (space == endpoint_size) { + *ppend = *ppbegin; /* Send zero-packet */ + } +#endif + + *ppbegin = + write_fifo_uint8 (pFDR, *ppbegin, + (cyg_uint8 *) ((cyg_uint32) * ppbegin + + MIN (space, endpoint_size))); + SET_BITS (pCSR, AT91_UDP_CSR_TXPKTRDY); + + if (*ppend == *ppbegin) { /* Last packet ? */ + *ppend = *ppbegin - 1; /* The packet isn't sent yet */ + } + + } else { + +#ifdef CYGSEM_DEVS_USB_AT91_ZERO_LENGTH_PACKET_TERMINATION + if (*ppend + 1 == *ppbegin) { + *ppend = *ppbegin; /* Flag for DSR */ + return true; + } else { + *ppend = *ppbegin - 1; /* Flag for zero-packet */ + SET_BITS (pCSR, AT91_UDP_CSR_TXPKTRDY); /* Send no data */ + } +#else + *ppend = *ppbegin; /* Flag for DSR */ + return true; +#endif + + } + } + + CLEAR_BITS (pCSR, + AT91_UDP_CSR_RX_DATA_BK0 | AT91_UDP_CSR_RX_DATA_BK1 | + AT91_UDP_CSR_RXSETUP | AT91_UDP_CSR_ISOERROR); + return false; +} + +static bool +usbs_at91_endpoint_isr_rx(cyg_uint8 epn) +{ + cyg_addrword_t pCSR = pCSRn(epn); + cyg_addrword_t pFDR = pFDRn(epn); + cyg_uint16 *pinfifo = &usbs_at91_endpoint_bytes_in_fifo[epn]; + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[epn]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[epn]; + cyg_uint16 *preceived = &usbs_at91_endpoint_bytes_received[epn]; + cyg_uint16 endpoint_size = usbs_at91_endpoint_fifo_size[epn]; + + if (*preceived == THERE_IS_A_NEW_PACKET_IN_THE_UDP) { + /* There is a new packet */ + *preceived = ((*(cyg_uint32 *)pCSR) >> 16) & 0x7ff; + *pinfifo = *preceived; + } + + while ((*ppbegin < *ppend) && *pinfifo) { + /* If we have buffer-space AND data in the FIFO */ + HAL_READ_UINT8(pFDR, **ppbegin); + (*ppbegin)++; + (*pinfifo)--; + } + + if (*ppbegin == *ppend) { + /* The buffer is full... call the DSR */ + return true; + } + + if (*pinfifo == 0) { + /* If the FIFO is empty, then we can release it */ + if (usbs_at91_endpoint_pingpong[epn]) { + /* Time to clear the interrupt flag */ + + if (usbs_at91_endpoint_bank1[epn]) { + CLEAR_BITS (pCSR, AT91_UDP_CSR_RX_DATA_BK1); + } else { + CLEAR_BITS (pCSR, AT91_UDP_CSR_RX_DATA_BK0); + } + usbs_at91_endpoint_bank1[epn] = !usbs_at91_endpoint_bank1[epn]; + } else { + CLEAR_BITS (pCSR, AT91_UDP_CSR_RX_DATA_BK0); + } + + if (*preceived < endpoint_size) { + /* If the last packet was smaller then the endpoint-size... */ + *ppend = *ppbegin; + *preceived = THERE_IS_A_NEW_PACKET_IN_THE_UDP; /* Set flag */ + + return true; /* We can call the completion-function in the DSR */ + } + + *preceived = THERE_IS_A_NEW_PACKET_IN_THE_UDP; /* Set flag */ + } + return false; +} + +// ISR for an endpoint. Handle receive and transmit interrupts. +static bool +usbs_at91_endpoint_isr (cyg_uint8 epn) +{ + cyg_addrword_t pCSR = pCSRn(epn); + + CYG_ASSERT (AT91_USB_ENDPOINTS > epn && epn, "Invalid end point"); + + // Host has acknowledged the endpoint being stall + if (BITS_ARE_SET (pCSR, AT91_UDP_CSR_ISOERROR)) { + CLEAR_BITS (pCSR, AT91_UDP_CSR_ISOERROR); + } + + if (BITS_ARE_SET (pCSR, 0x400)) { /* IN: tx_endpoint */ + if (usbs_at91_endpoint_isr_tx(epn)) + return true; // Call the DSR + } else { /* OUT: rx_endpoint */ + if (!BITS_ARE_CLEARED (pCSR, AT91_UDP_CSR_RX_DATA_BK0) || + !BITS_ARE_CLEARED (pCSR, AT91_UDP_CSR_RX_DATA_BK1)) { + /* Sometime something received ? */ + if(usbs_at91_endpoint_isr_rx(epn)) + return true; // Call the DSR; + } + + CLEAR_BITS (pCSR, + AT91_UDP_CSR_TXCOMP | AT91_UDP_CSR_RXSETUP | + AT91_UDP_CSR_ISOERROR); + } + + return false; +} + +// Handle a DSR for an endpoint +static void +usbs_at91_endpoint_dsr (cyg_uint8 epn) +{ + usbs_rx_endpoint *pep = (usbs_rx_endpoint *) usbs_at91_endpoints[epn]; + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[epn]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[epn]; + + CYG_ASSERT (AT91_USB_ENDPOINTS > epn && epn, "Invalid end point"); + CYG_ASSERT (pep->complete_fn, "No complete_fn()"); + + if (*ppend == *ppbegin) { /* Transmitted/Received ? */ + + pep->buffer_size = (cyg_uint32) * ppbegin - (cyg_uint32) pep->buffer; + if (pep->complete_fn) + { + /* Do not check on pep->buffer_size != 0, user should + * be allowed to send empty packet */ + if (!pep->halted) { + (*pep->complete_fn) (pep->complete_data, pep->buffer_size); + } else { + (*pep->complete_fn) (pep->complete_data, -EAGAIN); + } + } + usbs_at91_endpoint_interrupt_enable (epn, false); + } +} + +// Handle an error condition on the control endpoint +static ep0_low_level_status_t +usbs_at91_control_error(ep0_low_level_status_t status) +{ + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + + usbs_at91_ep0.buffer_size = 0; + usbs_at91_ep0.fill_buffer_fn = 0; + usbs_at91_ep0.complete_fn = 0; + + *ppbegin = usbs_at91_ep0.buffer; + *ppend = *ppbegin; + + if (status == EP0_LL_IDLE) { + if (usbs_at91_ep0.complete_fn) { + (*usbs_at91_ep0.complete_fn) (&usbs_at91_ep0, + USBS_CONTROL_RETURN_STALL); + } + } + + status = EP0_LL_IDLE; + + CLEAR_BITS (pCSR0, AT91_UDP_CSR_ISOERROR | AT91_UDP_CSR_FORCESTALL); + + return status; +} + +// Handle a get status setup message on the control end point +static ep0_low_level_status_t +usbs_at91_control_setup_get_status(void) +{ + ep0_low_level_status_t status; + usb_devreq *req = (usb_devreq *)usbs_at91_ep0.control_buffer; + cyg_uint8 recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + cyg_uint16 word = 0; + + status = EP0_LL_SEND_READY; + + switch (recipient) { + case USB_DEVREQ_RECIPIENT_DEVICE: + case USB_DEVREQ_RECIPIENT_INTERFACE: + // Nothing to do + break; + case USB_DEVREQ_RECIPIENT_ENDPOINT: + if ((usbs_at91_ep0.state == USBS_STATE_CONFIGURED) && + (req->index_lo > 0) && + (req->index_lo < AT91_USB_ENDPOINTS)) { + cyg_uint32 CSR; + + HAL_READ_UINT32(pCSRn(req->index_lo), CSR); + if (CSR & AT91_UDP_CSR_EPEDS) { + word = 1; + } + } else { + status = EP0_LL_STALL; + } + break; + default: + status = EP0_LL_STALL; + } + + *ppbegin = (cyg_uint8 *)&word; + *ppend = *ppbegin + sizeof (word); + return status; +} + +// Setup the begin and end pointers such that an ACK is sent +static void +usbs_at91_control_setup_send_ack(void) +{ + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + + *ppbegin = usbs_at91_ep0.buffer; + *ppend = *ppbegin; +} + +// Handle a get status set feature message on the control endpoint +static ep0_low_level_status_t +usbs_at91_control_setup_set_feature(void) +{ + ep0_low_level_status_t status; + usb_devreq *req = (usb_devreq *)usbs_at91_ep0.control_buffer; + cyg_uint8 recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + usbs_rx_endpoint * pep; + cyg_uint8 ep_num = req->index_lo & 0x0F; + + usbs_at91_control_setup_send_ack(); + status = EP0_LL_SEND_READY; + + switch(recipient) { + case USB_DEVREQ_RECIPIENT_DEVICE: + status = EP0_LL_STALL; + break; + case USB_DEVREQ_RECIPIENT_INTERFACE: + // Nothing to do + break; + case USB_DEVREQ_RECIPIENT_ENDPOINT: + if ((usbs_at91_ep0.state == USBS_STATE_CONFIGURED) && + (ep_num > 0) && + (ep_num < AT91_USB_ENDPOINTS)) { + cyg_uint32 CSR; + + HAL_READ_UINT32(pCSRn(ep_num), CSR); + pep = (usbs_rx_endpoint *) usbs_at91_endpoints[ep_num]; + if ( (CSR & AT91_UDP_CSR_EPEDS) ) { + usbs_at91_endpoint_set_halted ( pep , true ); + } + else + status = EP0_LL_STALL; + + } + else { + status = EP0_LL_STALL; + } + break; + default: + status = EP0_LL_STALL; + } + return status; +} + +// Handle a get status clear feature message on the control endpoint +static ep0_low_level_status_t +usbs_at91_control_setup_clear_feature(void) +{ + ep0_low_level_status_t status; + usb_devreq *req = (usb_devreq *)usbs_at91_ep0.control_buffer; + cyg_uint8 recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + usbs_rx_endpoint * pep; + cyg_uint8 ep_num = req->index_lo & 0x0F; + + usbs_at91_control_setup_send_ack(); + status = EP0_LL_SEND_READY; + + switch (recipient) { + case USB_DEVREQ_RECIPIENT_DEVICE: + status = EP0_LL_STALL; + break; + case USB_DEVREQ_RECIPIENT_INTERFACE: + // Nothing to do + break; + case USB_DEVREQ_RECIPIENT_ENDPOINT: + if ((usbs_at91_ep0.state == USBS_STATE_CONFIGURED) && + (ep_num > 0) && + (ep_num < AT91_USB_ENDPOINTS)) { + cyg_uint32 CSR; + + HAL_READ_UINT32(pCSRn(ep_num), CSR); + pep = (usbs_rx_endpoint *) usbs_at91_endpoints[ep_num]; + if ( (CSR & AT91_UDP_CSR_EPEDS) && pep->halted ) { + usbs_at91_endpoint_set_halted ( pep , false ); + } + else + status = EP0_LL_STALL; + + } + else { + status = EP0_LL_STALL; + } + break; + default: + status = EP0_LL_STALL; + } + return status; +} + +// Handle a setup message from the host +static ep0_low_level_status_t +usbs_at91_control_setup(ep0_low_level_status_t status) +{ + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + usb_devreq *req = (usb_devreq *) usbs_at91_ep0.control_buffer; + cyg_uint8 protocol; + cyg_uint16 length; + bool dev_to_host; + usbs_control_return usbcode; + bool handled = false; + + usbs_at91_ep0.buffer_size = 0; + usbs_at91_ep0.fill_buffer_fn = 0; + usbs_at91_ep0.complete_fn = 0; + + read_fifo_uint8 ((cyg_uint8 *)req, pFDR0, sizeof (usb_devreq)); + + length = (req->length_hi << 8) | req->length_lo;; + dev_to_host = req->type & USB_DEVREQ_DIRECTION_IN; + + CLEAR_BITS (pCSR0, AT91_UDP_CSR_DTGLE); + + status = EP0_LL_REQUEST; + + protocol = req->type & (USB_DEVREQ_TYPE_MASK); + + // Set the next transfer direction + if (dev_to_host) { + SET_BITS (pCSR0, AT91_UDP_CSR_DIR); /* Set IN direction */ + } else { + CLEAR_BITS (pCSR0, AT91_UDP_CSR_DIR); /* Set OUT direction */ + } + + if (protocol == USB_DEVREQ_TYPE_STANDARD) { + handled = true; + switch (req->request) { + case USB_DEVREQ_GET_STATUS: + status = usbs_at91_control_setup_get_status(); + break; + case USB_DEVREQ_SET_ADDRESS: + // Most of the hard work is done by the hardware. We just need + // to send an ACK. + usbs_at91_control_setup_send_ack(); + status = EP0_LL_SEND_READY; + break; + case USB_DEVREQ_SET_FEATURE: + status = usbs_at91_control_setup_set_feature(); + break; + case USB_DEVREQ_CLEAR_FEATURE: + status = usbs_at91_control_setup_clear_feature(); + break; + default: + handled = false; + } + } + if ((protocol != USB_DEVREQ_TYPE_STANDARD) || !handled) { + // Ask the layer above to process the message + usbcode = usbs_parse_host_get_command (&usbs_at91_ep0); + usbs_at91_ep0.buffer_size = MIN (usbs_at91_ep0.buffer_size, length); + + *ppbegin = usbs_at91_ep0.buffer; + *ppend = *ppbegin + usbs_at91_ep0.buffer_size; /* Ready to send... */ + + if (usbcode == USBS_CONTROL_RETURN_HANDLED) { /* OK */ + if (dev_to_host) { + status = EP0_LL_SEND_READY; + } else { + status = EP0_LL_RECEIVE_READY; + } + } else { + status = EP0_LL_STALL; + } + } + // Clear the setup bit so indicating we have processed the message + CLEAR_BITS (pCSR0, AT91_UDP_CSR_RXSETUP); + + return status; +} + +static ep0_low_level_status_t +usbs_at91_control_data_recv(ep0_low_level_status_t status) +{ + cyg_uint32 received = 0; + cyg_uint32 length; + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + usbs_control_return usbcode; + + if (status == EP0_LL_RECEIVE_READY) { + received = ((*(cyg_uint32 *) pCSR0) >> 16) & 0x7ff; + length = MIN (received, (cyg_uint32) *ppend - (cyg_uint32) *ppbegin); + *ppbegin = read_fifo_uint8 (*ppbegin, pFDR0, length); + + if (received < usbs_at91_endpoint_fifo_size[0]) { /* Last packet ? */ + *ppend = *ppbegin; + } + + if (*ppbegin == *ppend) { /* All received ? */ + usbs_at91_ep0.buffer_size = + (cyg_uint32) *ppend - (cyg_uint32) usbs_at91_ep0.buffer; + usbcode = USBS_CONTROL_RETURN_STALL; + + if (usbs_at91_ep0.complete_fn) { + usbcode = (*usbs_at91_ep0.complete_fn) (&usbs_at91_ep0, 0); + } + + if (usbcode == USBS_CONTROL_RETURN_HANDLED) { + status = EP0_LL_SEND_READY; + } else { + status = EP0_LL_STALL; + } + } + } + + CLEAR_BITS (pCSR0, AT91_UDP_CSR_RX_DATA_BK0); + + return status; +} + +static ep0_low_level_status_t +usbs_at91_control_data_sent(ep0_low_level_status_t status) +{ + cyg_uint8 **ppbegin = &usbs_at91_endpoint_pbegin[0]; + cyg_uint8 **ppend = &usbs_at91_endpoint_pend[0]; + cyg_uint32 bytes_to_write = 0; + usb_devreq *req = (usb_devreq *)usbs_at91_ep0.control_buffer; + cyg_uint16 value; + + switch (status) { + case EP0_LL_SEND_READY: + if (*ppbegin == *ppend && + usbs_at91_ep0.fill_buffer_fn == NULL) { + // All bytes are sent, send ACK + status = EP0_LL_ACK; + SET_BITS (pCSR0, AT91_UDP_CSR_TXPKTRDY); // Signal FIFO loaded + } else { + // We have more bytes to send + bytes_to_write = + MIN (*ppend - *ppbegin, usbs_at91_endpoint_fifo_size[0]); + *ppbegin = write_fifo_uint8 (pFDR0, *ppbegin, (cyg_uint8 *) + ((cyg_uint32) *ppbegin + bytes_to_write)); + // Send next few bytes + if (*ppbegin == *ppend) { /* Control-Endoints don't need ACK's */ + if (usbs_at91_ep0.fill_buffer_fn) { // More Records ? + (*usbs_at91_ep0.fill_buffer_fn) (&usbs_at91_ep0); + + *ppbegin = usbs_at91_ep0.buffer; + *ppend = *ppbegin + usbs_at91_ep0.buffer_size; + + /* Ready to send... */ + bytes_to_write = + MIN (*ppend - *ppbegin, + usbs_at91_endpoint_fifo_size[0] - bytes_to_write); + + *ppbegin = write_fifo_uint8 (pFDR0, *ppbegin, (cyg_uint8 *) + ((cyg_uint32) *ppbegin + bytes_to_write)); + // Send next few bytes + } else { + if (bytes_to_write == usbs_at91_endpoint_fifo_size[0]) { + // Last packet is full, so we need to send a zero bytes + // packet next time + status = EP0_LL_SEND_READY; + } else { + status = EP0_LL_IDLE; + } + + } + } + SET_BITS (pCSR0, AT91_UDP_CSR_TXPKTRDY); // Signal FIFO loaded + } + break; + case EP0_LL_RECEIVE_READY: + /* Maybe we have to send an ACK */ + if (*ppbegin == *ppend) { // All bytes are received, send ACK + status = EP0_LL_ACK; + SET_BITS (pCSR0, AT91_UDP_CSR_TXPKTRDY); // Signal FIFO loaded + } + break; + case EP0_LL_ACK: + if (req->request == USB_DEVREQ_SET_ADDRESS) { // Special-processing + HAL_WRITE_UINT32 (AT91_UDP + AT91_UDP_FADDR, + req->value_lo | AT91_UDP_FADDR_FEN); + value = (req->value_hi << 8) | req->value_lo; + if (value) { + usbs_at91_ep0.state = USBS_STATE_ADDRESSED; + } + } + + if (usbs_at91_ep0.complete_fn) { + (*usbs_at91_ep0.complete_fn) (&usbs_at91_ep0, + USBS_CONTROL_RETURN_HANDLED); + } + status = EP0_LL_IDLE; + usbs_state_notify (&usbs_at91_ep0); + break; + default: + break; + } + return status; +} + +static void +usbs_at91_control_dsr (void) +{ + static ep0_low_level_status_t status = EP0_LL_IDLE; + + while (!BITS_ARE_CLEARED(pCSR0, + AT91_UDP_CSR_TXCOMP | AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RXSETUP | AT91_UDP_CSR_ISOERROR | + AT91_UDP_CSR_RX_DATA_BK1)) { + + // Check and handle any error conditions + if (BITS_ARE_SET (pCSR0, AT91_UDP_CSR_ISOERROR)) { + status = usbs_at91_control_error(status); + } + + // Check for a setup message and handle it + if (BITS_ARE_SET (pCSR0, AT91_UDP_CSR_RXSETUP)) { + status = usbs_at91_control_setup(status); + } + + // Check for received data on the control endpoint + if (BITS_ARE_SET (pCSR0, AT91_UDP_CSR_RX_DATA_BK0)) { + status = usbs_at91_control_data_recv(status); + } + + // Check if the last packet has been sent + if (BITS_ARE_CLEARED (pCSR0, AT91_UDP_CSR_TXPKTRDY)) { + status = usbs_at91_control_data_sent(status); + } + + // Received an ACK packet + if (BITS_ARE_SET (pCSR0, AT91_UDP_CSR_TXCOMP)) { + CLEAR_BITS (pCSR0, AT91_UDP_CSR_TXCOMP); + } + + if (status == EP0_LL_STALL) { + CLEAR_BITS (pCSR0, 0x7f); + SET_BITS (pCSR0, AT91_UDP_CSR_FORCESTALL); + } + } +} + +static void +usbs_at91_dsr (cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + cyg_uint8 n; + + CYG_ASSERT (CYGNUM_HAL_INTERRUPT_UDP == vector, "Wrong interrupts"); + CYG_ASSERT (0 == data, "DSR needs no data"); + + CLEAR_BITS (AT91_UDP + AT91_UDP_GLB_STATE, 0x10); + + if (BITS_ARE_SET (pISR, AT91_UDP_WAKEUP)) { + usbs_at91_ep0.state = USBS_STATE_DEFAULT; + usbs_state_notify (&usbs_at91_ep0); + + HAL_WRITE_UINT32 (pICR, AT91_UDP_WAKEUP); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_ENDBUSRES)) { // RESET UDP + usbs_at91_ep0.state = USBS_STATE_POWERED; + usbs_state_notify (&usbs_at91_ep0); + usbs_at91_handle_reset (); + + HAL_WRITE_UINT32 (pCSR0, AT91_UDP_CSR_EPEDS | AT91_UDP_CSR_EPTYPE_CTRL); + HAL_WRITE_UINT32 (pIER, AT91_UDP_EPINT0); + + usbs_at91_ep0.state = USBS_STATE_DEFAULT; + usbs_state_notify (&usbs_at91_ep0); + + HAL_WRITE_UINT32 (pICR, AT91_UDP_ENDBUSRES); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_SOFINT)) { + HAL_WRITE_UINT32 (pICR, AT91_UDP_SOFINT); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_EXTRSM)) { + usbs_at91_ep0.state = usbs_at91_ep0.state & ~USBS_STATE_SUSPENDED; + usbs_state_notify (&usbs_at91_ep0); + HAL_WRITE_UINT32 (pICR, AT91_UDP_EXTRSM); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_RXRSM)) { + usbs_at91_ep0.state = usbs_at91_ep0.state & ~USBS_STATE_SUSPENDED; + usbs_state_notify (&usbs_at91_ep0); + HAL_WRITE_UINT32 (pICR, AT91_UDP_RXRSM); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_RXSUSP)) { + usbs_at91_ep0.state = usbs_at91_ep0.state | USBS_STATE_SUSPENDED; + usbs_state_notify (&usbs_at91_ep0); + HAL_WRITE_UINT32 (pICR, AT91_UDP_RXSUSP); + } + + if (BITS_ARE_SET (pISR, AT91_UDP_EPINT0)) { + usbs_at91_control_dsr (); + } + + for (n = 1; n < AT91_USB_ENDPOINTS; n++) { + if (*(cyg_uint32 *) pIMR & (1 << n)) { + usbs_at91_endpoint_dsr (n); + } + } + + cyg_drv_interrupt_unmask (vector); +} + +static cyg_uint32 +usbs_at91_isr (cyg_vector_t vector, cyg_addrword_t data) +{ + cyg_uint8 n; + bool need_dsr = false; + cyg_uint32 IMR; + cyg_uint32 ISR; + + CYG_ASSERT (CYGNUM_HAL_INTERRUPT_UDP == vector, "Wrong interrupts"); + CYG_ASSERT (0 == data, "ISR needs no data"); + + HAL_READ_UINT32(pIMR, IMR); + HAL_READ_UINT32(pISR, ISR); + + for (n = 1; n < AT91_USB_ENDPOINTS; n++) { + /* Do any data endpoint need a data transfer ? */ + if (IMR & ISR & (1 << n)) { + need_dsr = usbs_at91_endpoint_isr (n) || need_dsr; + } + } + /* If we don't need any DSR re-enable interrupts and finish */ + if (BITS_ARE_CLEARED (pISR, AT91_UDP_ALLOWED_IRQs & 0xffffff01) + && !need_dsr) { + cyg_drv_interrupt_acknowledge (vector); + return CYG_ISR_HANDLED; + } + + /* Call the DSR */ + cyg_drv_interrupt_mask (vector); + cyg_drv_interrupt_acknowledge (vector); + + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; +} + + + +// ---------------------------------------------------------------------------- +// Polling support. It is not clear that this is going to work particularly +// well since according to the documentation the hardware does not generate +// NAKs automatically - instead the ISR has to set the appropriate bits +// sufficiently quickly to avoid confusing the host. +// +// Calling the isr directly avoids duplicating code, but means that +// cyg_drv_interrupt_acknowledge() will get called when not inside a +// real interrupt handler. This should be harmless. + +static void +usbs_at91_poll (usbs_control_endpoint * endpoint) +{ + CYG_ASSERT (endpoint == &usbs_at91_ep0, "Wrong endpoint"); + if (CYG_ISR_CALL_DSR == usbs_at91_isr (CYGNUM_HAL_INTERRUPT_UDP, 0)) { + usbs_at91_dsr (CYGNUM_HAL_INTERRUPT_UDP, 0, 0); + } +} + +// ---------------------------------------------------------------------------- +// Initialization +// +// This routine gets called from a prioritized static constructor during +// eCos startup. + +void +usbs_at91_init (void) +{ + + cyg_uint32 reg; + + HAL_READ_UINT32 (AT91_PMC + AT91_PMC_PLLR, reg); + + /* Set USB divider so we have a 48MHz clock */ +#if ((CYGNUM_HAL_ARM_AT91_CLOCK_SPEED < 48120000) && \ + (CYGNUM_HAL_ARM_AT91_CLOCK_SPEED > 47880000)) + + // 48MHz clock, divider set to 1 + HAL_WRITE_UINT32 (AT91_PMC + AT91_PMC_PLLR, + (reg & 0x0fffffff) | AT91_PMC_PLLR_USBDIV_1); + +#elif ((CYGNUM_HAL_ARM_AT91_CLOCK_SPEED < 96240000) && \ + (CYGNUM_HAL_ARM_AT91_CLOCK_SPEED > 95760000)) + + // 96MHz clock, divider set to 2 + HAL_WRITE_UINT32 (AT91_PMC + AT91_PMC_PLLR, + (reg & 0x0fffffff) | AT91_PMC_PLLR_USBDIV_2); +#else +#error CYGNUM_HAL_ARM_AT91_CLOCK_SPEED is not 48, 96 or 192MHz plusminus 0.25% ... +#endif + + /* Enable USB clock */ + HAL_WRITE_UINT32 (AT91_PMC + AT91_PMC_SCER, AT91_PMC_SCER_UDP); + HAL_WRITE_UINT32 (AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_UDP); + + usbs_at91_set_pullup (false); +#ifndef CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN_NONE + HAL_ARM_AT91_GPIO_CFG_DIRECTION(CYGDAT_DEVS_USB_AT91_GPIO_SET_PULLUP_PIN, + AT91_PIN_OUT); +#endif +#ifndef CYGDAT_DEVS_USB_AT91_GPIO_READ_POWER_PIN_NONE + HAL_ARM_AT91_GPIO_CFG_DIRECTION(CYGDAT_DEVS_USB_AT91_GPIO_READ_POWER_PIN, + AT91_PIN_IN); +#endif + usbs_at91_reset_device (); + + cyg_drv_interrupt_create (CYGNUM_HAL_INTERRUPT_UDP, + 6, // priority + 0, // data + &usbs_at91_isr, + &usbs_at91_dsr, + &usbs_at91_intr_handle, &usbs_at91_intr_data); + + cyg_drv_interrupt_attach (usbs_at91_intr_handle); + cyg_drv_interrupt_unmask (CYGNUM_HAL_INTERRUPT_UDP); + + usbs_at91_ep0.state = USBS_STATE_POWERED; + usbs_state_notify (&usbs_at91_ep0); +} + +// ---------------------------------------------------------------------------- +// Testing support. +usbs_testing_endpoint usbs_testing_endpoints[] = { + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, + endpoint_number : 0, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &usbs_at91_ep0, +#ifdef CYGVAR_DEVS_USB_AT91_EP0_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "0c", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, // zero-byte control transfers are meaningless + max_size : 0x0FFFF, // limit imposed by protocol + max_in_padding : 0, + alignment : 0 + }, + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 1, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &usbs_at91_ep1, +#ifdef CYGVAR_DEVS_USB_AT91_EP1_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "1r", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : -1, // No hardware or driver limitation + max_in_padding : 0, + alignment : 0 + }, + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 2, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &usbs_at91_ep2, +#ifdef CYGVAR_DEVS_USB_AT91_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "2w", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : -1, // No hardware or driver limitation + max_in_padding : 1, // hardware limitation + alignment : 0 + }, + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 3, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &usbs_at91_ep3, +#ifdef CYGVAR_DEVS_USB_AT91_EP3_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "3w", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : -1, // No hardware or driver limitation + max_in_padding : 1, // hardware limitation + alignment : 0 + }, + USBS_TESTING_ENDPOINTS_TERMINATOR +}; diff --git a/ecos/packages/devs/usb/at91/current/src/usbs_at91_data.cxx b/ecos/packages/devs/usb/at91/current/src/usbs_at91_data.cxx new file mode 100644 index 0000000..c9d0665 --- /dev/null +++ b/ecos/packages/devs/usb/at91/current/src/usbs_at91_data.cxx @@ -0,0 +1,256 @@ +//========================================================================== +// +// usbs_at91_data.cxx +// +// Static data for the ATMEL AT91 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Oliver Munz +// Contributors: bartv +// Date: 2006-02-22 +// +// This file contains various objects that should go into extras.o +// rather than libtarget.a, e.g. devtab entries that would normally +// be eliminated by the selective linking. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/infra/diag.h> +#include <cyg/io/devtab.h> +#include <cyg/io/usb/usbs_at91.h> +#include <pkgconf/devs_usb_at91.h> + +// ---------------------------------------------------------------------------- +// Initialization. The goal here is to call usbs_at91_init() +// early on during system startup, to take care of things like +// registering interrupt handlers etc. which are best done +// during system init. +// +// If the endpoint 0 devtab entry is available then its init() +// function can be used to take care of this. However the devtab +// entries are optional so an alternative mechanism must be +// provided. Unfortunately although it is possible to give +// a C function the constructor attribute, it cannot be given +// an initpri attribute. Instead it is necessary to define a +// dummy C++ class. + +extern "C" void usbs_at91_init(void); + +#ifndef CYGVAR_DEVS_USB_AT91_EP0_DEVTAB_ENTRY + +class usbs_at91_initialization { +public: + usbs_at91_initialization() { + usbs_at91_init(); + } +}; + +static usbs_at91_initialization CYGBLD_ATTRIB_INIT_BEFORE(CYG_INIT_IO) + usbs_at91_init_object; +#endif + +// ---------------------------------------------------------------------------- +// The devtab entries. Each of these is optional, many applications +// will want to use the lower-level API rather than go via +// open/read/write/ioctl. + +#ifdef CYGVAR_DEVS_USB_AT91_EP0_DEVTAB_ENTRY +// For endpoint 0 the only legal operations are get_config() and +// set_config(), and these are provided by the common package. + +static bool usbs_at91_devtab_ep0_init(struct cyg_devtab_entry* tab){ + + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + + usbs_at91_init(); + + return true; +} + +CHAR_DEVIO_TABLE(usbs_at91_ep0_devtab_functions, + &cyg_devio_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep0_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "0", + 0, + &usbs_at91_ep0_devtab_functions, + &usbs_at91_devtab_ep0_init, + 0, + (void*) &usbs_at91_ep0); +#endif + +// ---------------------------------------------------------------------------- +// Common routines for ep1..3 + +#if defined(CYGVAR_DEVS_USB_AT91_EP1_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP2_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP3_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP4_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP5_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP6_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_AT91_EP7_DEVTAB_ENTRY) + +static bool usbs_at91_devtab_dummy_init(struct cyg_devtab_entry* tab){ + + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + return true; +} +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP1_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep1_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep1_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "1", + 0, + &usbs_at91_ep1_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep1); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP2_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep2_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep2_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "2", + 0, + &usbs_at91_ep2_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep2); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP3_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep3_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep3_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "3", + 0, + &usbs_at91_ep3_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep3); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP4_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep4_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep4_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "4", + 0, + &usbs_at91_ep4_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep4); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP5_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep5_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep5_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "5", + 0, + &usbs_at91_ep5_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep5); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP6_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep6_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep6_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "6", + 0, + &usbs_at91_ep6_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep6); +#endif + +#ifdef CYGVAR_DEVS_USB_AT91_EP7_DEVTAB_ENTRY +CHAR_DEVIO_TABLE(usbs_at91_ep7_devtab_functions, + &usbs_devtab_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +CHAR_DEVTAB_ENTRY(usbs_at91_ep7_devtab_entry, + CYGDAT_DEVS_USB_AT91_DEVTAB_BASENAME "7", + 0, + &usbs_at91_ep7_devtab_functions, + &usbs_at91_devtab_dummy_init, + 0, + (void*) &usbs_at91_ep7); +#endif diff --git a/ecos/packages/devs/usb/cortexm/stm32/current/ChangeLog b/ecos/packages/devs/usb/cortexm/stm32/current/ChangeLog new file mode 100644 index 0000000..f7f2434 --- /dev/null +++ b/ecos/packages/devs/usb/cortexm/stm32/current/ChangeLog @@ -0,0 +1,41 @@ +2012-04-13 Christophe Coutand <ecos@hotmail.co.uk> + + * src/usb_stm32.c (cyg_usbs_cortexm_stm32_init): + Peripheral clocks are enable / disable using new + CYGHWR_HAL_STM32_CLOCK_DISABLE / CYGHWR_HAL_STM32_CLOCK_ENABLE + macro supplied from the variant HAL + +2012-04-03 Jonathan Larmour <jifl@eCosCentric.com> + + * src/usb_stm32.c (cyg_usbs_cortexm_stm32_init): + CYGHWR_HAL_STM32_RCC_APB1ENR_CAN was renamed to + CYGHWR_HAL_STM32_RCC_APB1ENR_CAN1 in variant HAL. + +2010-10-27 Chris Holgate <chris@zynaptic.com> + + * src/usb_stm32.c, include/usb_stm32.h, cdl/usb_stm32.cdl: New + STM32 USB slave driver. [ Bugzilla 1001024 ] + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2010 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/cortexm/stm32/current/cdl/usb_stm32.cdl b/ecos/packages/devs/usb/cortexm/stm32/current/cdl/usb_stm32.cdl new file mode 100644 index 0000000..d2f35af --- /dev/null +++ b/ecos/packages/devs/usb/cortexm/stm32/current/cdl/usb_stm32.cdl @@ -0,0 +1,325 @@ +##============================================================================= +## +## usb_stm32.cdl +## +## STM32 USB driver configuration options. +## +##============================================================================= +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +##============================================================================= +#######DESCRIPTIONBEGIN#### +## +## Author(s): Chris Holgate +## Date: 2009-05-19 +## Purpose: Configure STM32 USB driver. +## +######DESCRIPTIONEND#### +## +##============================================================================= + +# +# TODO: Needs to check that the PLL is running off the HSE clock source and +# is set up to run at 48MHz or 72MHz. +# + +cdl_package CYGPKG_DEVS_USB_CORTEXM_STM32 { + display "ST STM32 USB driver" + description " + This package provides USB device side driver support for the ST + STM32 series of microcontrollers. + " + parent CYGPKG_IO_USB + active_if CYGPKG_IO_USB + hardware + include_dir "cyg/io/usb" + + # Make sure that we are running on the right hardware. + requires CYGPKG_HAL_CORTEXM_STM32 + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_EP0 { + display "Enable the control endpoint 0" + default_value CYGINT_IO_USB_SLAVE_CLIENTS + requires CYGPKG_IO_USB_SLAVE + requires !CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + compile usb_stm32.c + description " + Enable support for endpoint 0. If this support is disabled + then the entire USB port is unusable. Note that this driver does + not support slave side devtab entries." + } + + cdl_option CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM { + display "Max IN endpoints" + description " + Specify the maximum number of IN (transmit) endpoints supported by the device. + The total number of standard IN and OUT endpoints should not exceed 7. + " + flavor data + default_value { 2 } + legal_values { 0 to (7 - CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + } + + cdl_option CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM { + display "Max OUT endpoints" + description " + Specify the maximum number of OUT (receive) endpoints supported by the device. + The total number of standard IN and OUT endpoints should not exceed 7. + " + flavor data + default_value { 2 } + legal_values { 0 to (7 - CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + } + + cdl_option CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE { + display "Control endpoint buffer size" + description " + Selects the size of the control endpoint data buffer, which sets the maximum size + of the control endpoint data phase transactions. + " + flavor data + default_value { 128 } + legal_values { 8 to 256 } + } + + cdl_option CYGHWR_DEVS_USB_CORTEXM_STM32_SELF_POWERED { + display "Self powered" + description " + If set, the USB device will report itself to the host as being self-powered. + There is no support for dynamically detecting the power state for dual powered + devices. + " + flavor bool + default_value false + } + + cdl_option CYGHWR_DEVS_USB_CORTEXM_STM32_BULK_TERM_ZLP { + display "Variable length bulk transfers" + description " + Some USB device classes (eg. CDC ATM) require support for variable length bulk + messages. This is implemented by terminating bulk transfers using a short or + zero length packet. Selecting this option enables this form of variable length + bulk message transfer for all bulk endpoints. Note that this option is incompatible + with the standard eCos USB test framework. + " + flavor bool + default_value false + } + + cdl_option CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN { + display "USB disconnect pin" + description " + This is the GPIO which is used to control the USB bus D+ pullup resistor, which + allows the USB bus connection status to be changed under software control. + For the purposes of specifying which GPIO to use, pins are numbered consecutively + from 0 (A0) through 16 (B0) to 111 (G15). + " + flavor data + default_value { 30 } + } + + cdl_option CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN_ACT_LOW { + display "Active low USB disconnect" + description " + The USB disconnect pin is normally treated as active high - so that when set high + the device will be disconnected from the bus. Select this option for boards where + the USB disconnect pin is wired as active low. + " + flavor bool + default_value false + } + + cdl_option CYGNUM_DEVS_USB_CORTEXM_STM32_ISR_PRIORITY { + display "ISR priority" + description " + Sets the ISR priority level used by the USB driver. + " + flavor data + default_value { 4 } + } + + cdl_option CYGBLD_DEVS_USB_CORTEXM_STM32_DEBUG_TRACE { + display "Debug trace enable" + description " + Enables debug tracing for the USB driver. + " + flavor bool + default_value false + } + + cdl_component CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_CONFIG { + display "Physical endpoint configuration." + description " + This is the automatically generated physical endpoint configuration for the + STM32 USB device. Note that the physical endpoint numbering is only used + internally to the driver and has no bearing on the logical endpoint numbers + which may be specified in a given device configuration. + " + flavor none + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_CTRLEP0 { + display "Physical endpoint 0 CTRL" + flavor bool + active_if { (CYGFUN_DEVS_USB_CORTEXM_STM32_EP0) } + calculated { (CYGFUN_DEVS_USB_CORTEXM_STM32_EP0) } + implements CYGHWR_IO_USB_SLAVE + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP1 { + display "Physical endpoint 1 IN" + flavor bool + active_if { (1 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (1 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP2 { + display "Physical endpoint 2 IN" + flavor bool + active_if { (2 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (2 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP3 { + display "Physical endpoint 3 IN" + flavor bool + active_if { (3 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (3 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP4 { + display "Physical endpoint 4 IN" + flavor bool + active_if { (4 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (4 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP5 { + display "Physical endpoint 5 IN" + flavor bool + active_if { (5 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (5 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP6 { + display "Physical endpoint 6 IN" + flavor bool + active_if { (6 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (6 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_TXEP7 { + display "Physical endpoint 7 IN" + flavor bool + active_if { (7 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + calculated { (7 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP1 { + display "Physical endpoint 1 OUT" + flavor bool + active_if { (1 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (1 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (1 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (1 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP2 { + display "Physical endpoint 2 OUT" + flavor bool + active_if { (2 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (2 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (2 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (2 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP3 { + display "Physical endpoint 3 OUT" + flavor bool + active_if { (3 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (3 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (3 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (3 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP4 { + display "Physical endpoint 4 OUT" + flavor bool + active_if { (4 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (4 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (4 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (4 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP5 { + display "Physical endpoint 5 OUT" + flavor bool + active_if { (5 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (5 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (5 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (5 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP6 { + display "Physical endpoint 6 OUT" + flavor bool + active_if { (6 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (6 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (6 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (6 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + + cdl_option CYGFUN_DEVS_USB_CORTEXM_STM32_PHY_RXEP7 { + display "Physical endpoint 7 OUT" + flavor bool + active_if { (7 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (7 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + calculated { (7 > CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) && + (7 <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) } + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + } + } +} + +# EOF usb_stm32.cdl diff --git a/ecos/packages/devs/usb/cortexm/stm32/current/include/usb_stm32.h b/ecos/packages/devs/usb/cortexm/stm32/current/include/usb_stm32.h new file mode 100644 index 0000000..60a3905 --- /dev/null +++ b/ecos/packages/devs/usb/cortexm/stm32/current/include/usb_stm32.h @@ -0,0 +1,56 @@ +#ifndef CYGONCE_DEVS_USB_CORTEXM_STM32_H +#define CYGONCE_DEVS_USB_CORTEXM_STM32_H + +//============================================================================= +// +// usb_stm32.h +// +// USB slave driver for STM32 +// +//============================================================================= +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2008, 2009 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//============================================================================= +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Chris Holgate +// Date: 2009-05-19 +// Purpose: STM32 USB slave driver +// +//####DESCRIPTIONEND#### +// +//============================================================================= + +// Provide external access to the common endpoint 0 data structure. +extern usbs_control_endpoint cyg_usbs_cortexm_stm32_ep0c; + +#endif // CYGONCE_DEVS_USB_CORTEXM_STM32_H diff --git a/ecos/packages/devs/usb/cortexm/stm32/current/src/usb_stm32.c b/ecos/packages/devs/usb/cortexm/stm32/current/src/usb_stm32.c new file mode 100644 index 0000000..dcb0ea2 --- /dev/null +++ b/ecos/packages/devs/usb/cortexm/stm32/current/src/usb_stm32.c @@ -0,0 +1,2456 @@ +//============================================================================= +// +// usb_stm32.c +// +// USB slave driver implementation for STM32 +// +//============================================================================= +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//============================================================================= +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Chris Holgate +// Date: 2009-05-19 +// Purpose: STM32 USB slave driver implementation +// +//####DESCRIPTIONEND#### +// +//============================================================================= + +#include <cyg/infra/cyg_type.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/diag.h> + +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_if.h> + +#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> + +#include <string.h> + +#include <pkgconf/io_usb_slave.h> +#include <pkgconf/devs_usb_cortexm_stm32.h> + +//----------------------------------------------------------------------------- +// Maintenance and debug macros. + +#define TODO_USB(_msg_) CYG_ASSERT(false, "TODO (USB) : " _msg_) +#define FAIL_USB(_msg_) CYG_ASSERT(false, "FAIL (USB) : " _msg_) +#define ASSERT_USB(_test_, _msg_) CYG_ASSERT(_test_, "FAIL (USB) : " _msg_) + +#if defined(CYGBLD_DEVS_USB_CORTEXM_STM32_DEBUG_TRACE) +#define TRACE_USB(_msg_, _args_...) diag_printf ("STM32 USB : " _msg_, ##_args_) +#else +#define TRACE_USB(_msg_, _args_...) while(0){} +#endif + +//----------------------------------------------------------------------------- +// Shorthand for some of the configuration options. + +#define USB_BASE CYGHWR_HAL_STM32_USB +#define USB_RAM_BASE CYGHWR_HAL_STM32_USB_CAN_SRAM +#define USB_RAM_SIZE 512 +#define USB_EPNUM (1 + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) + +//----------------------------------------------------------------------------- +// Work out the bus clock frequencies and external timing constraints. +// NOTE: These require that the clock source is set to HSE, which should be +// forced by the CDL. + +#define PLL_FREQ (CYGARC_HAL_CORTEXM_STM32_INPUT_CLOCK * CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_MUL) + +#define APB1_FREQ ((CYGARC_HAL_CORTEXM_STM32_INPUT_CLOCK * CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_MUL) / \ + (CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV * CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV)) + +#define USB_TSTARTUP 1 + +//----------------------------------------------------------------------------- +// Provide macros for accessing the buffer layout pseudo-registers. + +// Pseudo-registers for single buffer configurations. +#define USB_RAM_SB_TXADDR(__ep) ((__ep)*16) +#define USB_RAM_SB_TXCOUNT(__ep) ((__ep)*16+4) +#define USB_RAM_SB_RXADDR(__ep) ((__ep)*16+8) +#define USB_RAM_SB_RXCOUNT(__ep) ((__ep)*16+12) + +// Pseudo-registers for double buffer configurations. +#define USB_RAM_DB_TXADDR(__ep,__buf) ((__ep)*16+(__buf)*8) +#define USB_RAM_DB_TXCOUNT(__ep,__buf) ((__ep)*16+(__buf)*8+4) +#define USB_RAM_DB_RXADDR(__ep,__buf) ((__ep)*16+(__buf)*8) +#define USB_RAM_DB_RXCOUNT(__ep,__buf) ((__ep)*16+(__buf)*8+4) + +#define USB_RAM_XX_RXCOUNT_BLOCKS(__x) VALUE_(10,__x) +#define USB_RAM_XX_RXCOUNT_COUNT_MASK VALUE_(0, 0x03FF) + +//----------------------------------------------------------------------------- +// Set up USB I/O pin configurations. + +#if (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x10) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (A, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x20) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (B, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x30) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (C, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x40) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (D, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x50) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (E, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x60) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (F, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) + +#elif (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN < 0x70) +#define USB_DISC_PIN CYGHWR_HAL_STM32_GPIO \ + (G, (CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN & 0x0F), OUT_2MHZ, OUT_PUSHPULL) +#endif + +#define USB_DP_PIN CYGHWR_HAL_STM32_GPIO (A, 12, IN, AIN) +#define USB_DM_PIN CYGHWR_HAL_STM32_GPIO (A, 11, IN, AIN) + +//============================================================================= +// Define USB transmit endpoint data structures. +//============================================================================= + +typedef enum { + TXTR_FLAGS_NONE = 0x00, // No flags set. + TXTR_FLAGS_ZLPKT = 0x01, // Set to enable zero length packet termination. + TXTR_FLAGS_DBUF = 0x02, // Set to enable doubtle buffered operation. +} txtr_flags; + +typedef enum { + TXTR_STATE_RESET, // TX transaction reset state. + TXTR_STATE_IDLE, // TX transaction idle (between transactions). + TXTR_STATE_SB_NEXT_PKT, // TX transaction send next packet (single buffer). + TXTR_STATE_SB_ZERO_PKT, // TX transaction send zero length packet (single buffer). + TXTR_STATE_SB_DONE, // TX transaction complete (single buffer). + TXTR_STATE_DB_FIRST_PKT, // TX transaction queue first packet (double buffer). + TXTR_STATE_DB_NEXT_PKT, // TX transaction queue next packet (double buffer). + TXTR_STATE_DB_ZERO_PKT, // TX transaction queue zero length packet (double buffer). + TXTR_STATE_DB_LAST_PKT, // TX transaction send last queued packet (double buffer). + TXTR_STATE_DB_DONE, // TX transaction complete (double buffer). +} txtr_state; + +typedef struct txtr_impl { + txtr_state state; // Current transaction state. + txtr_flags flags; // Transaction flags. + cyg_uint16 status; // Return status. + cyg_uint8 ep_num; // Endpoint number. + const cyg_uint8* buf_ptr; // Pointer to next user buffer location. + cyg_uint32 buf_size; // Size of user transmit buffer. + cyg_uint32 bytes_sent; // Track number of bytes sent. +} txtr_impl; + +typedef struct txep_impl { + usbs_tx_endpoint common; // High level driver data. + txtr_impl txtr; // Transaction data. +} txep_impl; + +//============================================================================= +// Define USB receive endpoint data structures. +//============================================================================= + +typedef enum { + RXTR_FLAGS_NONE = 0x00, // No flags set. + RXTR_FLAGS_ZLPKT = 0x01, // Set to enable zero length packet termination. + RXTR_FLAGS_DBUF = 0x02, // Set to enable doubtle buffered operation. +} rxtr_flags; + +typedef enum { + RXTR_STATE_RESET, // RX transaction reset state. + RXTR_STATE_IDLE, // RX transaction idle (between transactions). + RXTR_STATE_SB_NEXT_PKT, // RX transaction get next packet (single buffer). + RXTR_STATE_DB_NEXT_PKT, // RX transaction get next packet (double buffer). +} rxtr_state; + +typedef struct rxtr_impl { + rxtr_state state; // Current transaction state. + rxtr_flags flags; // Transaction flags. + cyg_uint16 status; // Return status. + cyg_uint8 ep_num; // Endpoint number. + cyg_uint8* buf_ptr; // Pointer to next user buffer location. + cyg_uint32 buf_size; // Size of receive buffer. + cyg_uint32 bytes_rcvd; // Track number of bytes received. +} rxtr_impl; + +typedef struct rxep_impl { + usbs_rx_endpoint common; // High level driver data. + rxtr_impl rxtr; // Transaction data. +} rxep_impl; + +//============================================================================= +// Instantiate USB control endpoint data structure. +//============================================================================= + +static void stm32_usb_start (usbs_control_endpoint*); +static void stm32_usb_poll (usbs_control_endpoint*); +static usbs_rx_endpoint* stm32_usb_get_rxep (usbs_control_endpoint*, cyg_uint8); +static usbs_tx_endpoint* stm32_usb_get_txep (usbs_control_endpoint*, cyg_uint8); + +// Track the control endpoint state. +typedef enum { + CTRLEP_MSG_STATE_IDLE, + CTRLEP_MSG_STATE_IN_DATA, + CTRLEP_MSG_STATE_IN_STATUS, + CTRLEP_MSG_STATE_OUT_DATA, + CTRLEP_MSG_STATE_OUT_STATUS, + CTRLEP_MSG_STATE_CTRL_ACK, +} ctrlep_msg_states; + +// Provide the control message buffer. +static cyg_uint8 ctrlep_msg_buffer [CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE]; + +// Provide STM32 control endpoint implementation. +typedef struct ctrlep_impl { + usbs_control_endpoint common; // High level driver data. + txtr_impl txtr; // Transmit transaction data. + rxtr_impl rxtr; // Receive transaction data. + ctrlep_msg_states msg_state; // Control endpoint messaging state. +} ctrlep_impl; + +// Instantiate control endpoint data structure. +static ctrlep_impl ctrlep = { + { // Set up data for common high level driver. + state : USBS_STATE_POWERED, + enumeration_data : (usbs_enumeration_data*) 0, + start_fn : &stm32_usb_start, + poll_fn : &stm32_usb_poll, + interrupt_vector : CYGNUM_HAL_INTERRUPT_USB_LP, + control_buffer : { 0, 0, 0, 0, 0, 0, 0, 0 }, + state_change_fn : 0, + state_change_data : 0, + standard_control_fn : 0, + standard_control_data : 0, + class_control_fn : 0, + class_control_data : 0, + vendor_control_fn : 0, + vendor_control_data : 0, + reserved_control_fn : 0, + reserved_control_data : 0, + buffer : 0, + buffer_size : 0, + fill_buffer_fn : 0, + fill_data : 0, + fill_index : 0, + complete_fn : 0, + get_rxep_fn : &stm32_usb_get_rxep, + get_txep_fn : &stm32_usb_get_txep, + }, + { // Initialise transmit transaction data. + state : TXTR_STATE_RESET, + flags : TXTR_FLAGS_ZLPKT, + status : ENOERR, + ep_num : 0, + }, + { // Initialise receive transaction data. + state : RXTR_STATE_RESET, + flags : RXTR_FLAGS_ZLPKT, + status : ENOERR, + ep_num : 0, + }, + msg_state : CTRLEP_MSG_STATE_IDLE, +}; + +extern usbs_control_endpoint cyg_usbs_cortexm_stm32_ep0c __attribute__((alias ("ctrlep"))); + +//============================================================================= +// ISR/DSR shared data structure. All data which is shared between ISR and +// DSR contexts is wrapped up in this data structure to make synchronisation +// between the two more manageable. +//============================================================================= + +typedef enum { + ISR_FLAGS_CLEARED = 0x00, // No flags set. + ISR_FLAGS_SETUP_READY = 0x01, // Setup packet ready in staging buffer. + ISR_FLAGS_DEVICE_RESET = 0x02, // Device reset detected. +} isr_flags; + +typedef struct isr_shared_data { + isr_flags flags; // Flags indicating events pending. + cyg_uint8 txtr_done; // Flags for indicating TX complete. + cyg_uint8 rxtr_done; // Flags for indicating RX complete. +} isr_shared_data; + +static isr_shared_data isr_shared = { + flags : ISR_FLAGS_CLEARED, + txtr_done : 0, + rxtr_done : 0, +}; + +//============================================================================= +// USB driver internal state variables. +//============================================================================= + +// Buffer management. +static cyg_uint16 stm32_usb_buf_offset; +static cyg_uint16 stm32_usb_buf_sizes [USB_EPNUM]; + +// Endpoint descriptors. +static txep_impl txep_list [CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM]; +static rxep_impl rxep_list [CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM]; +static txep_impl* txep_map [15]; +static rxep_impl* rxep_map [15]; + +// Interrupts and synchronisation primitives. +static cyg_interrupt interrupt_data; +static cyg_handle_t interrupt_handle; +static cyg_uint32 interrupt_mask_count; + +//============================================================================= +// Provide inlineable functions for implementing counted interrupt masking. +//============================================================================= + +//----------------------------------------------------------------------------- +// Mask interrupts if not already masked. + +static inline void stm32_usb_request_intr_mask + (void) +{ + cyg_scheduler_lock(); + if (interrupt_mask_count++ == 0) { + cyg_drv_interrupt_mask (CYGNUM_HAL_INTERRUPT_USB_LP); + } + cyg_scheduler_unlock(); +} + +//----------------------------------------------------------------------------- +// Release interrupt mask. Interrupts will be unmasked once all mask requests +// have been released. + +static inline void stm32_usb_release_intr_mask + (void) +{ + cyg_scheduler_lock(); + if (interrupt_mask_count == 0) { + FAIL_USB ("Interrupt mask counter decremented through 0."); + } + else if (--interrupt_mask_count == 0) { + cyg_drv_interrupt_unmask (CYGNUM_HAL_INTERRUPT_USB_LP); + } + cyg_scheduler_unlock(); +} + +//============================================================================= +// Provide inlineable functions for setting the 'flip bit' register values. +//============================================================================= + +//----------------------------------------------------------------------------- +// Set the transmit status bits to the desired value. + +static inline void stm32_usb_set_txep_status + (cyg_uint32 ep, cyg_uint32 txep_status) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + reg_val ^= txep_status & CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK; + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); +} + +//----------------------------------------------------------------------------- +// Set the receive status bits to the desired value. + +static inline void stm32_usb_set_rxep_status + (cyg_uint32 ep, cyg_uint32 rxep_status) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + reg_val ^= rxep_status & CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK; + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); +} + +//----------------------------------------------------------------------------- +// Assign conventional bits in the endpoint status registers without touching +// the flip-bit values. + +static inline void stm32_usb_assign_epxr + (cyg_uint32 ep, cyg_uint32 epxr_val) +{ + epxr_val &= ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK | CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | + CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), epxr_val); +} + +//----------------------------------------------------------------------------- +// Set specified conventional bits in the endpoint status registers. + +static inline void stm32_usb_set_epxr_bits + (cyg_uint32 ep, cyg_uint32 epxr_mask) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val |= epxr_mask; + stm32_usb_assign_epxr (ep, reg_val); +} + +//----------------------------------------------------------------------------- +// Clear specified conventional bits in the endpoint status registers. + +static inline void stm32_usb_clear_epxr_bits + (cyg_uint32 ep, cyg_uint32 epxr_mask) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= ~epxr_mask; + stm32_usb_assign_epxr (ep, reg_val); +} + +//----------------------------------------------------------------------------- +// Set specified toggle bits in the endpoint status registers. + +static inline void stm32_usb_set_epxr_toggle + (cyg_uint32 ep, cyg_uint32 epxr_mask) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= epxr_mask | ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK | CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | + CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + reg_val ^= epxr_mask; + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); +} + +//----------------------------------------------------------------------------- +// Clear specified toggle bits in the endpoint status registers. + +static inline void stm32_usb_clear_epxr_toggle + (cyg_uint32 ep, cyg_uint32 epxr_mask) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= epxr_mask | ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK | CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | + CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); +} + +//----------------------------------------------------------------------------- +// Flip specified toggle bits in the endpoint status registers. + +static inline void stm32_usb_flip_epxr_toggle + (cyg_uint32 ep, cyg_uint32 epxr_mask) +{ + cyg_uint32 reg_val; + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); + reg_val &= ~((cyg_uint32) (CYGHWR_HAL_STM32_USB_EPXR_STATTX_MASK | + CYGHWR_HAL_STM32_USB_EPXR_STATRX_MASK | CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | + CYGHWR_HAL_STM32_USB_EPXR_DTOGRX)); + reg_val |= epxr_mask; + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (ep), reg_val); +} + +//============================================================================= +// The following set of functions provide buffer management capabilities for +// allocating and then accessing USB buffers in the dual-port buffer RAM. +//============================================================================= + +//----------------------------------------------------------------------------- +// Get the allocated buffer size for a specified endpoint. + +static inline cyg_uint32 stm32_usb_buf_get_size + (cyg_uint32 ep) +{ + return (stm32_usb_buf_sizes[ep]); +} + +//----------------------------------------------------------------------------- +// Add a new set of endpoint buffers for the specified endpoint. + +static cyg_bool stm32_usb_buf_add_ep + (cyg_uint32 ep, cyg_uint32 ep_buf_size, cyg_bool is_tx, cyg_bool is_rx, cyg_bool is_db) +{ + cyg_uint32 alloc_size; + cyg_uint32 rx_blocks; + + // Check for valid configuration. + if ((is_db && is_tx && is_rx) || !(is_tx || is_rx)) { + FAIL_USB ("Invalid buffer configuration."); + goto failed; + } + + // Round up the allocation size so that it matches a valid RX block size. + alloc_size = (ep_buf_size & 1) ? ep_buf_size + 1 : ep_buf_size; + if ((alloc_size > 62) && (alloc_size & 31)) { + alloc_size &= ~31; + alloc_size += 32; + } + + // Check to see if there is enough RAM available. + if (is_db || (is_tx && is_rx)) { + if (stm32_usb_buf_offset + 2 * alloc_size > USB_RAM_SIZE) { + FAIL_USB ("Insufficient endpoint RAM for configuration."); + goto failed; + } + } + else { + if (stm32_usb_buf_offset + alloc_size > USB_RAM_SIZE) { + FAIL_USB ("Insufficient endpoint RAM for configuration."); + goto failed; + } + } + + // Calculate the blocks (size + num) field for the receive count register. + rx_blocks = (alloc_size <= 62) ? (alloc_size / 2) : (31 + alloc_size / 32); + stm32_usb_buf_sizes[ep] = (cyg_uint16) ep_buf_size; + + // Program up the pseudo-registers for double buffered transmit. + if (is_db && is_tx) { + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXADDR (ep, 0), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXCOUNT (ep, 0), 0); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXADDR (ep, 1), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXCOUNT (ep, 1), 0); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + } + + // Program up the pseudo-registers for double buffered receive. + else if (is_db) { + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXADDR (ep, 0), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXCOUNT (ep, 0), USB_RAM_XX_RXCOUNT_BLOCKS (rx_blocks)); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXADDR (ep, 1), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXCOUNT (ep, 1), USB_RAM_XX_RXCOUNT_BLOCKS (rx_blocks)); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + } + + // Program up the pseudo-registers for single buffered transmit/receive. + else { + if (is_tx) { + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_SB_TXADDR (ep), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_SB_TXCOUNT (ep), 0); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + } + if (is_rx) { + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_SB_RXADDR (ep), stm32_usb_buf_offset); + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_SB_RXCOUNT (ep), USB_RAM_XX_RXCOUNT_BLOCKS (rx_blocks)); + stm32_usb_buf_offset += (cyg_uint16) alloc_size; + } + } + + // Exit on success or failure. + return true; +failed : + return false; +} + +//----------------------------------------------------------------------------- +// Clear the buffer RAM layout for non-control endpoints, prior to setting a +// new configuration. + +static inline void stm32_usb_buf_clear_config + (void) +{ + cyg_uint32 i; + + // Clear buffer size table for non-control endpoints. + for (i = 1; i < USB_EPNUM; i++) + stm32_usb_buf_sizes[i] = 0; + + // Update the free pointer to point to the end of the control endpoint buffers. + stm32_usb_buf_offset = 8 * USB_EPNUM + 2 * stm32_usb_buf_sizes[0]; +} + +//----------------------------------------------------------------------------- +// Reset the buffer RAM layout, preallocating the requested buffer area for +// control endpoint 0. + +static inline cyg_bool stm32_usb_buf_reset_ep0 + (cyg_uint32 ep0_buf_size) +{ + cyg_uint32 i; + + // The buffer descriptor table is placed at the start of the dual-port RAM. + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_BTABLE, 0); + stm32_usb_buf_offset = 8 * USB_EPNUM; + + // Clear buffer size table prior to adding endpoint 0. A zero in this table + // essentially prevents any host-side access to a given endpoint buffer. + for (i = 0; i < USB_EPNUM; i++) + stm32_usb_buf_sizes[i] = 0; + return stm32_usb_buf_add_ep (0, ep0_buf_size, true, true, false); +} + +//----------------------------------------------------------------------------- +// Copy data from user memory to a specified USB buffer (double buffered). +// Returns the number of bytes transferred, which is capped at the buffer +// size. + +static cyg_uint32 __attribute__((hot)) stm32_usb_copy_to_dbuf + (const cyg_uint8* src, cyg_uint32 size, cyg_uint32 ep, cyg_uint32 buf) +{ + cyg_uint32 data, i; + cyg_haladdress waddr; + + // Truncate the size parameter to the buffer length. + if (size > stm32_usb_buf_sizes[ep]) + size = stm32_usb_buf_sizes[ep]; + + // Get the offset of the start of the buffer from the buffer tables + // and convert it into a host-side address. + HAL_READ_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXADDR (ep, buf), data); + waddr = USB_RAM_BASE + data * 2; + + // Copy over the data, remembering to skip the half-word gaps. If the size + // is not an integer number of half-words, we stick garbage in the last byte. + for (i = (size + 1) >> 1; i != 0; i--) { + data = *(src++); + data |= ((cyg_uint32) *(src++)) << 8; + HAL_WRITE_UINT32 (waddr, data); + waddr += 4; + } + + // Update the buffer count field and return the buffer size. + HAL_WRITE_UINT32 (USB_RAM_BASE + USB_RAM_DB_TXCOUNT (ep, buf), size); + return size; +} + +//----------------------------------------------------------------------------- +// Copy data from user memory to transmit buffer (single buffered endpoint). +// Returns the number of bytes transferred, which is capped at the buffer +// size. + +static inline cyg_uint32 stm32_usb_copy_to_sbuf + (const cyg_uint8* src, cyg_uint32 size, cyg_uint32 ep) +{ + return stm32_usb_copy_to_dbuf (src, size, ep, 0); +} + +//----------------------------------------------------------------------------- +// Copy data from a specified USB buffer to user memory (double buffered). +// Returns the number of bytes held by the buffer. Will not write beyond the +// end of the user buffer, but if the user buffer is not large enough to hold +// the received data (returned value > size) this implies an error condition. + +static cyg_uint32 __attribute__((hot)) stm32_usb_copy_from_dbuf + (cyg_uint8* dest, cyg_uint32 size, cyg_uint32 ep, cyg_uint32 buf) +{ + cyg_uint32 data, bufsize, i; + cyg_haladdress raddr; + + // Get the receive buffer size. + HAL_READ_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXCOUNT (ep, buf), bufsize); + bufsize &= USB_RAM_XX_RXCOUNT_COUNT_MASK; + + // Determine the actual amount of data to transfer. + if (bufsize > size) + FAIL_USB ("Receive buffer overflow detected."); + else + size = bufsize; + + // Get the offset of the start of the buffer from the buffer tables + // and convert it into a host-side address. + HAL_READ_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXADDR (ep, buf), data); + raddr = USB_RAM_BASE + data * 2; + + // Copy over the half-word aligned data. + for (i = size; i > 1; i -= 2) { + HAL_READ_UINT32 (raddr, data); + *(dest++) = (cyg_uint8) data; + *(dest++) = (cyg_uint8) (data >> 8); + raddr += 4; + } + + // Copy over the trailing byte if present. + if (i) { + HAL_READ_UINT32 (raddr, data); + *(dest) = (cyg_uint8) data; + } + return bufsize; +} + +//----------------------------------------------------------------------------- +// Copy data from a receive buffer to user memory (single buffered endpoint). +// Returns the number of bytes held by the buffer. Will not write beyond the +// end of the user buffer, but if the user buffer is not large enough to hold +// the received data (returned value > size) this implies an error condition. + +static inline cyg_uint32 stm32_usb_copy_from_sbuf + (cyg_uint8* dest, cyg_uint32 size, cyg_uint32 ep) +{ + return stm32_usb_copy_from_dbuf (dest, size, ep, 1); +} + +//============================================================================= +// The following set of functions provide support for managing multi-packet +// USB transactions. +//============================================================================= + +//----------------------------------------------------------------------------- +// Implement transmit transaction state machine. This is called on transaction +// start and then on all applicable interrupt events in order to progress the +// transmit transaction. + +static cyg_bool stm32_usb_txtr_run + (txtr_impl* txtr) +{ + cyg_bool completed = false; + cyg_uint32 tx_bytes_req, tx_bytes_sent, buf_sel, reg_val; + + switch (txtr->state) { + + // Send next packet (single buffer transfers). + case TXTR_STATE_SB_NEXT_PKT: + tx_bytes_req = txtr->buf_size - txtr->bytes_sent; + tx_bytes_sent = stm32_usb_copy_to_sbuf (txtr->buf_ptr, tx_bytes_req, txtr->ep_num); + txtr->buf_ptr += tx_bytes_sent; + txtr->bytes_sent += tx_bytes_sent; + + // All bytes sent - see if we need a zero length termination packet. + if (txtr->bytes_sent == txtr->buf_size) { + if ((tx_bytes_sent == stm32_usb_buf_get_size (txtr->ep_num)) && (txtr->flags & TXTR_FLAGS_ZLPKT)) + txtr->state = TXTR_STATE_SB_ZERO_PKT; + else + txtr->state = TXTR_STATE_SB_DONE; + } + + // Set buffer valid via the endpoint control register. + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_VALID); + break; + + // Send zero length termination packet (single buffer transfers). + case TXTR_STATE_SB_ZERO_PKT : + txtr->state = TXTR_STATE_SB_DONE; + stm32_usb_copy_to_sbuf (txtr->buf_ptr, 0, txtr->ep_num); + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_VALID); + break; + + // Complete transaction (single buffer transfers). + case TXTR_STATE_SB_DONE : + txtr->state = TXTR_STATE_IDLE; + completed = true; + break; + + // Sets up the first queued packet. Start by clearing the data toggle + // bits and leaving the endpoint in valid state. + case TXTR_STATE_DB_FIRST_PKT : + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (txtr->ep_num), reg_val); + buf_sel = (reg_val & CYGHWR_HAL_STM32_USB_EPXR_DTOGTX) ? 1 : 0; + goto queue_packet; + + // Commit the next queued packet for transmission then queue the next + // packet in the CPU-side buffer. + case TXTR_STATE_DB_NEXT_PKT : + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (txtr->ep_num), reg_val); + buf_sel = (reg_val & CYGHWR_HAL_STM32_USB_EPXR_DTOGTX) ? 0 : 1; + stm32_usb_flip_epxr_toggle (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_SWBUFTX); + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_VALID); + + // This section is common to both DB_NEXT_PKT and DB_FIRST_PKT states. + // However, a straight fallthrough won't work and we have to resort to goto. + queue_packet: + tx_bytes_req = txtr->buf_size - txtr->bytes_sent; + tx_bytes_sent = stm32_usb_copy_to_dbuf (txtr->buf_ptr, tx_bytes_req, txtr->ep_num, buf_sel); + txtr->buf_ptr += tx_bytes_sent; + txtr->bytes_sent += tx_bytes_sent; + + // All bytes queued - see if we need a zero length termination packet. + if (txtr->bytes_sent == txtr->buf_size) { + if ((tx_bytes_sent == stm32_usb_buf_get_size (txtr->ep_num)) && (txtr->flags & TXTR_FLAGS_ZLPKT)) + txtr->state = TXTR_STATE_DB_ZERO_PKT; + else + txtr->state = TXTR_STATE_DB_LAST_PKT; + } + + // More data remaining - send next packet. + else { + txtr->state = TXTR_STATE_DB_NEXT_PKT; + } + break; + + // Commit the next queued packet for transmission then queue a + // zero length packet in the CPU-side buffer. + case TXTR_STATE_DB_ZERO_PKT : + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (txtr->ep_num), reg_val); + buf_sel = (reg_val & CYGHWR_HAL_STM32_USB_EPXR_DTOGTX) ? 0 : 1; + stm32_usb_flip_epxr_toggle (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_SWBUFTX); + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_VALID); + stm32_usb_copy_to_dbuf (txtr->buf_ptr, 0, txtr->ep_num, buf_sel); + txtr->state = TXTR_STATE_DB_LAST_PKT; + break; + + // Commit the final queued packet for transmission. + case TXTR_STATE_DB_LAST_PKT : + stm32_usb_flip_epxr_toggle (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_SWBUFTX); + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_VALID); + txtr->state = TXTR_STATE_DB_DONE; + break; + + // Complete transaction (double buffer transfers). + case TXTR_STATE_DB_DONE : + stm32_usb_set_txep_status (txtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_NAK); + txtr->state = TXTR_STATE_IDLE; + completed = true; + break; + + // Unknown state - driver error. + default : + FAIL_USB ("TX transaction in invalid state."); + txtr->state = TXTR_STATE_IDLE; + txtr->status = EIO; + completed = true; + break; + } + return completed; +} + +//----------------------------------------------------------------------------- +// Initiate a data transmit transaction. This sets up a new transaction, +// priming the buffers and then kicking the state machine for the first time. + +static cyg_bool stm32_usb_txtr_start + (txtr_impl* txtr, cyg_bool int_safe) +{ + cyg_bool completed; + + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + + // Set up double buffer transactions. We need to prime the buffers which + // requires two ticks of the state machine. + if (txtr->flags & TXTR_FLAGS_DBUF) { + txtr->status = ENOERR; + txtr->bytes_sent = 0; + txtr->state = TXTR_STATE_DB_FIRST_PKT; + stm32_usb_txtr_run (txtr); + } + + // Set up single buffer transactions. + else { + txtr->status = ENOERR; + txtr->bytes_sent = 0; + txtr->state = TXTR_STATE_SB_NEXT_PKT; + } + + // Run the state machine for the first step. + completed = stm32_usb_txtr_run (txtr); + + // Exit critical section. + if (!int_safe) stm32_usb_release_intr_mask (); + return completed; +} + +//----------------------------------------------------------------------------- +// Halt a transmit endpoint. This places the endpoint in the stall condition +// and cancels any outstanding transaction, resetting the transaction state +// machine. + +static void stm32_usb_txep_halt + (txep_impl* txep, cyg_bool int_safe) +{ + cyg_bool call_completion; + + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + stm32_usb_set_txep_status (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_DIS); + + // Reset the transaction state. + call_completion = ((txep->txtr.state != TXTR_STATE_IDLE) && + (txep->txtr.state != TXTR_STATE_RESET)) ? true : false; + txep->txtr.state = TXTR_STATE_RESET; + txep->common.halted = true; + stm32_usb_clear_epxr_bits (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_CTRTX); + + // Exit critical section. + stm32_usb_set_txep_status (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL); + if (!int_safe) stm32_usb_release_intr_mask (); + + // Indicate error via the completion callback. + if (call_completion) { + if (txep->common.complete_fn) + (*txep->common.complete_fn) (txep->common.complete_data, -EIO); + TRACE_USB ("TX Transaction cancelled on halt.\n"); + } +} + +//----------------------------------------------------------------------------- +// Take a transmit endpoint out of halted state. This takes the endpoint out +// of the halted state and resets the endpoint. Note that according to the +// spec, the endpoint should also be reset and the toggle bits cleared if +// 'clear halted' is called while in normal operation. + +static void stm32_usb_txep_unhalt + (txep_impl* txep, cyg_bool int_safe) +{ + cyg_bool call_completion; + + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + stm32_usb_set_txep_status (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_DIS); + + // Reset the transaction state. + call_completion = ((txep->txtr.state != TXTR_STATE_IDLE) && + (txep->txtr.state != TXTR_STATE_RESET)) ? true : false; + txep->txtr.state = TXTR_STATE_IDLE; + txep->common.halted = false; + stm32_usb_clear_epxr_bits (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_CTRTX); + stm32_usb_clear_epxr_toggle (txep->txtr.ep_num, + CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | CYGHWR_HAL_STM32_USB_EPXR_SWBUFTX); + + // Exit critical section. + stm32_usb_set_txep_status (txep->txtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATTX_NAK); + if (!int_safe) stm32_usb_release_intr_mask (); + + // Indicate error via the completion callback. + if (call_completion) { + if (txep->common.complete_fn) + (*txep->common.complete_fn) (txep->common.complete_data, -EIO); + TRACE_USB ("TX Transaction cancelled on resume.\n"); + } +} + +//----------------------------------------------------------------------------- +// Implement receive transaction state machine. This is called on all +// applicable interrupt events in order to progress the transaction. + +static cyg_bool stm32_usb_rxtr_run + (rxtr_impl* rxtr) +{ + cyg_bool completed = false; + cyg_uint32 rx_bytes_req, rx_bytes_rcvd, buf_sel, reg_val; + + switch (rxtr->state) { + + // Receive new packet (single buffer transfers). + case RXTR_STATE_SB_NEXT_PKT: + rx_bytes_req = rxtr->buf_size - rxtr->bytes_rcvd; + rx_bytes_rcvd = stm32_usb_copy_from_sbuf (rxtr->buf_ptr, rx_bytes_req, rxtr->ep_num); + + // Check for buffer overflow condition before updating buffer pointer. + if (rx_bytes_rcvd > rx_bytes_req) { + FAIL_USB ("RX message exceeds allocated buffer size."); + rxtr->state = RXTR_STATE_IDLE; + rxtr->status = EMSGSIZE; + completed = true; + break; + } + rxtr->buf_ptr += rx_bytes_rcvd; + rxtr->bytes_rcvd += rx_bytes_rcvd; + + // Short packet received - transaction complete. + if (rx_bytes_rcvd < stm32_usb_buf_get_size (rxtr->ep_num)) { + rxtr->state = RXTR_STATE_IDLE; + completed = true; + break; + } + + // All bytes received - see if we need a zero length termination packet. + // Note that zero length packets can be treated as normal short packets + // here, so an additional state transition is not required. + if (rxtr->bytes_rcvd == rxtr->buf_size) { + if (!((rx_bytes_rcvd == stm32_usb_buf_get_size (rxtr->ep_num)) && (rxtr->flags & RXTR_FLAGS_ZLPKT))) { + rxtr->state = RXTR_STATE_IDLE; + completed = true; + break; + } + } + + // Set buffer clear via the endpoint control register. + stm32_usb_set_rxep_status (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_VALID); + break; + + // Receive new packet (double buffer transfers). Start by switching buffers. + case RXTR_STATE_DB_NEXT_PKT: + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (rxtr->ep_num), reg_val); + buf_sel = (reg_val & CYGHWR_HAL_STM32_USB_EPXR_DTOGRX) ? 0 : 1; + + // Get the receive buffer size. + HAL_READ_UINT32 (USB_RAM_BASE + USB_RAM_DB_RXCOUNT (rxtr->ep_num, buf_sel), rx_bytes_rcvd); + rx_bytes_req = rxtr->buf_size - rxtr->bytes_rcvd; + rx_bytes_rcvd &= USB_RAM_XX_RXCOUNT_COUNT_MASK; + + // Check for buffer overflow condition before updating buffer pointer. + if (rx_bytes_rcvd > rx_bytes_req) { + FAIL_USB ("RX message exceeds allocated buffer size."); + rxtr->state = RXTR_STATE_IDLE; + rxtr->status = EMSGSIZE; + completed = true; + break; + } + + // Short packet received - transaction complete. + if (rx_bytes_rcvd < stm32_usb_buf_get_size (rxtr->ep_num)) { + rxtr->state = RXTR_STATE_IDLE; + completed = true; + } + + // All bytes received - see if we need a zero length termination packet. + // Note that zero length packets can be treated as normal short packets + // here, so an additional state transition is not required. + if (rxtr->bytes_rcvd + rx_bytes_rcvd == rxtr->buf_size) { + if (!((rx_bytes_rcvd == stm32_usb_buf_get_size (rxtr->ep_num)) && (rxtr->flags & RXTR_FLAGS_ZLPKT))) { + rxtr->state = RXTR_STATE_IDLE; + completed = true; + } + } + + // Only enable the next receive buffer if this is not the last packet. + if (!completed) { + stm32_usb_flip_epxr_toggle (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_SWBUFRX); + stm32_usb_set_rxep_status (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_VALID); + } + + // Set the endpoint to NAK on completion. + else { + stm32_usb_set_rxep_status (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_NAK); + } + + // Copy the received data to our local buffer. + rx_bytes_rcvd = stm32_usb_copy_from_dbuf (rxtr->buf_ptr, rx_bytes_req, rxtr->ep_num, buf_sel); + rxtr->bytes_rcvd += rx_bytes_rcvd; + rxtr->buf_ptr += rx_bytes_rcvd; + break; + + // Unknown state - driver error. + default : + FAIL_USB ("RX transaction in invalid state."); + rxtr->state = RXTR_STATE_IDLE; + rxtr->status = EIO; + completed = true; + break; + + } + return completed; +} + +//----------------------------------------------------------------------------- +// Initiate a data receive transaction. This sets up a new transaction, +// enabling the receive buffers and then waiting for incoming data. + +static cyg_bool stm32_usb_rxtr_start + (rxtr_impl* rxtr, cyg_bool int_safe) +{ + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + + // Set up double buffer transactions. + if (rxtr->flags & RXTR_FLAGS_DBUF) { + rxtr->state = RXTR_STATE_DB_NEXT_PKT; + rxtr->status = ENOERR; + rxtr->bytes_rcvd = 0; + stm32_usb_flip_epxr_toggle (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_SWBUFRX); + stm32_usb_set_rxep_status (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_VALID); + } + + // Set up single buffer transactions. + else { + rxtr->state = RXTR_STATE_SB_NEXT_PKT; + rxtr->status = ENOERR; + rxtr->bytes_rcvd = 0; + stm32_usb_set_rxep_status (rxtr->ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_VALID); + } + + // Exit critical section. + if (!int_safe) stm32_usb_release_intr_mask (); + return false; +} + +//----------------------------------------------------------------------------- +// Halt a receive endpoint. This places the endpoint in the stall condition +// and cancels any outstanding transaction, resetting the transaction state +// machine. + +static void stm32_usb_rxep_halt + (rxep_impl* rxep, cyg_bool int_safe) +{ + cyg_bool call_completion; + + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + stm32_usb_set_rxep_status (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_DIS); + + // Reset the transaction state. + call_completion = ((rxep->rxtr.state != RXTR_STATE_IDLE) && + (rxep->rxtr.state != RXTR_STATE_RESET)) ? true : false; + rxep->rxtr.state = RXTR_STATE_RESET; + rxep->common.halted = true; + stm32_usb_clear_epxr_bits (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + + // Exit critical section. + stm32_usb_set_rxep_status (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL); + if (!int_safe) stm32_usb_release_intr_mask (); + + // Indicate error via the completion callback. + if (call_completion) { + if (rxep->common.complete_fn) + (*rxep->common.complete_fn) (rxep->common.complete_data, -EIO); + TRACE_USB ("RX Transaction cancelled on halt.\n"); + } +} + +//----------------------------------------------------------------------------- +// Take a receive endpoint out of halted state. This takes the endpoint out +// of the halted state and resets the endpoint. Note that according to the +// spec, the endpoint should also be reset and the toggle bits cleared if +// 'clear halted' is called while in normal operation. + +static void stm32_usb_rxep_unhalt + (rxep_impl* rxep, cyg_bool int_safe) +{ + cyg_bool call_completion; + + // Critical section - avoid races with the ISR. + if (!int_safe) stm32_usb_request_intr_mask (); + stm32_usb_set_rxep_status (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_DIS); + + // Reset the transaction state. + call_completion = ((rxep->rxtr.state != RXTR_STATE_IDLE) && + (rxep->rxtr.state != RXTR_STATE_RESET)) ? true : false; + rxep->rxtr.state = RXTR_STATE_IDLE; + rxep->common.halted = false; + stm32_usb_clear_epxr_bits (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + stm32_usb_clear_epxr_toggle (rxep->rxtr.ep_num, + CYGHWR_HAL_STM32_USB_EPXR_DTOGRX | CYGHWR_HAL_STM32_USB_EPXR_SWBUFRX); + + // Exit critical section. + stm32_usb_set_rxep_status (rxep->rxtr.ep_num, CYGHWR_HAL_STM32_USB_EPXR_STATRX_NAK); + if (!int_safe) stm32_usb_release_intr_mask (); + + // Indicate error via the completion callback. + if (call_completion) { + if (rxep->common.complete_fn) + (*rxep->common.complete_fn) (rxep->common.complete_data, -EIO); + TRACE_USB ("RX Transaction cancelled on resume.\n"); + } +} + +//============================================================================= +// Implement control endpoint protocol handling. +//============================================================================= + +//----------------------------------------------------------------------------- +// Forward endpoint 0 state change notifications. + +static void stm32_usb_ctrl_update_state + (int new_state, usbs_state_change state_change) +{ + int old_state = ctrlep.common.state; + ctrlep.common.state = new_state; + if (ctrlep.common.state_change_fn) + (*ctrlep.common.state_change_fn) (&ctrlep.common, + ctrlep.common.state_change_data, state_change, old_state); +} + +// -------------------------------------------------------------------------- +// Called on completion of an endpoint 0 control request transaction. + +static void stm32_usb_ctrl_completed + (int status) +{ + ctrlep.msg_state = CTRLEP_MSG_STATE_IDLE; + if (ctrlep.common.complete_fn) + (*ctrlep.common.complete_fn) (&cyg_usbs_cortexm_stm32_ep0c, -status); + if (status != 0) + TRACE_USB ("Transaction failed (status %d).\n", status); +} + +//----------------------------------------------------------------------------- +// Clear endpoint configuration. This disables all non-control endpoints, +// resetting their state. + +static void stm32_usb_ctrl_clear_config + (void) +{ + cyg_uint32 i; + txep_impl* txep = txep_list; + rxep_impl* rxep = rxep_list; + + // Disable all non-control endpoints, clearing any outstanding interrupts. + // This also ensures that the toggle bits are reset to 0. + for (i = 1; i < 8; i++) { + stm32_usb_set_txep_status (i, CYGHWR_HAL_STM32_USB_EPXR_STATTX_DIS); + stm32_usb_set_rxep_status (i, CYGHWR_HAL_STM32_USB_EPXR_STATRX_DIS); + stm32_usb_clear_epxr_toggle (i, + CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | CYGHWR_HAL_STM32_USB_EPXR_DTOGRX); + stm32_usb_clear_epxr_bits (i, 0xFFFF); + } + + // Reset the transaction state for all transmit endpoints. + for (i = 0; i < CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM; i++) { + txep->common.halted = true; + txep->txtr.state = TXTR_STATE_RESET; + txep++; + } + + // Reset the transaction state for all receive endpoints. + for (i = 0; i < CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM; i++) { + rxep->common.halted = true; + rxep->rxtr.state = RXTR_STATE_RESET; + rxep++; + } + + // Clear the logical to physical endpoint mappings. + for (i = 0; i < 15; i++) { + txep_map [i] = NULL; + rxep_map [i] = NULL; + } + + // Clear buffer RAM for non-control endpoints (preseverves endpoint 0). + stm32_usb_buf_clear_config (); +} + +//----------------------------------------------------------------------------- +// Reset the control endpoint - placing the device in the 'default' state. + +static void stm32_usb_ctrl_reset + (void) +{ + // Re-enable the device on address 0 only. + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_DADDR, CYGHWR_HAL_STM32_USB_DADDR_EF); + + // Reset the buffer RAM layout, allocating only the endpoint 0 buffers. + stm32_usb_buf_reset_ep0 (ctrlep.common.enumeration_data->device.max_packet_size); + + // Clear the previous endpoint configuration. + stm32_usb_ctrl_clear_config (); + + // Ensure endpoint 0 is assigned the correct endpoint type. + stm32_usb_assign_epxr (0, CYGHWR_HAL_STM32_USB_EPXR_EPTYPE_CTRL); + + // Reset endpoint 0, leaving it configured for valid incoming packets. + stm32_usb_set_txep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATTX_NAK); + stm32_usb_set_rxep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATRX_VALID); + stm32_usb_clear_epxr_toggle (0, + CYGHWR_HAL_STM32_USB_EPXR_DTOGTX | CYGHWR_HAL_STM32_USB_EPXR_DTOGRX); + stm32_usb_clear_epxr_bits (0, + CYGHWR_HAL_STM32_USB_EPXR_CTRTX | CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + + // Enter default state. + stm32_usb_ctrl_update_state (USBS_STATE_DEFAULT, USBS_STATE_CHANGE_RESET); +} + +//----------------------------------------------------------------------------- +// Fill the transmit control message buffer. This function is used to +// assemble control response messages for transmission to the host. + +static cyg_uint32 stm32_usb_ctrl_fill_msg_buffer + (void) +{ + cyg_uint32 msg_length = 0; + + // Deal with commands which place their data directly into the staging buffer. + if (ctrlep.common.buffer == ctrlep_msg_buffer) { + msg_length = ctrlep.common.buffer_size; + } + + // Loop until there are no more message segments to append. For buffer + // overflows, debug builds will assert and production builds will send a + // truncated message. + else do { + if (ctrlep.common.buffer_size != 0) { + if (msg_length + ctrlep.common.buffer_size <= CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE) { + memcpy (ctrlep_msg_buffer + msg_length, ctrlep.common.buffer, ctrlep.common.buffer_size); + msg_length += ctrlep.common.buffer_size; + } + else { + FAIL_USB ("Endpoint 0 transmit buffer overflow."); + break; + } + + // Get the next message segment, if available. + if (ctrlep.common.fill_buffer_fn) + (*ctrlep.common.fill_buffer_fn) (&cyg_usbs_cortexm_stm32_ep0c); + } + } while (ctrlep.common.fill_buffer_fn); + + return msg_length; +} + +//----------------------------------------------------------------------------- +// Handle set configuration setup packets. This is implemented in the low +// level driver because this is the point at which we need to configure the +// endpoint RAM and realise the endpoints. It 'falls through' to the high +// level handlers so that they can deal with notifying the application. +// Note: Only a single (default) interface is supported per configuration. + +static usbs_control_return stm32_usb_ctrl_set_config + (cyg_uint32 config_id) +{ + cyg_uint32 i, j; + cyg_uint32 start_interface, start_endpoint, num_endpoints; + cyg_uint32 total_interfaces, total_endpoints; + cyg_uint32 txep_count = 0; + cyg_uint32 rxep_count = 0; + cyg_bool alloc_ok; + const usb_configuration_descriptor* configurations; + const usb_interface_descriptor* interfaces; + const usb_endpoint_descriptor* endpoints; + + // Check for valid config. + if ((config_id <= 0) || + (config_id > ctrlep.common.enumeration_data->device.number_configurations)) + return USBS_CONTROL_RETURN_UNKNOWN; + + // Clear the previous endpoint configuration. + stm32_usb_ctrl_clear_config (); + + // Get the base pointers for the descriptor tables. + configurations = ctrlep.common.enumeration_data->configurations; + interfaces = ctrlep.common.enumeration_data->interfaces; + endpoints = ctrlep.common.enumeration_data->endpoints; + + start_interface = 0; + start_endpoint = 0; + num_endpoints = 0; + total_interfaces = ctrlep.common.enumeration_data->total_number_interfaces; + total_endpoints = ctrlep.common.enumeration_data->total_number_endpoints; + + // Skip over the descriptors until we get to the ones we want. + for (i = 0; i < (config_id-1); i++) { + ASSERT_USB (start_interface + configurations[i].number_interfaces <= total_interfaces, + "Invalid number of interfaces in enumeration data."); + for (j = 0; j < configurations[i].number_interfaces; j++) { + start_endpoint += interfaces[start_interface+j].number_endpoints; + ASSERT_USB (start_endpoint < total_endpoints, + "Invalid number of endpoints in enumeration data."); + } + start_interface += configurations[i].number_interfaces; + } + + // Determine the number of endpoint descriptors which need to be included for + // all interfaces within the specified configuration. + ASSERT_USB (start_interface + configurations[i].number_interfaces <= total_interfaces, + "Invalid number of interfaces in enumeration data."); + for (j = 0; j < configurations[i].number_interfaces; j++) { + num_endpoints += interfaces[start_interface+j].number_endpoints; + ASSERT_USB (start_endpoint + num_endpoints <= total_endpoints, + "Invalid number of endpoints in enumeration data."); + } + TRACE_USB ("Found %d interfaces and %d endpoints for configuration %d.\n", + configurations[i].number_interfaces, num_endpoints, config_id); + + // Realise the endpoints for the specified interfaces. Set up the buffer + // RAM and configure them to NAK the host until the higher layer application + // is ready to initiate transfers. Failures to allocate the endpoint buffers + // will put the endpoints in the stalled state. + for (i = 0; i < num_endpoints; i++) { + const usb_endpoint_descriptor* ep_desc = endpoints + start_endpoint + i; + cyg_uint32 pkt_size = ep_desc->max_packet_lo + (((cyg_uint32) ep_desc->max_packet_hi) << 8); + + // Deal with transmit (input) endpoints. + if (ep_desc->endpoint & USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN) { + if ((ep_desc->endpoint & 0x7F) < 1 || (ep_desc->endpoint & 0x7F) > 15) { + FAIL_USB ("Invalid endpoint ID in configuration."); + goto out; + } + if (txep_count >= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) { + FAIL_USB ("Too many TX endpoints in configuration."); + goto out; + } + + // Set up transmit (input) buffers. + if (ep_desc->attributes == USB_ENDPOINT_DESCRIPTOR_ATTR_BULK) { + alloc_ok = stm32_usb_buf_add_ep (txep_count + 1, pkt_size, true, false, true); + stm32_usb_set_epxr_bits (txep_count + 1, + CYGHWR_HAL_STM32_USB_EPXR_EPTYPE_BULK | CYGHWR_HAL_STM32_USB_EPXR_EPKIND); +#ifdef CYGHWR_DEVS_USB_CORTEXM_STM32_BULK_TERM_ZLP + (txep_list + txep_count)->txtr.flags = TXTR_FLAGS_ZLPKT | TXTR_FLAGS_DBUF; +#else + (txep_list + txep_count)->txtr.flags = TXTR_FLAGS_DBUF; +#endif + TRACE_USB ("Configured BULK IN endpoint ID %d.\n", ep_desc->endpoint & 0x7F); + } + else if (ep_desc->attributes == USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT) { + alloc_ok = stm32_usb_buf_add_ep (txep_count + 1, pkt_size, true, false, false); + (txep_list + txep_count)->txtr.flags = TXTR_FLAGS_NONE; + stm32_usb_set_epxr_bits (txep_count + 1, CYGHWR_HAL_STM32_USB_EPXR_EPTYPE_INTR); + TRACE_USB ("Configured INTERRUPT IN endpoint ID %d.\n", ep_desc->endpoint & 0x7F); + } + else { + FAIL_USB ("Isochronous endpoints are not currently supported."); + goto out; + } + + // Enable endpoints if buffer allocation was OK - stall them otherwise. + if (alloc_ok) { + txep_map [(ep_desc->endpoint & 0x7F) - 1] = txep_list + txep_count; + (txep_list + txep_count)->common.halted = false; + (txep_list + txep_count)->txtr.state = TXTR_STATE_IDLE; + stm32_usb_set_txep_status (txep_count + 1, CYGHWR_HAL_STM32_USB_EPXR_STATTX_NAK); + } + else { + stm32_usb_set_txep_status (txep_count + 1, CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL); + } + stm32_usb_set_epxr_bits (txep_count + 1, (ep_desc->endpoint & 0xF)); + txep_count++; + } + + // Deal with receive (output) endpoints. + else { + if ((ep_desc->endpoint & 0x7F) < 1 || (ep_desc->endpoint & 0x7F) > 15) { + FAIL_USB ("Invalid endpoint ID in configuration."); + goto out; + } + if (rxep_count >= CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM) { + FAIL_USB ("Too many RX endpoints in configuration."); + goto out; + } + + // Set up receive (output) buffers. + if (ep_desc->attributes == USB_ENDPOINT_DESCRIPTOR_ATTR_BULK) { + alloc_ok = stm32_usb_buf_add_ep (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + pkt_size, false, true, true); + stm32_usb_set_epxr_bits (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + CYGHWR_HAL_STM32_USB_EPXR_EPTYPE_BULK | CYGHWR_HAL_STM32_USB_EPXR_EPKIND); +#ifdef CYGHWR_DEVS_USB_CORTEXM_STM32_BULK_TERM_ZLP + (rxep_list + rxep_count)->rxtr.flags = RXTR_FLAGS_ZLPKT | RXTR_FLAGS_DBUF; +#else + (rxep_list + rxep_count)->rxtr.flags = RXTR_FLAGS_DBUF; +#endif + TRACE_USB ("Configured BULK OUT endpoint ID %d.\n", ep_desc->endpoint & 0x7F); + } + else if (ep_desc->attributes == USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT) { + alloc_ok = stm32_usb_buf_add_ep (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + pkt_size, false, true, false); + (rxep_list + rxep_count)->rxtr.flags = RXTR_FLAGS_NONE; + stm32_usb_set_epxr_bits (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + CYGHWR_HAL_STM32_USB_EPXR_EPTYPE_INTR); + TRACE_USB ("Configured INTERRUPT OUT endpoint ID %d.\n", ep_desc->endpoint & 0x7F); + } + else { + FAIL_USB ("Isochronous endpoints are not currently supported."); + goto out; + } + + // Enable endpoints if buffer allocation was OK - stall them otherwise. + if (alloc_ok) { + rxep_map [(ep_desc->endpoint & 0x7F) - 1] = rxep_list + rxep_count; + (rxep_list + rxep_count)->common.halted = false; + (rxep_list + rxep_count)->rxtr.state = RXTR_STATE_IDLE; + stm32_usb_set_rxep_status (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + CYGHWR_HAL_STM32_USB_EPXR_STATRX_NAK); + } + else { + stm32_usb_set_rxep_status (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL); + } + stm32_usb_set_epxr_bits (rxep_count + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1, + (ep_desc->endpoint & 0xF)); + rxep_count++; + } + } + + // Pass up to higher layers. +out: + return USBS_CONTROL_RETURN_UNKNOWN; +} + +//----------------------------------------------------------------------------- +// Handle get status setup packets. This function places the status +// information directly into the staging buffer. We deal with the device +// and endpoint status responses here. The interface status request is +// dealt with in the common USB slave layer. +// TODO - should #define the return fields in usb.h. + +static inline usbs_control_return stm32_usb_ctrl_get_status + (cyg_uint32 recipient, cyg_uint32 ep_sel) +{ + cyg_uint8 dev_state = ctrlep.common.state & USBS_STATE_MASK; + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + + // Device status requests are valid in addressed and configured states. + // TODO - remote wakeup is not currently supported by this driver. + if (recipient == USB_DEVREQ_RECIPIENT_DEVICE) { + if (dev_state == USBS_STATE_ADDRESSED || dev_state == USBS_STATE_CONFIGURED) { +#ifdef CYGHWR_DEVS_USB_CORTEXM_STM32_SELF_POWERED + ctrlep_msg_buffer[0] = 0x01; +#else + ctrlep_msg_buffer[0] = 0x00; +#endif + ctrlep_msg_buffer[1] = 0x00; + result = USBS_CONTROL_RETURN_HANDLED; + } + } + + // Endpoint 0 status requests are valid in addressed and configured states. + // Endpoint 0 cannot be halted. Endpoint number is in the lower 4 bits of the ID. + else if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT && (ep_sel & USB_DEVREQ_INDEX_ENDPOINT_MASK) == 0) { + if (dev_state == USBS_STATE_ADDRESSED || dev_state == USBS_STATE_CONFIGURED) { + ctrlep_msg_buffer[0] = 0x00; + ctrlep_msg_buffer[1] = 0x00; + result = USBS_CONTROL_RETURN_HANDLED; + } + } + + // Non-control endpoint status requests are only valid in the configured state. + // Returns the halted state of the requested endpoint. + else if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) { + if (dev_state == USBS_STATE_CONFIGURED) { + + // Transmit (IN) endpoint IDs have the top bit set. + if ((ep_sel & USB_DEVREQ_INDEX_DIRECTION_MASK) == USB_DEVREQ_INDEX_DIRECTION_IN) { + ep_sel = (ep_sel & USB_DEVREQ_INDEX_ENDPOINT_MASK) - 1; + if (txep_map[ep_sel] != NULL) { + ctrlep_msg_buffer[0] = (txep_map[ep_sel]->common.halted) ? 0x01 : 0x00; + ctrlep_msg_buffer[1] = 0x00; + result = USBS_CONTROL_RETURN_HANDLED; + } + } + + // Receive (OUT) endpoint IDs have the top bit clear. + else { + ep_sel = (ep_sel & USB_DEVREQ_INDEX_ENDPOINT_MASK) - 1; + if (rxep_map[ep_sel] != NULL) { + ctrlep_msg_buffer[0] = (rxep_map[ep_sel]->common.halted) ? 0x01 : 0x00; + ctrlep_msg_buffer[1] = 0x00; + result = USBS_CONTROL_RETURN_HANDLED; + } + } + } + } + + // Point the endpoint 0 response buffer at the staging buffer. + if (result != USBS_CONTROL_RETURN_UNKNOWN) { + ctrlep.common.buffer_size = 2; + ctrlep.common.buffer = ctrlep_msg_buffer; + } + return result; +} + +//----------------------------------------------------------------------------- +// Handle set and clear feature commands. Since remote wakeup support is not +// implemented and interface features are dealt with at a higher layer we only +// implement endpoint halting here. This will only ever be called from within +// the DSR so is interrupt safe. + +static usbs_control_return stm32_usb_ctrl_set_feature + (cyg_uint32 recipient, cyg_uint32 ep_sel, cyg_uint32 feature, cyg_bool set) +{ + cyg_uint8 dev_state = ctrlep.common.state & USBS_STATE_MASK; + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + + // Non-control endpoint feature control is valid in the configured state. + if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT && + dev_state == USBS_STATE_CONFIGURED && feature == USB_DEVREQ_FEATURE_ENDPOINT_HALT) { + + // Transmit (IN) endpoint IDs have the top bit set. + if ((ep_sel & USB_DEVREQ_INDEX_DIRECTION_MASK) == USB_DEVREQ_INDEX_DIRECTION_IN) { + ep_sel = (ep_sel & USB_DEVREQ_INDEX_ENDPOINT_MASK) - 1; + if (txep_map[ep_sel] != NULL) { + if (set) + stm32_usb_txep_halt (txep_map[ep_sel], true); + else + stm32_usb_txep_unhalt (txep_map[ep_sel], true); + result = USBS_CONTROL_RETURN_HANDLED; + } + } + + // Receive (OUT) endpoint IDs have the top bit clear. + else { + ep_sel = (ep_sel & USB_DEVREQ_INDEX_ENDPOINT_MASK) - 1; + if (rxep_map[ep_sel] != NULL) { + if (set) + stm32_usb_rxep_halt (rxep_map[ep_sel], true); + else + stm32_usb_rxep_unhalt (rxep_map[ep_sel], true); + result = USBS_CONTROL_RETURN_HANDLED; + } + } + } + return result; +} + +//----------------------------------------------------------------------------- +// Process standard endpoint 0 setup packets. + +static usbs_control_return stm32_usb_ctrl_setup_standard + (void) +{ + usb_devreq* req = (usb_devreq*) &ctrlep.common.control_buffer[0]; + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + cyg_uint32 recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + + switch (req->request) { + + // Assign device address. We can't actually update the address register + // until after the full bus handshake has completed, otherwise the ACK + // packet gets lost. + case USB_DEVREQ_SET_ADDRESS : + result = USBS_CONTROL_RETURN_HANDLED; + break; + + // Set device configuration. + case USB_DEVREQ_SET_CONFIGURATION : + TRACE_USB ("Setting USB configuration = %d\n", (cyg_uint32) req->value_lo); + result = stm32_usb_ctrl_set_config (req->value_lo); + break; + + // TODO: Interfaces with multiple settings are not currently supported. + // If a device only supports a default setting for a specified interface + // the spec says a stall should be sent. + case USB_DEVREQ_SET_INTERFACE : + TRACE_USB ("Using default interface settings.\n"); + result = USBS_CONTROL_RETURN_STALL; + break; + + // Get device status. + case USB_DEVREQ_GET_STATUS : + result = stm32_usb_ctrl_get_status (recipient, req->index_lo); + break; + + // Control endpoint halting. Halt on 'set feature'. + case USB_DEVREQ_SET_FEATURE : + result = stm32_usb_ctrl_set_feature (recipient, req->index_lo, req->value_lo, true); + break; + + // Control endpoint halting. Resume on 'clear feature'. + case USB_DEVREQ_CLEAR_FEATURE : + result = stm32_usb_ctrl_set_feature (recipient, req->index_lo, req->value_lo, false); + break; + + // Pass up to the user supplied handler, if present. + default : + if (ctrlep.common.standard_control_fn) + result = (*ctrlep.common.standard_control_fn) + (&ctrlep.common, ctrlep.common.standard_control_data); + break; + } + + // If not already handled, pass up to the high level driver. + if (result == USBS_CONTROL_RETURN_UNKNOWN) + result = usbs_handle_standard_control (&ctrlep.common); + + return result; +} + +//----------------------------------------------------------------------------- +// Perform initial processing of endpoint 0 setup packets. + +static void stm32_usb_ctrl_setup_handler + (void) +{ + cyg_uint32 req_length, req_type, req_dir; + usb_devreq* req = (usb_devreq*) &ctrlep.common.control_buffer[0]; + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + + // Extract the required fields from the setup packet. + req_length = 8 * (cyg_uint32) req->length_hi + req->length_lo; + req_type = req->type & USB_DEVREQ_TYPE_MASK; + req_dir = req->type & USB_DEVREQ_DIRECTION_MASK; + + // Always fail the transaction if the requested data stage exceeds the + // allocated buffer area. + if ((req_dir == USB_DEVREQ_DIRECTION_OUT) && + (req_length > CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE)) { + FAIL_USB ("Requested control data stage exceeds message buffer size."); + result = USBS_CONTROL_RETURN_STALL; + } + + // Pass standard requests to the standard handlers. + else if (req_type == USB_DEVREQ_TYPE_STANDARD) { + result = stm32_usb_ctrl_setup_standard (); + } + + // Hand off non-standard requests to their respective handlers. + else { + usbs_control_return (*callback_fn) (usbs_control_endpoint*, void*); + void* callback_arg; + + if (req_type == USB_DEVREQ_TYPE_CLASS) { + callback_fn = ctrlep.common.class_control_fn; + callback_arg = ctrlep.common.class_control_data; + } + else if (req_type == USB_DEVREQ_TYPE_VENDOR) { + callback_fn = ctrlep.common.vendor_control_fn; + callback_arg = ctrlep.common.vendor_control_data; + } + else { + callback_fn = ctrlep.common.reserved_control_fn; + callback_arg = ctrlep.common.reserved_control_data; + } + + result = (callback_fn) ? (*callback_fn) (&ctrlep.common, callback_arg) + : USBS_CONTROL_RETURN_STALL; + } + + // If correctly handled, initiate the data transfer phase. This is only + // called from within the DSR, so the transaction start is interrupt safe. + if (result == USBS_CONTROL_RETURN_HANDLED) { + + // ACK the transfer by sending a zero length packet. + if (req_length == 0) { + ctrlep.msg_state = CTRLEP_MSG_STATE_CTRL_ACK; + ctrlep.txtr.state = TXTR_STATE_IDLE; + ctrlep.txtr.buf_ptr = ctrlep_msg_buffer; + ctrlep.txtr.buf_size = 0; + stm32_usb_txtr_start (&ctrlep.txtr, true); + } + + // Send the inbound data. + else if (req_dir == USB_DEVREQ_DIRECTION_IN) { + ctrlep.msg_state = CTRLEP_MSG_STATE_IN_DATA; + ctrlep.txtr.state = TXTR_STATE_IDLE; + ctrlep.txtr.buf_ptr = ctrlep_msg_buffer; + ctrlep.txtr.buf_size = stm32_usb_ctrl_fill_msg_buffer (); + stm32_usb_txtr_start (&ctrlep.txtr, true); + } + + // Receive outbound data from the host. + else { + ctrlep.msg_state = CTRLEP_MSG_STATE_OUT_DATA; + ctrlep.rxtr.state = RXTR_STATE_IDLE; + ctrlep.rxtr.buf_ptr = ctrlep_msg_buffer; + ctrlep.rxtr.buf_size = CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE; + stm32_usb_rxtr_start (&ctrlep.rxtr, true); + } + } + + // Stall endpoint on request. + else if (result == USBS_CONTROL_RETURN_STALL) { + stm32_usb_set_txep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL); + stm32_usb_set_rxep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL); + } + + // Unsupported setup commands also stall the control endpoint. + else { + TRACE_USB ("Stall EP0 on UNKNOWN control message : %02X %02X %02X %02X %02X %02X %02X %02X\n", + ctrlep.common.control_buffer[0], ctrlep.common.control_buffer[1], + ctrlep.common.control_buffer[2], ctrlep.common.control_buffer[3], + ctrlep.common.control_buffer[4], ctrlep.common.control_buffer[5], + ctrlep.common.control_buffer[6], ctrlep.common.control_buffer[7]); + stm32_usb_set_txep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL); + stm32_usb_set_rxep_status (0, CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL); + } +} + +//----------------------------------------------------------------------------- +// Completion of control endpoint data transmit phase. + +static inline void stm32_usb_ctrl_txtr_done + (void) +{ + cyg_uint32 reg_val; + usb_devreq* req = (usb_devreq*) &ctrlep.common.control_buffer[0]; + + // If this is confirmation that an ACK packet has been sent, complete the + // transaction. We also take the opportunity to update the device address + // here if required. + if (ctrlep.msg_state == CTRLEP_MSG_STATE_CTRL_ACK) { + if (req->request == USB_DEVREQ_SET_ADDRESS) { + TRACE_USB ("Setting USB device address = %d\n", (cyg_uint32) req->value_lo); + reg_val = CYGHWR_HAL_STM32_USB_DADDR_EF; + reg_val |= CYGHWR_HAL_STM32_USB_DADDR_ADD ((cyg_uint32) req->value_lo); + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_DADDR, reg_val); + stm32_usb_ctrl_update_state (USBS_STATE_ADDRESSED, USBS_STATE_CHANGE_ADDRESSED); + } + stm32_usb_ctrl_completed (ctrlep.txtr.status); + } + + // Complete after transmitting the output status handshake. + else if (ctrlep.msg_state == CTRLEP_MSG_STATE_OUT_STATUS) { + stm32_usb_ctrl_completed (ctrlep.txtr.status); + } + + // If all packets in an inbound transaction have been sent, wait for status + // response from the host. Called from DSR so is interrupt safe. + else if (ctrlep.msg_state == CTRLEP_MSG_STATE_IN_DATA) { + if (ctrlep.txtr.status == ENOERR) { + ctrlep.msg_state = CTRLEP_MSG_STATE_IN_STATUS; + ctrlep.rxtr.state = RXTR_STATE_IDLE; + ctrlep.rxtr.buf_ptr = ctrlep_msg_buffer; + ctrlep.rxtr.buf_size = 0; + stm32_usb_rxtr_start (&ctrlep.rxtr, true); + } + else { + stm32_usb_ctrl_completed (ctrlep.txtr.status); + } + } +} + +//----------------------------------------------------------------------------- +// Completion of control endpoint data receive phase. + +static inline void stm32_usb_ctrl_rxtr_done + (void) +{ + // If waiting for a status response, we should get a zero length packet. + if (ctrlep.msg_state == CTRLEP_MSG_STATE_IN_STATUS) { + stm32_usb_ctrl_completed (ctrlep.rxtr.status); + } + + // Handle conventional data packets. Incoming packets on endpoint 0 can + // be overwritten by setup packets. Called from DSR so is interrupt safe. + else if (ctrlep.msg_state == CTRLEP_MSG_STATE_OUT_DATA) { + if (ctrlep.rxtr.status == ENOERR) { + ctrlep.msg_state = CTRLEP_MSG_STATE_OUT_STATUS; + ctrlep.txtr.state = TXTR_STATE_IDLE; + ctrlep.txtr.buf_ptr = ctrlep_msg_buffer; + ctrlep.txtr.buf_size = 0; + stm32_usb_txtr_start (&ctrlep.txtr, true); + } + else { + stm32_usb_ctrl_completed (ctrlep.rxtr.status); + } + } +} + +//============================================================================= +// Implement ISRs for low level data transfer. +//============================================================================= + +//----------------------------------------------------------------------------- +// ISR for handling control endpoint interrupts. + +static inline cyg_bool stm32_usb_ctrlep_ISR + (void) +{ + cyg_bool completed; + cyg_bool call_dsr = false; + cyg_uint32 usb_epxr; + + // Process packet transmit events. + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EP0R, usb_epxr); + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRTX) { + stm32_usb_clear_epxr_bits (0, CYGHWR_HAL_STM32_USB_EPXR_CTRTX); + + completed = stm32_usb_txtr_run (&ctrlep.txtr); + if (completed) { + isr_shared.txtr_done |= (1 << 0); + call_dsr = true; + } + } + + // Deal with incoming setup packets. These are copied directly to the + // staging buffer in the ISR/DSR shared area for subsequent handling in + // DSR context. + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRRX) { + stm32_usb_clear_epxr_bits (0, CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_SETUP) { + stm32_usb_copy_from_sbuf (ctrlep.common.control_buffer, 8, 0); + isr_shared.flags |= ISR_FLAGS_SETUP_READY; + call_dsr = true; + } + + // Handle conventional incoming packets. + else { + completed = stm32_usb_rxtr_run (&ctrlep.rxtr); + if (completed) { + isr_shared.rxtr_done |= (1 << 0); + call_dsr = true; + } + } + } + return call_dsr; +} + +//----------------------------------------------------------------------------- +// ISR for handling transmit endpoint interrupts. + +static inline cyg_bool stm32_usb_txep_ISR + (cyg_uint32 txep_id) +{ + cyg_bool completed; + cyg_bool call_dsr = false; + cyg_uint32 usb_epxr; + txep_impl* txep = txep_list + txep_id - 1; + + // Receive events for a transmit endpoint are an error - discard them. + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (txep_id), usb_epxr); + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRRX) { + FAIL_USB ("Received RX interrupt for TX endpoint."); + stm32_usb_clear_epxr_bits (txep_id, CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + } + + // Handle transmit interrupt events. + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRTX) { + stm32_usb_clear_epxr_bits (txep_id, CYGHWR_HAL_STM32_USB_EPXR_CTRTX); + completed = stm32_usb_txtr_run (&txep->txtr); + if (completed) { + isr_shared.txtr_done |= (1 << txep_id); + call_dsr = true; + } + } + return call_dsr; +} + +//----------------------------------------------------------------------------- +// ISR for handling receive endpoint interrupts. + +static inline cyg_bool stm32_usb_rxep_ISR + (cyg_uint32 rxep_id) +{ + cyg_bool completed; + cyg_bool call_dsr = false; + cyg_uint32 usb_epxr; + rxep_impl* rxep = rxep_list + rxep_id - CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM - 1; + + // Transmit events for a receive endpoint are an error - discard them. + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_EPXR (rxep_id), usb_epxr); + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRTX) { + FAIL_USB ("Received TX interrupt for RX endpoint."); + stm32_usb_clear_epxr_bits (rxep_id, CYGHWR_HAL_STM32_USB_EPXR_CTRTX); + } + + // Handle receive interrupt events. + if (usb_epxr & CYGHWR_HAL_STM32_USB_EPXR_CTRRX) { + stm32_usb_clear_epxr_bits (rxep_id, CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + completed = stm32_usb_rxtr_run (&rxep->rxtr); + if (completed) { + isr_shared.rxtr_done |= (1 << rxep_id); + call_dsr = true; + } + } + return call_dsr; +} + +//----------------------------------------------------------------------------- +// Main ISR for handling interrupt events. The interrupt generation for the +// USB endpoints is a little strange, with endpoint interrupts effectively being +// queued through the same interrupt status register. Clearing the interrupt +// condition has the effect of popping an endpoint interrupt from the queue so +// that the next endpoint can be serviced. This means that serialising the +// interrupt processing between ISR and DSR would carry an unacceptable latency +// penalty - which is why we have to do the buffer copies within the ISR. + +static cyg_uint32 stm32_usb_ISR + (cyg_vector_t vector, cyg_addrword_t data) +{ + cyg_bool call_dsr = false; + cyg_uint32 usb_istr, ep_id; + cyg_uint32 ret_val = CYG_ISR_HANDLED; + + // Check for device interrupts first. + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_ISTR, usb_istr); + + // Detect reset event and hand it up to the DSR. Note that these will not + // be merged with other events for DSR processing. + if (usb_istr & CYGHWR_HAL_STM32_USB_ISTR_RESET) { + usb_istr &= ~((cyg_uint32) CYGHWR_HAL_STM32_USB_ISTR_RESET); + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_ISTR, usb_istr); + isr_shared.flags |= ISR_FLAGS_DEVICE_RESET; + call_dsr = true; + } + + // TODO: Support for suspend and wake events can be added here if required. + + // Check for endpoint interrupts. These are indicated by the CTR flag. + // The endpoint direction is inferred from the endpoint number, since + // transmit endpoints are enumerated before receive endpoints. + else while (usb_istr & CYGHWR_HAL_STM32_USB_ISTR_CTR) { + ep_id = usb_istr & CYGHWR_HAL_STM32_USB_ISTR_EPID_MASK; + + // Service the control endpoint. + if (ep_id == 0) + call_dsr |= stm32_usb_ctrlep_ISR (); + + // Service transmit endpoints. + else if (ep_id <= CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM) + call_dsr |= stm32_usb_txep_ISR (ep_id); + + // Service receive endpoints. + else if (ep_id < USB_EPNUM) + call_dsr |= stm32_usb_rxep_ISR (ep_id); + + // Invalid endpoint. Fail in debug, clear down in production builds. + else { + FAIL_USB ("Interrupt for invalid endpoint detected."); + stm32_usb_clear_epxr_bits (ep_id, + CYGHWR_HAL_STM32_USB_EPXR_CTRTX | CYGHWR_HAL_STM32_USB_EPXR_CTRRX); + } + + // Check for all endpoints having been serviced. + HAL_READ_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_ISTR, usb_istr); + } + + // If interrupt has been handled, acknowledge it and return. Leave the + // interrupt unmasked until the DSR is called, since intervening ISR calls + // will safely post their events to the event masks. + cyg_drv_interrupt_acknowledge (vector); + if (call_dsr) { + ret_val |= CYG_ISR_CALL_DSR; + } + return ret_val; +} + +//============================================================================= +// Implement DSRs for handling high-level interrupt responses. +//============================================================================= + +//----------------------------------------------------------------------------- +// Main DSR for high-level interrupt processing. Information about the +// interrupt conditions is passed up via the ISR shared data area. + +static void stm32_usb_DSR + (cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + cyg_uint32 i; + + // Disable interrupts on entry to the DSR to avoid further events getting + // added to the event masks. + stm32_usb_request_intr_mask (); + + // Process device reset notifications. + if (isr_shared.flags & ISR_FLAGS_DEVICE_RESET) { + stm32_usb_ctrl_reset (); + } + + // Process control endpoint message transmit completions. + if (isr_shared.txtr_done & 1) { + stm32_usb_ctrl_txtr_done (); + } + + // Process control endpoint message receive completions. + if (isr_shared.rxtr_done & 1) { + stm32_usb_ctrl_rxtr_done (); + } + + // Process non-control endpoint message transmit completions. + if (isr_shared.txtr_done) { + for (i = 1; i < 8; i++) { + if (isr_shared.txtr_done & (1 << i)) { + txep_impl* txep = txep_list + i - 1; + int retval = (txep->txtr.status != ENOERR) ? -txep->txtr.status : txep->txtr.bytes_sent; + if (txep->common.complete_fn) + (*txep->common.complete_fn) (txep->common.complete_data, retval); + if (retval < 0) + TRACE_USB ("TX transaction failed (endpoint %d, status %d).\n", txep->txtr.ep_num, retval); + } + } + } + + // Process non-control endpoint message received completions. + if (isr_shared.rxtr_done) { + for (i = 1; i < 8; i++) { + if (isr_shared.rxtr_done & (1 << i)) { + rxep_impl* rxep = rxep_list + i - CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM - 1; + int retval = (rxep->rxtr.status != ENOERR) ? -rxep->rxtr.status : rxep->rxtr.bytes_rcvd; + if (rxep->common.complete_fn) + (*rxep->common.complete_fn) (rxep->common.complete_data, retval); + if (retval < 0) + TRACE_USB ("RX transaction failed (endpoint %d, status %d).\n", rxep->rxtr.ep_num, retval); + } + } + } + + // Process setup packets after ensuring that all outstanding EP0 completions + // associated with a previous setup transaction have been dealt with. + if (isr_shared.flags & ISR_FLAGS_SETUP_READY) { + stm32_usb_ctrl_setup_handler (); + } + + // Clear the ISR shared flags before unmasking the interrupt - they should + // all have been dealt with. + isr_shared.flags = ISR_FLAGS_CLEARED; + isr_shared.txtr_done = 0; + isr_shared.rxtr_done = 0; + + // Release the interrupt mask if possible. + stm32_usb_release_intr_mask (); +} + +//============================================================================= +// Provide standard USB driver API entry points. +//============================================================================= + +//----------------------------------------------------------------------------- +// API entry point for endpoint transmit requests. + +static void stm32_usb_tx_start + (usbs_tx_endpoint* usbs_txep) +{ + txep_impl* txep = (txep_impl*) usbs_txep; + txtr_impl* txtr = &txep->txtr; + cyg_int32 status = -EIO; + + // Check to see whether a transaction is in progress. + if (txtr->state != TXTR_STATE_IDLE) { + FAIL_USB ("Endpoint TX request when endpoint already busy."); + status = -EBUSY; + goto out; + } + + // Do a sanity check on the descriptor. + if (!txep->common.complete_fn) { + FAIL_USB ("Endpoint TX requires a completion function."); + goto out; + } + if (!txep->common.buffer) { + FAIL_USB ("Endpoint TX requires a valid transmit buffer."); + goto out; + } + if (stm32_usb_buf_get_size (txtr->ep_num) == 0) { + FAIL_USB ("TX request when endpoint buffers not allocated."); + goto out; + } + + // Attempt to start a transmit transaction and extract error status if it + // completes too early. This is not an interrupt safe call. + txtr->buf_ptr = txep->common.buffer; + txtr->buf_size = (cyg_uint32) txep->common.buffer_size; + if (stm32_usb_txtr_start (txtr, false)) + status = -txtr->status; + else + status = -ENOERR; + + // If the transaction failed to start, fire the completion handler. +out: + if ((status < 0) && (txep->common.complete_fn)) { + txep->common.complete_fn (txep->common.complete_data, status); + } +} + +//----------------------------------------------------------------------------- +// API entry point for endpoint receive requests. + +static void stm32_usb_rx_start + (usbs_rx_endpoint* usbs_rxep) +{ + rxep_impl* rxep = (rxep_impl*) usbs_rxep; + rxtr_impl* rxtr = &rxep->rxtr; + cyg_int32 status = -EIO; + + // Check to see whether a transaction is in progress. + if (rxtr->state != RXTR_STATE_IDLE) { + FAIL_USB ("Endpoint RX request when endpoint already busy."); + status = -EBUSY; + goto out; + } + + // Do a sanity check on the descriptor. + if (!rxep->common.complete_fn) { + FAIL_USB ("Endpoint RX requires a completion function."); + goto out; + } + if (!rxep->common.buffer) { + FAIL_USB ("Endpoint RX requires a valid receive buffer."); + goto out; + } + if (stm32_usb_buf_get_size (rxtr->ep_num) == 0) { + FAIL_USB ("RX request when endpoint buffers not allocated."); + goto out; + } + + // Attempt to start a receive transaction and extract error status if it + // completes too early. This is not an interrupt safe call. + rxtr->buf_ptr = rxep->common.buffer; + rxtr->buf_size = (cyg_uint32) rxep->common.buffer_size; + if (stm32_usb_rxtr_start (rxtr, false)) + status = -rxtr->status; + else + status = -ENOERR; + + // If the transaction failed to start, fire the completion handler. +out: + if ((status < 0) && (rxep->common.complete_fn)) { + rxep->common.complete_fn (rxep->common.complete_data, status); + } +} + +//----------------------------------------------------------------------------- +// API entry point for setting transmit endpoint halted state. + +static void stm32_usb_set_txep_halted + (usbs_tx_endpoint* txep, cyg_bool halted) +{ + if (halted) + stm32_usb_txep_halt ((txep_impl*) txep, false); + else + stm32_usb_txep_unhalt ((txep_impl*) txep, false); +} + +//----------------------------------------------------------------------------- +// API entry point for setting receive endpoint halted state. + +static void stm32_usb_set_rxep_halted + (usbs_rx_endpoint* rxep, cyg_bool halted) +{ + if (halted) + stm32_usb_rxep_halt ((rxep_impl*) rxep, false); + else + stm32_usb_rxep_unhalt ((rxep_impl*) rxep, false); +} + +//============================================================================= +// Initialise and reset the USB device. +//============================================================================= + +//----------------------------------------------------------------------------- +// One-time initialisation. This function is called during device startup +// in order to bring up the USB peripheral ready for operation. + +static void CYGBLD_ATTRIB_C_INIT_PRI(CYG_INIT_DEV_CHAR) cyg_usbs_cortexm_stm32_init + (void) +{ + cyg_uint32 reg_data; + + // First ensure that the APB bus is being clocked fast enough. + ASSERT_USB (APB1_FREQ > 8000000, "APB1 must be clocked faster than 8MHz."); + + // Check that the endpoint configuration is sane. + ASSERT_USB (CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM < 8, + "Too many hardware endpoints allocated in configuration."); + + // Make sure that the CAN controller is disabled and held in reset. + // TODO: If a CAN driver is to be added to the standard distribution, this + // should check for USB/CAN configuration clashes. + CYGHWR_HAL_STM32_CLOCK_DISABLE( CYGHWR_HAL_STM32_CAN1_CLOCK ); + + // Configure the IO pins for USB operation. + CYGHWR_HAL_STM32_GPIO_SET (USB_DISC_PIN); + CYGHWR_HAL_STM32_GPIO_SET (USB_DP_PIN); + CYGHWR_HAL_STM32_GPIO_SET (USB_DM_PIN); +#ifdef CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN_ACT_LOW + CYGHWR_HAL_STM32_GPIO_OUT (USB_DISC_PIN, 0); +#else + CYGHWR_HAL_STM32_GPIO_OUT (USB_DISC_PIN, 1); +#endif + + // Ensure that the USB clock is disabled prior to setting prescaler. + CYGHWR_HAL_STM32_CLOCK_DISABLE( CYGHWR_HAL_STM32_USB_CLOCK ); + + // Set up the USB 48MHz serial clock. There are only 2 valid prescaler + // settings which correspond to 72MHz and 48MHz PLL clock outputs. + HAL_READ_UINT32 (CYGHWR_HAL_STM32_RCC + CYGHWR_HAL_STM32_RCC_CFGR, reg_data); +#if (PLL_FREQ == 72000000) + reg_data &= ~((cyg_uint32) CYGHWR_HAL_STM32_RCC_CFGR_USBPRE); +#elif (PLL_FREQ == 48000000) + reg_data |= CYGHWR_HAL_STM32_RCC_CFGR_USBPRE; +#else +#error "SMT32 PLL clock must be set to 48MHz or 72MHz for correct USB operation." +#endif + HAL_WRITE_UINT32 (CYGHWR_HAL_STM32_RCC + CYGHWR_HAL_STM32_RCC_CFGR, reg_data); + + // Activate the USB clock after setting prescaler. + CYGHWR_HAL_STM32_CLOCK_ENABLE( CYGHWR_HAL_STM32_USB_CLOCK ); + + // Take USB transceiver out of powerdown state, but leave it in reset until we + // are ready to start. Leave interrupts disabled at source. + reg_data = CYGHWR_HAL_STM32_USB_CNTR_FRES; + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_CNTR, reg_data); + CYGACC_CALL_IF_DELAY_US (USB_TSTARTUP); + + // Initialise interrupt mask request counter. + interrupt_mask_count = 0; + + // Attach USB interrupts. Everything is done via the standard interrupt - the + // high priority interrupt is not used. + cyg_drv_interrupt_mask (CYGNUM_HAL_INTERRUPT_USB_HP); + cyg_drv_interrupt_create (CYGNUM_HAL_INTERRUPT_USB_LP, CYGNUM_DEVS_USB_CORTEXM_STM32_ISR_PRIORITY, + 0, stm32_usb_ISR, stm32_usb_DSR, &interrupt_handle, &interrupt_data); + cyg_drv_interrupt_attach (interrupt_handle); +} + +//----------------------------------------------------------------------------- +// Device endpoint 0 startup. This function is called once the application +// code has set up the desired USB control endpoint configuration. + +static void stm32_usb_start + (usbs_control_endpoint* endpoint) +{ + cyg_uint32 i; + txep_impl* txep = txep_list; + rxep_impl* rxep = rxep_list; + + // Fill in the generic endpoint data structures. + for (i = 0; i < CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM; i++) { + txep->common.start_tx_fn = stm32_usb_tx_start; + txep->common.set_halted_fn = stm32_usb_set_txep_halted; + txep->common.halted = true; + txep->txtr.state = TXTR_STATE_RESET; + txep->txtr.ep_num = i + 1; + txep++; + } + for (i = 0; i < CYGNUM_DEVS_USB_CORTEXM_STM32_RXEP_NUM; i++) { + rxep->common.start_rx_fn = stm32_usb_rx_start; + rxep->common.set_halted_fn = stm32_usb_set_rxep_halted; + rxep->common.halted = true; + rxep->rxtr.state = RXTR_STATE_RESET; + rxep->rxtr.ep_num = i + CYGNUM_DEVS_USB_CORTEXM_STM32_TXEP_NUM + 1; + rxep++; + } + + // Take the USB driver out of reset and cancel any spurious interrupts. + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_CNTR, 0); + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_ISTR, 0); + + // Enable interrupts at source. + HAL_WRITE_UINT32 (USB_BASE + CYGHWR_HAL_STM32_USB_CNTR, + CYGHWR_HAL_STM32_USB_CNTR_CTRM | CYGHWR_HAL_STM32_USB_CNTR_RESETM); + + // Reconnect the device to the USB bus if required. +#ifdef CYGHWR_DEVS_USB_CORTEXM_STM32_DISC_PIN_ACT_LOW + CYGHWR_HAL_STM32_GPIO_OUT (USB_DISC_PIN, 1); +#else + CYGHWR_HAL_STM32_GPIO_OUT (USB_DISC_PIN, 0); +#endif + stm32_usb_ctrl_update_state (USBS_STATE_POWERED, USBS_STATE_CHANGE_POWERED); + + // Allow interrupts to run - the bus reset must be driven by the host. + cyg_drv_interrupt_unmask (CYGNUM_HAL_INTERRUPT_USB_LP); +} + +//----------------------------------------------------------------------------- +// Device endpoint 0 poll. Polled operation just calls the ISR followed by +// the DSR. TODO: Polled operation needs further testing. + +static void stm32_usb_poll + (usbs_control_endpoint* endpoint) +{ + cyg_uint32 isr_retval; + isr_retval = stm32_usb_ISR (CYGNUM_HAL_INTERRUPT_USB_LP, 0); + if (isr_retval & CYG_ISR_CALL_DSR) + stm32_usb_DSR (CYGNUM_HAL_INTERRUPT_USB_LP, 1, 0); +} + +//----------------------------------------------------------------------------- +// Get a handle on the specified transmit (in) endpoint. + +static usbs_tx_endpoint* stm32_usb_get_txep + (usbs_control_endpoint* control_endpoint, cyg_uint8 ep_id) +{ + txep_impl* txep = NULL; + + // Map from endpoint ID to physical endpoint. + if (ep_id > 0 && ep_id < 16) + txep = txep_map [ep_id - 1]; + + // Return endpoint handle or null pointer for invalid endpoint. + if (txep == NULL) { + FAIL_USB ("Invalid endpoint ID when accessing transmit (in) endpoint."); + return NULL; + } + return (usbs_tx_endpoint*) txep; +} + +//----------------------------------------------------------------------------- +// Get a handle on the specified receive (out) endpoint. + +static usbs_rx_endpoint* stm32_usb_get_rxep + (usbs_control_endpoint* control_endpoint, cyg_uint8 ep_id) +{ + rxep_impl* rxep = NULL; + + // Map from endpoint ID to physical endpoint. + if (ep_id > 0 && ep_id < 16) + rxep = rxep_map [ep_id - 1]; + + // Return endpoint handle or null pointer for invalid endpoint. + if (rxep == NULL) { + FAIL_USB ("Invalid endpoint ID when accessing receive (out) endpoint."); + return NULL; + } + return (usbs_rx_endpoint*) rxep; +} + +//============================================================================= +// Instantiate the test endpoint data structures if required. This creates a +// single endpoint of each supported type - bulk transmit, bulk receive, +// interrupt transmit and interrupt receive. +//============================================================================= + +#ifdef CYGBLD_IO_USB_SLAVE_USBTEST + +usbs_testing_endpoint usbs_testing_endpoints[] = { + + { // Control endpoint. + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, + endpoint_number : 0, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ctrlep, + devtab_entry : 0, + min_size : 1, + max_size : CYGNUM_DEVS_USB_CORTEXM_STM32_EPO_MAX_MSG_SIZE, + max_in_padding : 0, + alignment : 0 + }, + + { // Bulk transmit (input) endpoint. + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 1, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &txep_list[0], + devtab_entry : 0, + min_size : 0, + max_size : 0x1000, // 4k max for testing. + max_in_padding : 0, + alignment : 0 + }, + + { // Bulk receive (output) endpoint. + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 2, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &rxep_list[0], + devtab_entry : 0, + min_size : 0, + max_size : 0x1000, // 4k max for testing. + max_in_padding : 0, + alignment : 0 + }, + + { // Interrupt transmit (input) endpoint + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT, + endpoint_number : 3, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &txep_list[1], + devtab_entry : 0, + min_size : 0, + max_size : 8, // Maximum for low speed devices. + max_in_padding : 0, + alignment : 0 + }, + + { // Interrupt receive (output) endpoint. + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT, + endpoint_number : 4, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &rxep_list[1], + devtab_entry : 0, + min_size : 0, + max_size : 8, // Maximum for low speed devices. + max_in_padding : 0, + alignment : 0 + }, + + USBS_TESTING_ENDPOINTS_TERMINATOR +}; + +#endif + +//============================================================================= diff --git a/ecos/packages/devs/usb/d12/current/ChangeLog b/ecos/packages/devs/usb/d12/current/ChangeLog new file mode 100644 index 0000000..aeeca78 --- /dev/null +++ b/ecos/packages/devs/usb/d12/current/ChangeLog @@ -0,0 +1,27 @@ +2006-06-06 Frank Pagliughi <fpagliughi@mindspring.com> + + * First version of the USB device driver for the philips D12 + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2006 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/d12/current/cdl/usbs_d12.cdl b/ecos/packages/devs/usb/d12/current/cdl/usbs_d12.cdl new file mode 100644 index 0000000..2065b12 --- /dev/null +++ b/ecos/packages/devs/usb/d12/current/cdl/usbs_d12.cdl @@ -0,0 +1,318 @@ +# ==================================================================== +# +# usbs_d12.cdl +# +# USB device driver for the Philips PDIUSBD12 Full Speed USB +# peripheral chip. +# +# ==================================================================== +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 2003, 2004, 2006 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +# ==================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): Frank M. Pagliughi (fmp), SoRo Systems, Inc. +# Contributors: +# Date: 2004-05-24 +# +#####DESCRIPTIONEND#### +# ==================================================================== + +cdl_package CYGPKG_DEVS_USB_D12 { + display "Philips D12 USB Device Driver" + include_dir "cyg/io/usb" + parent CYGPKG_USB + implements CYGHWR_IO_USB_SLAVE + doc ref/devs-usb-philips-pdiusbd12.html + + description " + The Philips PDIUSBD12 is a USB peripheral controller (slave) + chip that can connect to a microcontroller or microprocessor + through an 8-bit parallel bus. The SoRo Systems USB-D12-104 is + a slave board for the PC's ISA or PC/104 bus that contains a + D12 chip placed in the PC's I/O space with jumpered selections + for IRQ and DMA settings. This package provides an eCos device + driver." + + requires CYGIMP_DEVS_USB_D12_HW_ACCESS_HEADER + + cdl_option CYGIMP_DEVS_USB_D12_HW_ACCESS_HEADER { + display "Inline file implementing hardware access" + flavor booldata + default_value false + description " + This option should contain the header file which + implements basic access to the D12 registers" + } + + cdl_component CYGFUN_DEVS_USB_D12_EP0 { + display "Support the Control Endpoint 0" + default_value CYGINT_IO_USB_SLAVE_CLIENTS + requires CYGPKG_IO_USB CYGPKG_IO_USB_SLAVE + compile usbs_d12.c + compile -library=libextras.a usbs_d12_data.cxx + description " + Enable support for endpoint 0. If this support is disabled + then the entire USB port is unusable." + + cdl_option CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 0" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 0 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and ioctl calls then a devtab entry is needed. + " + } + cdl_option CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE { + display "Size of statically-allocated endpoint 0 transmit buffer" + flavor data + default_value 256 + requires { CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE >= + CYGNUM_DEVS_USB_D12_EP0_PKTSIZE } + description " + The implementation of the support for endpoint 0 uses + a single static buffer to hold the response to the + current control message. Typically this buffer can be + fairly small since replies to control messages tend to + be small: typically some tens of bytes for the enumeration + data, perhaps a bit more for unicode-encoded string + descriptors. However if some application-specific protocol + depends on larger control messages then this buffer + size may need to be increased." + } + } + + cdl_option CYGNUM_DEVS_USB_D12_BASEADDR { + display "Base Address of D12 chip" + flavor data + active_if CYGFUN_DEVS_USB_D12_EP0 + description " + The base memory or I/O address where the USB chip resides. + The value is set by the hardware specific driver's CDL." + } + + cdl_option CYGNUM_DEVS_USB_D12_IRQ { + display "IRQ for the D12 chip" + active_if CYGFUN_DEVS_USB_D12_EP0 + flavor data + description " + The IRQ assigned to the D12 chip. The value + is set by the hardware specific drivers's CDL." + } + + cdl_option CYGNUM_DEVS_USB_D12_INT { + display "INT for the D12 chip" + active_if CYGFUN_DEVS_USB_D12_EP0 + flavor data + default_value { CYGNUM_DEVS_USB_D12_IRQ + 32 } + description " + The interrupt vector assigned to the D12 chip." + } + + cdl_component CYGPKG_DEVS_USB_D12_THREAD { + display "Use a thread to service D12 chip" + active_if CYGFUN_DEVS_USB_D12_EP0 + default_value 0 + description " + Services the D12 USB chip with a thread, rather than at + the DSR level. This allows for increased debug support, + like TRACE output from the driver at the expense of some + throughput & reaction time. The service thread MUST be at + a higher priority than any application thread that uses + the USB port. " + + cdl_option CYGNUM_DEVS_USB_D12_THREAD_PRIORITY { + display "Thread Priority" + flavor data + legal_values 1 to 30 + default_value 4 + description " + The priority of the D12 device driver thread." + } + + cdl_option CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE { + display "USB Thread Stack Size" + flavor data + default_value 4096 + description " + The stack size for the D12 device driver thread." + } + } + + cdl_component CYGFUN_DEVS_USB_D12_DEBUG { + display "Debug output from the D12 Device Driver" + requires CYGPKG_DEVS_USB_D12_THREAD + default_value 0 + description " + Provide debugging output from the D12 Device Driver" + + cdl_option CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS { + display "Dump the contents of EP0 buffers" + flavor bool + default_value 0 + description " + + Dump the contents of the packages going through + EP0. This allows you to see things like device + requests and responses." + } + + cdl_option CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS { + display "Dump the contents of data buffers" + flavor bool + default_value 0 + description " + Dump the contents of the packages going through the generic + endpoints. This allow you to see all of the data going through + the device." + } + } + + cdl_component CYGPKG_DEVS_USB_D12_TX_EP1 { + display "Endpoint 1 Interrupt IN, (tx_ep1)" + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + requires CYGFUN_DEVS_USB_D12_EP0 + default_value CYGFUN_DEVS_USB_D12_EP0 + description " + On the D12, Endpoint 1 IN can be used for Interrupt, + Bulk, or Control packages. This driver currently only supports + Interrupt packages on Endpoint 1 (slave -> host) transfers." + + cdl_option CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY { + display "Provide a devtab entry for Endpoint 1 IN" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If Endpoint 1 IN will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed." + } + } + + cdl_component CYGPKG_DEVS_USB_D12_RX_EP1 { + display "Endpoint 1 Interrupt OUT, (rx_ep1)" + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + requires CYGFUN_DEVS_USB_D12_EP0 + default_value CYGFUN_DEVS_USB_D12_EP0 + description " + In the D12, Endpoint 1 OUT can be used for Interrupt, + Bulk, or Control packages. This driver currently only supports + Interrupt packages on Endpoint 1 for (host -> slave) transfers" + + cdl_option CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY { + display "Provide a devtab entry for Endpoint 1 OUT" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If Endpoint 1 OUT will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed." + } + } + + cdl_component CYGPKG_DEVS_USB_D12_TX_EP2 { + display "Endpoint 2 Bulk IN, (tx_ep2)" + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + requires CYGFUN_DEVS_USB_D12_EP0 + default_value CYGFUN_DEVS_USB_D12_EP0 + description " + In the D12, Endpoint 2 IN can be used for Bulk, Interrupt, + or Control packages. This driver currently only supports + Bulk packages on Endpoint 2 for (slave -> host) transfers." + + cdl_option CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY { + display "Provide a devtab entry for Endpoint 2 IN" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If Endpoint 2 IN will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed." + } + } + + cdl_component CYGPKG_DEVS_USB_D12_RX_EP2 { + display "Endpoint 2 Bulk OUT, (rx_ep2)" + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + requires CYGFUN_DEVS_USB_D12_EP0 + default_value CYGFUN_DEVS_USB_D12_EP0 + description " + In the D12, Endpoint 2 OUT can be used for Bulk, Interrupt, + Control packages. This driver currently only supports + Bulk packages on Endpoint 2 for (host -> slave) transfers." + + cdl_option CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY { + display "Provide a devtab entry for Endpoint 2 OUT" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If Endpoint 2 OUT will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed." + } + } + + cdl_option CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME { + display "Base name for devtab entries" + flavor data + active_if { CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY } + default_value { "\"/dev/usbs\"" } + description " + If the D12 USB device driver package provides devtab entries + for any of the endpoints then this option gives + control over the names of these entries. By default the + endpoints will be called \"/dev/usbs0c\", \"/dev/usbs1r\" + \"/dev/usbs1w\", \"/dev/usbs2r\", \"/dev/usbs2w\" + (assuming those endpoints are all enabled. The common + part \"/dev/usbs\" is determined by this configuration + option. It may be necessary to change this if there are + multiple USB slave-side devices on the target hardware to + prevent a name clash." + } +} + diff --git a/ecos/packages/devs/usb/d12/current/include/usbs_d12.h b/ecos/packages/devs/usb/d12/current/include/usbs_d12.h new file mode 100644 index 0000000..3758450 --- /dev/null +++ b/ecos/packages/devs/usb/d12/current/include/usbs_d12.h @@ -0,0 +1,74 @@ +#ifndef CYGONCE_USBS_D12_H +#define CYGONCE_USBS_D12_H +//========================================================================== +// +// include/usbs_d12.h +// +// The interface exported by the D12 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Frank Pagliughi (fmp) +// Contributors: fmp +// Date: 2004-05-24 +// Purpose: +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/usb/usbs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The Philips D12 is a full speed (12Mbps) USB peripheral controller + * chip, with a parallel interface allowing it to be connected to nearly + * any microcontroller or microprocessor. + */ +extern usbs_control_endpoint usbs_d12_ep0; + +extern usbs_rx_endpoint usbs_d12_rx_ep1; +extern usbs_tx_endpoint usbs_d12_tx_ep1; +extern usbs_rx_endpoint usbs_d12_rx_ep2; +extern usbs_tx_endpoint usbs_d12_tx_ep2; + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* CYGONCE_USBS_D12_H */ diff --git a/ecos/packages/devs/usb/d12/current/src/usbs_d12.c b/ecos/packages/devs/usb/d12/current/src/usbs_d12.c new file mode 100644 index 0000000..f353cd9 --- /dev/null +++ b/ecos/packages/devs/usb/d12/current/src/usbs_d12.c @@ -0,0 +1,2323 @@ +//========================================================================== +// +// usbs_d12.c +// +// Driver for the D12 USB Slave Board +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Frank M. Pagliughi (fmp) +// Date: 2004-05-22 +// +// This code is a device driver for the SoRo Systems USB-D12-104, a PC/104 +// (ISA) Full-Speed USB slave board, which turns a PC/104 stack into a USB +// slave device. The board contains a Philips PDIUSBD12 Peripheral Controller +// Chip mapped into the PC's I/O space, with jumper-selectable I/O base +// address, IRQ, and DMA settings. The eCos config tool is used to adjust +// settings for this driver to match the physical jumper settings. The chip +// could run in polled mode without an IRQ, but this wouldn't be a great idea +// other than maybe a debug environment. +// +// The board supports DMA transfers over the Main endpoint, but I temporarily +// removed that code to make the driver portable to other platforms. +// +// *** This driver should also work with the Philips ISA Eval Board +// for the D12, but I couldn't get one of them from Philips, so +// I couldn't test it. +// +// The D12 uses an indexed register set, which it describes as "commands." +// You first write a command (index) to the command register then you can +// read or write data to that register. Each multi-byte command read or write +// must be dione atomically, so all access to the chip must be serialized. +// +// The D12 requests service through a single interrupt. The driver can +// be configured to service the chip through a DSR or a thread. In either +// case, the "service" code assumes it has unfettered access to the chip. +// The interrupt, therefore never touches the chip. It just schedules the +// DSR or service thread. +// Currently, the code gets exclusive access to the chip by locking the +// scheduler. This is suboptimal (locking the whole OS to touch one I/O +// chip), and better method should be explored. +// +// This version of the driver does not support Isocronous transfers. +// +// Additional notes on the D12: +// +// - The D12 has 4 endpoints (2 IN, and 2 OUT) in addition to the main +// control endpoint: +// - Endp 0 (Control In & Out, 16 byte buffer) +// - Endp 1 (IN & OUT, Bulk or Interrupt, 16 byte ea) +// - Endp 2 (IN and/or OUT, Bulk, Interrupt, or Isoc, 64 bytes ea) +// +// - The "main" endpoint (as Philips calls it) is Endp 2. It's double +// buffered and has a DMA interface, and thus, is suited for high +// throughput. For applications that perform either Isoc In or Out, +// the buffers for Endp 2 can be combined for a 128 byte space. +// This driver, however, currently does not support this. +// +// - There may be a flaw in the double buffering of the rx main endpoint. +// According to the documentation it should be invisible to the software, +// but if both buffers fill (on an rx/OUT), they must both be read +// together, otherwise it appears that the buffers/packets are returned +// in reverse order. ReadMainEndpointBuf() returns the data properly. +// +// - All the interrupt sources on the chip - individual endpoints, bus reset, +// suspend, and DMA - are OR'ed together and can be checked via the +// interrupt status register. When using edge-sensitive interrupts, as +// we do here, the ISR/DSR must be sure all interrupts are cleared before +// returning otherwise no new interrupts will be latched. +// +// - If the DMA controller is not used for the Main Endpoint, you MUST enable +// the main endpoint interrupts in the DMA register (bits 6 & 7). +// Personally, I think this should be the default at reset, to make it +// compatible with the other endpoints, but Philips didn't see it that +// way. +// +// - When a Setup (Device Request) packet arrives in the control endpoint, a +// bit is set in the endpoint's status register indicating the packet is +// setup and not data. By the USB standard, a setup packet can not be +// NAK'ed or STALL'ed, so when the chip receives a setup packet, it +// flushes the Ctrl (EP0) IN buffer and disables the Validate and Clear +// Buffer commands. We must send an "acknowledge setup" to both +// EP0 IN and OUT before a Validate or Clear Buffer command is effective. +// See ReadSetupPacket(). +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/infra/cyg_type.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/cyg_trac.h> +#include <cyg/infra/diag.h> + +#include <pkgconf/devs_usb_d12.h> + +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_cache.h> +#include <cyg/error/codes.h> + +#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> + +#include <string.h> + +// -------------------------------------------------------------------------- +// Common Types +// -------------------------------------------------------------------------- + +typedef cyg_uint8 byte; +typedef cyg_uint8 uint8; +typedef cyg_int16 int16; +typedef cyg_uint16 uint16; +typedef cyg_int32 int32; +typedef cyg_uint32 uint32; + +// -------------------------------------------------------------------------- +// Tracing & Debug +// -------------------------------------------------------------------------- +// If the driver is configured to use a thread to service the chip, then it +// can also be configured to dump a lot of debug output. +// Care must be taken that USB timing requirements are not violated by +// dumping debug info. If the data is sent to a serial port, it should use +// a hardware driver and have a large output buffer (115200 baud & 2kB +// buffer works for me). + +#if defined(CYGFUN_DEVS_USB_D12_DEBUG) && CYGFUN_DEVS_USB_D12_DEBUG +#define TRACE_D12 diag_printf +#else +#define TRACE_D12 (1) ? (void)0 : diag_printf +#endif + +#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS +#define TRACE_EP0 1 +#endif + +#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS +#define TRACE_EP 1 +#endif + +#if defined(TRACE_EP0) || defined(TRACE_EP) +static void _trace_buf(const char *hdr, const byte* buf, unsigned n) +{ + unsigned i; + + if (buf != 0 && n != 0) { + if (hdr && hdr[0]) + TRACE_D12("%s ", hdr); + + TRACE_D12("["); + for (i=0; i<n; i++) + TRACE_D12(" x%02X", buf[i]); + TRACE_D12("]\n"); + } +} +#endif + +#if defined(TRACE_EP0) +#define TRACE_BUF0 _trace_buf +#else +#define TRACE_BUF0(hdr, buf, n) +#endif + +#if defined(TRACE_EP) +#define TRACE_BUF _trace_buf +#else +#define TRACE_BUF(hdr, buf, n) +#endif + +// ========================================================================== +// Chip Wrapper +// ========================================================================== + +// This section contains functions that wrapper the low-level access to the +// chip. There's a function around each register access on the chip, and then +// some. + +#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED) +typedef void* d12_addr_type; +#else +typedef byte* d12_addr_type; +#endif + +#define D12_BASE_ADDR ((d12_addr_type) CYGNUM_DEVS_USB_D12_BASEADDR) + +#define D12_ENDP0_SIZE 16 // Size of Ctrl Endp +#define D12_MAIN_ENDP 2 // The D12's "Main" Endp is special, double buffered +#define D12_MAIN_ENDP_SIZE 64 // Size of each main endp buffer +#define D12_MAX_PACKET_SIZE 128 // Max packet is actually double main endp + +#define D12_CHIP_ID 0x1012 // Value that's returned by a read of + //the D12's Chip ID register + +// ----- Endpoint Indices ----- + +enum { + D12_ENDP_INVALID = 0xFF, + D12_ENDP_MIN = 0, + + D12_RX_CTRL_ENDP = D12_ENDP_MIN, // Rx/Tx Nomenclature + D12_TX_CTRL_ENDP, + + D12_RX_ENDP0 = D12_ENDP_MIN, + D12_TX_ENDP0, + D12_RX_ENDP1, + D12_TX_ENDP1, + D12_RX_ENDP2, + D12_TX_ENDP2, + D12_RX_MAIN_ENDP = D12_RX_ENDP2, + D12_TX_MAIN_ENDP = D12_TX_ENDP2, + + + D12_CTRL_ENDP_OUT = D12_ENDP_MIN, // IN/OUT Nomenclature + D12_CTRL_ENDP_IN, + + D12_ENDP0_OUT = D12_ENDP_MIN, + D12_ENDP0_IN, + D12_ENDP1_OUT, + D12_ENDP1_IN, + D12_ENDP2_OUT, + D12_ENDP2_IN, + D12_MAIN_ENDP_OUT = D12_ENDP2_OUT, + D12_MAIN_ENDP_IN = D12_ENDP2_IN, + + D12_ENDP_INSERT_BEFORE, + D12_ENDP_MAX = D12_ENDP_INSERT_BEFORE-1 +}; + +// ----- Set Mode Reg configuration byte ----- + +enum { + D12_MODE_CFG_NO_LAZYCLOCK = 0x02, + D12_MODE_CFG_CLOCK_RUNNING = 0x04, + D12_MODE_CFG_INTERRUPT = 0x08, + D12_MODE_CFG_SOFT_CONNECT = 0x10, + + D12_MODE_CFG_NON_ISO = 0x00, + D12_MODE_CFG_ISO_OUT = 0x40, + D12_MODE_CFG_ISO_IN = 0x80, + D12_MODE_CFG_ISO_IO = 0xC0, + + D12_MODE_CFG_DFLT = (D12_MODE_CFG_NO_LAZYCLOCK | + D12_MODE_CFG_CLOCK_RUNNING | + D12_MODE_CFG_NON_ISO) +}; + +// ----- Set Mode Reg clock div factor ----- + +enum { + D12_MODE_CLK_24_MHZ = 1, + D12_MODE_CLK_16_MHZ = 2, + D12_MODE_CLK_12_MHZ = 3, + D12_MODE_CLK_8_MHZ = 5, + D12_MODE_CLK_6_MHZ = 7, + D12_MODE_CLK_4_MHZ = 11, + + D12_MODE_CLK_DIV_MASK = 0x0F, + + D12_MODE_CLK_SET_TO_ONE = 0x40, + D12_MODE_CLK_SOF_ONLY_INTR = 0x80, + + D12_MODE_CLK_DFLT = (D12_MODE_CLK_4_MHZ | + D12_MODE_CLK_SET_TO_ONE) +}; + +// ----- Set DMA Register ----- + +enum { + D12_DMA_SINGLE_CYCLE, + D12_DMA_BURST_4_CYCLE, + D12_DMA_BURST_8_CYCLE, + D12_DMA_BURST_16_CYCLE, + + D12_DMA_ENABLE = 0x04, + D12_DMA_DIR_WRITE = 0x08, + D12_DMA_DIR_READ = 0x00, + D12_DMA_AUTO_RELOAD = 0x10, + D12_DMA_INTR_PIN_MODE = 0x20, + + D12_DMA_MAIN_ENDP_OUT_INTR_ENABLE = 0x40, + D12_DMA_MAIN_RX_ENDP_INTR_ENABLE = 0x40, + + D12_DMA_MAIN_ENDP_IN_INTR_ENABLE = 0x80, + D12_DMA_MAIN_TX_ENDP_INTR_ENABLE = 0x80, + + D12_DMA_MAIN_ENDP_INTR_ENABLE = 0xC0 // Enables IN & OUT Intr +}; + +// ----- Interrupt Register Bits ----- + +enum { + D12_INTR_RX_CTRL_ENDP = 0x0001, + D12_INTR_TX_CTRL_ENDP = 0x0002, + + D12_INTR_RX_ENDP0 = D12_INTR_RX_CTRL_ENDP, + D12_INTR_TX_ENDP0 = D12_INTR_TX_CTRL_ENDP, + D12_INTR_RX_ENDP1 = 0x0004, + D12_INTR_TX_ENDP1 = 0x0008, + D12_INTR_RX_ENDP2 = 0x0010, + D12_INTR_TX_ENDP2 = 0x0020, + + D12_INTR_BUS_RESET = 0x0040, + D12_INTR_SUSPEND_CHANGE = 0x0080, + D12_INTR_DMA_EOT = 0x0100 +}; + +// ----- Read Endpoint Status ----- + +enum { + D12_ENDP_STAT_SETUP_PACKET = 0x04, + D12_ENDP_STAT_BUF0_FULL = 0x20, + D12_ENDP_STAT_BUF1_FULL = 0x40, + D12_ENDP_STAT_ANY_BUF_FULL = 0x60, + D12_ENDP_STAT_BOTH_BUF_FULL = 0x60, + D12_ENDP_STAT_STALL = 0x80, +}; + +// ----- Last Transaction Status Bits ----- + +enum { + D12_LAST_TRANS_DATA_SUCCESS = 0x01, + D12_LAST_TRANS_ERR_CODE_MASK = 0x1E, + D12_LAST_TRANS_SETUP_PACKET = 0x20, + D12_LAST_TRANS_DATA1_PACKET = 0x40, + D12_LAST_TRANS_PREV_STAT_NOT_READ = 0x80 +}; + +static const byte RX_ENDP_INDEX[] = + { D12_RX_ENDP0, D12_RX_ENDP1, D12_RX_ENDP2 }; +static const byte TX_ENDP_INDEX[] = + { D12_TX_ENDP0, D12_TX_ENDP1, D12_TX_ENDP2 }; + +static const int RX_ENDP_SIZE[] = { 16, 16, 64 }; +static const int TX_ENDP_SIZE[] = { 16, 16, 64 }; + +typedef void (*completion_fn)(void*, int); + +#ifndef USB_SETUP_PACKET_LEN +#define USB_SETUP_PACKET_LEN 8 +#endif + +// ----- Command Definitions ----- + +enum { + CMD_SET_ADDR_EN = 0xD0, // Write 1 byte + CMD_SET_ENDP_EN = 0xD8, // Write 1 byte + CMD_SET_MODE = 0xF3, // Write 2 bytes + CMD_SET_DMA = 0xFB, // Write/Read 1 byte + CMD_READ_INTR_REG = 0xF4, // Read 2 bytes + CMD_SEL_ENDP = 0x00, // (+ Endp Index) Read 1 byte (opt) + CMD_READ_LAST_TRANS_STAT = 0x40, // (+ Endp Index) Read 1 byte (opt) + CMD_READ_ENDP_STAT = 0x80, // (+ Endp Index) Read 1 byte + CMD_READ_BUF = 0xF0, // Read n bytes + CMD_WRITE_BUF = 0xF0, // Write n bytes + CMD_SET_ENDP_STAT = 0x40, // (+ Endp Index) Write 1 byte + CMD_ACK_SETUP = 0xF1, // None + CMD_CLEAR_BUF = 0xF2, // None + CMD_VALIDATE_BUF = 0xFA, // None + CMD_SEND_RESUME = 0xF6, // None + CMD_READ_CURR_FRAME_NUM = 0xF5, // Read 1 or 2 bytes + CMD_READ_CHIP_ID = 0xFD // Read 2 bytes +}; + +// ----- Set Endpoint Enable Register ----- + +enum { + ENDP_DISABLE, + ENDP_ENABLE +}; + +// ----- Select Endpoint Results ----- + +enum { + SEL_ENDP_FULL = 0x01, + SEL_ENDP_STALL = 0x02 +}; + +// ----- Error Codes from ReadLastTrans (need to be bit shifter) ----- + +enum { + ERROR_NO_ERROR, + ERROR_PID_ENCODING, + ERROR_PID_UNKNOWN, + ERROR_UNEXPECTED_PACKET, + ERROR_TOKEN_CRC, + ERROR_DATA_CRC, + ERROR_TIMEOUT, + ERROR_BABBLE, + ERROR_UNEXPECTED_EOP, + ERROR_NAK, + ERROR_PACKET_ON_STALL, + ERROR_OVERFLOW, + ERROR_BITSTUFF, + ERROR_WRONG_DATA_PID +}; + +// ------------------------------------------------------------------------ +// Routines to access the D12 registers. The hardware specific driver +// provides 8bit access functions and block access functions. + +#include CYGIMP_DEVS_USB_D12_HW_ACCESS_HEADER + + +static inline uint16 +make_word(byte hi, byte lo) +{ + return ((uint16) hi << 8) | lo; +} + +// These routines read or write 16 bit values to the data area. + +static inline uint16 +d12_read_data_word(d12_addr_type base_addr) +{ + uint16 val = d12_read_data_byte(base_addr); + val |= ((uint16) d12_read_data_byte(base_addr)) << 8; + return val; +} + +static inline void +d12_write_data_word(d12_addr_type base_addr, uint16 val) +{ + d12_write_data_byte(base_addr, (byte) val); + d12_write_data_byte(base_addr, (byte) (val >> 8)); +} + +// ------------------------------------------------------------------------ +// Command & Data I/O +// ------------------------------------------------------------------------ +// +// These routines read & write the registers in the D12. The procedure is +// to write a register/command value to the command address (A0=1) then +// read or write any required data a byte at a time to the data address +// (A0=0). The data can be one byte or two. If two, the low byte is read/ +// written first. + +// NOTE: These MUST be atomic operations. It's up to the caller +// to insure this. + +// The hardware specific driver provides the basic access function. +// + +static inline void +d12_write_byte(d12_addr_type base_addr, byte cmd, byte val) +{ + d12_write_cmd(base_addr, cmd); + d12_write_data_byte(base_addr, val); +} + +static inline void +d12_write_word(d12_addr_type base_addr, byte cmd, uint16 val) +{ + d12_write_cmd(base_addr, cmd); + d12_write_data_word(base_addr, val); +} + +static inline byte +d12_read_byte(d12_addr_type base_addr, byte cmd) +{ + d12_write_cmd(base_addr, cmd); + return d12_read_data_byte(base_addr); +} + +static inline uint16 +d12_read_word(d12_addr_type base_addr, byte cmd) +{ + d12_write_cmd(base_addr, cmd); + return d12_read_data_word(base_addr); +} + +// ------------------------------------------------------------------------ +// Higher Level Commands +// ------------------------------------------------------------------------ + +// Stalls or Unstalls the endpoint. Bit0=1 for stall, =0 to unstall. + +static inline void +d12_set_endp_status(d12_addr_type base_addr, byte endp_idx, byte stat) +{ + d12_write_byte(base_addr, CMD_SET_ENDP_STAT + endp_idx, stat); +} + +// ------------------------------------------------------------------------ +// Stalls the control endpoint (both in & out). + +static void +d12_stall_ctrl_endp(d12_addr_type base_addr, bool stall) +{ + d12_set_endp_status(base_addr, D12_TX_CTRL_ENDP, stall ? 1 : 0); + d12_set_endp_status(base_addr, D12_RX_CTRL_ENDP, stall ? 1 : 0); +} + +// ------------------------------------------------------------------------ +// Stalls/unstalls the specified endpoint. + +void inline +d12_stall_endp(d12_addr_type base_addr, byte endp_idx, bool stall) +{ + d12_set_endp_status(base_addr, endp_idx, stall ? 1 : 0); +} + +// ------------------------------------------------------------------------ */ +// Tells the chip that the selected endpoint buffer has been completely +// read. This should be called after the application reads all the data +// from an endpoint. While there's data in the buffer the chip will +// automatically NAK any additional OUT packets from the host. + +static inline void +d12_clear_buffer(d12_addr_type base_addr) +{ + d12_write_cmd(base_addr, CMD_CLEAR_BUF); +} + +// ------------------------------------------------------------------------ +// Tells the chip that the data in the selected endpoint buffer is complete +// and ready to be sent to the host. + +static inline void +d12_validate_buffer(d12_addr_type base_addr) +{ + d12_write_cmd(base_addr, CMD_VALIDATE_BUF); +} + +// ------------------------------------------------------------------------ +// Sends an upstream resume signal for 10ms. This command is normally +// issued when the device is in suspend. + +static inline void +d12_send_resume(d12_addr_type base_addr) +{ + d12_write_cmd(base_addr, CMD_SEND_RESUME); +} + +// ------------------------------------------------------------------------ +// Gets the frame number of the last successfully received +// start-of-frame (SOF). + +static inline uint16 +d12_read_curr_frame_num(d12_addr_type base_addr) +{ + return d12_read_word(base_addr, CMD_READ_CURR_FRAME_NUM); +} + +// ------------------------------------------------------------------------ +// This routine acknowledges a setup packet by writing an Ack Setup command +// to the currently selected Endpoint. This must be done for both EP0 out +// and EP0 IN whenever a setup packet is received. + +static inline void +d12_ack_setup(d12_addr_type base_addr) +{ + d12_write_cmd(base_addr, CMD_ACK_SETUP); +} + +// ------------------------------------------------------------------------ +// Gets the value of the 16-bit interrupt register, which indicates the +// source of an interrupt (if interrupts are not used, this reg can be +// polled to find when service is required). + +static inline uint16 +d12_read_intr_reg(d12_addr_type base_addr) +{ + return d12_read_word(base_addr, CMD_READ_INTR_REG) & 0x01FF; +} + +// ------------------------------------------------------------------------ +// Gets/Sets the contents of the DMA register. + +static inline byte +d12_get_dma(d12_addr_type base_addr) +{ + return d12_read_byte(base_addr, CMD_SET_DMA); +} + +static inline void +d12_set_dma(d12_addr_type base_addr, byte mode) +{ + d12_write_byte(base_addr, CMD_SET_DMA, mode); +} + +// ------------------------------------------------------------------------ +// Sends the "Select Endpoint" command (0x00 - 0x0D) to the chip. +// This command initializes an internal pointer to the start of the +// selected buffer. +// +// Returns: Bitfield containing status of the endpoint + +static byte +d12_select_endp(d12_addr_type base_addr, byte endp_idx) +{ + return d12_read_byte(base_addr, CMD_SEL_ENDP + endp_idx); +} + +// ------------------------------------------------------------------------ +// Gets the status of the last transaction of the endpoint. It also resets +// the corresponding interrupt flag in the interrupt register, and clears +// the status, indicating that it was read. +// +// Returns: Bitfield containing the last transaction status. + +static inline byte +d12_read_last_trans_status(d12_addr_type base_addr, byte endp_idx) +{ + return d12_read_byte(base_addr, CMD_READ_LAST_TRANS_STAT + endp_idx); +} + +// ------------------------------------------------------------------------ +// Reads the status of the requested endpoint. +// Just for the heck of it, we mask off the reserved bits. +// +// Returns: Bitfield containing the endpoint status. + +static inline byte +d12_read_endp_status(d12_addr_type base_addr, byte endp_idx) +{ + return d12_read_byte(base_addr, CMD_READ_ENDP_STAT + endp_idx) & 0xE4; +} + +// ------------------------------------------------------------------------ +// Returns true if there is data available in the specified endpoint's +// ram buffer. This is determined by the buf full flags in the endp status +// register. + +static inline bool +d12_data_available(d12_addr_type base_addr, byte endp_idx) +{ + byte by = d12_read_endp_status(base_addr, endp_idx); + return (bool) (by & D12_ENDP_STAT_ANY_BUF_FULL); +} + +// ------------------------------------------------------------------------ +// Clears the transaction status for each of the endpoints by calling the +// d12_read_last_trans_status() function for each. + +static void +d12_clear_all_intr(d12_addr_type base_addr) +{ + uint8 endp; + + d12_read_intr_reg(base_addr); + + for (endp=D12_ENDP_MIN; endp<=D12_ENDP_MAX; ++endp) + d12_read_last_trans_status(base_addr, endp); +} + +// ------------------------------------------------------------------------ +// Loads a value into the Set Address / Enable register. This sets the +// device's USB address (lower 7 bits) and enables/disables the function +// (msb). + +static void +d12_set_addr_enable(d12_addr_type base_addr, byte usb_addr, bool enable) +{ + if (enable) + usb_addr |= 0x80; + + d12_write_byte(base_addr, CMD_SET_ADDR_EN, usb_addr); +} + +// ------------------------------------------------------------------------ +// Enables/disables the generic endpoints. + +static inline void +d12_set_endp_enable(d12_addr_type base_addr, bool enable) +{ + d12_write_byte(base_addr, CMD_SET_ENDP_EN, + (enable) ? ENDP_ENABLE : ENDP_DISABLE); +} + +// ------------------------------------------------------------------------ +// Sets the device's configuration and CLKOUT frequency. + +static void +d12_set_mode(d12_addr_type base_addr, byte config, byte clk_div) +{ + uint16 w = make_word(clk_div, config); + d12_write_word(base_addr, CMD_SET_MODE, w); +} + +// ------------------------------------------------------------------------ +// Reads a setup packet from the control endpoint. This procedure is +// somewhat different than reading a data packet. By the USB standard, a +// setup packet can not be NAK'ed or STALL'ed, so when the chip receives a +// setup packet, it flushes the Ctrl (EP0) IN buffer and disables the +// Validate and Clear Buffer commands. The processor must send an +// acknowledge setup to both EP0 IN and OUT before a Validate or Clear +// Buffer command is effective. +// +// Parameters: +// buf buffer to receive the contents of the setup packet. Must +// be at least 8 bytes. +// Returns: +// true if there are 8 bytes waiting in the EP0 OUT RAM buffer +// on the D12 (i.e., true if successful) +// false otherwise + +static bool +d12_read_setup_packet(d12_addr_type base_addr, byte *buf) +{ + uint8 n; + + d12_select_endp(base_addr, D12_RX_CTRL_ENDP); + + d12_read_byte(base_addr, CMD_READ_BUF); // Read & discard reserved byte + n = d12_read_data_byte(base_addr); // # bytes available + + if (n > USB_SETUP_PACKET_LEN) { + //TRACE("* Warning: Setup Packet too large: %u *\n", (unsigned) n); + n = USB_SETUP_PACKET_LEN; + } + + n = d12_read_data(base_addr, buf, n); + + d12_ack_setup(base_addr); + d12_clear_buffer(base_addr); + + // ----- Ack Setup to EP0 IN ------ + + d12_select_endp(base_addr, D12_TX_CTRL_ENDP); + d12_ack_setup(base_addr); + + return n == USB_SETUP_PACKET_LEN; +} + +// ------------------------------------------------------------------------ +// Reads the contents of the currently selected endpoint's RAM buffer into +// the buf[] array. +// +// The D12's buffer comes in as follows: +// [0] junk ("reserved" - can be anything). Just disregard +// [1] # data bytes to follow +// [2] data byte 0, ... +// up to +// [N+2] data byte N-1 +// +// Parameters: +// buf byte array to receive data. This MUST be at least the size +// of the chip's RAM buffer for the currently selected endpoint. +// If buf is NULL, the data is read & discarded. +// +// Returns: the actual number of bytes read (could be <= n) + +static uint8 +d12_read_selected_endp_buf(d12_addr_type base_addr, byte *buf) +{ + uint8 n; + + d12_read_byte(base_addr, CMD_READ_BUF); // Read & discard reserved byte + n = d12_read_data_byte(base_addr); // # bytes in chip's buf + d12_read_data(base_addr, buf, n); + d12_clear_buffer(base_addr); + + return n; +} + +// ------------------------------------------------------------------------ +// Selects the specified endpoint and reads the contents of it's RAM buffer +// into the buf[] array. For the Main OUT endpoint, it will check whether +// both buffers are full, and if so, read them both. +// +// Side Effects: +// - Leaves endp_idx as the currently selected endpoint. +// +// Parameters: +// endp_idx the endpoint from which to read +// buf buffer to receive the data. This MUST be at least the size +// of the chip's RAM buffer for the specified endpoint. +// For the Main endp, it must be 2x the buffer size (128 total) +// +// Returns: the # of bytes read. + +static uint8 +d12_read_endp_buf(d12_addr_type base_addr, byte endp_idx, byte *buf) +{ + return (d12_select_endp(base_addr, endp_idx) & SEL_ENDP_FULL) + ? d12_read_selected_endp_buf(base_addr, buf) : 0; +} + +// ------------------------------------------------------------------------ +// Does a read of the "main" endpoint (#2). Since it's double buffered, +// this will check if both buffers are full, and if so it will read them +// both. Thus the caller's buffer, buf, must be large enough to hold all +// the data - 128 bytes total. +// +// If either buffer contains less than the full amount, the done flag +// is set indicating that a Bulk OUT transfer is complete. +// +// This determines if a bulk transfer is done, since the caller can't +// necessarily determine this from the size of the return buffer. +// If either buffer is less than full, '*done' is set to a non-zero value. + +static uint8 +d12_read_main_endp_buf(d12_addr_type base_addr, byte *buf, int *done) +{ + int nBuf = 1; + uint8 n = 0; + byte stat = d12_read_endp_status(base_addr, D12_RX_MAIN_ENDP) & + D12_ENDP_STAT_ANY_BUF_FULL; + + if (stat == 0) + return 0; + + if (stat == D12_ENDP_STAT_BOTH_BUF_FULL) + nBuf++; + + *done = false; + + while (nBuf--) { + if (d12_select_endp(base_addr, D12_RX_MAIN_ENDP) & SEL_ENDP_FULL) { + uint8 n1 = d12_read_selected_endp_buf(base_addr, buf+n); + n += n1; + if (n1 < D12_MAIN_ENDP_SIZE) { + *done = true; + break; + } + } + else + *done = true; + } + return n; +} + +// ------------------------------------------------------------------------ +// Writes the contents of the buf[] array to the currently selected +// endpoint's RAM buffer. The host will get the data on the on the next IN +// packet from the endpoint. +// +// Note: +// - The length of the buffer, n, must be no more than the size of the +// endpoint's RAM space, though, currently, this is not checked. +// - It's feasible that the application needs to send an empty (NULL) +// packet. It's valid for 'n' to be zero, and/or buf NULL. + +static uint8 +d12_write_selected_endp_buf(d12_addr_type base_addr, const byte *buf, uint8 n) +{ + d12_write_byte(base_addr, CMD_WRITE_BUF, 0); + d12_write_data_byte(base_addr, n); + d12_write_data(base_addr, buf, n); + d12_validate_buffer(base_addr); + + return n; +} + +// ------------------------------------------------------------------------ +// Writes the contents of the buf[] array to the specified endoint's RAM +// buffer. The host will get this data on the next IN packet from the +// endpoint. +// +// Side Effects: +// - Leaves endp_idx as the currently selected endpoint. + +static uint8 +d12_write_endp_buf(d12_addr_type base_addr, byte endp_idx, + const byte *buf, uint8 n) +{ + d12_select_endp(base_addr, endp_idx); + return d12_write_selected_endp_buf(base_addr, buf, n); +} + +// ------------------------------------------------------------------------ +// Reads & returns the contents of the Chip ID register. + +static inline uint16 +d12_read_chip_id(d12_addr_type base_addr) +{ + return d12_read_word(base_addr, CMD_READ_CHIP_ID); +} + + +// ========================================================================== +// eCos-Specific Device Driver Code +// ========================================================================== + +static void usbs_d12_reset(void); + +// Make some abbreviations for the configuration options. + +#if defined(CYGPKG_DEVS_USB_D12_RX_EP1) +#define _RX_EP1 +#endif + +#if defined(CYGPKG_DEVS_USB_D12_TX_EP1) +#define _TX_EP1 +#endif + +#if defined(CYGPKG_DEVS_USB_D12_RX_EP2) +#define _RX_EP2 +#endif + +#if defined(CYGPKG_DEVS_USB_D12_TX_EP2) +#define _TX_EP2 +#endif + +// -------------------------------------------------------------------------- +// Endpoint 0 Data +// -------------------------------------------------------------------------- + +static cyg_interrupt usbs_d12_intr_data; +static cyg_handle_t usbs_d12_intr_handle; + +static byte ep0_tx_buffer[CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE]; + +static void usbs_d12_start(usbs_control_endpoint*); +static void usbs_d12_poll(usbs_control_endpoint*); + +typedef enum endp_state { + ENDP_STATE_IDLE, + ENDP_STATE_IN, + ENDP_STATE_OUT +} endp_state; + +typedef struct ep0_impl { + usbs_control_endpoint common; + endp_state ep_state; + int length; + int transmitted; + bool tx_empty; +} ep0_impl; + +static ep0_impl ep0 = { + common: + { + state: USBS_STATE_POWERED, + enumeration_data: (usbs_enumeration_data*) 0, + start_fn: &usbs_d12_start, + poll_fn: &usbs_d12_poll, + interrupt_vector: CYGNUM_DEVS_USB_D12_IRQ, + control_buffer: { 0, 0, 0, 0, 0, 0, 0, 0 }, + state_change_fn: 0, + state_change_data: 0, + standard_control_fn: 0, + standard_control_data: 0, + class_control_fn: 0, + class_control_data: 0, + vendor_control_fn: 0, + vendor_control_data: 0, + reserved_control_fn: 0, + reserved_control_data: 0, + buffer: 0, + buffer_size: 0, + fill_buffer_fn: 0, + fill_data: 0, + fill_index: 0, + complete_fn: 0 + }, + ep_state: ENDP_STATE_IDLE, + length: 0, + transmitted: 0, + tx_empty: 0 +}; + +extern usbs_control_endpoint usbs_d12_ep0 __attribute__((alias ("ep0"))); + +// -------------------------------------------------------------------------- +// Rx Endpoints 1 & 2 Data +// -------------------------------------------------------------------------- + +#if defined(_RX_EP1) || defined(_RX_EP2) + +typedef struct rx_endpoint { + usbs_rx_endpoint common; + int endp, received; +} rx_endpoint; + +static void usbs_d12_api_start_rx_ep(usbs_rx_endpoint*); +static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint*, cyg_bool); + +static void usbs_d12_ep_rx_complete(rx_endpoint *ep, int result); +static void usbs_d12_stall_rx_ep(rx_endpoint*, cyg_bool); + +#endif + + +#if defined(_RX_EP1) + +static rx_endpoint rx_ep1 = { + common: { + start_rx_fn: &usbs_d12_api_start_rx_ep, + set_halted_fn: &usbs_d12_api_stall_rx_ep, + halted: 0 + }, + endp: 1 +}; + +extern usbs_rx_endpoint usbs_d12_rx_ep1 __attribute__((alias ("rx_ep1"))); + +#endif + + +#if defined(_RX_EP2) + +static rx_endpoint rx_ep2 = { + common: { + start_rx_fn: &usbs_d12_api_start_rx_ep, + set_halted_fn: &usbs_d12_api_stall_rx_ep, + halted: 0 + }, + endp: 2 +}; + +extern usbs_rx_endpoint usbs_d12_rx_ep2 __attribute__((alias ("rx_ep2"))); + +#endif + +// -------------------------------------------------------------------------- +// Tx Endpoints 1 & 2 Data +// -------------------------------------------------------------------------- + +#if defined(_TX_EP1) || defined(_TX_EP2) + +typedef struct tx_endpoint { + usbs_tx_endpoint common; + int endp, transmitted; + bool tx_empty; +} tx_endpoint; + +static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint*); +static void usbs_d12_api_stall_tx_ep(usbs_tx_endpoint*, cyg_bool); + +static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result); +static void usbs_d12_stall_tx_ep(tx_endpoint*, cyg_bool); +#endif + +#if defined(_TX_EP1) + +static tx_endpoint tx_ep1 = { + common: { + start_tx_fn: &usbs_d12_api_start_tx_ep, + set_halted_fn: &usbs_d12_api_stall_tx_ep, + halted: 0 + }, + endp: 1 +}; + +extern usbs_tx_endpoint usbs_d12_tx_ep1 __attribute__((alias ("tx_ep1"))); +#endif + +#if defined(_TX_EP2) + +static tx_endpoint tx_ep2 = { + common: { + start_tx_fn: &usbs_d12_api_start_tx_ep, + set_halted_fn: &usbs_d12_api_stall_tx_ep, + halted: 0 + }, + endp: 2 +}; + +extern usbs_tx_endpoint usbs_d12_tx_ep2 __attribute__((alias ("tx_ep2"))); + +#endif + +// -------------------------------------------------------------------------- +// Synchronization + +static inline void usbs_d12_lock(void) { cyg_scheduler_lock(); } +static inline void usbs_d12_unlock(void) { cyg_scheduler_unlock(); } + +// -------------------------------------------------------------------------- +// Control Endpoint +// -------------------------------------------------------------------------- + +// Fills the EP0 transmit buffer with a packet. Partial data packets are +// retrieved by repeatedly calling the fill function. + +static int +ep0_fill_tx_buffer(void) +{ + int nFilled = 0; + + while (nFilled < CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) { + if (ep0.common.buffer_size != 0) { + if ((nFilled + ep0.common.buffer_size) < + CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) { + memcpy(&ep0_tx_buffer[nFilled], ep0.common.buffer, + ep0.common.buffer_size); + nFilled += ep0.common.buffer_size; + ep0.common.buffer_size = 0; + } + else { + break; + } + } + else if (ep0.common.fill_buffer_fn) { + (*ep0.common.fill_buffer_fn)(&ep0.common); + } + else { + break; + } + } + CYG_ASSERT((ep0.common.buffer_size == 0) && (!ep0.common.fill_buffer_fn), + "EP0 transmit buffer overflow"); + TRACE_D12("EP0: Filled Tx Buf with %d bytes\n", nFilled); + + ep0.length = nFilled; + + ep0.common.fill_buffer_fn = 0; + ep0.common.fill_data = 0; + ep0.common.fill_index = 0; + + return nFilled; +} + +// -------------------------------------------------------------------------- +// Called when a transfer is complete on the control endpoint EP0. +// It resets the endpoint's data structure and calls the completion function, +// if any. +// +// PARAMETERS: +// result 0, on success +// -EPIPE or -EIO to indicate a cancellation + +static usbs_control_return +usbs_d12_ep0_complete(int result) +{ + usbs_control_return ret = USBS_CONTROL_RETURN_UNKNOWN; + + ep0.ep_state = ENDP_STATE_IDLE; + + if (ep0.common.complete_fn) + ret = (*ep0.common.complete_fn)(&ep0.common, result); + + ep0.common.buffer = 0; + ep0.common.buffer_size = 0; + ep0.common.complete_fn = 0; + //ep0.common.fill_buffer_fn = 0; + + return ret; +} + +// -------------------------------------------------------------------------- +// This routine is called when we want to send the next packet to the tx ep0 +// on the chip. It is used to start a new transfer, and is also called when +// the chip interrupts to indicate that the ep0 tx buffer is empty and ready +// to receive a new packet. +// +// NOTE: +// On the D12, when you send a zero-length packet to a tx endpoint, the +// chip transmits the empty packet to the host, but doesn't interrupt +// indicating that it is complete. So immediately after sending the +// empty packet we complete the transfer. + +static void +usbs_d12_ep0_tx(void) +{ + int nRemaining = ep0.length - ep0.transmitted; + uint8 n; + + // ----- Intermittent interrupt? Get out ----- + + if (!ep0.common.buffer) { + TRACE_D12("EP0: Tx no buffer (%d)\n", nRemaining); + return; + } + + // ----- If prev packet was last, signal that we're done ----- + + if (nRemaining == 0 && !ep0.tx_empty) { + TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, + ep0.common.complete_fn); + usbs_d12_ep0_complete(0); + return; + } + + // ----- Load the next tx packet onto the chip ----- + + if (nRemaining < D12_ENDP0_SIZE) { + n = (uint8) nRemaining; + ep0.tx_empty = false; + } + else + n = D12_ENDP0_SIZE; + + d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, + &ep0_tx_buffer[ep0.transmitted], n); + + TRACE_D12("EP0: Wrote %u bytes\n", (unsigned) n); + TRACE_BUF0("\t", &ep0_tx_buffer[ep0.transmitted], n); + + ep0.transmitted += n; + + // ----- If empty packet, D12 won't interrupt, so end now ----- */ + + if (n == 0) { + TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, + ep0.common.complete_fn); + usbs_d12_ep0_complete(0); + } +} + +// -------------------------------------------------------------------------- +// This function is called when a packet has been successfully sent on the +// primary control endpoint (ep0). It indicates that the chip is ready for +// another packet. We read the LastTransStatus for the endpoint to clear +// the interrupt bit, then call ep0_tx() to continue the transfer. + +static void +usbs_d12_ep0_tx_intr(void) +{ + d12_read_last_trans_status(D12_BASE_ADDR, D12_TX_ENDP0); + usbs_d12_ep0_tx(); +} + +// -------------------------------------------------------------------------- +// Try to handle standard requests. This is a three step process: +// 1. If it's something we should handle internally we take care of it. +// Currently we can handle SET_ADDRESS requests, and a few others. +// 2. If the upper level code has installed a standard control handler +// we let that function have a crack at it. +// 3. If neither of those handle the packet we let +// usbs_handle_standard_control() have a last try at it. +// +// Locally: +// SET_ADDRESS: The host is demanding that we change our USB address. +// This is done by updating the Address/Enable register on the D12. +// Note, however that the USB protocol requires us to ack at the old +// address, change address, and then accept the next control message +// at the new address. The D12 address reg is buffered to do this +// automatically for us. The updated address on the chip won't take +// affect until after the empty ack is sent. Nice. +// + +static usbs_control_return +usbs_d12_handle_std_req(usb_devreq *req) +{ + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + int recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + + if (req->request == USB_DEVREQ_SET_ADDRESS) { + TRACE_D12("Setting Addr: %u\n", (unsigned) req->value_lo); + d12_set_addr_enable(D12_BASE_ADDR, req->value_lo, true); + result = USBS_CONTROL_RETURN_HANDLED; + } + else if (req->request == USB_DEVREQ_GET_STATUS) { + if (recipient == USB_DEVREQ_RECIPIENT_DEVICE) { + const usbs_enumeration_data *enum_data = ep0.common.enumeration_data; + if (enum_data && enum_data->device.number_configurations == 1 && + enum_data->configurations) { + ep0.common.control_buffer[0] = + (enum_data->configurations[0].attributes + & USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED) ? 1 : 0; + ep0.common.control_buffer[0] |= + (enum_data->configurations[0].attributes + & USB_CONFIGURATION_DESCRIPTOR_ATTR_REMOTE_WAKEUP) ? 2 : 0; + ep0.common.control_buffer[1] = 0; + result = USBS_CONTROL_RETURN_HANDLED; + } + } + else if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) { + bool halted = false; + result = USBS_CONTROL_RETURN_HANDLED; + + switch (req->index_lo) { +#if defined(_RX_EP1) + case 0x01 : halted = rx_ep1.common.halted; break; +#endif +#if defined(_TX_EP1) + case 0x81 : halted = tx_ep1.common.halted; break; +#endif +#if defined(_RX_EP2) + case 0x02 : halted = rx_ep2.common.halted; break; +#endif +#if defined(_TX_EP2) + case 0x82 : halted = tx_ep2.common.halted; break; +#endif + + default: + result = USBS_CONTROL_RETURN_STALL; + } + + TRACE_D12("Get Status: Endp [0x%02X] %s\n", (unsigned) req->index_lo, + halted ? "Halt" : "Unhalt"); + if (result == USBS_CONTROL_RETURN_HANDLED) { + ep0.common.control_buffer[0] = (halted) ? 1 : 0; + ep0.common.control_buffer[1] = 0; + } + } + + if (result == USBS_CONTROL_RETURN_HANDLED) { + ep0.common.buffer = ep0.common.control_buffer; + ep0.common.buffer_size = 2; + ep0.common.fill_buffer_fn = 0; + ep0.common.complete_fn = 0; + } + } + else if ((req->request == USB_DEVREQ_SET_FEATURE || + req->request == USB_DEVREQ_CLEAR_FEATURE) && + recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) { + + bool halt = (req->request == USB_DEVREQ_SET_FEATURE); + result = USBS_CONTROL_RETURN_HANDLED; + TRACE_D12("Endpoint [0x%02X] %s\n", (unsigned) req->index_lo, + halt ? "Halt" : "Unhalt"); + + switch (req->index_lo) { +#if defined(_RX_EP1) + case 0x01 : usbs_d12_stall_rx_ep(&rx_ep1, halt); break; +#endif +#if defined(_TX_EP1) + case 0x81 : usbs_d12_stall_tx_ep(&tx_ep1, halt); break; +#endif +#if defined(_RX_EP2) + case 0x02 : usbs_d12_stall_rx_ep(&rx_ep2, halt); break; +#endif +#if defined(_TX_EP2) + case 0x82 : usbs_d12_stall_tx_ep(&tx_ep2, halt); break; +#endif + + default: + result = USBS_CONTROL_RETURN_STALL; + } + } + else if (ep0.common.standard_control_fn != 0) { + result = (*ep0.common.standard_control_fn) + (&ep0.common, + ep0.common.standard_control_data); + } + + if (result == USBS_CONTROL_RETURN_UNKNOWN) + result = usbs_handle_standard_control(&ep0.common); + + return result; +} + +// -------------------------------------------------------------------------- +// Handler for the receipt of a setup (dev request) packet from the host. +// We examine the packet to determine what function(s) should get a crack +// at trying to handle it, then pass control to the proper function. If +// the function handles the message we either ACK (len==0) or prepare for +// an IN or OUT data phase. If no one handled the message, we stall the +// control endpoint. + +static void +usbs_d12_ep0_setup_packet(usb_devreq* req) +{ + int len, dir, protocol, recipient; + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + + // ----- See who should take the request ----- + + len = make_word(req->length_hi, req->length_lo); + + dir = req->type & USB_DEVREQ_DIRECTION_MASK; + protocol = req->type & USB_DEVREQ_TYPE_MASK; + recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + + TRACE_BUF0("DevReq: ", ep0.common.control_buffer, sizeof(usb_devreq)); + + if (protocol == USB_DEVREQ_TYPE_STANDARD) + result = usbs_d12_handle_std_req(req); + else { + // Pass on non-standard requests to registered handlers + + usbs_control_return (*callback_fn)(usbs_control_endpoint*, void*); + void *callback_arg; + + if (protocol == USB_DEVREQ_TYPE_CLASS) { + callback_fn = ep0.common.class_control_fn; + callback_arg = ep0.common.class_control_data; + } + else if (protocol == USB_DEVREQ_TYPE_VENDOR) { + callback_fn = ep0.common.vendor_control_fn; + callback_arg = ep0.common.vendor_control_data; + } + else { + callback_fn = ep0.common.reserved_control_fn; + callback_arg = ep0.common.reserved_control_data; + } + + result = (callback_fn) ? (*callback_fn)(&ep0.common, callback_arg) + : USBS_CONTROL_RETURN_STALL; + } + + // ----- If handled prep/handle data phase, otherwise stall ----- + + if (result == USBS_CONTROL_RETURN_HANDLED) { + if (len == 0) { + TRACE_D12("\tCtrl ACK\n"); + d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0); + } + else { + // Set EP0 state to IN or OUT mode for data phase + ep0.transmitted = 0; + ep0.length = len; + + if (dir == USB_DEVREQ_DIRECTION_OUT) { + // Wait for the next packet from the host. + ep0.ep_state = ENDP_STATE_OUT; + CYG_ASSERT(ep0.common.buffer != 0, + "A rx buffer should have been provided for EP0"); + CYG_ASSERT(ep0.common.complete_fn != 0, + "A completion function should be provided for EP0 OUT control messages"); + } + else { + ep0.tx_empty = true; + ep0.ep_state = ENDP_STATE_IN; + ep0_fill_tx_buffer(); + usbs_d12_ep0_tx(); + } + } + } + else { + TRACE_D12("\t*** Unhandled Device Request ***\n"); + // The request wasn't handled, so stall control endpoint + d12_stall_ctrl_endp(D12_BASE_ADDR, true); + } +} + +// -------------------------------------------------------------------------- +// This is called when the chip indicates that a packet has been received +// on control endpoint 0. If it's a setup packet, we handle it accordingly, +// otherwise it's a data packet coming in on ep0. +// + +static void +usbs_d12_ep0_rx_intr(void) +{ + byte byStat = d12_read_last_trans_status(D12_BASE_ADDR, D12_RX_ENDP0); + TRACE_D12("\tEP0 Status: 0x%02X\n", (unsigned) byStat); + + if (byStat & D12_LAST_TRANS_SETUP_PACKET) { + usb_devreq *req = (usb_devreq *) ep0.common.control_buffer; + + if (!d12_read_setup_packet(D12_BASE_ADDR, (byte*) req)) { + TRACE_D12("ep0_rx_dsr: Error reading setup packet\n"); + d12_stall_ctrl_endp(D12_BASE_ADDR, true); + } + else + usbs_d12_ep0_setup_packet(req); + } + else { + if (ep0.common.buffer) { + uint8 n = d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, + ep0.common.buffer + ep0.transmitted); + ep0.transmitted += n; + + TRACE_D12("EP0: Received %d bytes\n", (unsigned) n); + + if (n < D12_ENDP0_SIZE || + ep0.common.buffer_size - ep0.transmitted < D12_ENDP0_SIZE) { + TRACE_D12("\tEP0: Rx Complete (%d) %p\n", + ep0.transmitted, ep0.common.complete_fn); + + if (usbs_d12_ep0_complete(0) == USBS_CONTROL_RETURN_HANDLED) + d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0); + else + d12_stall_ctrl_endp(D12_BASE_ADDR, true); + } + } + else { + TRACE_D12("EP0: No Rx buffer. Discarding packet\n"); + d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, NULL); + } + } +} + +// -------------------------------------------------------------------------- +// Handler for when the device is put into or taken out of suspend mode. +// It updates the state variable in the control endpoint and calls the +// registered state change function, if any. + +// TODO: Put the chip into low power mode??? Stop clocks, etc??? + +static void +usbs_d12_suspend(bool suspended) +{ + int old_state = ep0.common.state; + usbs_state_change state_change; + + if (suspended) { + ep0.common.state |= USBS_STATE_SUSPENDED; + state_change = USBS_STATE_CHANGE_SUSPENDED; + } + else { + ep0.common.state &= USBS_STATE_MASK; + state_change = USBS_STATE_CHANGE_RESUMED; + } + + if (ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + state_change, old_state); + } +} + +// -------------------------------------------------------------------------- +// Common Rx Endpoint 1 & 2 +// -------------------------------------------------------------------------- + +#if defined(_RX_EP1) || defined(_RX_EP2) + +static void usbs_d12_clear_rx_ep(rx_endpoint *ep) +{ + ep->common.buffer = 0; + ep->common.buffer_size = 0; + ep->common.complete_fn = 0; + ep->common.complete_data = 0; + + ep->received = 0; +} + +// -------------------------------------------------------------------------- +// This is called when an rx operation is completed. It resets the endpoint +// vars and calls the registered completion function. +// + +static void +usbs_d12_ep_rx_complete(rx_endpoint *ep, int result) +{ + completion_fn fn = ep->common.complete_fn; + void *data = ep->common.complete_data; + + usbs_d12_clear_rx_ep(ep); + + if (fn) + (*fn)(data, result); +} + +// -------------------------------------------------------------------------- +// This routine is called when an rx buffer in the chip is full and ready to +// be read. If there's an endpoint buffer available and room to hold the data +// we read it in, otherwise we call the completion function, but leave the +// data in the chip. The hardware will automatically NAK packages from the +// host until the app calls another start read to continue receiving data. +// +// CONTEXT: +// Called from either the DSR or application thread, via start rx. +// In either case, it's assumed that the chip is locked. +// + +static void +usbs_d12_ep_rx(rx_endpoint *ep) +{ + int n, ep_size, buf_remaining, endp = ep->endp; + bool done; + + // The main endp is double buffered and we need to be prepared + // to read both simultaneously. + ep_size = (endp == D12_MAIN_ENDP) ? (2 * D12_MAIN_ENDP_SIZE) + : RX_ENDP_SIZE[endp]; + + buf_remaining = ep->common.buffer_size - ep->received; + + // ----- If no space left in buffer, call completion fn ----- + + if (!ep->common.buffer || buf_remaining < ep_size) { + int ret = ep->received; + + // See if caller requested a read smaller than the endp. Read & + // throw away extra + if (ep->common.buffer_size < ep_size) { + byte tmp_buf[D12_MAX_PACKET_SIZE]; + + if (endp == D12_MAIN_ENDP) + n = d12_read_main_endp_buf(D12_BASE_ADDR, tmp_buf, &done); + else + n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp], tmp_buf); + + if (n > ep->common.buffer_size) { + n = ep->received = ep->common.buffer_size; + ret = -ENOMEM; + TRACE_D12("\tEP%d: *** Rx Buffer too small. Data Lost ***\n", endp); + } + else + ret = ep->received = n; + + memcpy(ep->common.buffer, tmp_buf, n); + buf_remaining = ep->common.buffer_size - n; + } + + TRACE_D12("\tEP%d: Rx Complete. Buffer (nearly) full. [%d]\n", + endp, buf_remaining); + usbs_d12_ep_rx_complete(ep, ret); + return; + } + + // ----- Read the data from the chip ----- + + if (endp == D12_MAIN_ENDP) + n = d12_read_main_endp_buf(D12_BASE_ADDR, + ep->common.buffer + ep->received, &done); + else { + n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp], + ep->common.buffer + ep->received); + done = (n < RX_ENDP_SIZE[endp]); + } + + ep->received += n; + buf_remaining = ep->common.buffer_size - ep->received; + + done = done || (buf_remaining < ep_size); + + TRACE_D12("EP%d: Received %d bytes.\n", endp, n); + TRACE_BUF("\t", ep->common.buffer + ep->received-n, n); + + // ----- If we're done, complete the receive ----- + + if (done) { + TRACE_D12("\tEP%d Rx Complete (%d) %p\n", endp, + ep->received, ep->common.complete_fn); + usbs_d12_ep_rx_complete(ep, ep->received); + } +} + +// -------------------------------------------------------------------------- +// Stalls/unstalls the specified endpoint. + +static void +usbs_d12_stall_rx_ep(rx_endpoint *ep, cyg_bool halt) +{ + ep->common.halted = halt; + d12_stall_endp(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp], halt); +} + +// -------------------------------------------------------------------------- +// Handler for an Rx endpoint full interrupt. It clears the interrupt on the +// D12 by reading the endpoint's status register then calls the routine to +// read the data into the buffer. +// +// Called from the DSR context only. +// + +static void +usbs_d12_ep_rx_intr(rx_endpoint *ep) +{ + d12_read_last_trans_status(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp]); + usbs_d12_ep_rx(ep); +} + +#endif + +// -------------------------------------------------------------------------- +// Common Tx Endpoint 1 & 2 +// -------------------------------------------------------------------------- + +#if defined(_TX_EP1) || defined(_TX_EP2) + +// Clears out the endpoint data structure before/after a tx is complete. + +static void usbs_d12_clear_tx_ep(tx_endpoint *ep) +{ + ep->common.buffer = 0; + ep->common.buffer_size = 0; + ep->common.complete_fn = 0; + ep->common.complete_data = 0; + + ep->transmitted = 0; + ep->tx_empty = false; +} + +// -------------------------------------------------------------------------- +// This is called when a transmit is completed. It resets the endpoint vars +// and calls the registered completion function, if any. +// +// CONTEXT: +// Called from either the DSR or the app thread that started tx. + +static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result) +{ + completion_fn fn = ep->common.complete_fn; + void *data = ep->common.complete_data; + + usbs_d12_clear_tx_ep(ep); + + if (fn) + (*fn)(data, result); +} + +// -------------------------------------------------------------------------- +// The routine writes data to the chip and updates the endpoint's counters. +// It gets called at the start of a transfer operation to prime the device +// and then gets called each time the chip finishes sending a packet to the +// host and is ready for more data. If the amount of data remaining is +// smaller than can fit in the chip's endpoint buffer, then this is the last +// packet to send, so we call the completion function. +// +// CONTEXT: +// Called from either the DSR or the app thread that started the tx +// In either case, it's assumed the chip is locked. + +static void +usbs_d12_ep_tx(tx_endpoint *ep) +{ + int n, nRemaining; + + // ----- Already done. Intermittent interrupt, so get out ----- + + if (!ep->common.buffer) + return; + + // ----- See how many bytes remaining in buffer ----- + + nRemaining = ep->common.buffer_size - ep->transmitted; + + TRACE_D12("EP%d: Tx %p, %d Done, %d Remaining\n", ep->endp, + ep->common.buffer, ep->transmitted, nRemaining); + + // ----- If prev packet was last, signal that we're done ----- + + if (nRemaining == 0 && !ep->tx_empty) { + TRACE_D12("\tEP%d: Tx complete (%d) %p\n", ep->endp, + ep->transmitted, ep->common.complete_fn); + usbs_d12_ep_tx_complete(ep, ep->transmitted); + return; + } + + // ----- Write the next packet to chip ----- + + if (nRemaining < TX_ENDP_SIZE[ep->endp]) { + n = nRemaining; + ep->tx_empty = false; + } + else + n = TX_ENDP_SIZE[ep->endp]; + + TRACE_D12("EP%d: Writing %d bytes. %s\n", ep->endp, + n, (n == 0) ? "DONE" : ""); + TRACE_BUF("\t", ep->common.buffer + ep->transmitted, n); + + d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], + ep->common.buffer + ep->transmitted, (uint8) n); + + ep->transmitted += n; + + // ----- If empty packet, complete now ----- + + if (n == 0) { + TRACE_D12("\tEP%d: Tx complete (%d) %p\n", ep->endp, + ep->transmitted, ep->common.complete_fn); + usbs_d12_ep_tx_complete(ep, ep->transmitted); + return; + } +} + +// -------------------------------------------------------------------------- +// Stalls/unstalls the specified tx endpoint. + +static void +usbs_d12_stall_tx_ep(tx_endpoint *ep, cyg_bool halt) +{ + ep->common.halted = halt; + d12_stall_endp(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], halt); +} + +// -------------------------------------------------------------------------- +// Handler for when the chip's tx RAM for an endoint has just been emptied +// (sent to the host) and the chip is ready for more data. +// We read the endpoint's last trans status register to clear the interrupt +// on the D12, then call the tx function to send the next packet or +// complete the transfer. + +static void +usbs_d12_ep_tx_intr(tx_endpoint *ep) +{ + d12_read_last_trans_status(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp]); + usbs_d12_ep_tx(ep); +} + +#endif // defined(_TX_EP1) || defined(_TX_EP2) + +// -------------------------------------------------------------------------- +// Application Program Interface (API) +// -------------------------------------------------------------------------- + +#if defined(_RX_EP1) || defined(_RX_EP2) +// Starts a receive operation on the specified endpoint. If the buffer size +// is zero the completion function is called immediately. The routine checks +// if tehre is data in the chip's endpoint buffer, and if so it will call +// ep_rx() to start reading the data out of the chip. +// +// If the endpoint is currently stalled, a read size of zero can be used to +// block the calling thread until the stall is cleared. If the read size is +// non-zero and the endpoint is stalled the completion function is called +// immediately with an error result. + +static void +usbs_d12_api_start_rx_ep(usbs_rx_endpoint *ep) +{ + rx_endpoint *epx = (rx_endpoint *) ep; + + if (ep->halted) { + if (ep->buffer_size != 0) + usbs_d12_ep_rx_complete(epx, -EAGAIN); + } + else if (ep->buffer_size == 0) { + usbs_d12_ep_rx_complete(epx, 0); + } + else { + TRACE_D12("EP%d: Starting Rx, %p, %d\n", epx->endp, ep->buffer, + ep->buffer_size); + usbs_d12_lock(); + + epx->received = 0; + if (d12_data_available(D12_BASE_ADDR, RX_ENDP_INDEX[epx->endp])) + usbs_d12_ep_rx(epx); + + usbs_d12_unlock(); + } +} + +// -------------------------------------------------------------------------- +// Halts/unhalts one of the generic rx (OUT) endpoints. +// + +static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint *ep, cyg_bool halt) +{ + usbs_d12_lock(); + usbs_d12_stall_rx_ep((rx_endpoint*) ep, halt); + usbs_d12_unlock(); +} + +#endif // defined(_RX_EP1) || defined(_RX_EP2) + +// -------------------------------------------------------------------------- +// Tx API +// -------------------------------------------------------------------------- + +#if defined(_TX_EP1) || defined(_TX_EP2) + +// This starts a transmit on one of the data endpoints. If the endpoint is +// stalled a buffer size of zero can be used to block until the stall is +// cleared. Any other size on a stalled endpoint will result in an error +// callback immediately. The first packet is sent to the chip immediately, +// in the application context. If the chip's buffer can contain the whole +// transfer, the completion function will be called immediately, again, +// still in the application context. +// +// If an empty packet is requested we send one from here and call the +// completion function. This should not cause an intr on the D12. +// +// CONTEXT: +// Called from an application thread + +static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint *ep) +{ + tx_endpoint *epx = (tx_endpoint*) ep; + + if (ep->halted) { + if (ep->buffer_size != 0) + usbs_d12_ep_tx_complete(epx, -EAGAIN); + } + else if (ep->buffer_size == 0) { + usbs_d12_lock(); + + d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[epx->endp], 0, 0); + usbs_d12_ep_tx_complete(epx, 0); + + usbs_d12_unlock(); + } + else { + TRACE_D12("EP%d: Starting Tx, %p, %d\n", epx->endp, ep->buffer, + ep->buffer_size); + usbs_d12_lock(); + + epx->tx_empty = true; + epx->transmitted = 0; + usbs_d12_ep_tx(epx); + + usbs_d12_unlock(); + } +} + +// -------------------------------------------------------------------------- +// Halts/unhalts one of the generic endpoints. + +static void +usbs_d12_api_stall_tx_ep(usbs_tx_endpoint *ep, cyg_bool halt) +{ + usbs_d12_lock(); + usbs_d12_stall_tx_ep((tx_endpoint*) ep, halt); + usbs_d12_unlock(); +} + +#endif // defined(_TX_ENDP1) || defined(_TX_EP2) + +// -------------------------------------------------------------------------- +// DSR +// -------------------------------------------------------------------------- + +// The DSR for the D12 chip. This is normally called in the DSR context when +// the D12 has raised its interrupt flag indicating that it needs to be +// serviced. The interrupt register contains bit flags that are OR'ed togther +// indicating what items need to be serviced. There are flags for the +// following: +// - The endpoints (one bit for each) +// - Bus Reset +// - Suspend Change +// - DMA (terminal count) +// +// Care must be taken in that the D12's interrupt output is level-sensitive +// (in that the interrupt sources are OR'ed together and not all cleared +// atomically in a single operation). Platforms (such as the PC) may be +// expecting edge-triggered interrupts, so we must work around that. +// So, we loop on the interrupt register. Even though, in each loop, we +// perform all of the required operations to clear the interrupts, a new +// one may have arrived before we finished clearing the previous ones. +// So we read the intr reg again. Once the intr reg gives a zero reading +// we know that the D12 has dropped its IRQ line. +// +// Note, if we're configured to use a thread, this routine is called from +// within a thread context (not a DSR context). +// + +static void +usbs_d12_dsr(cyg_vector_t vector, cyg_ucount32 count, + cyg_addrword_t data) +{ + uint16 status; + bool suspended; + + CYG_ASSERT(vector == CYGNUM_DEVS_USB_D12_INT, + "DSR should only be invoked for D12 interrupts"); + + while ((status = d12_read_intr_reg(D12_BASE_ADDR)) != 0) { + TRACE_D12("Intr Status: 0x%04X\n", (unsigned) status); + + if (status & D12_INTR_BUS_RESET) { + TRACE_D12("\n>>> Bus Reset <<<\n"); + usbs_d12_reset(); + } + else { + + // ----- Suspend Change ----- + + suspended = (bool) (ep0.common.state & USBS_STATE_SUSPENDED); + + if (status & D12_INTR_SUSPEND_CHANGE) { + if (!suspended && (status & ~D12_INTR_SUSPEND_CHANGE) == 0) + usbs_d12_suspend(true); + } + else if (suspended) + usbs_d12_suspend(false); + + // ----- Bulk Endpoints ----- + +#ifdef _TX_EP2 + if (status & D12_INTR_TX_ENDP2) + usbs_d12_ep_tx_intr(&tx_ep2); +#endif + +#ifdef _RX_EP2 + if (status & D12_INTR_RX_ENDP2) + usbs_d12_ep_rx_intr(&rx_ep2); +#endif + + // ----- Interrupt Endpoints ----- + +#ifdef _TX_EP1 + if (status & D12_INTR_TX_ENDP1) + usbs_d12_ep_tx_intr(&tx_ep1); +#endif + +#ifdef _RX_EP1 + if (status & D12_INTR_RX_ENDP1) + usbs_d12_ep_rx_intr(&rx_ep1); +#endif + + // ----- Control Endpoint ----- + + if (status & D12_INTR_TX_CTRL_ENDP) + usbs_d12_ep0_tx_intr(); + + if (status & D12_INTR_RX_CTRL_ENDP) + usbs_d12_ep0_rx_intr(); + } + } + + cyg_drv_interrupt_unmask(vector); +} + +// -------------------------------------------------------------------------- +// Interrupt +// -------------------------------------------------------------------------- + +// Here, the ISR does nothing but schedule the DSR to run. The ISR's/DSR's +// are serialized. The CPU won't process another ISR until after the DSR +// completes. + +static uint32 +usbs_d12_isr(cyg_vector_t vector, cyg_addrword_t data) +{ + CYG_ASSERT(CYGNUM_DEVS_USB_D12_INT == vector, + "usbs_isr: Incorrect interrupt"); + + // Prevent another interrupt until DSR completes + cyg_drv_interrupt_mask(vector); + cyg_drv_interrupt_acknowledge(vector); + + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; +} + +// -------------------------------------------------------------------------- +// Polling +// -------------------------------------------------------------------------- + +static void +usbs_d12_poll(usbs_control_endpoint *endp) +{ + CYG_ASSERT(endp == &ep0.common, "usbs_poll: wrong endpoint"); + + usbs_d12_lock(); + usbs_d12_dsr(CYGNUM_DEVS_USB_D12_INT, 0, 0); + usbs_d12_unlock(); +} + +// -------------------------------------------------------------------------- +// Thread Processing +// -------------------------------------------------------------------------- + +// The user can opt to configure the driver to service the D12 using a high +// priority thread. The thread's priority MUST be greater than the priority +// of any application thread making calls into the driver. +// When we use a thread, the DSR simply signals a semaphore tio wake the +// thread up. The thread, in turn, calls the the routine to service the D12, +// now in a thread context. This allows for greater debug options, including +// tracing. + +#ifdef CYGPKG_DEVS_USB_D12_THREAD + +static byte usbs_d12_thread_stack[CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE]; +static cyg_thread usbs_d12_thread; +static cyg_handle_t usbs_d12_thread_handle; +static cyg_sem_t usbs_d12_sem; + +static void +usbs_d12_thread_dsr(cyg_vector_t vector, cyg_ucount32 count, + cyg_addrword_t data) +{ + cyg_semaphore_post(&usbs_d12_sem); + + CYG_UNUSED_PARAM(cyg_vector_t, vector); + CYG_UNUSED_PARAM(cyg_ucount32, count); + CYG_UNUSED_PARAM(cyg_addrword_t, data); +} + + +static void +usbs_d12_thread_fn(cyg_addrword_t param) +{ + while (1) { + cyg_semaphore_wait(&usbs_d12_sem); + usbs_d12_poll(&ep0.common); + } + + CYG_UNUSED_PARAM(cyg_addrword_t, param); +} + + +static void +usbs_d12_thread_init(void) +{ + cyg_semaphore_init(&usbs_d12_sem, 0); + + cyg_thread_create(CYGNUM_DEVS_USB_D12_THREAD_PRIORITY, + &usbs_d12_thread_fn, 0, "D12 USB Driver Thread", + usbs_d12_thread_stack, + CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE, + &usbs_d12_thread_handle, &usbs_d12_thread); + cyg_thread_resume(usbs_d12_thread_handle); +} + +#endif // CYGPKG_DEVS_USB_D12_THREAD + +// -------------------------------------------------------------------------- +// Start/Reset +// -------------------------------------------------------------------------- + +// Chip initialization and handler for a USB Bus Reset. This gets called at +// system startup and after a USB Bus Reset. It puts the chip into the +// default state, with USB Address 0, connected to the bus (i.e. +// "SoftConnect" asserted). Interrupts to the main endpoint are turned on +// via the DMA register. + +static void +usbs_d12_reset(void) +{ + int old_state = ep0.common.state; + ep0.common.state = USBS_STATE_DEFAULT; + + if (ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_RESET, old_state); + } + + d12_set_addr_enable(D12_BASE_ADDR, 0, true); + d12_set_endp_enable(D12_BASE_ADDR, true); + d12_set_dma(D12_BASE_ADDR, D12_DMA_MAIN_ENDP_INTR_ENABLE); + d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT | D12_MODE_CFG_SOFT_CONNECT, + D12_MODE_CLK_DFLT); + + // If any endpoints were going, signal the end of transfers + +#if defined(_TX_EP2) + usbs_d12_ep_tx_complete(&tx_ep2, -EPIPE); +#endif + +#if defined(_RX_EP2) + usbs_d12_ep_rx_complete(&rx_ep2, -EPIPE); +#endif + +#if defined(_TX_EP1) + usbs_d12_ep_tx_complete(&tx_ep1, -EPIPE); +#endif + +#if defined(_RX_EP1) + usbs_d12_ep_rx_complete(&rx_ep1, -EPIPE); +#endif +} + +// -------------------------------------------------------------------------- +// The start function is called indirectly by the application when +// initialization is complete. By this time, the enumeration data has been +// assigned to the control endpoint and we're ready to connect to the host. +// Within the reset function the D12's SoftConnect line is asserted which +// allows the host (hub) to see us on the USB bus. If connected, the host +// should start the enumeration process. +// + +static void usbs_d12_start(usbs_control_endpoint *endpoint) +{ +#if defined(_TRACE) && !defined(_TRACE_STDOUT) + TRACE_OPEN(TRACE_SINK); +#endif + + CYG_ASSERT(endpoint == &ep0.common, "ep0 start: wrong endpoint"); + TRACE_D12("USBS D12: Starting.\n"); + + d12_clear_all_intr(D12_BASE_ADDR); + usbs_d12_reset(); +} + +// -------------------------------------------------------------------------- +// Initialization +// -------------------------------------------------------------------------- + +// This routine is called early in the program's startup, possibly before +// main (from within a C++ object initialization). We want to put this chip +// and driver in a neutral, but ready, state until the application gets +// control, initializes itself and calls the usb start function. +// +// The D12 has a "Soft Connect" feature to tristate the USB bus, making it +// appear that the USB device is not connected to the bus. We initially +// keep seperated from the bus to allow for initialization. + +void +usbs_d12_init(void) +{ + cyg_DSR_t *pdsr; + + d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT & ~D12_MODE_CFG_SOFT_CONNECT, + D12_MODE_CLK_DFLT); + + d12_set_addr_enable(D12_BASE_ADDR, 0, false); + d12_set_endp_enable(D12_BASE_ADDR, false); + + // ----- Clear the endpoints ----- + +#if defined(_TX_EP2) + usbs_d12_clear_tx_ep(&tx_ep2); +#endif + +#if defined(_RX_EP2) + usbs_d12_clear_rx_ep(&rx_ep2); +#endif + +#if defined(_TX_EP1) + usbs_d12_clear_tx_ep(&tx_ep1); +#endif + +#if defined(_RX_EP1) + usbs_d12_clear_rx_ep(&rx_ep1); +#endif + + // ----- Start the thread (if we're using it) ----- + +#ifdef CYGPKG_DEVS_USB_D12_THREAD + usbs_d12_thread_init(); + pdsr = &usbs_d12_thread_dsr; +#else + pdsr = &usbs_d12_dsr; +#endif + + // ----- Attach the ISR ----- + + cyg_drv_interrupt_create(CYGNUM_DEVS_USB_D12_INT, + 0, 0, &usbs_d12_isr, pdsr, + &usbs_d12_intr_handle, &usbs_d12_intr_data); + + cyg_drv_interrupt_attach(usbs_d12_intr_handle); + cyg_drv_interrupt_unmask(CYGNUM_DEVS_USB_D12_INT); +} + +// ---------------------------------------------------------------------------- +// Testing support. + +usbs_testing_endpoint usbs_testing_endpoints[] = { + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, + endpoint_number : 0, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep0.common, +#ifdef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "0c", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE, + max_in_padding : 0, + alignment : 0 + }, + + /* +#ifdef _TX_EP1 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT, + endpoint_number : 1, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &tx_ep1.common, +# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1w", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 0, + max_size : 0x0FFFF, // Driver limitation, only a single + // buffer descriptor is used + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + +#ifdef _RX_EP1 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT, + endpoint_number : 1, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &rx_ep1.common, +# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1r", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 1, + max_size : 0x0FFFF, // Driver limitation + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + */ + +#ifdef _TX_EP2 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 2, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &tx_ep2.common, +# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2w", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 0, + max_size : 0x1000, // 4k for testing + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + +#ifdef _RX_EP2 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 2, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &rx_ep2.common, +# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2r", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 1, + max_size : 0x1000, // 4k for testing + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + + USBS_TESTING_ENDPOINTS_TERMINATOR +}; + diff --git a/ecos/packages/devs/usb/d12/current/src/usbs_d12_data.cxx b/ecos/packages/devs/usb/d12/current/src/usbs_d12_data.cxx new file mode 100644 index 0000000..4fef23e --- /dev/null +++ b/ecos/packages/devs/usb/d12/current/src/usbs_d12_data.cxx @@ -0,0 +1,231 @@ +//========================================================================== +// +// usbs_d12_data.cxx +// +// Static data for the D12 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): fmp +// Contributors: fmp +// Date: 2004-05-27 +// +// This file contains various objects that should go into extras.o +// rather than libtarget.a, e.g. devtab entries that would normally +// be eliminated by the selective linking. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/devtab.h> +#include <cyg/io/usb/usbs_d12.h> +#include <pkgconf/devs_usb_d12.h> + +// ---------------------------------------------------------------------------- +// Initialization. The goal here is to call usbs_d12_init() +// early on during system startup, to take care of things like +// registering interrupt handlers etc. which are best done +// during system init. +// +// If the endpoint 0 devtab entry is available then its init() +// function can be used to take care of this. However the devtab +// entries are optional so an alternative mechanism must be +// provided. Unfortunately although it is possible to give +// a C function the constructor attribute, it cannot be given +// an initpri attribute. Instead it is necessary to define a +// dummy C++ class. + +extern "C" void usbs_d12_init(void); + +#ifndef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY +class usbs_d12_initialization { +public: + usbs_d12_initialization() { + usbs_d12_init(); + } +}; + +static usbs_d12_initialization usbs_d12_init_object + CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO); +#endif + +// ---------------------------------------------------------------------------- +// The devtab entries. Each of these is optional, many applications +// will want to use the lower-level API rather than go via +// open/read/write/ioctl. + +#ifdef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY + +// For endpoint 0 the only legal operations are get_config() and +// set_config(), and these are provided by the common package. + +static bool +usbs_d12_devtab_ep0_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + usbs_d12_init(); + return true; +} + +static +CHAR_DEVIO_TABLE(usbs_d12_ep0_devtab_functions, + &cyg_devio_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static CHAR_DEVTAB_ENTRY(usbs_d12_ep0_devtab_entry, + CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "0c", + 0, + &usbs_d12_ep0_devtab_functions, + &usbs_d12_devtab_ep0_init, + 0, + (void*) &usbs_d12_ep0); +#endif + +// ---------------------------------------------------------------------------- +// Common routines for ep1 and ep2. + +#if defined(CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY) + +static bool +usbs_d12_devtab_dummy_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + return true; +} +#endif + +// ---------------------------------------------------------------------------- +// tx (in) ep1 devtab entry. This can only be used for slave->host, +// so only the cwrite() function makes sense. + +#ifdef CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY + +static +CHAR_DEVIO_TABLE(usbs_d12_tx_ep1_devtab_functions, + &usbs_devtab_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static +CHAR_DEVTAB_ENTRY(usbs_d12_tx_ep1_devtab_entry, + CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1w", + 0, + &usbs_d12_tx_ep1_devtab_functions, + &usbs_d12_devtab_dummy_init, + 0, + (void*) &usbs_d12_tx_ep1); +#endif + +// ---------------------------------------------------------------------------- +// rx (out) ep1 devtab entry. This can only be used for host->slave, +// so only the cread() function makes sense. + +#ifdef CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY + +static +CHAR_DEVIO_TABLE(usbs_d12_rx_ep1_devtab_functions, + &cyg_devio_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static +CHAR_DEVTAB_ENTRY(usbs_d12_rx_ep1_devtab_entry, + CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1r", + 0, + &usbs_d12_rx_ep1_devtab_functions, + &usbs_d12_devtab_dummy_init, + 0, + (void*) &usbs_d12_rx_ep1); +#endif + + +// ---------------------------------------------------------------------------- +// tx (in) ep2 devtab entry. This can only be used for slave->host, so only the +// cwrite() function makes sense. + +#ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY + +static +CHAR_DEVIO_TABLE(usbs_d12_tx_ep2_devtab_functions, + &usbs_devtab_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static +CHAR_DEVTAB_ENTRY(usbs_d12_tx_ep2_devtab_entry, + CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2w", + 0, + &usbs_d12_tx_ep2_devtab_functions, + &usbs_d12_devtab_dummy_init, + 0, + (void*) &usbs_d12_tx_ep2); +#endif + +// ---------------------------------------------------------------------------- +// rx (out) ep2 devtab entry. This can only be used for host->slave, +// so only the cread() function makes sense. + +#ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY + +static +CHAR_DEVIO_TABLE(usbs_d12_rx_ep2_devtab_functions, + &cyg_devio_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static +CHAR_DEVTAB_ENTRY(usbs_d12_rx_ep2_devtab_entry, + CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2r", + 0, + &usbs_d12_rx_ep2_devtab_functions, + &usbs_d12_devtab_dummy_init, + 0, + (void*) &usbs_d12_rx_ep2); +#endif diff --git a/ecos/packages/devs/usb/i386/SoRoD12/current/ChangeLog b/ecos/packages/devs/usb/i386/SoRoD12/current/ChangeLog new file mode 100644 index 0000000..62c4456 --- /dev/null +++ b/ecos/packages/devs/usb/i386/SoRoD12/current/ChangeLog @@ -0,0 +1,28 @@ +2006-06-06 Frank Pagliughi <fpagliughi@mindspring.com> + + * First version of the USB device driver using the philips D12 + for i386 targets. + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2006 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/i386/SoRoD12/current/cdl/usbs_i386_sorod12.cdl b/ecos/packages/devs/usb/i386/SoRoD12/current/cdl/usbs_i386_sorod12.cdl new file mode 100644 index 0000000..4e76de2 --- /dev/null +++ b/ecos/packages/devs/usb/i386/SoRoD12/current/cdl/usbs_i386_sorod12.cdl @@ -0,0 +1,92 @@ +# ==================================================================== +# +# usbs_i386_sorod12.cdl +# +# Hardware specific parts for the SoRo D12 PC 104 USB card. +# +# ==================================================================== +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 2003, 2004, 2006 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +# ==================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): Frank M. Pagliughi (fmp), SoRo Systems, Inc., asl +# Contributors: +# Date: 2006-04-27 +# +#####DESCRIPTIONEND#### +# ==================================================================== + +cdl_package CYGPKG_DEVS_USB_I386_SOROD12 { + display "Hardware specific part for SoRo D12 USB Device Driver" + include_dir "cyg/io/usb" + parent CYGPKG_DEVS_USB_D12 + + requires { CYGIMP_DEVS_USB_D12_HW_ACCESS_HEADER == + "<cyg/io/usb/usbs_i386_sorod12.inl>" } + + cdl_option CYGSEM_DEVS_USB_I386_SOROD12_IO_MAPPED { + display "I/O mapped." + flavor bool + default_value 1 + description " + The PDIUSBD12 can be mapped into the processor's I/O space or memory + space. If this is set the driver accesses the chip through HAL_READ + and HAL_WRITE macros, otherwise it uses direct memory access." + } + + cdl_option CYGNUM_DEVS_USB_I386_SOROD12_BASEADDR { + display "Base Address of D12 chip" + flavor data + no_define + legal_values 1 to 0xFF8 + default_value 544 + active_if CYGFUN_DEVS_USB_D12_EP0 + requires { CYGNUM_DEVS_USB_D12_BASEADDR == + CYGNUM_DEVS_USB_I386_SOROD12_BASEADDR } + description " + The base memory or I/O address where the USB chip resides." + } + + cdl_option CYGNUM_DEVS_USB_I386_SORODD12_IRQ { + display "IRQ for the D12 chip" + flavor data + no_define + legal_values { 3 5 7 } + default_value 5 + active_if CYGFUN_DEVS_USB_D12_EP0 + requires { CYGNUM_DEVS_USB_D12_IRQ == + CYGNUM_DEVS_USB_I386_SORODD12_IRQ } + description " + The IRQ assigned to the D12 chip." + } +} diff --git a/ecos/packages/devs/usb/i386/SoRoD12/current/include/usbs_i386_sorod12.inl b/ecos/packages/devs/usb/i386/SoRoD12/current/include/usbs_i386_sorod12.inl new file mode 100644 index 0000000..d74883b --- /dev/null +++ b/ecos/packages/devs/usb/i386/SoRoD12/current/include/usbs_i386_sorod12.inl @@ -0,0 +1,130 @@ +#ifndef USBS_I386_SORO12_INL +#define USBS_I386_SORO12_INL +//========================================================================== +// +// usbS_i386_sorod12.inl +// +// Hardware specific parts for the SoRo D12 PC 104 USB card. +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2006 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): Frank M. Pagliughi (fmp), ASL +// Date: 2004-05-22 +// +// This code is a hardware specific device driver for the SoRo Systems +// USB-D12-104, a PC/104 (ISA) Full-Speed USB slave board, which turns +// a PC/104 stack into a USB slave device. The board contains a +// Philips PDIUSBD12 Peripheral Controller Chip mapped into the PC's +// I/O space, with jumper-selectable I/O base address, IRQ, and DMA +// settings. The eCos config tool is used to adjust settings for this +// driver to match the physical jumper settings. The chip could run in +// polled mode without an IRQ, but this wouldn't be a great idea other +// than maybe a debug environment. + +// ------------------------------------------------------------------------ +// Data-Only I/O +// ------------------------------------------------------------------------ +// + +#include <pkgconf/devs_usb_i386_sorod12.h> + +// These routines read or write 8 bit values to the data area. + +static inline byte d12_read_data_byte(d12_addr_type base_addr) +{ + #if defined(CYGSEM_DEVS_USB_I386_SOROD12_IO_MAPPED) + byte val; + HAL_READ_UINT8((unsigned) base_addr, val); + return val; + #else + return *base_addr; + #endif +} + +static inline void d12_write_data_byte(d12_addr_type base_addr, byte val) +{ + #if defined(CYGSEM_DEVS_USB_I386_SOROD12_IO_MAPPED) + HAL_WRITE_UINT8((unsigned) base_addr, val); + #else + *base_addr = val; + #endif +} + +// This routine writes a command to the device. + +static inline void d12_write_cmd(d12_addr_type base_addr, byte cmd) +{ + #if defined(CYGSEM_DEVS_USB_I386_SOROD12_IO_MAPPED) + HAL_WRITE_UINT8((unsigned) base_addr+1, cmd); + #else + *(base_addr+1) = cmd; + #endif +} + +// ------------------------------------------------------------------------ +// Reads 'n' bytes from the data address of the D12 and places them into +// the buf[] array. Buf can be NULL, in which case the specified number of +// bytes are read and discarded. + +static uint8 d12_read_data(d12_addr_type base_addr, byte *buf, uint8 n) +{ + uint8 i; + + if (buf) { + for (i=0; i<n; ++i) + buf[i] = d12_read_data_byte(base_addr); + } + else { + for (i=0; i<n; ++i) + d12_read_data_byte(base_addr); + n = 0; + } + + return n; +} + +// ------------------------------------------------------------------------ +// Writes 'n' bytes out the data reg of the chip + +static void d12_write_data(d12_addr_type base_addr, const byte *buf, uint8 n) +{ + uint8 i; + + for (i=0; i<n; ++i) + d12_write_data_byte(base_addr, buf[i]); +} + +#endif // USBS_I386_SORO12_INL diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/ChangeLog b/ecos/packages/devs/usb/nec_upd985xx/current/ChangeLog new file mode 100644 index 0000000..8235811 --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/ChangeLog @@ -0,0 +1,125 @@ +2003-02-25 Jonathan Larmour <jifl@eCosCentric.com> + + * doc/usbs_upd985xx.sgml: Declare as <part> not <reference> to get + correct TOC numbering. + +2003-02-24 Jonathan Larmour <jifl@eCosCentric.com> + + * cdl/usbs_upd985xx.cdl: Fix doc link. + + * doc/usbs_upd985xx.sgml: Comment out DOCTYPE for now to allow building + with standard doc build. + Add an enclosing <reference> so it's structured better with standard + doc build. + +2003-01-22 Anssi Pulkkinen <anssi.pulkkinen@ascom.ch> + + * src/usbs_upd985xx.c (ep0_rx_dsr): After sending a stall response + on ep0, start a new receive process for the next control message. + +2002-12-01 Bart Veer <bartv@ecoscentric.com> + + * src/usbs_upd985xx.c, cdl/usbs_upd985xx.cdl: + Make the control packet size configurable, to work around a + problem detected by USB compliance testing. Based on work + by Clark Williams and Andrew Lunn. + +2002-10-26 Bart Veer <bartv@ecoscentric.com> + + * src/usbs_upd985xx.c (ep0_rx_dsr): + Fix typo in expression, reported by Andrew Lunn. The system's + behaviour should not be affected. + +2001-09-20 Bart Veer <bartv@redhat.com> + + * src/usbs_upd985xx.c (ep0_init): + During a reset, also reset the tx_in_progress and + tx_pending locks. Otherwise if there are ongoing + transmits while the host is issuing a reset no + further transmits would be possible. + +2001-09-14 Bart Veer <bartv@redhat.com> + + * src/usbs_upd985xx.c: + Various changes related to USB testing. Also include + potential work-arounds for some unconfirmed hardware + problems, but these are disabled for now. + + * src/usbs_upd985xx.c: + Change how the reset signal interrupt bit gets masked, to + cope with the way Windows initializes a new USB device. + +2001-08-09 Bart Veer <bartv@redhat.com> + + * doc/usbs_upd985xx.sgml, doc/*.html: + Updated to describe the driver's current behaviour. + + * src/usbs_upd985xx.c, cdl/usbs_upd985xx.cdl: + Implement workaround for some hardware problems, + by serializing transmit operations. + +2001-08-08 Bart Veer <bartv@redhat.com> + + * cdl/usbs_upd985xx.cdl, include/usbs_upd985xx.h, + src/usbs_upd985xx.c, src/usbs_upd985xx.cxx: + Implement workarounds for some hardware problems. + 1) flush the ibus after every write operation. + 2) implement support for endpoint 5 transfers, and + optionally for emulating bulk transfers over this + endpoint (normally this endpoint is used for interrupt + transfers). + All under the control of suitable configuration options, + which by default are set to work around the hardware problems. + +2001-08-06 Bart Veer <bartv@redhat.com> + + * src/usbs_upd985xx.c: + Add initial support for USB testing. + +2001-07-02 Bart Veer <bartv@redhat.com> + + * doc/usbs_upd985xx.sgml: + Document alignment restrictions for receive buffers, and + the optional platform-specific INIT macro. + + * doc/devs-usb-nec-upd985xx.html + Regenerate following above change. + + * src/usbs_upd985xx.c: + Remove FIXME related to cacheline alignment, not an issue + for MIPS. + Remove FIXME related to platform-specific USB startup, + now implemented. + +2001-06-28 Bart Veer <bartv@redhat.com> + + * src/usbs_upd985xx.c, cdl/usbs_upd985xx.cdl: + Device driver now functional. + +2001-05-22 Bart Veer <bartv@redhat.com> + + * USB device driver work started. + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/cdl/usbs_upd985xx.cdl b/ecos/packages/devs/usb/nec_upd985xx/current/cdl/usbs_upd985xx.cdl new file mode 100644 index 0000000..55ce56f --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/cdl/usbs_upd985xx.cdl @@ -0,0 +1,291 @@ +# ==================================================================== +# +# usbs_upd985xx.cdl +# +# USB device driver for the NEC uPD985xx family of processors. +# +# ==================================================================== +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +# ==================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): bartv +# Original data: bartv +# Contributors: +# Date: 2001-05-22 +# +#####DESCRIPTIONEND#### +# ==================================================================== + +cdl_package CYGPKG_DEVS_USB_UPD985XX { + display "NEC uPD985xx USB Device Driver" + include_dir "cyg/io/usb" + parent CYGPKG_USB + implements CYGHWR_IO_USB_SLAVE + doc ref/devs-usb-nec-upd985xx.html + + # Make sure that we are running on the right hardware. + requires CYGPKG_HAL_MIPS + requires CYGPKG_HAL_MIPS_UPD985XX + + description " + The NEC uPD985xx family of processors implements an + on-chip USB device controller, facilitating the use of this + processor in USB peripherals. This package provides a + suitable eCos device driver." + + cdl_component CYGFUN_DEVS_USB_UPD985XX_EP0 { + display "Support the control endpoint 0" + default_value CYGINT_IO_USB_SLAVE_CLIENTS + requires CYGPKG_IO_USB CYGPKG_IO_USB_SLAVE + compile usbs_upd985xx.c + compile -library=libextras.a usbs_upd985xx_data.cxx + description " + Enable support for endpoint 0. If this support is disabled + then the entire USB port is unusable." + + cdl_option CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 0" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 0 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and ioctl calls then a devtab entry is needed. + " + } + + cdl_option CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE { + display "Size of endpoint 0 control packets" + flavor data + default_value 8 + legal_values { 8 16 32 64 } + description " + Control messages on endpoint 0 are split into packets of + 8, 16, 32 or 64 bytes - these are the values permitted by the + USB specification. The same packet size is used for both + receives and transmits. This value must also be used for the + max_packet_size field of the device descriptor in the + application's USB enumeration data. + + According to section 5.5.5 of the USB specification, if a new + control message is received before the previous transaction + has completed then the previous transaction must be aborted. + If that transaction involved transferring data to the host + then there is a problem: that data may still be queued for + transmission and the NEC USB device appears to provide no way + of aborting that transmit. The problem is unlikely to arise + with normal usage, but may be detected by compliance + testsuites. Increasing the packet size to its maximum value + of 64 reduces the probability of failure. + " + } + + cdl_option CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE { + display "Size of statically-allocated endpoint 0 transmit buffer" + flavor data + default_value 256 + requires { CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE >= CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE } + description " + The implementation of the support for endpoint 0 uses + a single static buffer to hold the response to the + current control message. Typically this buffer can be + fairly small since replies to control messages tend to + be small: typically some tens of bytes for the enumeration + data, perhaps a bit more for unicode-encoded string + descriptors. However if some application-specific protocol + depends on larger control messages then this buffer + size may need to be increased. + " + } + + cdl_option CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE { + display "Size of statically-allocated endpoint 0 transmit buffer" + flavor data + default_value 64 + requires { CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE >= CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE } + description " + The implementation of the support for endpoint 0 uses + a single static buffer to hold incoming control messages. + Typically this buffer can be small: standard control messages + involve an initial eight-byte header, sometimes followed by + a small amount of additional data. However if some + application-specific protocol depends on larger control + messages then this buffer size may need to be increased. + " + } + } + cdl_component CYGPKG_DEVS_USB_UPD985XX_EP3 { + display "Support endpoint 3, used for slave->host IN bulk transfers" + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + requires CYGFUN_DEVS_USB_UPD985XX_EP0 + default_value 0 + description " + In the uPD985xx USB implementation endpoint 3 can only be + used for slave->host IN bulk transfers. If the intended application + only involves host->slave transfers then this endpoint is + not relevant. + + By default this endpoint is disabled: according to NEC erratum + U3 there may be problems when doing transfers of 192 bytes or + greater. Instead the interrupt endpoint 5 is used, with + software emulation of the bulk protocol. If the application + involves only transfers of less than 192 bytes then endpoint + 3 can be enabled. + " + + cdl_option CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 3" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 3 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed. + " + } + } + + cdl_component CYGPKG_DEVS_USB_UPD985XX_EP4 { + display "Support endpoint 4, used for slave->host OUT bulk transfers" + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + requires CYGFUN_DEVS_USB_UPD985XX_EP0 + default_value CYGFUN_DEVS_USB_UPD985XX_EP0 + description " + In the uPD985xx USB implementation endpoint 4 can only be + used for host->slave OUT bulk transfers. If the intended application + only involves slave->host transfers then the support for + endpoint 4 can be disabled. Note that this does not affect + control messages which always go via endpoint 0." + + cdl_option CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 4" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 4 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed." + } + } + + cdl_component CYGPKG_DEVS_USB_UPD985XX_EP5 { + display "Support endpoint 5, used for slave->host IN transfers" + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + requires CYGFUN_DEVS_USB_UPD985XX_EP0 + default_value CYGFUN_DEVS_USB_UPD985XX_EP0 + description " + In the uPD985xx USB implementation endpoint 5 can only be + used for slave->host communication. This endpoint is + intended primarily for interrupt transfers, but can be + used for bulk transfers given a small amount of additional + software support." + + cdl_option CYGIMP_DEVS_USB_UPD985XX_EP5_BULK { + display "Implement bulk transfers rather than interrupt transfers" + default_value 1 + description " + Endpoint 5 is normally used for interrupt transfers, which + are limited to 64 bytes. However with a little bit of software + support it is possible to implement bulk transfers instead. + With some revisions of the silicon this provides a workaround + for problems with endpoint 3 - NEC erratum U3 should be consulted + for additional information." + } + + cdl_option CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 5" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 5 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed. + " + } + } + + cdl_option CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME { + display "Base name for devtab entries" + flavor data + active_if { CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY + } + default_value { "\"/dev/usbs\"" } + description " + If the uPD985xx USB device driver package provides devtab + entries for any of the endpoints then this option gives + control over the names of these entries. By default the + endpoints will be called \"/dev/usbs0c\", \"/dev/usbs3w\" + and \"/dev/usbs4r\" (assuming all three endpoints are + enabled. The common part \"/dev/usbs\" is determined + by this configuration option. It may be necessary to + change this if there are multiple USB slave-side + devices on the target hardware to prevent a name clash. + " + } + + cdl_option CYGIMP_DEVS_USB_UPD985XX_IBUS_WRITE_LIMIT { + display "Work around potential hardware problem with IBUS writes" + default_value 1 + description " + With some revisions of the silicon there may be problems if + a device driver performs multiple writes to the IBUS in + quick succession. By default this driver avoids such problems, + at the cost of some cpu cycles and a small amount of extra code. + NEC erratum S1 should be consulted for more details." + } + + cdl_option CYGIMP_DEVS_USB_UPD985XX_SERIALIZE_TRANSMITS { + display "Work around potential hardware problem with concurrent transmits" + default_value 1 + description " + With some revisions of the silicon there may be problems if + the device driver is asked to perform concurrent slave->host + transmissions on different endpoints, for example sending + a reply to a control message while there is a bulk transfer + in progress. This option enables a workaround for the + problem by ensuring that only one transmit operation is in + progress at any one time. NEC errata U3 and U4 should be + consulted for more details." + } +} diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/doc/devs-usb-nec-upd985xx.html b/ecos/packages/devs/usb/nec_upd985xx/current/doc/devs-usb-nec-upd985xx.html new file mode 100644 index 0000000..6317b66 --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/doc/devs-usb-nec-upd985xx.html @@ -0,0 +1,372 @@ +<!-- Copyright (C) 2001 Free Software Foundation, Inc. --> +<!-- This material may be distributed only subject to the terms --> +<!-- and conditions set forth in the Open Publication License, v1.0 --> +<!-- or later (the latest version is presently available at --> +<!-- http://www.opencontent.org/openpub/). --> +<!-- Distribution of substantively modified versions of this --> +<!-- document is prohibited without the explicit permission of the --> +<!-- copyright holder. --> +<!-- Distribution of the work or derivative of the work in any --> +<!-- standard (paper) book form is prohibited unless prior --> +<!-- permission is obtained from the copyright holder. --> +<HTML +><HEAD +><TITLE +>NEC uPD985xx USB Device Driver</TITLE +><meta name="MSSmartTagsPreventParsing" content="TRUE"> +<META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.64 +"></HEAD +><BODY +CLASS="REFENTRY" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><H1 +><A +NAME="DEVS-USB-NEC-UPD985XX" +>NEC uPD985xx USB Device Driver</A +></H1 +><DIV +CLASS="REFNAMEDIV" +><A +NAME="AEN4" +></A +><H2 +>Name</H2 +>NEC uPD985xx USB Support -- Device driver for the on-chip NEC uPD985xx USB device</DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN7" +></A +><H2 +>NEC uPD985xx USB Hardware</H2 +><P +>The NEC uPD985xx family of processors is supplied with an on-chip USB +slave device, the UDC (USB Device Controller). This supports seven +endpoints. Endpoint 0 can only be used for control messages. Endpoints +1 and 2 are for isochronous transmits and receives respectively. +Endpoints 3 and 4 support bulk transmits and receives. Endpoints 5 and +6 normally support interrupt transmits and receives,but endpoint 5 can +also be configured to support bulk transmits. At this time only the +control endpoint 0, the bulk endpoints 3 and 4, and the interrupt +endpoint 5 are supported.</P +></DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN10" +></A +><H2 +>Endpoint Data Structures</H2 +><P +>The uPD985xx USB device driver can provide up to four data structures +corresponding to the four supported endpoints: a +<SPAN +CLASS="STRUCTNAME" +>usbs_control_endpoint</SPAN +> structure +<TT +CLASS="VARNAME" +>usbs_upd985xx_ep0</TT +>; +<SPAN +CLASS="STRUCTNAME" +>usbs_tx_endpoint</SPAN +> structures +<TT +CLASS="VARNAME" +>usbs_upd985xx_ep3</TT +> and +<TT +CLASS="VARNAME" +>usbs_upd985xx_ep5</TT +>; and a +<SPAN +CLASS="STRUCTNAME" +>usbs_rx_endpoint</SPAN +> +<TT +CLASS="VARNAME" +>usbs_upd985xx_ep4</TT +>. The header file +<TT +CLASS="FILENAME" +>cyg/io/usb/usbs_nec_upd985xx.h</TT +> +provides declarations for these.</P +><P +>Not all applications will require support for all the endpoints. For +example, if the intended use of the UDC only involves peripheral to +host transfers then <TT +CLASS="LITERAL" +>usbs_upd985xx_ep4</TT +> is redundant. +The device driver provides configuration options to control the +presence of each endpoint:</P +><P +></P +><OL +TYPE="1" +><LI +><P +>Endpoint 0 is controlled by +<TT +CLASS="LITERAL" +>CYGFUN_DEVS_USB_UPD985XX_EP0</TT +>. This defaults to +enabled if there are any higher-level packages that require USB +hardware or if the global preference +<TT +CLASS="LITERAL" +>CYGGLO_IO_USB_SLAVE_APPLICATION</TT +> is enabled, +otherwise it is disabled. Usually this has the desired effect. It may +be necessary to override this in special circumstances, for example if +the target board uses an external USB chip in preference to the UDC +and it is that external chip's device driver that should be used +rather than the on-chip UDC. It is not possible to disable endpoint 0 +and at the same time enable one or both of the other endpoints, since +a USB device is only usable if it can process the standard control +messages.</P +></LI +><LI +><P +>Endpoint 3 is controlled by +<TT +CLASS="LITERAL" +>CYGPKG_DEVS_USB_UPD985XX_EP3</TT +>. By default this +endpoint is disabled: according to NEC erratum U3 there may be +problems when attempting bulk transfers of 192 bytes or greater. As an +alternative the device driver provides endpoint 5 configured to +support bulk transfers. Endpoint 3 can be enabled if the application +only requires bulk transfers of less than 192 bytes, or if this +erratum is not applicable to the system being developed for other +reasons.</P +></LI +><LI +><P +>Similarly endpoint 4 is controlled by +<TT +CLASS="LITERAL" +>CYGPKG_DEVS_USB_UPD985XX_EP4</TT +>. This is enabled by +default whenever endpoint 0 is enabled, but it can be disabled +manually.</P +></LI +><LI +><P +>Endpoint 5 is controlled by +<TT +CLASS="LITERAL" +>CYGPKG_DEVS_USB_UPD985XX_EP5</TT +>. This is enabled by +default whenever endpoint 0 is enabled, but it can be disabled +manually. There is also a configuration option +<TT +CLASS="LITERAL" +>CYGIMP_DEVS_USB_UPD985XX_EP5_BULK</TT +>, enabled by +default. This option allows the endpoint to be used for bulk +transfers rather than interrupt transfers.</P +></LI +></OL +><P +>The uPD985xx USB device driver implements the interface specified by the +common eCos USB Slave Support package. The documentation for that +package should be consulted for further details. </P +><P +>The device driver assumes a bulk packet size of 64 bytes, so this +value should be used in the endpoint descriptors in the enumeration +data provided by application code. The device driver also assumes +a control packet size of eight bytes, and again this should be +reflected in the enumeration data. If endpoint 5 is configured for +interrupt rather than bulk transfers then the maximum packet size is +limited to 64 bytes by the USB standard.</P +></DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN40" +></A +><H2 +>Devtab Entries</H2 +><P +>In addition to the endpoint data structures the uPD985xx USB device +driver can also provide devtab entries for each endpoint. This allows +higher-level code to use traditional I/O operations such as +<TT +CLASS="FUNCTION" +>open</TT +>/<TT +CLASS="FUNCTION" +>read</TT +>/<TT +CLASS="FUNCTION" +>write</TT +> +rather than the USB-specific non-blocking functions like +<TT +CLASS="FUNCTION" +>usbs_start_rx_buffer</TT +>. These devtab entries are +optional since they are not always required. The relevant +configuration options are +<TT +CLASS="LITERAL" +>CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY</TT +>, +<TT +CLASS="LITERAL" +>CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY</TT +>, +<TT +CLASS="LITERAL" +>CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY</TT +>, and +<TT +CLASS="LITERAL" +>CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY</TT +>. By +default these devtab entries are provided if the global preference +<TT +CLASS="LITERAL" +>CYGGLO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES</TT +> is enabled, +which is usually the case. Obviously a devtab entry for a given +endpoint will only be provided if the underlying endpoint is enabled. +For example, there will not be a devtab entry for endpoint 4 if +<TT +CLASS="LITERAL" +>CYGPKG_DEVS_USB_UPD985XX_EP4</TT +> is disabled.</P +><P +>The names for the devtab entries are determined by using a +configurable base name and appending <TT +CLASS="LITERAL" +>0c</TT +>, +<TT +CLASS="LITERAL" +>3w</TT +>, <TT +CLASS="LITERAL" +>4r</TT +> or <TT +CLASS="LITERAL" +>5w</TT +>. +The base name is determined by the configuration option +<TT +CLASS="LITERAL" +>CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME</TT +> and has a +default value of <TT +CLASS="LITERAL" +>/dev/usbs</TT +>, so the devtab entry for +endpoint 4 would default to <TT +CLASS="LITERAL" +>/dev/usbs4r</TT +>. If the +target hardware involves multiple USB devices then application +developers may have to change the base name to prevent a name clash +with other USB device drivers.</P +></DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN61" +></A +><H2 +>Restrictions</H2 +><P +>The current device driver imposes a restriction on certain bulk +receives on endpoint 4. If the protocol being used involves +variable-length transfers, in other words if the host is allowed to +send less data than a maximum-sized transfer, then the buffer passed +to the device driver for receives must be aligned to a 16-byte +cacheline boundary and it must be a multiple of this 16-byte cacheline +size. This restriction does not apply if the protocol only involves +fixed-size transfers.</P +></DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN64" +></A +><H2 +>Optional Hardware Workarounds</H2 +><P +>The NEC errata list a number of other problems that affect the +USB device driver. The device driver contains workarounds for these, +which are enabled by default but can be disabled if the application +developer knows that the relevant errata are not relevant to the +system being developed.</P +><P +>Erratum S1 lists a possible problem if the device driver attempts +multiple writes to the USB hardware. This is circumvented by a +dummy read operation after every write. If this workaround is not +required then the configuration option +<TT +CLASS="LITERAL" +>CYGIMP_DEVS_USB_UPD985XX_IBUS_WRITE_LIMIT</TT +> can be disabled.</P +><P +>Errata U3 and U4 describe various problems related to concurrent +transmissions on different endpoints. By default the device driver +works around this by serializing all transmit operations. For example +if the device driver needs to send a response to a control message on +endpoint 0 while there is an ongoing bulk transfer on endpoint 5, the +response is delayed until the bulk transfer has completed. Under +typical operating conditions this does not cause any problems: +endpoint 0 traffic usually happens only during initialization, when +the target is connected to the host, while endpoint 5 traffic only +happens after initialization. However if transmit serialization is +inappropriate for the system being developed then it can be disabled +using the configuration option +<TT +CLASS="LITERAL" +>CYGIMP_DEVS_USB_UPD985XX_SERIALIZE_TRANSMITS</TT +>. </P +></DIV +><DIV +CLASS="REFSECT1" +><A +NAME="AEN71" +></A +><H2 +>Platform Dependencies</H2 +><P +>On some platforms it is necessary for the low-level USB device driver +to perform some additional operations during start-up. For example it +may be necessary to manipulate one of the processor's GPIO lines +before the host can detect a new USB peripheral and attempt to +communicate with it. This avoids problems if the target involves a +significant amount of work prior to device driver initialization, for +example a power-on self-test sequence. If the USB host attempted to +contact the target before the USB device driver had been initialized, +it would fail to get the expected responses and conclude that the +target was not a functional USB peripheral.</P +><P +>Platform-specific initialization code can be provided via a macro +<TT +CLASS="FUNCTION" +>UPD985XX_USB_PLATFORM_INIT</TT +>. Typically this macro +would be defined in the platform HAL's header file +<TT +CLASS="FILENAME" +>cyg/hal/plf_io.h</TT +>. If the +current platform defines such a macro, the USB device driver will +invoke it during the endpoint 0 start-up operation.</P +></DIV +></BODY +></HTML +> diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/doc/makefile b/ecos/packages/devs/usb/nec_upd985xx/current/doc/makefile new file mode 100644 index 0000000..ed36ede --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/doc/makefile @@ -0,0 +1,54 @@ +#============================================================================= +# +# makefile +# +# For building the NEC uPD985xx USB device driver documentation +# +#============================================================================= +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +#============================================================================= +#####DESCRIPTIONBEGIN#### +# +# Author(s): bartv +# Date: 2001-01-11 +#####DESCRIPTIONEND#### +#============================================================================= + +TOPLEVEL := ../../../../.. +MAIN_SGML := usbs_upd985xx.sgml +MAIN_HTML := devs-usb-upd985xx.html +MAIN_PDF := devs-usb-upd985xx.pdf +OTHER_SGML := +PICTURES := + +include $(TOPLEVEL)/pkgconf/rules.doc diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/doc/usbs_upd985xx.sgml b/ecos/packages/devs/usb/nec_upd985xx/current/doc/usbs_upd985xx.sgml new file mode 100644 index 0000000..74a4e4c --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/doc/usbs_upd985xx.sgml @@ -0,0 +1,257 @@ +<!-- DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" --> + +<!-- {{{ Banner --> + +<!-- =============================================================== --> +<!-- --> +<!-- usbs_nec_upd9850x.sgml --> +<!-- --> +<!-- Documentation for the NEC uPD9850x USB Device Driver. --> +<!-- --> +<!-- =============================================================== --> +<!-- ####ECOSDOCCOPYRIGHTBEGIN#### --> +<!-- =============================================================== --> +<!-- Copyright (C) 2001, 2002 Free Software Foundation, Inc. --> +<!-- This material may be distributed only subject to the terms --> +<!-- and conditions set forth in the Open Publication License, v1.0 --> +<!-- or later (the latest version is presently available at --> +<!-- http://www.opencontent.org/openpub/) --> +<!-- Distribution of the work or derivative of the work in any --> +<!-- standard (paper) book form is prohibited unless prior --> +<!-- permission obtained from the copyright holder --> +<!-- =============================================================== --> +<!-- ####ECOSDOCCOPYRIGHTEND#### --> +<!-- =============================================================== --> +<!-- #####DESCRIPTIONBEGIN#### --> +<!-- --> +<!-- Author(s): bartv --> +<!-- Contact(s): bartv --> +<!-- Date: 2001/05/22 --> +<!-- Version: 0.01 --> +<!-- --> +<!-- ####DESCRIPTIONEND#### --> +<!-- =============================================================== --> + +<!-- }}} --> + +<part id="devs-usb-nec-upd985xx-ref"> +<!-- reference id="devs-usb-nec-upd985xx-ref" --> + <title>NEC uPD985xx USB Device Driver</title> + +<refentry id="devs-usb-nec-upd985xx"> +<refmeta> +<refentrytitle>NEC uPD985xx USB Device Driver</refentrytitle> +</refmeta> +<refnamediv> +<refname>NEC uPD985xx USB Support</refname> +<refpurpose>Device driver for the on-chip NEC uPD985xx USB device</refpurpose> +</refnamediv> + +<refsect1><title>NEC uPD985xx USB Hardware</title> +<para> +The NEC uPD985xx family of processors is supplied with an on-chip USB +slave device, the UDC (USB Device Controller). This supports seven +endpoints. Endpoint 0 can only be used for control messages. Endpoints +1 and 2 are for isochronous transmits and receives respectively. +Endpoints 3 and 4 support bulk transmits and receives. Endpoints 5 and +6 normally support interrupt transmits and receives, but endpoint 5 can +also be configured to support bulk transmits. At this time only the +control endpoint 0, the bulk endpoints 3 and 4, and the interrupt +endpoint 5 are supported. +</para> +</refsect1> + +<refsect1><title>Endpoint Data Structures</title> +<para> +The uPD985xx USB device driver can provide up to four data structures +corresponding to the four supported endpoints: a +<structname>usbs_control_endpoint</structname> structure +<varname>usbs_upd985xx_ep0</varname>; +<structname>usbs_tx_endpoint</structname> structures +<varname>usbs_upd985xx_ep3</varname> and +<varname>usbs_upd985xx_ep5</varname>; and a +<structname>usbs_rx_endpoint</structname> +<varname>usbs_upd985xx_ep4</varname>. The header file +<filename class="headerfile">cyg/io/usb/usbs_nec_upd985xx.h</filename> +provides declarations for these. +</para> +<para> +Not all applications will require support for all the endpoints. For +example, if the intended use of the UDC only involves peripheral to +host transfers then <literal>usbs_upd985xx_ep4</literal> is redundant. +The device driver provides configuration options to control the +presence of each endpoint: +</para> +<orderedlist> +<listitem> +<para> +Endpoint 0 is controlled by +<literal>CYGFUN_DEVS_USB_UPD985XX_EP0</literal>. This defaults to +enabled if there are any higher-level packages that require USB +hardware or if the global preference +<literal>CYGGLO_IO_USB_SLAVE_APPLICATION</literal> is enabled, +otherwise it is disabled. Usually this has the desired effect. It may +be necessary to override this in special circumstances, for example if +the target board uses an external USB chip in preference to the UDC +and it is that external chip's device driver that should be used +rather than the on-chip UDC. It is not possible to disable endpoint 0 +and at the same time enable one or both of the other endpoints, since +a USB device is only usable if it can process the standard control +messages. +</para> +</listitem> +<listitem> +<para> +Endpoint 3 is controlled by +<literal>CYGPKG_DEVS_USB_UPD985XX_EP3</literal>. By default this +endpoint is disabled: according to NEC erratum U3 there may be +problems when attempting bulk transfers of 192 bytes or greater. As an +alternative the device driver provides support for endpoint 5, +configured to allow bulk transfers. Endpoint 3 can be enabled if the +application only requires bulk transfers of less than 192 bytes, or if +this erratum is not applicable to the system being developed for other +reasons. +</para> +</listitem> +<listitem> +<para> +Endpoint 4 is controlled by +<literal>CYGPKG_DEVS_USB_UPD985XX_EP4</literal>. This is enabled by +default whenever endpoint 0 is enabled, but it can be disabled +manually. +</para> +</listitem> +<listitem> +<para> +Endpoint 5 is controlled by +<literal>CYGPKG_DEVS_USB_UPD985XX_EP5</literal>. This is enabled by +default whenever endpoint 0 is enabled, but it can be disabled +manually. There is also a configuration option +<literal>CYGIMP_DEVS_USB_UPD985XX_EP5_BULK</literal>, enabled by +default. This option allows the endpoint to be used for bulk +transfers rather than interrupt transfers. +</para> +</listitem> +</orderedlist> +<para> +The uPD985xx USB device driver implements the interface specified by the +common eCos USB Slave Support package. The documentation for that +package should be consulted for further details. +</para> +<para> +The device driver assumes a bulk packet size of 64 bytes, so this +value should be used in the endpoint descriptors in the enumeration +data provided by application code. The device driver also assumes +a control packet size of eight bytes, and again this should be +reflected in the enumeration data. If endpoint 5 is configured for +interrupt rather than bulk transfers then the maximum packet size is +limited to 64 bytes by the USB standard. +</para> +</refsect1> + +<refsect1><title>Devtab Entries</title> +<para> +In addition to the endpoint data structures the uPD985xx USB device +driver can also provide devtab entries for each endpoint. This allows +higher-level code to use traditional I/O operations such as +<function>open</function>/<function>read</function>/<function>write</function> +rather than the USB-specific non-blocking functions like +<function>usbs_start_rx_buffer</function>. These devtab entries are +optional since they are not always required. The relevant +configuration options are +<literal>CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY</literal>, +<literal>CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY</literal>, +<literal>CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY</literal>, and +<literal>CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY</literal>. By +default these devtab entries are provided if the global preference +<literal>CYGGLO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES</literal> is enabled, +which is usually the case. Obviously a devtab entry for a given +endpoint will only be provided if the underlying endpoint is enabled. +For example, there will not be a devtab entry for endpoint 4 if +<literal>CYGPKG_DEVS_USB_UPD985XX_EP4</literal> is disabled. +</para> +<para> +The names for the devtab entries are determined by using a +configurable base name and appending <literal>0c</literal>, +<literal>3w</literal>, <literal>4r</literal> or <literal>5w</literal>. +The base name is determined by the configuration option +<literal>CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME</literal> and has a +default value of <literal>/dev/usbs</literal>, so the devtab entry for +endpoint 4 would default to <literal>/dev/usbs4r</literal>. If the +target hardware involves multiple USB devices then application +developers may have to change the base name to prevent a name clash +with other USB device drivers. +</para> +</refsect1> + +<refsect1><title>Restrictions</title> +<para> +The current device driver imposes a restriction on certain bulk +receives on endpoint 4. If the protocol being used involves +variable-length transfers, in other words if the host is allowed to +send less data than a maximum-sized transfer, then the buffer passed +to the device driver for receives must be aligned to a 16-byte +cacheline boundary and it must be a multiple of this 16-byte cacheline +size. This restriction does not apply if the protocol only involves +fixed-size transfers. +</para> +</refsect1> + +<refsect1><title>Optional Hardware Workarounds</title> +<para> +The NEC errata list a number of other problems that affect the USB +device driver. The device driver contains workarounds for these, which +are enabled by default but can be disabled if the application +developer knows that the errata are not relevant to the system being +developed. +</para> +<para> +Erratum S1 lists a possible problem if the device driver attempts +multiple writes to the USB hardware. This is circumvented by a +dummy read operation after every write. If the workaround is not +required then the configuration option +<literal>CYGIMP_DEVS_USB_UPD985XX_IBUS_WRITE_LIMIT</literal> can be disabled. +</para> +<para> +Errata U3 and U4 describe various problems related to concurrent +transmissions on different endpoints. By default the device driver +works around this by serializing all transmit operations. For example +if the device driver needs to send a response to a control message on +endpoint 0 while there is an ongoing bulk transfer on endpoint 5, the +response is delayed until the bulk transfer has completed. Under +typical operating conditions this does not cause any problems: +endpoint 0 traffic usually happens only during initialization, when +the target is connected to the host, while endpoint 5 traffic only +happens after initialization. However if transmit serialization is +inappropriate for the system being developed then it can be disabled +using the configuration option +<literal>CYGIMP_DEVS_USB_UPD985XX_SERIALIZE_TRANSMITS</literal>. +</para> +</refsect1> + +<refsect1><title>Platform Dependencies</title> +<para> +On some platforms it is necessary for the low-level USB device driver +to perform some additional operations during start-up. For example it +may be necessary to manipulate one of the processor's GPIO lines +before the host can detect a new USB peripheral and attempt to +communicate with it. This avoids problems if the target involves a +significant amount of work prior to device driver initialization, for +example a power-on self-test sequence. If the USB host attempted to +contact the target before the USB device driver had been initialized, +it would fail to get the expected responses and conclude that the +target was not a functional USB peripheral. +</para> +<para> +Platform-specific initialization code can be provided via a macro +<function>UPD985XX_USB_PLATFORM_INIT</function>. Typically this macro +would be defined in the platform HAL's header file +<filename class="headerfile">cyg/hal/plf_io.h</filename>. If the +current platform defines such a macro, the USB device driver will +invoke it during the endpoint 0 start-up operation. +</para> +</refsect1> + +</refentry> +</part> +<!-- /reference --> diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/include/usbs_upd985xx.h b/ecos/packages/devs/usb/nec_upd985xx/current/include/usbs_upd985xx.h new file mode 100644 index 0000000..96b1b9d --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/include/usbs_upd985xx.h @@ -0,0 +1,78 @@ +#ifndef CYGONCE_USBS_UPD985XX_H +# define CYGONCE_USBS_UPD985XX_H +//========================================================================== +// +// include/usbs_upd985xx.h +// +// The interface exported by the NEC uPD985xx USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2000-05-22 +// Purpose: +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/usb/usbs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The NEC UPD985xx family comes with on-chip USB slave support. This + * provides seven endpoints. Endpoint 0 can only be used for control + * messages. Endpoints 1 and 2 can only be used for isochronous + * transfers, and are not supported at this time. Endpoints 3 and 4 + * are for bulk transfers, although endpoint 3 is normally disabled + * and endpoint 5 is used for bulk transfers instead. Endpoints 5 + * and 6 are normally used for interrupt transfers, but endpoint 5 can + * also be used bulk transfers. Endpoint 6 is not currently supported. + */ +extern usbs_control_endpoint usbs_upd985xx_ep0; +extern usbs_tx_endpoint usbs_upd985xx_ep3; +extern usbs_rx_endpoint usbs_upd985xx_ep4; +extern usbs_tx_endpoint usbs_upd985xx_ep5; + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + + +#endif /* CYGONCE_USBS_UPD985XX_H */ diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx.c b/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx.c new file mode 100644 index 0000000..b104edf --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx.c @@ -0,0 +1,2687 @@ +//========================================================================== +// +// usbs_upd985xx.c +// +// Driver for the NEC uPD985xx USB device +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2001-05-22 +// +// This code implements support for the on-chip USB port on the NEC +// uPD985xx family of processors. The code has been developed on the +// uPD98503 and may or may not work on other members of the uPD985xx +// family. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/infra/cyg_type.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/cyg_trac.h> +#include <cyg/infra/diag.h> + +#include <pkgconf/hal_mips_upd985xx.h> +#include <pkgconf/devs_usb_upd985xx.h> + +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_cache.h> +#include <cyg/error/codes.h> + +#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> + +// For memcpy() +#include <string.h> + +// ---------------------------------------------------------------------------- +// Toplevel FIXME's. +// +// The device supports remote wakeups, but this driver does not. Note that +// the device GET_STATUS, SET_FEATURE and CLEAR_FEATURE operations are +// affected by remote wakeup support. + +// ---------------------------------------------------------------------------- +// Debugging-related odds and ends. +#if 0 +# define DBG(a) diag_printf a +#else +# define DBG(a) +#endif + +// ---------------------------------------------------------------------------- +// Hardware definitions. +// +// The NEC uPD985Xx on-chip USB device provides the following: +// +// endpoint 0 - control messages only +// endpoint 1 - isochronous transmits +// endpoint 2 - isochronous receives +// endpoint 3 - bulk transmits +// endpoint 4 - bulk receives +// endpoint 5 - interrupt transmits +// endpoint 6 - interrupt receives + +// All acess to the USB controller registers goes via the IBUS, which +// always runs little-endian. Hence when the CPU is running +// little-endian no extra work is needed, but when running big-endian +// all register updates involve swapping. +#ifdef CYGPKG_HAL_MIPS_LSBFIRST +# define IBUS_SWAP32(_a_) (_a_) +# define IBUS_SWAPPTR(_type_, _a_) (_a_) +#else +# error IBUS_SWAP32() needs to be defined and tested +#endif + +// Move an address to kseg1. Or'ing in the relevant bits means +// that this macro will work even if the specified address is +// already in kseg1 +#define MIPS_TO_UNCACHED(_a_) ((void*)(((cyg_uint32)(_a_)) | MIPS_KSEG1_BASE)) + +// For now access the various registers directly. A structure might +// be marginally more inefficient in that if a function accesses +// several registers this could be handled by a single base plus +// offsets, rather than by separate addresses. +#define USBS_REGISTER(_a_) ((volatile cyg_uint32*)(MIPS_IO_BASE + UPD985XX_SYSUSB_OFF + (_a_))) +#define USBS_ADDRREG(_type_, _a_) ((_type_* volatile*)(MIPS_IO_BASE + UPD985XX_SYSUSB_OFF + (_a_))) + +#define USBS_GMR USBS_REGISTER(0x0000) +#define USBS_VER USBS_REGISTER(0x0004) +#define USBS_GSR1 USBS_REGISTER(0x0010) +#define USBS_IMR1 USBS_REGISTER(0x0014) +#define USBS_GSR2 USBS_REGISTER(0x0018) +#define USBS_IMR2 USBS_REGISTER(0x001C) +#define EP0_CR USBS_REGISTER(0x0020) +#define EP1_CR USBS_REGISTER(0x0024) +#define EP2_CR USBS_REGISTER(0x0028) +#define EP3_CR USBS_REGISTER(0x002C) +#define EP4_CR USBS_REGISTER(0x0030) +#define EP5_CR USBS_REGISTER(0x0034) +#define EP6_CR USBS_REGISTER(0x0038) +#define USBS_CMR USBS_REGISTER(0x0040) +#define USBS_CA USBS_ADDRREG(void, 0x0044) +#define USBS_TEPSR USBS_REGISTER(0x0048) +#define USBS_RP0IR USBS_REGISTER(0x0050) +#define USBS_RP0AR USBS_ADDRREG(RxBufferDescriptor, 0x0054) +#define USBS_RP1IR USBS_REGISTER(0x0058) +#define USBS_RP1AR USBS_ADDRREG(RxBufferDescriptor, 0x005C) +#define USBS_RP2IR USBS_REGISTER(0x0060) +#define USBS_RP2AR USBS_ADDRREG(RxBufferDescriptor, 0x0064) +#define USBS_TMSA USBS_ADDRREG(TxMailbox, 0x0070) +#define USBS_TMBA USBS_ADDRREG(TxMailbox, 0x0074) +#define USBS_TMRA USBS_ADDRREG(TxMailbox, 0x0078) +#define USBS_TMWA USBS_ADDRREG(TxMailbox, 0x007C) +#define USBS_RMSA USBS_ADDRREG(RxMailbox, 0x0080) +#define USBS_RMBA USBS_ADDRREG(RxMailbox, 0x0084) +#define USBS_RMRA USBS_ADDRREG(RxMailbox, 0x0088) +#define USBS_RMWA USBS_ADDRREG(RxMailbox, 0x008C) + +// There are additional counter registers from offset 0x100 onwards. +// These registers are not used by the driver, and anyway may not be +// available on all hardware. + +// The General Mode register USBS_GMR +#define USBS_GMR_VT (0x01 << 23) +#define USBS_GMR_FA_MASK (0x7F << 16) +#define USBS_GMR_FA_SHIFT 16 +#define USBS_GMR_SOFINTVL_MASK (0x00FF << 8) +#define USBS_GMR_SOFINTVL_SHIFT 8 +#define USBS_GMR_SOFINTVL_DEFAULT_VALUE (0x18 << 8) +#define USBS_GMR_AU (0x01 << 2) +#define USBS_GMR_LE (0x01 << 1) +#define USBS_GMR_RR (0x01 << 0) + +// The Frame Number/Version register +#define USBS_VER_UVER_MASK (0x0FFFF << 16) +#define USBS_VER_UVER_SHIFT 16 +#define USBS_VER_UFNR_MASK (0x03FF << 0) +#define USBS_VER_UFNR_SHIFT 0 + +// General status register 1 +#define USBS_GSR1_GSR2 (0x01 << 31) +#define USBS_GSR1_TMF (0x01 << 23) +#define USBS_GSR1_RMF (0x01 << 22) +#define USBS_GSR1_RPE2 (0x01 << 21) +#define USBS_GSR1_RPE1 (0x01 << 20) +#define USBS_GSR1_RPE0 (0x01 << 19) +#define USBS_GSR1_RPA2 (0x01 << 18) +#define USBS_GSR1_RPA1 (0x01 << 17) +#define USBS_GSR1_RPA0 (0x01 << 16) +#define USBS_GSR1_DER (0x01 << 10) +#define USBS_GSR1_EP2FO (0x01 << 9) +#define USBS_GSR1_EP1FU (0x01 << 8) +#define USBS_GSR1_EP6RF (0x01 << 7) +#define USBS_GSR1_EP5TF (0x01 << 6) +#define USBS_GSR1_EP4RF (0x01 << 5) +#define USBS_GSR1_EP3TF (0x01 << 4) +#define USBS_GSR1_EP2RF (0x01 << 3) +#define USBS_GSR1_EP1TF (0x01 << 2) +#define USBS_GSR1_EP0RF (0x01 << 1) +#define USBS_GSR1_EP0TF (0x01 << 0) + +// The Interrupt mask 1 bits correspond to the GSR1 bits above + +// General status register 2 +#define USBS_GSR2_FW (0x01 << 21) +#define USBS_GSR2_IFN (0x01 << 20) +#define USBS_GSR2_IEA (0x01 << 19) +#define USBS_GSR2_URSM (0x01 << 18) +#define USBS_GSR2_URST (0x01 << 17) +#define USBS_GSR2_USPD (0x01 << 16) +#define USBS_GSR2_EP2OS (0x01 << 7) +#define USBS_GSR2_EP2ED (0x01 << 6) +#define USBS_GSR2_EP2ND (0x01 << 5) +#define USBS_GSR2_EP1NT (0x01 << 4) +#define USBS_GSR2_EP1ET (0x01 << 3) +#define USBS_GSR2_EP1ND (0x01 << 2) +#define USBS_GSR2_ES (0x01 << 1) +#define USBS_GSR2_SL (0x01 << 0) + +// Interrupt mask 2 bits correspond to GSR2 + +// Endpoint control registers. +// EP0 - control messages +#define EP0_CR_EP0EN (0x01 << 31) +#define EP0_CR_ISS (0x01 << 20) +#define EP0_CR_INAK (0x01 << 19) +#define EP0_CR_OSS (0x01 << 18) +#define EP0_CR_NHSK0 (0x01 << 17) +#define EP0_CR_ONAK (0x01 << 16) +#define EP0_CR_MAXP0_MASK (0x7F << 0) +#define EP0_CR_MAXP0_SHIFT 0 + +// EP1 - isochronous transmits +#define EP1_CR_EP1EN (0x01 << 31) +#define EP1_CR_TM1 (0x01 << 19) +#define EP1_CR_TM1_MASK (0x01 << 19) +#define EP1_CR_TM1_SZLP (0x00 << 19) +#define EP1_CR_TM1_NZLP (0x01 << 19) +#define EP1_CR_MAXP1_MASK (0x3FF << 0) +#define EP1_CR_MAXP1_SHIFT 0 + +// EP2 - isochronous receives +#define EP2_CR_EP2EN (0x01 << 31) +#define EP2_CR_RM2_MASK (0x03 << 19) +#define EP2_CR_RM2_NORMAL (0x00 << 19) +#define EP2_CR_RM2_ASSEMBLE (0x02 << 19) +#define EP2_CR_RM2_SEPARATE (0x03 << 19) +#define EP2_CR_MAXP2_MASK (0x3FF << 0) +#define EP2_CR_MAXP2_SHIFT 0 + +// EP3 - bulk transmits +#define EP3_CR_EP3EN (0x01 << 31) +#define EP3_CR_TM3 (0x01 << 19) +#define EP3_CR_TM3_MASK (0x01 << 19) +#define EP3_CR_TM3_SZLP (0x00 << 19) +#define EP3_CR_TM3_NZLP (0x01 << 19) +#define EP3_CR_SS3 (0x01 << 18) +#define EP3_CR_NAK3 (0x01 << 16) +#define EP3_CR_MAXP3_MASK (0x7F << 0) +#define EP3_CR_MAXP3_SHIFT 0 + +// EP4 - bulk receives +#define EP4_CR_EP4EN (0x01 << 31) +#define EP4_CR_RM4_MASK (0x03 << 19) +#define EP4_CR_RM4_NORMAL (0x00 << 19) +#define EP4_CR_RM4_ASSEMBLE (0x02 << 19) +#define EP4_CR_RM4_SEPARATE (0x03 << 19) +#define EP4_CR_SS4 (0x01 << 18) +#define EP4_CR_NHSK4 (0x01 << 17) +#define EP4_CR_NAK4 (0x01 << 16) +#define EP4_CR_MAXP4_MASK (0x7F << 0) +#define EP4_CR_MAXP4_SHIFT 0 + +// EP5 - interrupt transmits +#define EP5_CR_EP5EN (0x01 << 31) +#define EP5_CR_FM (0x01 << 19) +#define EP5_CR_SS5 (0x01 << 18) +#define EP5_CR_NAK5 (0x01 << 16) +#define EP5_CR_MAXP5_MASK (0x7F << 0) +#define EP5_CR_MAXP5_SHIFT 0 + +// EP6 - interrupt receives +#define EP6_CR_EP6EN (0x01 << 31) +#define EP6_CR_SS6 (0x01 << 18) +#define EP6_CR_NHSK6 (0x01 << 17) +#define EP6_CR_NAK6 (0x01 << 16) +#define EP6_CR_MAXP6_MASK (0x7F << 0) +#define EP6_CR_MAXP6_SHIFT 0 + +// Some bits which can be applied to multiple transmit or receive +// endpoint control registers, thus avoiding unnecessary code +// duplication. These will not work for the isochronous endpoints +// because those are just too special. +#define EPtx_CR_EpxEN (0x01 << 31) +#define EPtx_CR_SSx (0x01 << 18) +#define EPtx_CR_NAKx (0x01 << 16) +#define EPtx_CR_MAXPx_MASK (0x7F << 0) +#define EPtx_CR_MAXPx_SHIFT 0 + +#define EPrx_CR_EPxEN (0x01 << 31) +#define EPrx_CR_SSx (0x01 << 18) +#define EPrx_CR_NHSKx (0x01 << 17) +#define EPrx_CR_NAKx (0x01 << 16) +#define EPrx_CR_MAXPx_MASK (0x7F << 0) +#define EPrx_CR_MAXPx_SHIFT 0 + +// USB command register +#define USBS_CMR_BUSY (0x01 << 31) +#define USBS_CMR_COMMAND_MASK (0x07 << 24) +#define USBS_CMR_COMMAND_SHIFT 24 +#define USBS_CMR_COMMAND_TX_EP0 (0x00 << 24) +#define USBS_CMR_COMMAND_TX_EP1 (0x01 << 24) +#define USBS_CMR_COMMAND_TX_EP3 (0x02 << 24) +#define USBS_CMR_COMMAND_TX_EP5 (0x03 << 24) +#define USBS_CMR_COMMAND_ADD_POOL0 (0x04 << 24) +#define USBS_CMR_COMMAND_ADD_POOL1 (0x05 << 24) +#define USBS_CMR_COMMAND_ADD_POOL2 (0x06 << 24) +#define USBS_CMR_SIZE_MASK (0x0FFFF << 0) +#define USBS_CMR_SIZE_SHIFT 0 + +// TX Endpoint status +#define USBS_TEPSR_EP5TS_MASK (0x03 << 24) +#define USBS_TEPSR_EP5TS_SHIFT 24 +#define USBS_TEPSR_EP5TS_IDLE (0x00 << 24) +#define USBS_TEPSR_EP5TS_ONE (0x01 << 24) +#define USBS_TEPSR_EP5TS_TWO (0x02 << 24) +#define USBS_TEPSR_EP3TS_MASK (0x03 << 16) +#define USBS_TEPSR_EP3TS_SHIFT 16 +#define USBS_TEPSR_EP3TS_IDLE (0x00 << 16) +#define USBS_TEPSR_EP3TS_ONE (0x01 << 16) +#define USBS_TEPSR_EP3TS_TWO (0x02 << 16) +#define USBS_TEPSR_EP1TS_MASK (0x03 << 8) +#define USBS_TEPSR_EP1TS_SHIFT 8 +#define USBS_TEPSR_EP1TS_IDLE (0x00 << 8) +#define USBS_TEPSR_EP1TS_ONE (0x01 << 8) +#define USBS_TEPSR_EP1TS_TWO (0x02 << 8) +#define USBS_TEPSR_EP0TS_MASK (0x03 << 0) +#define USBS_TEPSR_EP0TS_SHIFT 0 +#define USBS_TEPSR_EP0TS_IDLE (0x00 << 0) +#define USBS_TEPSR_EP0TS_ONE (0x01 << 0) +#define USBS_TEPSR_EP0TS_TWO (0x02 << 0) + +// Receive pools. The RP0IR, RP1IR and RP2IR registers +// all use the same bits. +#define USBS_RPxIR_AL_MASK (0x07 << 28) +#define USBS_RPxIR_AL_SHIFT 28 +#define USBS_RPxIR_AL_NONE (0 << 28) +#define USBS_RPxIR_RNOD_MASK (0x0FFFF << 0) +#define USBS_RPxIR_RNOD_SHIFT 0 + +// The other registers do not have special bits. + +// Data transfers involve buffer descriptors and mailboxes. The +// relevant data structures and fields need to be defined. For now +// assume 32-bit mode of operation, i.e. there will be no padding +// between two successive 32-bit entities + +// A transmit packet directory consists of up to 255 buffer +// descriptors. Each buffer descriptor specifies a buffer and a size +// of up to 64K. + +typedef struct TxBufferDescriptor { + cyg_uint32 control; + void* buffer; +} TxBufferDescriptor; + +#define TXBUFDESC_CTRL_LAST (0x01 << 31) +#define TXBUFDESC_CTRL_BUFDESC_MASK (0x01 << 30) +#define TXBUFDESC_CTRL_BUFDESC_SHIFT 30 +#define TXBUFDESC_CTRL_BUFDESC_LINK (0x00 << 30) +#define TXBUFDESC_CTRL_BUFDESC_BUFDESC (0x01 << 30) +#define TXBUFDESC_CTRL_SIZE_MASK (0x0FFFF << 0) +#define TXBUFDESC_CTRL_SIZE_SHIFT 0 + +// The result of a transmit operation gets written to a mailbox +// structure in memory. +typedef struct TxMailbox { + cyg_uint32 status; +} TxMailbox; + +#define TXMBOX_STATUS_IBUS_ERROR (0x01 << 10) +#define TXMBOX_STATUS_UNDERRUN (0x01 << 9) +#define TXMBOX_STATUS_MODE_MASK (0x01 << 8) +#define TXMBOX_STATUS_MODE_SHIFT 8 +#define TXMBOX_STATUS_MODE_SZLP (0x00 << 8) +#define TXMBOX_STATUS_MODE_NZLP (0x01 << 8) +#define TXMBOX_STATUS_EPN_MASK (0x07 << 0) +#define TXMBOX_STATUS_EPN_SHIFT 0 +#define TXMBOX_STATUS_EPN_EP0 (0x00 << 0) +#define TXMBOX_STATUS_EPN_EP1 (0x02 << 0) +#define TXMBOX_STATUS_EPN_EP3 (0x04 << 0) +#define TXMBOX_STATUS_EPN_EP5 (0x06 << 0) + +// Now for receive operations. This involves adding buffer descriptors +// to one of three pools. The pools are managed by registers. +typedef struct RxBufferDescriptor { + cyg_uint32 control; + void* buffer; +} RxBufferDescriptor; + +#define RXBUFDESC_CTRL_LAST (0x01 << 31) +#define RXBUFDESC_CTRL_BUFDESC_MASK (0x01 << 30) +#define RXBUFDESC_CTRL_BUFDESC_SHIFT 30 +#define RXBUFDESC_CTRL_BUFDESC_LINK (0x00 << 30) +#define RXBUFDESC_CTRL_BUFDESC_BUFDESC (0x01 << 30) +#define RXBUFDESC_CTRL_SIZE_MASK (0x0FFFF << 0) +#define RXBUFDESC_CTRL_SIZE_SHIFT 0 + +typedef struct RxMailbox { + cyg_uint32 status; + void* address; +} RxMailbox; + +#define RXMBOX_STATUS_EPN_MASK (0x07 << 29) +#define RXMBOX_STATUS_EPN_SHIFT 29 +#define RXMBOX_STATUS_EPN_EP0 (0x01 << 29) +#define RXMBOX_STATUS_EPN_EP2 (0x03 << 29) +#define RXMBOX_STATUS_EPN_EP4 (0x05 << 29) +#define RXMBOX_STATUS_EPN_EP6 (0x07 << 29) +#define RXMBOX_STATUS_CORRUPTION (0x01 << 25) +#define RXMBOX_STATUS_IBUS_ERROR (0x01 << 24) +#define RXMBOX_STATUS_SETUP_MASK (0x01 << 23) +#define RXMBOX_STATUS_SETUP_SHIFT 23 +#define RXMBOX_STATUS_SETUP_NORMAL (0x00 << 23) +#define RXMBOX_STATUS_SETUP_SETUP (0x01 << 23) +#define RXMBOX_STATUS_OVERRUN (0x01 << 22) +#define RXMBOX_STATUS_DATA_TOGGLE (0x01 << 21) +#define RXMBOX_STATUS_CRC (0x01 << 20) +#define RXMBOX_STATUS_BIT_STUFFING (0x01 << 19) +#define RXMBOX_STATUS_64K (0x01 << 18) +#define RXMBOX_STATUS_MODE_MASK (0x03 << 16) +#define RXMBOX_STATUS_MODE_SHIFT 16 +#define RXMBOX_STATUS_MODE_NORMAL (0x00 << 16) +#define RXMBOX_STATUS_MODE_NORMAL2 (0x01 << 16) +#define RXMBOX_STATUS_MODE_ASSEMBLE (0x02 << 16) +#define RXMBOX_STATUS_MODE_SEPARATE (0x03 << 16) +#define RXMBOX_STATUS_SIZE_MASK (0x0FFFF << 0) +#define RXMBOX_STATUS_SIZE_SHIFT 0 + + +// ---------------------------------------------------------------------------- +// Hardware work around - see NEC erratum S1, CPU to IBUS write restriction. +// Reading back from the USB device after every write prevents any problems. +// Strictly speaking it is only necessary to do this after every three +// writes, but if there is concurrent ethernet activity then doing it +// after eveyr write is safer. The frame number/version register seems +// like a good one to read back from. + +#ifdef CYGIMP_DEVS_USB_UPD985XX_IBUS_WRITE_LIMIT +# define FLUSH_IBUS() \ + CYG_MACRO_START \ + (void)*USBS_VER; \ + CYG_MACRO_END + +#else +# define FLUSH_IBUS() CYG_EMPTY_STATEMENT +#endif + +// ---------------------------------------------------------------------------- +// Static data. There is a data structure for each endpoint. The +// implementation is essentially a private class that inherits from +// common classes for control and data endpoints, but device drivers +// are supposed to be written in C so some ugliness is required. +// +// Devtab entries are defined in usbs_upd985xx_data.cxx to make sure +// that the linker does not garbage-collect them. + +// Support for the interrupt handling code. +static cyg_interrupt usbs_upd985xx_intr_data; +static cyg_handle_t usbs_upd985xx_intr_handle; + +// The various bits in the two interrupt status registers are read-once, +// i.e. reading the register clears the bits. Since much of the processing +// is deferred to DSR level, it is necessary to keep track of pending +// interrupts in separate variables. If another interrupt happens during +// DSR processing, these variables will be updated. The main DSR loops +// until there are no interesting bits left. Interrupts have to be +// disabled briefly when clearing bits. +static volatile cyg_uint32 usbs_upd985xx_gsr1 = 0; +static volatile cyg_uint32 usbs_upd985xx_gsr2 = 0; + +// Many of the interrupt bits are of no interest and it is convenient +// to mask them out in the ISR, thus avoiding unnecessary dsr +// invocations. +static cyg_uint32 usbs_upd985xx_gsr1_mask = 0; +static cyg_uint32 usbs_upd985xx_gsr2_mask = 0; + +// Sizes for the receive and transmit mboxes. +// NOTE: it is not clear what the optimal size for these +// mailboxes is. For receives maybe one per rx endpoint, +// plus a spare. For transmits maybe just two, since only +// one transmit at a time is supported. Mailboxes are +// relatively small, so for now four each should be ok. +#define RXMBOX_COUNT 4 +#define TXMBOX_COUNT 4 + +// There is one instance of this data structure. It is allocated +// in kseg0 cached memory, but during initialization a separate +// pointer value is set to the kseg1 uncached equivalent. This +// makes it easier to point the hardware at uncached memory without +// having to worry about cache line boundaries everywhere. + +typedef struct uncached_data { + // This partial cacheline does not actually store any data. + // However it ensures that the data does not share a cacheline + // with some other static, with updates to that other static + // causing funny side effects on the uncached data. There is a + // memory optimisation of subtracting sizeof(RxMailbox.status), + // i.e. exploit knowledge of alignment. + unsigned char cacheline_start[HAL_DCACHE_LINE_SIZE - sizeof(cyg_uint32)]; + + RxMailbox rx_mboxes[RXMBOX_COUNT]; + TxMailbox tx_mboxes[TXMBOX_COUNT]; + + // For transmits a single buffer descriptor per endpoint suffices. + // If transmit locking is enabled then actually a single buffer + // descriptor for the whole system would suffice. + TxBufferDescriptor ep0_tx_bufdesc; +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + TxBufferDescriptor ep3_tx_bufdesc; +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + TxBufferDescriptor ep5_tx_bufdesc; +#endif + + + // More buffer descriptors are needed than might be expected, see + // the start_rx routines. + RxBufferDescriptor ep0_rx_bufdescs[4]; +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + RxBufferDescriptor ep4_rx_bufdescs[8]; +#endif + + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + // Space for the start and end of a transfer, avoiding problems + // with invalidating partial cache lines. + unsigned char ep4_head[HAL_DCACHE_LINE_SIZE]; + unsigned char ep4_tail[HAL_DCACHE_LINE_SIZE]; +#endif + + // The "big" buffers come last, reducing the offsets for the previous + // structures. It is not clear this really matters for MIPS. + // + // Endpoint 0 receive and transmit buffers. A transmit buffer is + // convenient because the hardware pretty much expects all of the + // data to be in contiguous memory, as opposed to the normal eCos + // USB driver model with refill buffers etc. An alternative + // implementation would keep the data in separate areas but would + // require lots of TxBufferDescriptors, so in memory terms the + // overheads of a single transmit buffer are not as big as might + // seem. It might be possible to get things working eight bytes + // at a time since the hardware appears to depend on zero-byte + // terminating packets in places, but that has not been attempted. + // + // A separate receive buffer is useful because it can be placed in + // uncached memory, avoiding the need for invalidation and + // worrying about other data in the cache lines. Note that this + // buffer may also get used for endpoint 6 interrupt receives + // because the two endpoints share a single pool. + unsigned char ep0_rx_buffer[CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE]; + unsigned char ep0_tx_buffer[CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE]; + + // Another cacheline to prevent overlap with other statics. + // This has to be full-sized since the previous field is only byte-aligned. + unsigned char cacheline_end[HAL_DCACHE_LINE_SIZE]; +} uncached_data; + +// This data structure is quite large so making it all uninitialized +// means a potentially big saving in ROM-booting systems. This +// requires additional effort by the endpoint initialization routines. +static uncached_data cached_copy; + +static uncached_data* uncached = (uncached_data*)0; + +// Endpoint 0. See the description below. + +static void usbs_upd985xx_ep0_start(usbs_control_endpoint*); +static void usbs_upd985xx_poll(usbs_control_endpoint*); + +typedef struct ep0_impl { + usbs_control_endpoint common; + cyg_bool rx_expecting_data; + cyg_bool rx_indicator_valid; + RxMailbox rx_indicator; + cyg_bool tx_indicator_valid; + TxMailbox tx_indicator; + cyg_bool tx_needs_zero_transfer; + cyg_uint32 tx_size; +} ep0_impl; + +static ep0_impl ep0 = { + common: + { + state: USBS_STATE_POWERED, // The hardware does not distinguish between detached, attached and powered. + enumeration_data: (usbs_enumeration_data*) 0, + start_fn: &usbs_upd985xx_ep0_start, + poll_fn: &usbs_upd985xx_poll, + interrupt_vector: CYGNUM_HAL_INTERRUPT_USB, + control_buffer: { 0, 0, 0, 0, 0, 0, 0, 0 }, + state_change_fn: (void (*)(usbs_control_endpoint*, void*, usbs_state_change, int)) 0, + state_change_data: (void*) 0, + standard_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + standard_control_data: (void*) 0, + class_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + class_control_data: (void*) 0, + vendor_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + vendor_control_data: (void*) 0, + reserved_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + reserved_control_data: (void*) 0, + buffer: (unsigned char*) 0, + buffer_size: 0, + fill_buffer_fn: (void (*)(usbs_control_endpoint*)) 0, + fill_data: (void*) 0, + fill_index: 0, + complete_fn: (usbs_control_return (*)(usbs_control_endpoint*, int)) 0 + }, + rx_expecting_data: false, + rx_indicator_valid: false, + rx_indicator: { 0, (void*) 0 }, + tx_indicator_valid: false, + tx_indicator: { 0 }, + tx_needs_zero_transfer: 0 +}; + +extern usbs_control_endpoint usbs_upd985xx_ep0 __attribute__((alias ("ep0"))); + +// Endpoint 1, isochronous transmits. This endpoint is not yet +// supported. Although the interface for bulk transmits should be +// mostly re-usable, there are some additional error conditions if +// either the host or the target fails to achieve the desired +// throughput. + +// Endpoint 2, isochronous receives. Not yet supported for now, just +// like endpoint 1. + +// Endpoints 3 and 5 can share some code. +#if defined(CYGPKG_DEVS_USB_UPD985XX_EP3) || defined(CYGPKG_DEVS_USB_UPD985XX_EP5) +// Endpoint 3, bulk transmits, and endpoint 5, either interrupt transmits +// or emulation of bulk transmits. The hardware does most +// of the work. +typedef struct ep35_impl { + usbs_tx_endpoint common; + cyg_bool tx_indicator_valid; + TxMailbox tx_indicator; + int send_command; + volatile cyg_uint32* cr; + TxBufferDescriptor* tx_bufdesc; +} ep35_impl; + +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 +static void ep3_start_tx(usbs_tx_endpoint*); +static void ep3_set_halted(usbs_tx_endpoint*, cyg_bool); + +static ep35_impl ep3 = { + common: { + start_tx_fn: &ep3_start_tx, + set_halted_fn: &ep3_set_halted, + complete_fn: (void (*)(void*, int)) 0, + complete_data: (void*) 0, + buffer: (const unsigned char*) 0, + buffer_size: 0, + halted: 0, + }, + tx_indicator_valid: false, + tx_indicator: { 0 }, + send_command: USBS_CMR_COMMAND_TX_EP3, + cr: EP3_CR, + tx_bufdesc: 0 // Needs run-time initialization +}; + +extern usbs_tx_endpoint usbs_upd985xx_ep3 __attribute__ ((alias ("ep3"))); +# endif + +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 +static void ep5_start_tx(usbs_tx_endpoint*); +static void ep5_set_halted(usbs_tx_endpoint*, cyg_bool); + +static ep35_impl ep5 = { + common: { + start_tx_fn: &ep5_start_tx, + set_halted_fn: &ep5_set_halted, + complete_fn: (void (*)(void*, int)) 0, + complete_data: (void*) 0, + buffer: (const unsigned char*) 0, + buffer_size: 0, + halted: 0, + }, + tx_indicator_valid: false, + tx_indicator: { 0 }, + send_command: USBS_CMR_COMMAND_TX_EP5, + cr: EP5_CR, + tx_bufdesc: 0 // Needs run-time initialization +}; + +extern usbs_tx_endpoint usbs_upd985xx_ep5 __attribute__ ((alias ("ep5"))); +# endif +#endif + + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 +// Endpoint 4, bulk receives. Again the hardware does the hard work. +// Receive pool 2 is reserved for this endpoint. + +typedef struct ep4_impl { + usbs_rx_endpoint common; + cyg_uint32 head_size; + cyg_uint32 direct_size; + cyg_uint32 tail_size; + cyg_bool rx_indicator_valid; + RxMailbox rx_indicator; + cyg_int32 tail_index; +} ep4_impl; + +static void ep4_start_rx(usbs_rx_endpoint*); +static void ep4_set_halted(usbs_rx_endpoint*, cyg_bool); + +static ep4_impl ep4 = { + common: { + start_rx_fn: &ep4_start_rx, + set_halted_fn: &ep4_set_halted, + complete_fn: (void (*)(void*, int)) 0, + complete_data: (void*) 0, + buffer: (unsigned char*) 0, + buffer_size: 0, + halted: 0, + }, + rx_indicator_valid: false, + rx_indicator: { 0, (void*) 0 }, + tail_index: -1 +}; + +extern usbs_rx_endpoint usbs_upd985xx_ep4 __attribute__((alias ("ep4"))); +#endif + +// Endpoint 6, interrupt receives. Not yet implemented. There may +// be conflicts because the hardware is shared with endpoint 0. + +// ---------------------------------------------------------------------------- +// Mailbox support. +// +// The transmit and receive mailboxes are shared between the +// appropriate endpoints. This causes some complications if e.g. +// transmits on several endpoints complete at the same time. For +// example the tx mailbox might contain send indicators for endpoints +// 3 and 0, but the DSR code will process endpoint 0 before endpoint +// 3. +// +// This device driver works on the basis that there can be only one +// transmit and/or receive in progress for any given endpoint, so the +// relevant information can be extracted from the mailbox and put into +// the per-endpoint structures. The routines below can be used to +// move data from the mailboxes. They will be called in DSR context +// so there is no need to worry about locking. + +static void +drain_tx_mailbox(void) +{ + TxMailbox* tmra = IBUS_SWAPPTR(TxMailbox, *USBS_TMRA); + TxMailbox* tmwa = IBUS_SWAPPTR(TxMailbox, *USBS_TMWA); + if (tmra != tmwa) { + do { + TxMailbox mbox = *tmra; + tmra++; + if (tmra == &(uncached->tx_mboxes[TXMBOX_COUNT])) { + tmra = &(uncached->tx_mboxes[0]); + } + + switch(mbox.status & TXMBOX_STATUS_EPN_MASK) { + case TXMBOX_STATUS_EPN_EP0: + CYG_ASSERT(false == ep0.tx_indicator_valid, "Only one ep0 transmit should be in progress at a time"); + ep0.tx_indicator = mbox; + ep0.tx_indicator_valid = true; + break; +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + case TXMBOX_STATUS_EPN_EP3: + CYG_ASSERT(false == ep3.tx_indicator_valid, "Only one ep3 transmit should be in progress at a time"); + ep3.tx_indicator = mbox; + ep3.tx_indicator_valid = true; + break; +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + case TXMBOX_STATUS_EPN_EP5: + CYG_ASSERT(false == ep5.tx_indicator_valid, "Only one ep5 transmit should be in progress at a time"); + ep5.tx_indicator = mbox; + ep5.tx_indicator_valid = true; + break; +#endif + default: + break; + } + } while (tmra != tmwa); + *USBS_TMRA = IBUS_SWAPPTR(TxMailbox, tmra); FLUSH_IBUS(); + } +} + +static void +drain_rx_mailbox(void) +{ + RxMailbox* rmra = IBUS_SWAPPTR(RxMailbox, *USBS_RMRA); + RxMailbox* rmwa = IBUS_SWAPPTR(RxMailbox, *USBS_RMWA); + + if (rmra != rmwa) { + do { + RxMailbox mbox = *rmra; + rmra++; + if (rmra == &(uncached->rx_mboxes[RXMBOX_COUNT])) { + rmra = &(uncached->rx_mboxes[0]); + } + + switch(mbox.status & RXMBOX_STATUS_EPN_MASK) { + case RXMBOX_STATUS_EPN_EP0: + // Ignore zero-byte transfers. It is not clear why + // these happen, but they have been observed. + if (0 != (mbox.status & RXMBOX_STATUS_SIZE_MASK)) { + CYG_ASSERT(false == ep0.rx_indicator_valid, "Only one ep0 receive should be in progress at a time"); + ep0.rx_indicator = mbox; + ep0.rx_indicator_valid = true; + } + break; +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + case RXMBOX_STATUS_EPN_EP4: + // If an error occurs then the hardware may report + // multiple rx completions, each with an IBUS error + // indicator. For now only the last rx indicator is + // taken into account, which means we could lose + // a successful receive that happens to be followed + // by an error. + // NOTE: any possibility of improving on this? +#if 1 + CYG_ASSERT(false == ep4.rx_indicator_valid, "Only one ep4 receive should be in progress at a time"); +#endif + ep4.rx_indicator = mbox; + ep4.rx_indicator_valid = true; + break; +#endif + default: + break; + } + } while (rmra != rmwa); + *USBS_RMRA = IBUS_SWAPPTR(RxMailbox, rmra); FLUSH_IBUS(); + } +} + +// ---------------------------------------------------------------------------- +// Transmit locking. +// +// According to NEC errata U3 and U4 the hardware may exhibit +// undesirable behaviour if there are concurrent transmissions. There +// are various ways of resolving this, but the simplest is to perform +// locking in software so that at most one transmit endpoint is in use +// at any one time. This approach works fine if transmissions only +// involve one tx endpoint plus the control endpoint because the +// control endpoint generally only gets used during initialization and +// the other endpoint only gets used after initialization. If multiple +// transmit endpoints are used then locking in software becomes less +// acceptable, especially if isochronous transfers are used because +// timing is important for those. +// +// There is a theoretical problem if e.g. there is a very large bulk +// transfer on a busy bus and it is necessary to respond to a control +// message. The control reply would be delayed, possibly causing a +// violation of the USB standard and a timeout on the host. + +#ifdef CYGIMP_DEVS_USB_UPD985XX_SERIALIZE_TRANSMITS +static void ep0_start_tx(void); +# if defined(CYGPKG_DEVS_USB_UPD985XX_EP3) || defined(CYGPKG_DEVS_USB_UPD985XX_EP5) +static void ep35_start_tx(ep35_impl*); +# endif + +static cyg_bool tx_in_progress = false; +static cyg_bool ep0_tx_pending = false; +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 +static cyg_bool ep3_tx_pending = false; +# endif +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 +static cyg_bool ep5_tx_pending = false; +# endif + +// Invoked from ep?_start_tx(). Scheduling may or may not be locked. +static cyg_bool +tx_try_lock(cyg_bool* which) +{ + cyg_bool result; + cyg_drv_dsr_lock(); + if (tx_in_progress) { + result = false; + *which = true; + } else { + result = true; + tx_in_progress = true; + } + cyg_drv_dsr_unlock(); + return result; +} + +// Invoked only from dsr context. +static void +tx_unlock(void) +{ + tx_in_progress = false; + if (ep0_tx_pending) { + ep0_tx_pending = false; + ep0_start_tx(); + return; + } +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + if (ep3_tx_pending) { + ep3_tx_pending = false; + ep35_start_tx(&ep3); + return; + } +# endif +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + if (ep5_tx_pending) { + ep5_tx_pending = false; + ep35_start_tx(&ep5); + return; + } +# endif +} + +# define TX_TRY_LOCK(_x_) tx_try_lock(_x_) +# define TX_UNLOCK() tx_unlock() + +#else + +# define TX_TRY_LOCK(_x_) 1 +# define TX_UNLOCK() CYG_EMPTY_STATEMENT + +#endif + + +// ---------------------------------------------------------------------------- +// Endpoint 0 +// +// As usual, control messages are more complicated than the rest. +// +// 1) during initialization a receive is initiated into the common +// eight-byte buffer, used for the standard header of the control +// packet. Until that header has been received and analysed, +// there is no way of knowing whether or not the host will be +// sending any more data. +// +// 2) the control packet may indicate that the host will be sending +// more data. A higher-level handler for the control message should +// have provided a suitable buffer, so a receive can be started +// into that buffer. A flag indicates whether we are currently +// receiving a new control packet or additional data. +// +// 3) the host may decide to cancel that extra data and send a new +// control message instead. There is a flag to indicate that +// the transfer included a SETUP token. +// +// 4) transmits only happen when the control packet involves returning +// data. Unfortunately there is a problem in that, with eCos, the +// return data will generally not be in a single contiguous buffer. +// Discontinuous data could be handled by having a separate buffer +// descriptor for each bit of data, but it is not known in advance +// how many buffer descriptors might be needed so allocating +// those statically presents a problem as well. Instead a single +// static buffer is used, and data from higher-level code is copied +// there. This introduces a new problem: how big should that buffer +// be? A configuration option is used for that. +// +// If endpoint 6 is in use as well then things get more complicated +// because a single receive pool will be shared between endpoints 0 +// and 6, and when adding a buffer to a pool there is no way of +// specifying the endpoint. Hence it will be necessary to receive +// into a static buffer and then copy into either an endpoint 0 or +// and endpoint 6 buffer. + +// Fill the transmit buffer by repeatedly invoking the refill function +// and copying into the ep0 tx buffer. The relevant fields in the +// ep0 structure are cleared immediately and the completion function +// is called, even though the data has not actually gone out. That avoids +// a possible race condition where the host sends a new control packet +// immediately, before the transmit-complete has been processed +// (unlikely in practice, not least because ep0_tx_dsr() will get called +// before ep0_rx_dsr()). +static int +ep0_fill_txbuffer(void) +{ + int filled = 0; + while (filled < CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE) { + if (0 != ep0.common.buffer_size) { + if ((filled + ep0.common.buffer_size) < CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE) { + memcpy(&(uncached->ep0_tx_buffer[filled]), ep0.common.buffer, ep0.common.buffer_size); + filled += ep0.common.buffer_size; + ep0.common.buffer_size = 0; + } else { + break; + } + } else if ((void (*)(usbs_control_endpoint*))0 != ep0.common.fill_buffer_fn) { + (*ep0.common.fill_buffer_fn)(&ep0.common); + } else { + break; + } + } + CYG_ASSERT((0 == ep0.common.buffer_size) && ((void (*)(usbs_control_endpoint*))0 == ep0.common.fill_buffer_fn), \ + "Endpoint 0 transmit buffer overflow"); + + if ((usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn) { + (*ep0.common.complete_fn)(&ep0.common, 0); + } + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = 0; + ep0.common.complete_fn = 0; + + return filled; +} + +// Start a new receive operation on endpoint 0. This needs to happen +// from a number of places, including from initialization. +// +// IMHO the hardware is somewhat overengineered here. All that is +// needed is to receive a single eight-byte control packet, or a +// small amount of additional control data. That could be achieved +// by using a single buffer descriptor in the uncached structure, +// plus a suitably-sized static uncached ep0_rx_buffer. +// +// But no, buffer descriptors must be linked and new buffers must +// be added to the end. When a control packet arrives, the +// receive pool continues to point at the old buffer descriptor. +// So we need two buffer descriptors plus two links, switching +// between them as appropriate. +// +// It is not at all clear what would happen if another packet +// started to happen while things were being updated. There is +// also potential confusion between endpoint 0 and endpoint 6 +// receives. + +static void +ep0_start_rx(cyg_uint32 size) +{ + // The buffer descriptor to be added. This will be either + // ep0_rxbufdescs[0] or ep0_rxbufdescs[2]; + RxBufferDescriptor* desc = &(uncached->ep0_rx_bufdescs[0]); + + CYG_ASSERTC(size > 0); + + // Block interrupts for the duration. This does not prevent + // problems if the hardware sees another packet and starts + // doing things, but should prevent some software race + // conditions. + cyg_drv_isr_lock(); + + // We are about to start a new rx operation, so the + // current indicator may get invalidated. + ep0.rx_indicator_valid = false; + + // Start by looking at the current pool0 status. There are + // three possibilities: during init or after reset, the pool + // will be empty; otherwise the pool should point at either + // rx_bufdescs[0] or rx_bufdescs[2], corresponding to the + // last received packet. + if (0 == (*USBS_RP0IR & USBS_RPxIR_RNOD_MASK)) { + // Nothing currently in the pool. Use ep0_rx_bufdescs[0], + // and no need to update a link. + } else if (desc == *USBS_RP0AR) { + // The pool already points at bufdescs[0], switch to bufdescs[2], + // and link from bufdescs[1]. + desc = &(uncached->ep0_rx_bufdescs[2]); + uncached->ep0_rx_bufdescs[1].buffer = (void*) desc; + } else { + // The pool should point at bufdescs[2], stick with bufdescs[0] + CYG_ASSERT(&(uncached->ep0_rx_bufdescs[2]) == *USBS_RP0AR, "Endpoint 0 rx buffer confusion"); + uncached->ep0_rx_bufdescs[3].buffer = (void*) desc; + } + + // Now fill in the buffer directory being added + desc[0].control = RXBUFDESC_CTRL_LAST | RXBUFDESC_CTRL_BUFDESC_BUFDESC | size; + desc[0].buffer = (void*) uncached->ep0_rx_buffer; + desc[1].control = RXBUFDESC_CTRL_BUFDESC_LINK; + desc[1].buffer = 0; + + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, desc); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(USBS_CMR_COMMAND_ADD_POOL0 | 1); FLUSH_IBUS(); + cyg_drv_isr_unlock(); +} + +// Ditto for transmits. The data is assumed to be in +// uncached->ep0_tx_buffer already. A size of 0 indicates +// a need to send a terminating packet explicitly. +static void +ep0_start_tx(void) +{ + if (!TX_TRY_LOCK(&ep0_tx_pending)) { + return; + } + + uncached->ep0_tx_bufdesc.buffer = uncached->ep0_tx_buffer; + uncached->ep0_tx_bufdesc.control = TXBUFDESC_CTRL_LAST | TXBUFDESC_CTRL_BUFDESC_BUFDESC | ep0.tx_size; + + cyg_drv_isr_lock(); + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, &(uncached->ep0_tx_bufdesc)); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(USBS_CMR_COMMAND_TX_EP0 | ep0.tx_size); FLUSH_IBUS(); + cyg_drv_isr_unlock(); +} + +// An endpoint 0 transmission has completed. Usually the only action +// that is needed is to drain the tx mailbox entry, otherwise it is +// possible that we could end up with ep0 transmits using up all +// available slots. The endpoint 0 hardware requires no further +// attention, and as far as higher-level code is concerned the +// transmission completed a long time ago when ep0_fill_txbuffer() +// called the completion function. +// +// There is one special case. If the host asked for e.g. a string +// descriptor and asked for 255 bytes, but the string was only +// e.g. 32 bytes, then there is a problem. With a default value +// for CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE, the data will be +// transferred as four 8-byte packets, but it is necessary to +// terminate the transfer with a 0-byte packet. Endpoint 0 always +// operates in NZLP mode so the hardware will never generate +// this last packet. Instead it is necessary to set up an +// additional transfer of zero bytes. That could be done at the +// same time as the main data transfer, but then it would be +// necessary to poll the hardware and wait until it has finished +// processing that initial transfer. +static void +ep0_tx_dsr(void) +{ + if (!ep0.tx_indicator_valid) { + drain_tx_mailbox(); + if (!ep0.tx_indicator_valid) { + // A transmit interrupt when there does not appear to be + // any data? + CYG_FAIL("EP0 tx DSR invoked when there is no valid tx indicator"); + return; + } + } + // There is not actually anything worth looking at in the status. + ep0.tx_indicator_valid = false; + + if (ep0.tx_needs_zero_transfer) { + ep0.tx_needs_zero_transfer = false; + uncached->ep0_tx_bufdesc.buffer = uncached->ep0_tx_buffer; + uncached->ep0_tx_bufdesc.control = TXBUFDESC_CTRL_LAST | TXBUFDESC_CTRL_BUFDESC_BUFDESC | 0; + + cyg_drv_isr_lock(); + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, &(uncached->ep0_tx_bufdesc)); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(USBS_CMR_COMMAND_TX_EP0 | 0); FLUSH_IBUS(); + cyg_drv_isr_unlock(); + + } else { + TX_UNLOCK(); + } +} + +// An endpoint 0 receive has completed. This could be a new control +// message. Or it could be the data for a previous control message. Or +// it could be a new control message when expecting the data from +// a previous one. The ep0.rx_expecting_data field indicates +// whether or not a new control message is expected. +// +// At times an interrupt triggers and there is an rx indication for a +// zero-byte transfer. Such a transfer may be followed immediately by +// a real transfer. It is not understood why the zero-byte transfer +// occurs. They are ignored by the drain_rx_mailbox() code, to make +// sure that there is at most one valid rx indicator at a time. +static void +ep0_rx_dsr(void) +{ + // Start by checking the rx indicator to make sure that a packet + // really has been received. + if (!ep0.rx_indicator_valid) { + drain_rx_mailbox(); + if (!ep0.rx_indicator_valid) { + // Do not assert, in case of a spurious interrupt for a + // zero-byte transfer. + return; + } + } + + // We have a valid receive, with the data held in uncached->ep0_rx_buffer. + // Are we expecting the remaining data of a control transfer? + if (ep0.rx_expecting_data) { + // Was this data interrupted by a new setup packet? + if (0 != (ep0.rx_indicator.status & RXMBOX_STATUS_SETUP_SETUP)) { + // NOTE: it is not clear from the documentation exactly what + // happens here, e.g. is it guaranteed that the new control + // packet appears at the start of the buffer rather than + // after any data previously received? Given typical + // USB host-side implementations this scenario is considered + // sufficiently unlikely that no further investigation has + // been carried out. + + // Inform higher-level code that the receive has been aborted. + if ((usbs_control_return (*)(usbs_control_endpoint*, int)) 0 != ep0.common.complete_fn) { + (*ep0.common.complete_fn)(&ep0.common, -EIO); + } + ep0.rx_expecting_data = false; + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + // Fall through the main control message handling code below. + } else { + // Data was expected and received. Transfer the data to the + // user's buffer, and perform completion. + usbs_control_return result; + cyg_uint32 size = ep0.rx_indicator.status & RXMBOX_STATUS_SIZE_MASK; + + CYG_ASSERT( (usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn, \ + "A completion function should be provided for OUT control messages"); + CYG_ASSERT(size == ep0.common.buffer_size, "Inconsistency between buffer and transfer sizes"); + memcpy(ep0.common.buffer, uncached->ep0_rx_buffer, size); + result = (*ep0.common.complete_fn)(&ep0.common, 0); + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + ep0.rx_expecting_data = false; + + // Start another receive for the next control message. + // Note that there has been a window where there was no receive + // in progress for endpoint 0, even though according to the + // USB spec a device must always be able to accept new + // control messages. + ep0_start_rx(8); + return; + } + } + + // When we get here we should have an eight-byte control message + // in uncached->ep0_rx_buffer. This should get moved into + // the ep0.common.control_buffer so that higher-level code sees + // it in the appropriate location. + CYG_ASSERT((ep0.rx_indicator.address == &(uncached->ep0_rx_bufdescs[0])) || \ + (ep0.rx_indicator.address == &(uncached->ep0_rx_bufdescs[2])), \ + "Received ep0 data should involve the ep0 rx buffer descriptor"); + + CYG_ASSERT(8 == (ep0.rx_indicator.status & RXMBOX_STATUS_SIZE_MASK), "Control messages should be 8 bytes"); + memcpy(ep0.common.control_buffer, uncached->ep0_rx_buffer, 8); + + // If we have received a control packet then any reset signals really + // will have come from the host and must be processed normally. + // Make sure that reset interrupts are no longer masked off. + if (0 == (*USBS_IMR2 & IBUS_SWAP32(USBS_GSR2_URST))) { + *USBS_IMR2 |= IBUS_SWAP32(USBS_GSR2_URST); FLUSH_IBUS(); + usbs_upd985xx_gsr2_mask |= USBS_GSR2_URST; + } + + { + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + usb_devreq* req = (usb_devreq*) ep0.common.control_buffer; + int length, direction, protocol, recipient; + + // Now we need to do some decoding of the data. A non-zero + // length field indicates that there will be a subsequent + // IN or OUT phase. The direction is controlled by the + // top bit of the first byte. The protocol is determined + // by other bits of the top byte. + length = (req->length_hi << 8) | req->length_lo; + direction = req->type & USB_DEVREQ_DIRECTION_MASK; + protocol = req->type & USB_DEVREQ_TYPE_MASK; + recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + + DBG(("ep0, new control request: type %x, code %x\n", req->type, req->request)); + DBG((" %s, length %d, value hi %x lo %x, index hi %x lo %x\n", + (USB_DEVREQ_DIRECTION_OUT == direction) ? "out" : "in", + length, req->value_hi, req->value_lo, req->index_hi, req->index_lo)); + + if (USB_DEVREQ_TYPE_STANDARD == protocol) { + + // First see if the request can be handled entirely in + // this module. + if (USB_DEVREQ_SET_ADDRESS == req->request) { + // The USB device address should be in value_lo. + // No more data is expected. + int old_state = ep0.common.state; + int address = req->value_lo; + if ((0 != length) || (address > 127)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + *USBS_GMR = (*USBS_GMR & ~(USBS_GMR_FA_MASK | USBS_GMR_VT)) | (address << USBS_GMR_FA_SHIFT); FLUSH_IBUS(); + result = USBS_CONTROL_RETURN_HANDLED; + } + // Switch to addressed state, informing higher-level + // code of this. + if (USBS_STATE_ADDRESSED != (old_state & USBS_STATE_MASK)) { + ep0.common.state = USBS_STATE_ADDRESSED; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_ADDRESSED, old_state); + } + } + // End of SET_ADDRESS handling + } else if (USB_DEVREQ_GET_STATUS == req->request) { + // GET_STATUS on the device as a whole is used to + // check the remote-wakeup and self-powered bits. + // GET_STATUS on an endpoint is used to determine + // the halted condition. + // GET_STATUS on anything else has to be left to + // other code. + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + // The host should expect two bytes back. + if ((2 == length) && (USB_DEVREQ_DIRECTION_IN == direction)) { + ep0.common.control_buffer[0] = 0; // Not self-powered, no remote wakeup + ep0.common.control_buffer[1] = 0; + ep0.common.buffer = ep0.common.control_buffer; + ep0.common.buffer_size = 2; + result = USBS_CONTROL_RETURN_HANDLED; + } else { + result = USBS_CONTROL_RETURN_STALL; + } + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + if ((2 == length) && (USB_DEVREQ_DIRECTION_IN == direction)) { + int endpoint = req->index_lo; + if (0 == endpoint) { + // get-status on endpoint 0 is either undefined or always valid. + // endpoint 0 is always up. + ep0.common.control_buffer[0] = 0; + result = USBS_CONTROL_RETURN_HANDLED; + } +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 3) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep0.common.control_buffer[0] = ep3.common.halted; + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + else if (((USB_DEVREQ_INDEX_DIRECTION_OUT | 4) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep0.common.control_buffer[0] = ep4.common.halted; + result = USBS_CONTROL_RETURN_HANDLED; + + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 5) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep0.common.control_buffer[0] = ep5.common.halted; + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif + else { + // An invalid endpoint has been specified or the + // endpoint can only be examined in configured state. + result = USBS_CONTROL_RETURN_STALL; + } + if (USBS_CONTROL_RETURN_HANDLED == result) { + ep0.common.control_buffer[1] = 0; + ep0.common.buffer = ep0.common.control_buffer; + ep0.common.buffer_size = 2; + } + } else { + result = USBS_CONTROL_RETURN_STALL; + } + } // Endpoint or device get-status + + } else if (USB_DEVREQ_CLEAR_FEATURE == req->request) { + + // CLEAR_FEATURE operates in much the same way as + // GET_STATUS + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + + // No data should be transferred, and only remote-wakeup can be cleared. + if ((0 != length) || (USB_DEVREQ_FEATURE_DEVICE_REMOTE_WAKEUP != req->value_lo)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + // Clearing remote-wakeup is a no-op. + result = USBS_CONTROL_RETURN_HANDLED; + } + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + // The only feature that can be cleared is endpoint-halt, no data should be transferred. + if ((0 != length) || (USB_DEVREQ_FEATURE_ENDPOINT_HALT != req->value_lo)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + int endpoint = req->index_lo; + if (0 == endpoint) { + // Clearing halt on endpoint 0 is always a no-op since that endpoint cannot be halted + } +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 3) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep3_set_halted(&ep3.common, false); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + else if (((USB_DEVREQ_INDEX_DIRECTION_OUT | 4) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep4_set_halted(&ep4.common, false); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 5) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep5_set_halted(&ep5.common, false); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif + else { + // Invalid endpoint or not in configured state. + result = USBS_CONTROL_RETURN_STALL; + } + } + } // Endpoing or device clear-feature + + } else if (USB_DEVREQ_SET_FEATURE == req->request) { + + // SET_FEATURE also operates in much the same way as + // GET_STATUS + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + // The only valid feature that can be set is remote-wakeup, + // which is not supported by this driver. + result = USBS_CONTROL_RETURN_STALL; + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + + // Only the halt condition can be set, and no data should be transferred. + // Halting endpoint 0 should probably be disallowed although the + // standard does not explicitly say so. + if ((0 != length) || + (USB_DEVREQ_FEATURE_ENDPOINT_HALT != req->value_lo) || + (USBS_STATE_CONFIGURED != (ep0.common.state & USBS_STATE_MASK))) { + + result = USBS_CONTROL_RETURN_STALL; + + } else { + int endpoint = req->index_lo; + if (0) { + } +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + else if ((USB_DEVREQ_INDEX_DIRECTION_IN | 3) == endpoint) { + ep3_set_halted(&ep3.common, true); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + else if ((USB_DEVREQ_INDEX_DIRECTION_OUT | 4) == endpoint) { + ep4_set_halted(&ep4.common, true); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + else if ((USB_DEVREQ_INDEX_DIRECTION_IN | 5) == endpoint) { + ep5_set_halted(&ep5.common, true); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif + else { + result = USBS_CONTROL_RETURN_STALL; + } + } + } // Endpoint or device set-feature + } + + // If the result has not been handled yet, pass it to + // the installed callback function (if any). + if (USBS_CONTROL_RETURN_UNKNOWN == result) { + if ((usbs_control_return (*)(usbs_control_endpoint*, void*))0 != ep0.common.standard_control_fn) { + result = (*ep0.common.standard_control_fn)(&ep0.common, ep0.common.standard_control_data); + } + } + + // If the result has still not been handled, leave it to + // the default implementation in the USB slave common place. + if (USBS_CONTROL_RETURN_UNKNOWN == result) { + result = usbs_handle_standard_control(&ep0.common); + } + } else { + // The other three types of control message can be + // handled by similar code. + usbs_control_return (*callback_fn)(usbs_control_endpoint*, void*); + void* callback_arg; + + if (USB_DEVREQ_TYPE_CLASS == protocol) { + callback_fn = ep0.common.class_control_fn; + callback_arg = ep0.common.class_control_data; + } else if (USB_DEVREQ_TYPE_VENDOR == protocol) { + callback_fn = ep0.common.vendor_control_fn; + callback_arg = ep0.common.vendor_control_data; + } else { + callback_fn = ep0.common.reserved_control_fn; + callback_arg = ep0.common.reserved_control_data; + } + + if ((usbs_control_return (*)(usbs_control_endpoint*, void*)) 0 == callback_fn) { + result = USBS_CONTROL_RETURN_STALL; + } else { + result = (*callback_fn)(&ep0.common, callback_arg); + } + } + + if (USBS_CONTROL_RETURN_HANDLED != result) { + // This control request cannot be handled. Generate a stall. + // These stalls will be cleared automaticaly by the next + // setup packet. + *EP0_CR |= (EP0_CR_ISS | EP0_CR_OSS); FLUSH_IBUS(); + // Start a receive for the next control message + ep0_start_rx(8); + } else { + // The control request has been handled. Is there any more + // data to be transferred? + if (0 == length) { + // Definitely start a receive for another control message + ep0_start_rx(8); + + // This operation is complete so we need to ack. It + // appears that the way to achieve this is to send a + // zero-byte packet. + ep0.tx_size = 0; + ep0_start_tx(); + + } else { + // Time to check the direction. + + if (USB_DEVREQ_DIRECTION_OUT == direction) { + // The host expects to send more data. Higher-level code + // should have provided an appropriate buffer. + CYG_ASSERT( (unsigned char*) 0 != ep0.common.buffer, "A receive buffer should have been provided"); + CYG_ASSERT( (usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn, \ + "A completion function should be provided for OUT control messages"); + CYG_ASSERT(length <= CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE, "Insufficient buffer space configured"); + + ep0.rx_expecting_data = true; + ep0_start_rx(length); + } else { + // The host expects to be able to read some data. + // This needs to go into a single contiguous + // buffer, and then the transfer can be started. + // Care has to be taken with various boundary conditions. + int actual_length = ep0_fill_txbuffer(); + if (actual_length > length) { + actual_length = length; + } + if ((length != actual_length) && (0 == (actual_length % CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE))) { + ep0.tx_needs_zero_transfer = true; + } else { + ep0.tx_needs_zero_transfer = false; + } + ep0.tx_size = actual_length; + ep0_start_tx(); + + // And make sure that there is another receive in progress + // for the next setup packet. + ep0_start_rx(8); + } + } + } // Control message handled + } +} + +// Endpoint 0 initialization also takes care of initializing generic bits +// of the USB controller, for example letting through resume and suspend +// interrupts and setting up the mailboxes. Also, it is necessary to +// start a receive operation so that the first control message can +// be processed. This code gets called during device driver initialization +// and after a reset from the host. +static void +ep0_init(void) +{ + // Reset the various fields in the ep0 structure. + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = (void (*)(usbs_control_endpoint*)) 0; + ep0.common.fill_data = (void*) 0; + ep0.common.fill_index = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + ep0.rx_expecting_data = false; + ep0.tx_indicator_valid = false; + ep0.rx_indicator_valid = false; + ep0.tx_needs_zero_transfer = false; + +#ifdef CYGIMP_DEVS_USB_UPD985XX_SERIALIZE_TRANSMITS + tx_in_progress = false; + ep0_tx_pending = false; +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + ep3_tx_pending = false; +# endif +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + ep5_tx_pending = false; +# endif +#endif + + // The general mode register. We do not have an address yet. The + // SOFINTVL field needs to be set to its default value. The other + // bits should be zero for now. + *USBS_GMR = USBS_GMR_SOFINTVL_DEFAULT_VALUE; FLUSH_IBUS(); + + // The version register and the status registers are read-only. + + // Interrupt masks. Endpoint 0 transmits and receives both have + // to be detected, as do the control operations. There should + // be no need to worry about full mailboxes or empty receive + // pools. DMA errors might be of interest, but it is not clear + // what to do about them since there does not appear to be + // a way of figuring out which transfer is affected. Frame number + // and addressing problems are ignored, there is nothing obvious + // that can be done about these. The other endpoints have their + // own initialization routines. + // + // Care has to be taken with reset interrupts. With some hardware + // the usb lines may be left floating during initialization, so + // the chip believes it sees continuous reset interrupts. There + // also appear to be problems if the host does generate a real + // reset signal, with interrupt storms lasting 10 or more + // milliseconds and preventing any other activity from taking + // place. What is done here is that reset interrupts are enabled + // if in the initial POWERED state. When a reset is detected, + // either a spurious one or a real reset from the host, + // handle_reset() will move the target to DEFAULT state, call + // ep0_init() again, and reset interrupts will be masked out. + // When a real control request is received from the host we + // know we have a good connection and the reset interrupt will + // be unmasked in ep0_rx_dsr(), so further resets from the + // host will be processed correctly. If the target is disconnected + // then we may again get a spurious reset interrupt, so we end + // up back in DEFAULT state and the reset interrupt would be + // masked again. + *USBS_IMR1 = IBUS_SWAP32(USBS_GSR1_GSR2 | USBS_GSR1_EP0TF | USBS_GSR1_EP0RF); FLUSH_IBUS(); + usbs_upd985xx_gsr1_mask = (USBS_GSR1_EP0TF | USBS_GSR1_EP0RF); + if (USBS_STATE_DEFAULT == (ep0.common.state & USBS_STATE_MASK)) { + *USBS_IMR2 = IBUS_SWAP32(USBS_GSR2_URSM | USBS_GSR2_USPD); FLUSH_IBUS(); + usbs_upd985xx_gsr2_mask = (USBS_GSR2_URSM | USBS_GSR2_USPD); + } else { + *USBS_IMR2 = IBUS_SWAP32(USBS_GSR2_URSM | USBS_GSR2_URST | USBS_GSR2_USPD); FLUSH_IBUS(); + usbs_upd985xx_gsr2_mask = (USBS_GSR2_URSM | USBS_GSR2_URST | USBS_GSR2_USPD); + } + + // Writing to the command register is a bad idea, because even + // writing 0 constitutes a command. Similarly there is no point + // in writing to the command address register. + + // The endpoint status register is read-only. + + // Set the rx pool information registers to disable alerts. + *USBS_RP0IR = IBUS_SWAP32(USBS_RPxIR_AL_NONE); FLUSH_IBUS(); + *USBS_RP1IR = IBUS_SWAP32(USBS_RPxIR_AL_NONE); FLUSH_IBUS(); + *USBS_RP2IR = IBUS_SWAP32(USBS_RPxIR_AL_NONE); FLUSH_IBUS(); + + // The pool address registers are read-only. The documentation + // that describes initialization says that these registers need to + // be filled in, but that seems wrong: providing receive buffers + // involves the command register. Presumably on early revisions it + // was necessary to fill in the address register. + + // Sort out the mailboxes. + *USBS_TMSA = IBUS_SWAPPTR(TxMailbox, &(uncached->tx_mboxes[0])); FLUSH_IBUS(); + *USBS_TMBA = IBUS_SWAPPTR(TxMailbox, &(uncached->tx_mboxes[TXMBOX_COUNT])); FLUSH_IBUS(); + *USBS_RMSA = IBUS_SWAPPTR(RxMailbox, &(uncached->rx_mboxes[0])); FLUSH_IBUS(); + *USBS_RMBA = IBUS_SWAPPTR(RxMailbox, &(uncached->rx_mboxes[RXMBOX_COUNT])); FLUSH_IBUS(); + // It is not clear whether these registers actually need to be initialized. + // The documentation suggests that they do, unlike TMWA and RMWA which + // are taken care of by the hardware. +#if 0 + *USBS_TMRA = IBUS_SWAPPTR(TxMailbox, &(uncached->tx_mboxes[0])); FLUSH_IBUS(); + *USBS_RMRA = IBUS_SWAPPTR(RxMailbox, &(uncached->rx_mboxes[0])); FLUSH_IBUS(); +#endif + + // Start a receive operation for a control message. + ep0_start_rx(8); + + // The endpoint 0 control register. The control packet size is + // configurable, with a default value of 8. Setting the + // enabled bit here affects the state as seen by the host. + *EP0_CR = IBUS_SWAP32(EP0_CR_EP0EN | CYGNUM_DEVS_USB_UPD985XX_EP0_PKTSIZE); FLUSH_IBUS(); + + // The other endpoint registers will be initialized by the appropriate + // _init() functions. Note that those other _init() functions should + // probably be called before the ep0-enabled bit is set. +} + +// ---------------------------------------------------------------------------- +// Endpoint 1 - isochronous transmits. +#if 0 +// A real implementation +#else +static inline void +ep1_init(void) +{ + *EP1_CR = IBUS_SWAP32(0); // Clear EP1EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +#endif + +// ---------------------------------------------------------------------------- +// Endpoint 2 - isochronous receives +#if 0 +// A real implementation +#else +static inline void +ep2_init(void) +{ + *EP2_CR = IBUS_SWAP32(0); // Clear EP2EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +#endif + +// ---------------------------------------------------------------------------- +// Generic transmit support. This is intended for use with both endpoints +// 3 and 5. For now the endpoint 0 code is too different. + +#if defined(CYGPKG_DEVS_USB_UPD985XX_EP3) || defined(CYGPKG_DEVS_USB_UPD985XX_EP5) + +// A utility routine for completing a transfer. This takes care of the +// callback as well as resetting the buffer. +static void +ep35_tx_complete(ep35_impl* ep, int result) +{ + void (*complete_fn)(void*, int) = ep->common.complete_fn; + void* complete_data = ep->common.complete_data; + + ep->common.buffer = (unsigned char*) 0; + ep->common.buffer_size = 0; + ep->common.complete_fn = (void (*)(void*, int)) 0; + ep->common.complete_data = (void*) 0; + + if ((void (*)(void*, int))0 != complete_fn) { + (*complete_fn)(complete_data, result); + } +} + +static void +ep35_start_tx(ep35_impl* ep) +{ + // Is this endpoint currently stalled? If so then a size of 0 can + // be used to block until the stall condition is clear, anything + // else should result in an immediate callback. + if (ep->common.halted) { + if (0 != ep->common.buffer_size) { + ep35_tx_complete(ep, -EAGAIN); + } + } else if (0 == ep->common.buffer_size) { + // A check to see if the endpoint is halted. It isn't. + ep35_tx_complete(ep, 0); + } else { + cyg_uint32 send_command; +#if 0 + diag_printf("Tx: %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[0], ep->common.buffer[1], ep->common.buffer[2], ep->common.buffer[3], + ep->common.buffer[4], ep->common.buffer[5], ep->common.buffer[6], ep->common.buffer[7]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[8], ep->common.buffer[9], ep->common.buffer[10], ep->common.buffer[11], + ep->common.buffer[12], ep->common.buffer[13], ep->common.buffer[14], ep->common.buffer[15]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[16], ep->common.buffer[17], ep->common.buffer[18], ep->common.buffer[19], + ep->common.buffer[20], ep->common.buffer[21], ep->common.buffer[22], ep->common.buffer[23]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[24], ep->common.buffer[25], ep->common.buffer[26], ep->common.buffer[27], + ep->common.buffer[28], ep->common.buffer[29], ep->common.buffer[30], ep->common.buffer[31]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[32], ep->common.buffer[33], ep->common.buffer[34], ep->common.buffer[35], + ep->common.buffer[36], ep->common.buffer[37], ep->common.buffer[38], ep->common.buffer[39]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep->common.buffer[40], ep->common.buffer[41], ep->common.buffer[42], ep->common.buffer[43], + ep->common.buffer[44], ep->common.buffer[45], ep->common.buffer[46], ep->common.buffer[47]); +#endif + + // Update the static buffer descriptor. + ep->tx_bufdesc->buffer = MIPS_TO_UNCACHED(ep->common.buffer); + ep->tx_bufdesc->control = ep->common.buffer_size | TXBUFDESC_CTRL_LAST | TXBUFDESC_CTRL_BUFDESC_BUFDESC; + + // Make sure that the entire transmit buffer is flushed to + // memory. + HAL_DCACHE_STORE(ep->common.buffer, ep->common.buffer_size); + + // Issue the send command. It is known that no transmits are + // in progress for this endpoint so the upper bound of 2 + // pending transmits can be ignored. The send command involves + // writing to two registers in succession, so interrupts had + // better be disabled while doing this. + send_command = ep->send_command | ep->common.buffer_size; + cyg_drv_isr_lock(); + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, (void*)ep->tx_bufdesc); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(send_command); FLUSH_IBUS(); + cyg_drv_isr_unlock(); + } +} + +// The stalled state is controlled by a single bit in the appropriate +// control register. However there is a problem in that it is not +// possible to abort a transmission that is already going out. +// Furthermore there is no way of detecting whether or not any +// packets have already gone out for this transfer: setting the halt +// bit before any data has gone out is reasonably ok, doing so +// in the middle of a transfer could be confusing. +// +// The approach taken here is to check whether or not there is a +// current tx buffer. If not then the stall bit can be set immediately. +// Otherwise the halted flag is set here, and it is left to the dsr +// to set the stall bit when the transfer completes. This may not +// be totally standards-compliant, but is probably the best solution +// for now. +static void +ep35_set_halted(ep35_impl* ep, cyg_bool new_value) +{ + if (ep->common.halted == new_value) { + return; + } + + // Avoid race conditions with the DSR updating the buffer fields. + cyg_drv_dsr_lock(); + + if (new_value ){ + // Set the halted flag to prevent further transmits, and if + // there is no transmission currently in progress then set + // the stalled bit immediately. + ep->common.halted = true; + if ((void*)0 == ep->common.buffer) { + *(ep->cr) |= IBUS_SWAP32(EPtx_CR_SSx); FLUSH_IBUS(); + } + } else { + // Update the hardware (that may be a no-op if the stalled bit + // never got set by the DSR), and clear the halted flag. + *(ep->cr) &= IBUS_SWAP32(~EPtx_CR_SSx); FLUSH_IBUS(); + ep->common.halted = false; + + // If there is a pending request to wait until the endpoint stall + // condition is clear, inform higher-level code. This test may + // give false positives but those would be harmless. + if (0 == ep->common.buffer_size) { + ep35_tx_complete(ep, 0); + } + } + + cyg_drv_dsr_unlock(); +} + +// An interrupt has occured related to endpoint 3 or 5 - i.e. the EP3TF +// or EP5TF interrupts, nothing else seems especially relevant. It is +// necessary to extract the appropriate send indicator from the tx +// mailbox to determine whether or not the transmission was successful +// and report status to higher-level code. +static void +ep35_dsr(ep35_impl* ep) +{ + TxMailbox mbox; + + // Extract the transmit indicator if that has not happened + // already courtesy of another DSR. + if (!ep->tx_indicator_valid) { + drain_tx_mailbox(); + if (!ep->tx_indicator_valid) { + // A transmit interrupt when there does not appear to be + // any data? + CYG_FAIL("ep35_dsr invoked when there is no valid tx indicator"); + return; + } + } + mbox = ep->tx_indicator; + ep->tx_indicator_valid = false; + +#ifdef CYGIMP_DEVS_USB_UPD985XX_EP5_BULK + // If emulating bulk transfers over the interrupt endpoint, and + // the transfer is an exact multiple of 64 bytes, then an extra + // zero-byte terminating packet needs to be sent. Care has to be + // taken to do this only once. + if ( (ep == &ep5) && (0 == (ep5.common.buffer_size % 64)) ) { + static cyg_bool sending_zero = false; + if (!sending_zero) { + sending_zero = true; + uncached->ep5_tx_bufdesc.buffer = uncached->ep0_tx_buffer; + uncached->ep5_tx_bufdesc.control = 0 | TXBUFDESC_CTRL_LAST | TXBUFDESC_CTRL_BUFDESC_BUFDESC; + cyg_drv_isr_lock(); + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, (void*) &(uncached->ep5_tx_bufdesc)); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(USBS_CMR_COMMAND_TX_EP5 | 0); FLUSH_IBUS(); + cyg_drv_isr_unlock(); + + // Do not complete the transfer. Instead completion has to + // wait for another interrupt. + return; + } else { + // This interrupt was for the zero-byte packet, so drop through + sending_zero = false; + } + } +#endif + + // If the endpoint should be halted but there was a transmit + // in progress, update the hardware now. + if (ep->common.halted) { + *(ep->cr) |= IBUS_SWAP32(EPtx_CR_SSx); FLUSH_IBUS(); + } + + // Allow any blocked transmits to proceed. + TX_UNLOCK(); + + if (0 != (mbox.status & TXMBOX_STATUS_IBUS_ERROR)) { + // This appears to be the only type of error that can be + // detected. Possibly the transmit should be retried here + // rather than reported. + ep35_tx_complete(ep, -EPIPE); + } else { + ep35_tx_complete(ep, ep->common.buffer_size); + } +} +#endif // Endpoints 3 or 5 + +// ---------------------------------------------------------------------------- +// Endpoint 3 - bulk transmits. + +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 +static void +ep3_start_tx(usbs_tx_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep3.common, "USB data transfer involves the wrong endpoint"); + CYG_ASSERT( ep3.common.buffer_size < (64 * 1024), "Specified transfer size too large for current implementation"); + if (TX_TRY_LOCK(&ep3_tx_pending)) { + ep35_start_tx(&ep3); + } +} + +static void +ep3_set_halted(usbs_tx_endpoint* endpoint, cyg_bool new_value) +{ + CYG_ASSERT(endpoint = &ep3.common, "USB set-stall operation involves the wrong endpoint"); + ep35_set_halted(&ep3, new_value); +} + +// Initialization. This gets called during the device driver +// initialization and after a reset. The main job is to initialize the +// EP3 control register, but the relevant bits of the interrupt mask +// are set here as well. The tx mailboxes are shared with other +// endpoints, so that is handled by ep0_init(). Any traffic that +// happened before the reset needs to be cleaned up. +static void +ep3_init(void) +{ + // Assume 64 byte packets, terminate transfers with a zero-byte packet + // if necessary since this endpoint is used for bulk transfers. + *EP3_CR = IBUS_SWAP32(EP3_CR_EP3EN | EP3_CR_TM3_SZLP | 64); FLUSH_IBUS(); + *USBS_IMR1 |= IBUS_SWAP32(USBS_GSR1_EP3TF); FLUSH_IBUS(); + usbs_upd985xx_gsr1_mask |= IBUS_SWAP32(USBS_GSR1_EP3TF); + ep3.common.halted = false; + ep3.tx_indicator_valid = false; + ep3.tx_bufdesc = &(uncached->ep3_tx_bufdesc); + ep35_tx_complete(&ep3, -EPIPE); +} +#else +static inline void +ep3_init(void) +{ + *EP3_CR = 0; // Clear EP3EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +# endif // Endpoint 3 configured in + +// ---------------------------------------------------------------------------- +// Repeat for endpoint 5 +# ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + +static void +ep5_start_tx(usbs_tx_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep5.common, "USB data transfer involves the wrong endpoint"); +#ifdef CYGIMP_DEVS_USB_UPD985XX_EP5_BULK + CYG_ASSERT( ep5.common.buffer_size < (64 * 1024), "Specified transfer size too large for current implementation"); +#else + CYG_ASSERT( ep5.common.buffer_size <= 64, "Specified transfer size too large for current implementation"); +#endif + if (TX_TRY_LOCK(&ep5_tx_pending)) { + ep35_start_tx(&ep5); + } +} + +static void +ep5_set_halted(usbs_tx_endpoint* endpoint, cyg_bool new_value) +{ + CYG_ASSERT(endpoint = &ep5.common, "USB set-stall operation involves the wrong endpoint"); + ep35_set_halted(&ep5, new_value); +} + +// Initialization. This gets called during the device driver +// initialization and after a reset. The main job is to initialize the +// EP5 control register, but the relevant bits of the interrupt mask +// are set here as well. The tx mailboxes are shared with other +// endpoints, so that is handled by ep0_init(). Any traffic that +// happened before the reset needs to be cleaned up. +static void +ep5_init(void) +{ + // Assume 64 byte packets, terminate transfers with a zero-byte packet + // if necessary since this endpoint is used for bulk transfers. + *EP5_CR = IBUS_SWAP32(EP5_CR_EP5EN | 64); FLUSH_IBUS(); + *USBS_IMR1 |= IBUS_SWAP32(USBS_GSR1_EP5TF); FLUSH_IBUS(); + usbs_upd985xx_gsr1_mask |= IBUS_SWAP32(USBS_GSR1_EP5TF); + ep5.common.halted = false; + ep5.tx_indicator_valid = false; + ep5.tx_bufdesc = &(uncached->ep5_tx_bufdesc); + ep35_tx_complete(&ep5, -EPIPE); +} +#else +static inline void +ep5_init(void) +{ + *EP5_CR = 0; // Clear EP5EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +#endif // Endpoint 5 configured in + + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 +// ---------------------------------------------------------------------------- +// Endpoint 4 - bulk receives. +// +// Bulk receives are mostly straightforward, but the cache does involve +// a complication. The assumption is that the receive buffer will be in +// cached memory, and is unlikely to be cacheline-aligned. Clearly the bulk +// of the buffer has to be invalidated. However the receive buffer may share +// some cache lines with other data at the head and tail, and invalidating +// those would be wrong. +// +// The solution here is to split up a receive in to up to three areas, +// head, main, and tail, where the head and tail are statically +// allocated in uncached memory. Any one or two of these areas may be +// unused, depending on alignment and transfer size. The main area +// corresponds to the central section of the supplied receive buffer, +// will be cacheline-aligned, and invalidated at the start of a receive. +// Data will be copied from the head and tail areas into the receive +// buffer by the dsr on completion of the transfer. +// +// There are additional complications caused by the hardware's need for +// linked buffers. + +static void +ep4_rx_complete(int result) +{ + void (*complete_fn)(void*, int) = ep4.common.complete_fn; + void* complete_data = ep4.common.complete_data; + +#if 0 + *EP4_CR = IBUS_SWAP32(EP4_CR_EP4EN | EP4_CR_RM4_ASSEMBLE | EP4_CR_NAK4 | 64); FLUSH_IBUS(); +#endif + +#if 0 + if (result > 0) { + diag_printf("Rx: %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[0], ep4.common.buffer[1], ep4.common.buffer[2], ep4.common.buffer[3], + ep4.common.buffer[4], ep4.common.buffer[5], ep4.common.buffer[6], ep4.common.buffer[7]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[8], ep4.common.buffer[9], ep4.common.buffer[10], ep4.common.buffer[11], + ep4.common.buffer[12], ep4.common.buffer[13], ep4.common.buffer[14], ep4.common.buffer[15]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[16], ep4.common.buffer[17], ep4.common.buffer[18], ep4.common.buffer[19], + ep4.common.buffer[20], ep4.common.buffer[21], ep4.common.buffer[22], ep4.common.buffer[23]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[24], ep4.common.buffer[25], ep4.common.buffer[26], ep4.common.buffer[27], + ep4.common.buffer[28], ep4.common.buffer[29], ep4.common.buffer[30], ep4.common.buffer[31]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[32], ep4.common.buffer[33], ep4.common.buffer[34], ep4.common.buffer[35], + ep4.common.buffer[36], ep4.common.buffer[37], ep4.common.buffer[38], ep4.common.buffer[39]); + diag_printf(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + ep4.common.buffer[40], ep4.common.buffer[41], ep4.common.buffer[42], ep4.common.buffer[43], + ep4.common.buffer[44], ep4.common.buffer[45], ep4.common.buffer[46], ep4.common.buffer[47]); + } +#endif + + ep4.common.buffer = (unsigned char*) 0; + ep4.common.buffer_size = 0; + ep4.common.complete_fn = (void (*)(void*, int)) 0; + ep4.common.complete_data = (void*) 0; + + if ((void (*)(void*, int))0 != complete_fn) { + (*complete_fn)(complete_data, result); + } +} + +static void +ep4_start_rx(usbs_rx_endpoint* ep) +{ + CYG_ASSERT( ep == &ep4.common, "USB data transfer involves the wrong endpoint"); + + // Is this endpoint currently stalled? If so then a size of 0 can + // be used to block until the stall condition is clear, anything + // else should result in an immediate callback. + if (ep4.common.halted) { + if (0 != ep4.common.buffer_size) { + ep4_rx_complete(-EAGAIN); + } + } else if (0 == ep4.common.buffer_size) { + // A check to see if the endpoint is halted. It isn't. + ep4_rx_complete(0); + } else { + + // Time to work out how much data should go into the uncached + // head and tail buffers, how much can go directly into the + // receive buffer, how much memory needs to be invalidated, and so on. + cyg_uint32 buffer_arith; + + // Where to start filling in buffer descriptors. + cyg_uint32 first_bufdesc; + + // And the current buffer descriptor. + cyg_uint32 current_bufdesc; + + CYG_ASSERT( ep4.common.buffer_size < (64 * 1024), "Specified transfer size too large for current implementation"); + + // If there has not been a receive operation, tail_index + // will still be set to -1. Otherwise it will be somewhere + // between 1 and 3, or between 5 and 7, depending on + // whether the previous receive operation used the + // first four buffer descriptors or the last four. + if (ep4.tail_index < 4) { + first_bufdesc = 4; + } else { + first_bufdesc = 0; + } + current_bufdesc = first_bufdesc; + + // Arithmetic, especially remainder operators, requires + // integers rather than a pointer. + buffer_arith = (cyg_uint32) ep4.common.buffer; + + // The size of the "head" area. This involves up to + // (cacheline-1) bytes, so that the main receive buffer + // is suitably aligned. + ep4.head_size = ((buffer_arith + HAL_DCACHE_LINE_SIZE - 1) & ~(HAL_DCACHE_LINE_SIZE - 1)) - buffer_arith; + if (ep4.head_size > ep4.common.buffer_size) { + ep4.head_size = ep4.common.buffer_size; + } + if (0 < ep4.head_size) { + // It is necessary to receive some data into the uncached head area. + uncached->ep4_rx_bufdescs[current_bufdesc].buffer = uncached->ep4_head; + uncached->ep4_rx_bufdescs[current_bufdesc].control = RXBUFDESC_CTRL_BUFDESC_BUFDESC | ep4.head_size; + current_bufdesc++; + } + + // Now for the size of the main area. This is the rest of the + // transfer size, minus the tail area. + ep4.direct_size = ep4.common.buffer_size - ep4.head_size; + ep4.direct_size &= ~(HAL_DCACHE_LINE_SIZE - 1); + if (ep4.direct_size > 0) { + uncached->ep4_rx_bufdescs[current_bufdesc].buffer = MIPS_TO_UNCACHED(ep4.common.buffer + ep4.head_size); + uncached->ep4_rx_bufdescs[current_bufdesc].control = RXBUFDESC_CTRL_BUFDESC_BUFDESC | ep4.direct_size; + current_bufdesc++; + HAL_DCACHE_INVALIDATE(ep4.common.buffer + ep4.head_size, ep4.direct_size); + } + + // And the size of the tail. This is the transfer size minus what we have accumulated so far. + ep4.tail_size = ep4.common.buffer_size - (ep4.head_size + ep4.direct_size); + if (ep4.tail_size > 0) { + uncached->ep4_rx_bufdescs[current_bufdesc].buffer = uncached->ep4_tail; + uncached->ep4_rx_bufdescs[current_bufdesc].control = RXBUFDESC_CTRL_BUFDESC_BUFDESC | ep4.tail_size; + current_bufdesc++; + } + + // Or the LAST bit into the last of these buffer descriptors. + uncached->ep4_rx_bufdescs[current_bufdesc - 1].control |= RXBUFDESC_CTRL_LAST; + + // Turn the current one into a link descriptor. + uncached->ep4_rx_bufdescs[current_bufdesc].control = RXBUFDESC_CTRL_BUFDESC_LINK; + uncached->ep4_rx_bufdescs[current_bufdesc].buffer = 0; + + // The buffer descriptors have now been sorted out. Time to + // add this receive buffer to the pool. Atomicity becomes + // important for some of these steps. + cyg_drv_isr_lock(); + + // Update the link pointer used for the last receive operation + // to point at the new set of buffer descriptors. + if (-1 != ep4.tail_index) { + uncached->ep4_rx_bufdescs[ep4.tail_index].buffer = (void*) &(uncached->ep4_rx_bufdescs[first_bufdesc]); + } + + // Keep track of the link pointer used for the last receive + // operation. + ep4.tail_index = current_bufdesc; + + while (0 != (*USBS_CMR & IBUS_SWAP32(USBS_CMR_BUSY))) { + // Do nothing: this situation should be short-lived. + } + *USBS_CA = IBUS_SWAPPTR(void, (void*)&(uncached->ep4_rx_bufdescs[first_bufdesc])); FLUSH_IBUS(); + *USBS_CMR = IBUS_SWAP32(USBS_CMR_COMMAND_ADD_POOL2 | 1); FLUSH_IBUS(); +#if 0 + *EP4_CR = IBUS_SWAP32(EP4_CR_EP4EN | EP4_CR_RM4_ASSEMBLE | 64); FLUSH_IBUS(); +#endif + cyg_drv_isr_unlock(); + } +} + +// The stalled state is controlled by a single bit in the appropriate +// control register. When it comes to ongoing receives, the reasoning +// here is much the same as for ep3_set_halted(). Arguably this is not +// quite right. set_halted() is most likely to be called in response +// to a control request from the host, and the host is unlikely to +// transmit any data at the same time as making this request. Hence the +// host will see the wrong behaviour: it can still make one transfer +// after asking for the stalled bit to be set. Since there does not +// appear to be any way to cancel a supplied receive, this behaviour +// still seems the most sensible. +static void +ep4_set_halted(usbs_rx_endpoint* ep, cyg_bool new_state) +{ + CYG_ASSERT(ep == &ep4.common, "USB set-stall operation involves the wrong endpoint"); + + if (ep4.common.halted == new_state) { + return; + } + + // Avoid race conditions with the DSR updating the buffer fields. + cyg_drv_dsr_lock(); + + if (new_state){ + // Set the halted flag to prevent further transmits, and if + // there is no receive currently in progress then set + // the stalled bit immediately. + ep4.common.halted = true; + if ((void*)0 == ep4.common.buffer) { + *EP4_CR |= IBUS_SWAP32(EP4_CR_SS4); FLUSH_IBUS(); + } + } else { + // Update the hardware (that may be a no-op if the stalled bit + // never got set by the DSR), and clear the halted flag. + *EP4_CR &= IBUS_SWAP32(~EP4_CR_SS4); FLUSH_IBUS(); + ep4.common.halted = false; + + // If there is a pending request to wait until the endpoint stall + // condition is clear, inform higher-level code. This test may + // give false positives but those would be harmless. + if (0 == ep4.common.buffer_size) { + ep4_rx_complete(0); + } + } + + cyg_drv_dsr_unlock(); +} + +// An interrupt has occured related to endpoint 4 - i.e. the EP4RF +// interrupt, nothing else seems especially relevant. The ISR will +// have set the NAK bit in the control register. It is necessary to +// extract the appropriate receive indicator from the rx mailbox to +// determine whether or not the transmission was successful and how +// much data was actually received, clear the interrupt bit, and +// report status to higher-level code. +static void +ep4_dsr(void) +{ + // Extract the transmit indicator if that has not happened + // already courtesy of another DSR. + if (!ep4.rx_indicator_valid) { + drain_rx_mailbox(); + if (!ep4.rx_indicator_valid) { + // A receive interrupt when there does not appear to be + // any data? It appears that this can happen when there + // are error conditions. +#if 1 + CYG_FAIL("EP4_DSR invoked when there is no valid rx indicator"); +#endif + return; + } + } + ep4.rx_indicator_valid = false; + + // If the endpoint should be halted but there was a transmit + // in progress, update the hardware now. + if (ep4.common.halted) { + *EP4_CR |= IBUS_SWAP32(EP4_CR_SS4); FLUSH_IBUS(); + } + if (0 != (ep4.rx_indicator.status & (RXMBOX_STATUS_IBUS_ERROR | 0))) { + // This appears to be the only type of error that can be + // detected. Everything else gets retried by the hardware, + // except when using the isochronous endpoint. + ep4_rx_complete(-EPIPE); + } else { + cyg_uint32 actual_size = ep4.rx_indicator.status & RXMBOX_STATUS_SIZE_MASK; + + // Either the transfer size must match the requested size, or the + // supplied buffer should have been aligned to cacheline boundaries. + // Anything else risks leaving the receive pool in a confused + // state and there is no way of cleaning things up. + CYG_ASSERT((actual_size == ep4.common.buffer_size) || ((0 == ep4.head_size) && (0 == ep4.tail_size)), \ + "Buffers should be cacheline aligned if the protocol involves partial transfers"); + + // If there was some data in the head, move it from uncached + // to cached. Ditto for tail. Note that these copies may be + // for data that has not actually been transferred if the + // actual transfer is less than expected, but overwriting bits + // of the receive buffer in such circumstances should be + // harmless. + if (ep4.head_size > 0) { + memcpy(ep4.common.buffer, uncached->ep4_head, ep4.head_size); + } + if (ep4.tail_size > 0) { + memcpy(ep4.common.buffer + ep4.head_size + ep4.direct_size, uncached->ep4_tail, ep4.tail_size); + } + ep4_rx_complete(actual_size); + } +} + +// Initialization. This gets called during the device driver +// initialization and after a reset. The main job is to initialize the +// EP4 control register, but the relevant bits of the interrupt mask +// are set here as well. The rx mailboxes are shared with other +// endpoints, so that is handled by ep0_init(). Any traffic that +// happened before the reset needs to be cleaned up. +static void +ep4_init(void) +{ + // Assume 64 byte packets, and use assemble mode so that we get a + // single rx indication per transfer. In practice the buffer + // directory will only ever contain one entry so there should be + // no discernible difference between normal, assemble, or separate + // mode. +#if 0 + *EP4_CR = IBUS_SWAP32(EP4_CR_EP4EN | EP4_CR_RM4_ASSEMBLE | EP4_CR_NAK4 | 64); FLUSH_IBUS(); +#else + *EP4_CR = IBUS_SWAP32(EP4_CR_EP4EN | EP4_CR_RM4_ASSEMBLE | 64); FLUSH_IBUS(); +#endif + + *USBS_IMR1 |= IBUS_SWAP32(USBS_GSR1_EP4RF); FLUSH_IBUS(); + usbs_upd985xx_gsr1_mask |= USBS_GSR1_EP4RF; + ep4.common.halted = false; + ep4.rx_indicator_valid = false; + ep4.tail_index = -1; + ep4_rx_complete(-EPIPE); +} +#else +static inline void +ep4_init(void) +{ + *EP4_CR = 0; // Clear EP4EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +#endif // Endpoint 4 configured in + +// ---------------------------------------------------------------------------- +// Endpoint 6 - interrupt receives +#if 0 +// A real implementation +#else +static inline void +ep6_init(void) +{ + *EP6_CR = 0; // Clear EP6EN bit, thus disabling the endpoint + FLUSH_IBUS(); +} +#endif + +// ---------------------------------------------------------------------------- +// Make sure the hardware is in a known state by completely resetting +// the USB controller. This gets called during device driver +// initialization, and again whenever the host issues a reset signal. +// The previous state is unknown. Even during eCos initialization +// RedBoot may have involved USB I/O, or some POST code may have +// performed loopback tests. The various endpoint init routines will +// also perform software resets as appropriate. + +static void +usbs_upd985xx_handle_reset(void) +{ + // Reset the USB hardware. This involves poking the warm reset + // register and then polling the matching status register. It is + // assumed that this poll will take a short time, and in practice + // the loop appears to terminate immediately. + *S_WRCR = S_WRCR_USBWR; FLUSH_IBUS(); + while (0 == (*S_WRSR & S_WRCR_USBWR)) { + // Do nothing. + } + + // Get all the endpoints into a known state - for disabled + // endpoints these init calls are inlined and just disable the + // relevant hardware. + ep0_init(); + ep1_init(); + ep2_init(); + ep3_init(); + ep4_init(); + ep5_init(); + ep6_init(); +} + +// ---------------------------------------------------------------------------- +// Start(). This is typically called by the application itself once +// everything else has been initialized, i.e. when the host should be +// able to start talking to this device. There is no actual bit in the +// chip itself to switch the USB pins from tri-state, so instead the +// platform HAL has to supply appropriate functionality. In the absence +// of such functionality things will only work if you start up eCos +// with the USB cable disconnected, then plug in the cable once +// start() has been called. +// +// The device driver initialization will have already set up the +// hardware, and the first action from the host should be a reset +// signal which will cause a re-initialization. There is no need +// to do anything else here. + +static void +usbs_upd985xx_ep0_start(usbs_control_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep0.common, "USB startup involves the wrong endpoint"); + + // If there is additional platform-specific initialization to + // perform, do it now. This macro can come from the platform HAL, + // but may not be available on all platforms. +#ifdef UPD985XX_USB_PLATFORM_INIT + UPD985XX_USB_PLATFORM_INIT(); +#endif +} + +// ---------------------------------------------------------------------------- +// The main DSR +// +// This gets called by the interrupt system or by the polling code +// when one or more USB-related events have occurred. The ISR code +// will have updated globals usbs_upd985xx_gsr1 and usbs_upd98x0x_gsr2 +// to indicate which events are pending. When running in interrupt +// mode it is possible that further interrupts will occur while the +// DSR is running, and hence that these globals will be updated +// while the DSR is running. This is handled by a loop. A side effect +// is that the DSR may get called when all the work has already +// been done by a previous DSR, but that is harmless. + +static void +usbs_upd985xx_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + CYG_ASSERT(CYGNUM_HAL_INTERRUPT_USB == vector, "USB DSR should only be invoked for USB interrupts" ); + CYG_ASSERT(0 == data, "The USB DSR needs no global data pointer"); + + while ((0 != usbs_upd985xx_gsr1) || (0 != usbs_upd985xx_gsr2)) { + // Only update the globals in one place since it involves + // the overhead of two function calls. + cyg_uint32 gsr1, gsr2; + cyg_drv_isr_lock(); + gsr1 = usbs_upd985xx_gsr1; + gsr2 = usbs_upd985xx_gsr2; + usbs_upd985xx_gsr1 = 0; + usbs_upd985xx_gsr2 = 0; + cyg_drv_isr_unlock(); + + if (0 != gsr2) { + // Treat reset specially. If there has been a reset then none of + // the other bits are of any interest. + if (0 != (USBS_GSR2_URST & gsr2)) { + int old_state = ep0.common.state; + // Update the state. ep0_init() detects this state change and + // updates imr2 appropriate, preventing a continuous storm + // of reset interrupts. + ep0.common.state = USBS_STATE_DEFAULT; + usbs_upd985xx_handle_reset(); + // This state change must be reported to higher-level code + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_RESET, old_state); + } + break; + } + // There is possible confusion if both suspend and resume + // bits are set. Was there a suspend, quickly followed by + // a resume? Were we already suspended, then resumed, now + // suspended again? For now this complication is ignored and + // resume is given priority over suspend. + if (0 != (USBS_GSR2_URSM & gsr2)) { + int old_state = ep0.common.state; + if (0 != (old_state & USBS_STATE_SUSPENDED)) { + ep0.common.state &= ~USBS_STATE_SUSPENDED; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_RESUMED, old_state); + } + } + } else if (0 != (USBS_GSR2_USPD & gsr2)) { + int old_state = ep0.common.state; + if (0 == (old_state & USBS_STATE_SUSPENDED)) { + ep0.common.state |= USBS_STATE_SUSPENDED; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_SUSPENDED, old_state); + } + } + } else { + // Handle error conditions on the isochronous endpoints? + } + } + if (0 != gsr1) { + + if (0 != (USBS_GSR1_EP0TF & gsr1)) { + ep0_tx_dsr(); + } + if (0 != (USBS_GSR1_EP0RF & gsr1)) { + ep0_rx_dsr(); + } +#if 0 + // EP1FU? + if (0 != (USBS_GSR1_EP1TF & gsr1)) { + ep1_dsr(); + } +#endif +#if 0 + // EP2FO? + if (0 != (USBS_GSR1_EP2RF & gsr1)) { + ep1_dsr(); + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + if (0 != (USBS_GSR1_EP3TF & gsr1)) { + ep35_dsr(&ep3); + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + if (0 != (USBS_GSR1_EP4RF & gsr1)) { + ep4_dsr(); + } +#endif +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + if (0 != (USBS_GSR1_EP5TF & gsr1)) { + ep35_dsr(&ep5); + } +#endif +#if 0 + if (0 != (USBS_GSR1_EP6RF & gsr1)) { + ep6_dsr(); + } +#endif + } + } // while there are unprocessed interrupts +} + +// ---------------------------------------------------------------------------- +// Interrupt handling. +// +// There are two status registers to look at. These are read-once +// registers, i.e. reading the register causes all bits to be cleared, +// so the relevant state has to be preserved in volatile globals which +// can then be examined by the DSR. In theory the top bit of the first +// status register can be used to check whether or not there is +// anything of interest in the second one, but it seems quicker to +// just read both registers. After masking out interrupts that are of +// no interest, some global flags are updated. If this leaves a +// non-zero value then the DSR must be invoked. + +static cyg_uint32 +usbs_upd985xx_isr(cyg_vector_t vector, cyg_addrword_t data) +{ + CYG_ASSERT(CYGNUM_HAL_INTERRUPT_USB == vector, "USB ISR should only be invoked for USB interrupts"); + CYG_ASSERT(0 == data, "The UPD985xx ISR needs no global data pointer"); + + usbs_upd985xx_gsr1 |= IBUS_SWAP32(*USBS_GSR1) & usbs_upd985xx_gsr1_mask; + usbs_upd985xx_gsr2 |= IBUS_SWAP32(*USBS_GSR2) & usbs_upd985xx_gsr2_mask; + + cyg_drv_interrupt_acknowledge(vector); + return ((0 == usbs_upd985xx_gsr1) && (0 == usbs_upd985xx_gsr2)) ? + CYG_ISR_HANDLED : CYG_ISR_CALL_DSR; +} + +// ---------------------------------------------------------------------------- +// Polling support. It is not clear that this is going to work particularly +// well since according to the documentation the hardware does not generate +// NAKs automatically - instead the ISR has to set the appropriate bits +// sufficiently quickly to avoid confusing the host. +// +// Calling the isr directly avoids duplicating code, but means that +// cyg_drv_interrupt_acknowledge() will get called when not inside a +// real interrupt handler. This should be harmless. + +static void +usbs_upd985xx_poll(usbs_control_endpoint* endpoint) +{ + CYG_ASSERT(endpoint == &ep0.common, "USB poll involves the wrong endpoint"); + if (CYG_ISR_CALL_DSR == usbs_upd985xx_isr(CYGNUM_HAL_INTERRUPT_USB, 0)) { + usbs_upd985xx_dsr(CYGNUM_HAL_INTERRUPT_USB, 0, 0); + } +} + +// ---------------------------------------------------------------------------- +// Initialization +// +// This routine gets called from a prioritized static constructor during +// eCos startup. +void +usbs_upd985xx_init(void) +{ + // Make sure the uncached data structure is accessed through + // kseg1. + uncached = (uncached_data*) MIPS_TO_UNCACHED(&cached_copy); + + // Perform a full hardware reset. + usbs_upd985xx_handle_reset(); + + // It is possible and desirable to install the interrupt handler + // here, even though there will be no interrupts for a while yet. + // FIXME: is 99 a sensible interrupt priority :-? + cyg_drv_interrupt_create(CYGNUM_HAL_INTERRUPT_USB, + 99, // priority + 0, // data + &usbs_upd985xx_isr, + &usbs_upd985xx_dsr, + &usbs_upd985xx_intr_handle, + &usbs_upd985xx_intr_data); + + cyg_drv_interrupt_attach(usbs_upd985xx_intr_handle); + cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_USB); +} + +// ---------------------------------------------------------------------------- +// Testing support. + +usbs_testing_endpoint usbs_testing_endpoints[] = { + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, + endpoint_number : 0, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep0.common, +#ifdef CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "0c", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, // zero-byte control transfers are meaningless +#if (CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE < CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE) + max_size : CYGNUM_DEVS_USB_UPD985XX_EP0_RXBUFSIZE, +#else + max_size : CYGNUM_DEVS_USB_UPD985XX_EP0_TXBUFSIZE, +#endif + max_in_padding : 0, + alignment : 0 + }, + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP3 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 3, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep3.common, +# ifdef CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "3w", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 0, + max_size : 0x0FFFF, // Driver limitation, only a single buffer descriptor is used + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP4 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 4, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &ep4.common, +# ifdef CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "4r", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 1, + max_size : 0x0FFFF, // Driver limitation + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + +#ifdef CYGPKG_DEVS_USB_UPD985XX_EP5 + { +# ifdef CYGIMP_DEVS_USB_UPD985XX_EP5_BULK + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, +# else + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT, +# endif + endpoint_number : 5, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep5.common, +# ifdef CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "5w", +# else + devtab_entry : (const char*) 0, +# endif + min_size : 1, + max_size : 0x0FFFF, // Driver limitation, only a single buffer descriptor is used + max_in_padding : 0, + alignment : HAL_DCACHE_LINE_SIZE + }, +#endif + + USBS_TESTING_ENDPOINTS_TERMINATOR +}; diff --git a/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx_data.cxx b/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx_data.cxx new file mode 100644 index 0000000..45acda8 --- /dev/null +++ b/ecos/packages/devs/usb/nec_upd985xx/current/src/usbs_upd985xx_data.cxx @@ -0,0 +1,189 @@ +//========================================================================== +// +// usbs_nec_upd9850x.c +// +// Static data for the NEC uPD9850x USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2001-05-22 +// +// This file contains various objects that should go into extras.o +// rather than libtarget.a, e.g. devtab entries that would normally +// be eliminated by the selective linking. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/infra/diag.h> +#include <cyg/io/devtab.h> +#include <cyg/io/usb/usbs_upd985xx.h> +#include <pkgconf/devs_usb_upd985xx.h> + +// ---------------------------------------------------------------------------- +// Initialization. The goal here is to call usbs_upd985xx_init() +// early on during system startup, to take care of things like +// registering interrupt handlers etc. which are best done +// during system init. +// +// If the endpoint 0 devtab entry is available then its init() +// function can be used to take care of this. However the devtab +// entries are optional so an alternative mechanism must be +// provided. Unfortunately although it is possible to give +// a C function the constructor attribute, it cannot be given +// an initpri attribute. Instead it is necessary to define a +// dummy C++ class. + +extern "C" void usbs_upd985xx_init(void); + +#ifndef CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY +class usbs_upd985xx_initialization { + public: + usbs_upd985xx_initialization() { + usbs_upd985xx_init(); + } +}; + +static usbs_upd985xx_initialization usbs_upd985xx_init_object CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO); +#endif + +// ---------------------------------------------------------------------------- +// The devtab entries. Each of these is optional, many applications +// will want to use the lower-level API rather than go via +// open/read/write/ioctl. + +#ifdef CYGVAR_DEVS_USB_UPD985XX_EP0_DEVTAB_ENTRY + +// For endpoint 0 the only legal operations are get_config() and +// set_config(), and these are provided by the common package. + +static bool +usbs_upd985xx_devtab_ep0_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + usbs_upd985xx_init(); + return true; +} + +static CHAR_DEVIO_TABLE(usbs_upd985xx_ep0_devtab_functions, + &cyg_devio_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static CHAR_DEVTAB_ENTRY(usbs_upd985xx_ep0_devtab_entry, + CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "0c", + 0, + &usbs_upd985xx_ep0_devtab_functions, + &usbs_upd985xx_devtab_ep0_init, + 0, + (void*) &usbs_upd985xx_ep0); +#endif + +// ---------------------------------------------------------------------------- +// Common routines for ep3, ep4 and ep5 +#if defined(CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY) +static bool +usbs_upd985xx_devtab_dummy_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + return true; +} +#endif + +// ---------------------------------------------------------------------------- +// ep3 devtab entry. This can only be used for slave->host, so only +// the cwrite() function makes sense. The same function table can be +// used for ep5. + +#if defined(CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY) || \ + defined(CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY) +static CHAR_DEVIO_TABLE(usbs_upd985xx_ep35_devtab_functions, + &usbs_devtab_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +# if defined(CYGVAR_DEVS_USB_UPD985XX_EP3_DEVTAB_ENTRY) +static CHAR_DEVTAB_ENTRY(usbs_upd985xx_ep3_devtab_entry, + CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "3w", + 0, + &usbs_upd985xx_ep35_devtab_functions, + &usbs_upd985xx_devtab_dummy_init, + 0, + (void*) &usbs_upd985xx_ep3); + +# endif + +# if defined(CYGVAR_DEVS_USB_UPD985XX_EP5_DEVTAB_ENTRY) +static CHAR_DEVTAB_ENTRY(usbs_upd985xx_ep5_devtab_entry, + CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "5w", + 0, + &usbs_upd985xx_ep35_devtab_functions, + &usbs_upd985xx_devtab_dummy_init, + 0, + (void*) &usbs_upd985xx_ep5); +# endif +#endif + +// ---------------------------------------------------------------------------- +// ep4 devtab entry. This can only be used for host->slave, so only the +// cread() function makes sense. + +#ifdef CYGVAR_DEVS_USB_UPD985XX_EP4_DEVTAB_ENTRY + +static CHAR_DEVIO_TABLE(usbs_upd985xx_ep4_devtab_functions, + &cyg_devio_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static CHAR_DEVTAB_ENTRY(usbs_upd985xx_ep4_devtab_entry, + CYGDAT_DEVS_USB_UPD985XX_DEVTAB_BASENAME "4r", + 0, + &usbs_upd985xx_ep4_devtab_functions, + &usbs_upd985xx_devtab_dummy_init, + 0, + (void*) &usbs_upd985xx_ep4); +#endif + diff --git a/ecos/packages/devs/usb/sa11x0/current/ChangeLog b/ecos/packages/devs/usb/sa11x0/current/ChangeLog new file mode 100644 index 0000000..1571bbe --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/ChangeLog @@ -0,0 +1,185 @@ +2003-02-25 Jonathan Larmour <jifl@eCosCentric.com> + + * doc/usbs_sa11x0.sgml: Declare as <part> not <reference> to get + correct TOC numbering. + +2003-02-24 Jonathan Larmour <jifl@eCosCentric.com> + + * cdl/usbs_sa11x0.cdl: Fix doc link. + + * doc/usbs_sa11x0.sgml: Comment out DOCTYPE for now to allow building + with standard doc build. + Add an enclosing <reference> so it's structured better with standard + doc build. + +2002-02-11 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Add a handler for the SET_INTERFACE standard control message. + This should not be needed, but appears to avoid hardware problems + when a compliance testing program sends certain requests. Also + improve the handling of halted endpoints since the hardware + does not allow transfers to be aborted. + +2002-01-23 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Add missing assertions for non-NULL buffers + Try to improve the behaviour when the host sends data before + the target is ready. The hardware is not capable of handling + this situation, but some recovery is possible some of the time. + +2001-09-14 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Update support for USB testing + +2001-08-06 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Add initial support for USB testing. + +2001-05-21 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c (usbs_sa11x0_ep2_dsr): + Fix the boundary condition where the transmission is an exact + multiple of 64 bytes. ep2_process_packet() already did the + right thing but was not getting called. + Also, some cosmetic changes to the receive code for the + same boundary condition. These actually have no effect + because of the hardware, but may be useful for other + people writing USB device drivers. + +2001-04-05 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c (usbs_sa11x0_ep0_fill_fifo): + Set the DATA_END and IN_READY bits in one operation. + This seems to avoid problems when the target needs to send + back a zero-length control packet. + +2001-02-02 Bart Veer <bartv@redhat.com> + + * cdl/usbs_sa11x0.cdl: + Add doc property to the html + + * doc/usbs_sa11x0.sgml, devs-usbs-sa11x0.html: + Incorporate changes from docs department, regenerate html + +2001-01-25 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0_data.cxx: + * cdl/usbs_sa11x0.cdl: + Devtab entries were never actually being built - and did not + build... + + * cdl/usbs_sa11x0.cdl: + Sort out the dependencies for minimal environments such as + RedBoot. + +2001-01-24 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Invoke additional platform-specific initialization, if defined + via <cyg/hal/hal_io.h> and CYGBLD_HAL_PLATFORM_IO_H + +2001-01-22 Bart Veer <bartv@redhat.com> + + * doc/usbs_sa11x0.sgml, doc/makefile: + Added documentation. + +2001-01-16 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Make sure that the resume interrupt source is enabled, + even when the USB bus is not suspended. For some reason + this makes it possible to disconnect and reconnect. + +2001-01-16 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Use the HAL macros for virtual->physical address translation + Update poll() and start() to match the documentation + Fix the handling of control messages affecting endpoints 1 and 2 + if those endpoints are not currently configured. + + +2001-01-02 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Change ep2_tx_packet() to start the DMA operation after the + UDC packet-complete bit has been set. This avoids a very high + error rate. Add some scheduler locking to eliminate a resulting + race condition, and sort out the error handling to match. + Comment out some assertions relating to spurious interrupts, which + have been observed. Instead the code now recovers from these. + +2000-12-15 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Add debug code for tracking stats and simulating failures. + Change the EP1 code to switch between DMA channels A and B as + required, rather than always using channel A. This is more robust + and was needed for the failure simulation. + + Make the ep1 packet processing code robust in case an unexpected + failure occurs during its invocation. It is not clear how this + can ever happen, but on a couple of occasions it did and caused an + infinite loop. + +2000-11-30 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Fix DMA_CONTROL_CLEAR_ALL constant, it was ignoring START_B + In ep2_init(), separate out the fifo write and the IN_SIZE + write to avoid a hardware problem. + +2000-11-29 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Disable some debugging features and add retries when manipulating + certain DMA registers - needed with some Silicon revisions. + +2000-11-28 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + More rewriting, plus implementing the endpoint halt support. + +2000-11-24 Bart Veer <bartv@redhat.com> + + * src/usbs_sa11x0.c: + Clean up some of the debugging. + Largely rewrite the endpoint 2 support to try and get it working + Transmit a runt packet during initialization to work around + hardware problem. + +2000-11-22 Bart Veer <bartv@redhat.com> + + * include/usbs_sa11x0.h: Fix nested #include protection + +2000-11-21 Bart Veer <bartv@redhat.com> + + * First check-in of eCos USB support. + +//=========================================================================== +// ####GPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. +// ------------------------------------------- +// ####GPLCOPYRIGHTEND#### +//=========================================================================== diff --git a/ecos/packages/devs/usb/sa11x0/current/cdl/usbs_sa11x0.cdl b/ecos/packages/devs/usb/sa11x0/current/cdl/usbs_sa11x0.cdl new file mode 100644 index 0000000..704319f --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/cdl/usbs_sa11x0.cdl @@ -0,0 +1,202 @@ +# ==================================================================== +# +# usbs_sa11x0.cdl +# +# SA11X0 USB support. +# +# ==================================================================== +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +# ==================================================================== +######DESCRIPTIONBEGIN#### +# +# Author(s): bartv +# Original data: bartv +# Contributors: +# Date: 2000-10-04 +# +#####DESCRIPTIONEND#### +# ==================================================================== + +cdl_package CYGPKG_DEVS_USB_SA11X0 { + display "SA11X0 USB Device Driver" + include_dir "cyg/io/usb" + parent CYGPKG_USB + implements CYGHWR_IO_USB_SLAVE + doc ref/devs-usb-sa11x0.html + + # Make sure that we are running on the right hardware. + requires CYGPKG_HAL_ARM + requires CYGPKG_HAL_ARM_SA11X0 + + + description " + The on-chip serial port 0 on the SA11X0 implements a USB + device controller, facilitating the use of this processor + in USB peripherals. This package provides a suitable eCos + device driver." + + + cdl_option CYGFUN_DEVS_USB_SA11X0_EP0 { + display "Support the control endpoint 0" + default_value CYGINT_IO_USB_SLAVE_CLIENTS + # And the USB support packages + requires CYGPKG_IO_USB CYGPKG_IO_USB_SLAVE + compile usbs_sa11x0.c + compile -library=libextras.a usbs_sa11x0_data.cxx + description " + Enable support for endpoint 0. If this support is disabled + then the entire USB port is unusable." + } + + cdl_option CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 0" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 0 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and ioctl calls then a devtab entry is needed. + " + } + + cdl_component CYGPKG_DEVS_USB_SA11X0_EP1 { + display "Support endpoint 1, used for host->slave communications" + implements CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS + requires CYGFUN_DEVS_USB_SA11X0_EP0 + default_value CYGFUN_DEVS_USB_SA11X0_EP0 + description " + In the SA11X0 USB implementation endpoint 1 can only be + used for host->slave communication. If the intended application + only involves slave->host transfers then the support for + endpoint 1 can be disabled. Note that this does not affect + control messages which always go via endpoint 0." + + cdl_option CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL { + display "Control DMA usage for endpoint 1" + flavor booldata + legal_values 0 to 5 + default_value 4 + description " + In the SA11X0 USB implementation endpoint 1 only has + a 20-byte fifo. If the application only involves + small transfers then this may prove sufficient, but + for larger transfers the use of a DMA engine is + mandated. This configuration option allows the + use of DMA engine to be disabled or enabled, and the + specific DMA channel to be selected. The SA11X0 + supports 6 DMA channels numbered 0 to 5. If DMA + is enabled for endpoint 1 then the selected channel + cannot be used by any other code." + } + + cdl_option CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 1" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 1 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and read calls then a devtab entry is needed. + " + } + } + + cdl_component CYGPKG_DEVS_USB_SA11X0_EP2 { + display "Support endpoint 2, used for slave->host communications" + implements CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS + requires CYGFUN_DEVS_USB_SA11X0_EP0 + default_value CYGFUN_DEVS_USB_SA11X0_EP0 + description " + In the SA11X0 USB implementation endpoint 2 can only be + used for slave->host communication. If the intended application + only involves host->slave transfers then the support for + endpoint 2 can be disabled. Note that this does not affect + control messages which always go via endpoint 0." + + cdl_option CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL { + display "Control DMA usage for endpoint 2" + flavor booldata + legal_values 0 to 5 + default_value 5 + description " + In the SA11X0 USB implementation endpoint 2 only has + a 16-byte fifo. If the application only involves + small transfers then this may prove sufficient, but + for larger transfers the use of a DMA engine is + mandated. This configuration option allows the + use of DMA engine to be disabled or enabled, and the + specific DMA channel to be selected. The SA11X0 + supports 6 DMA channels numbered 0 to 5. If DMA + is enabled for endpoint 2 then the selected channel + cannot be used by any other code." + } + + cdl_option CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY { + display "Provide a devtab entry for endpoint 2" + default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES + requires CYGPKG_IO + description " + If endpoint 2 will only be accessed via the low-level + USB-specific calls then there is no need for an entry + in the device table, saving some memory. If the + application intends to access the endpoint by means + of open and write calls then a devtab entry is needed. + " + } + } + + cdl_option CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME { + display "Base name for devtab entries" + flavor data + active_if { CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY || + CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY + } + default_value { "\"/dev/usbs\"" } + description " + If the SA11X0 USB device driver package provides devtab + entries for any of the endpoints then this option gives + control over the names of these entries. By default the + endpoints will be called \"/dev/usbs0c\", \"/dev/usbs1r\" + and \"/dev/usbs2w\" (assuming all three endpoints are + enabled. The common part \"/dev/usbs\" is determined + by this configuration option. It may be necessary to + change this if there are multiple USB slave-side + devices on the target hardware to prevent a name clash. + " + } +} diff --git a/ecos/packages/devs/usb/sa11x0/current/doc/devs-usb-sa11x0.html b/ecos/packages/devs/usb/sa11x0/current/doc/devs-usb-sa11x0.html new file mode 100644 index 0000000..4c3791f --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/doc/devs-usb-sa11x0.html @@ -0,0 +1,341 @@ +<!-- Copyright (C) 2001 Free Software Foundation, Inc. --> +<!-- This material may be distributed only subject to the terms --> +<!-- and conditions set forth in the Open Publication License, v1.0 --> +<!-- or later (the latest version is presently available at --> +<!-- http://www.opencontent.org/openpub/) --> +<!-- Distribution of substantively modified versions of this --> +<!-- document is prohibited without the explicit permission of the --> +<!-- copyright holder. --> +<!-- Distribution of the work or derivative of the work in any --> +<!-- standard (paper) book form is prohibited unless prior --> +<!-- permission obtained from the copyright holder --> +<HTML> +<HEAD> +<TITLE> +SA11X0 USB Device Driver</TITLE> +<META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.54"></HEAD> +<BODY +CLASS="REFENTRY" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF"> +<H1> +<A +NAME="DEVS-USB-SA11X0"> +SA11X0 USB Device Driver</A> +</H1> +<DIV +CLASS="REFNAMEDIV"> +<A +NAME="AEN4"> +</A> +<H2> +Name</H2> +SA11X0 USB Support -- Device driver for the on-chip SA11X0 USB device</DIV> +<DIV +CLASS="REFSECT1"> +<A +NAME="AEN7"> +</A> +<H2> +SA11X0 USB Hardware</H2> +<P> +The Intel StrongARM SA11x0 family of processors is supplied with an +on-chip USB slave device, the UDC (USB Device Controller). This +supports three endpoints. Endpoint 0 can only be used for control +messages. Endpoint 1 can only be used for bulk transfers from host to +peripheral. Endpoint 2 can only be used for bulk transfers from +peripheral to host. Isochronous and interrupt transfers are not +supported.</P> +<DIV +CLASS="CAUTION"> +<P> +</P> +<TABLE +CLASS="CAUTION" +BORDER="1" +WIDTH="100%"> +<TR> +<TD +ALIGN="CENTER"> +<B> +Caution</B> +</TD> +</TR> +<TR> +<TD +ALIGN="LEFT"> +<P> +Different revisions of the SA11x0 silicon have had various problems +with the USB support. The device driver has been tested primarily +against stepping B4 of the SA1110 processor, and may not function as +expected with other revisions. Application developers should obtain +the manufacturer's current errata sheets and specification updates. +The B4 stepping still has a number of problems, but the device driver +can work around these. However there is a penalty in terms of extra +code, extra cpu cycles, and increased dispatch latency because extra +processing is needed at DSR level. Interrupt latency should not be +affected.</P> +<P> +There is one specific problem inherent in the UDC design of which +application developers should be aware: the hardware cannot fully +implement the USB standard for bulk transfers. A bulk transfer +typically consists of some number of full-size 64-byte packets and is +terminated by a packet less than the full size. If the amount of data +transferred is an exact multiple of 64 bytes then this requires a +terminating packet of 0 bytes of data (plus header and checksum). The +SA11x0 USB hardware does not allow a 0-byte packet to be transmitted, +so the device driver is forced to substitute a 1-byte packet and the +host receives more data than expected. Protocol support is needed so +that the appropriate host-side device driver can allow buffer space +for the extra byte, detect when it gets sent, and discard it. +Consequently certain standard USB class protocols cannot be +implemented using the SA11x0, and therefore custom host-side device +drivers will generally have to be provided, rather than re-using +existing ones that understand the standard protocol.</P> +</TD> +</TR> +</TABLE> +</DIV> +</DIV> +<DIV +CLASS="REFSECT1"> +<A +NAME="AEN13"> +</A> +<H2> +Endpoint Data Structures</H2> +<P> +The SA11x0 USB device driver can provide up to three data structures +corresponding to the three endpoints: a +<SPAN +CLASS="STRUCTNAME"> +usbs_control_endpoint</SPAN> + structure +<TT +CLASS="LITERAL"> +usbs_sa11x0_ep0</TT> +; a +<SPAN +CLASS="STRUCTNAME"> +usbs_rx_endpoint</SPAN> + +<TT +CLASS="LITERAL"> +usbs_sa11x0_ep1</TT> +; and a +<SPAN +CLASS="STRUCTNAME"> +usbs_tx_endpoint</SPAN> + +<TT +CLASS="LITERAL"> +usbs_sa11x0_ep2</TT> +. The header file +<TT +CLASS="FILENAME"> +cyg/io/usb/usbs_sa11x0.h</TT> + +provides declarations for these.</P> +<P> +Not all applications will require support for all the endpoints. For +example, if the intended use of the UDC only involves peripheral to +host transfers then <TT +CLASS="LITERAL"> +usbs_sa11x0_ep1</TT> + is redundant. +The device driver provides configuration options to control the +presence of each endpoint:</P> +<P> +</P> +<OL +TYPE="1"> +<LI> +<P> +Endpoint 0 is controlled by +<TT +CLASS="LITERAL"> +CYGFUN_DEVS_USB_SA11X0_EP0</TT> +. This defaults to +enabled if there are any higher-level packages that require USB +hardware or if the global preference +<TT +CLASS="LITERAL"> +CYGGLO_IO_USB_SLAVE_APPLICATION</TT> + is enabled, +otherwise it is disabled. Usually this has the desired effect. It may +be necessary to override this in special circumstances, for example if +the target board uses an external USB chip in preference to the UDC +and it is that external chip's device driver that should be used +rather than the on-chip UDC. It is not possible to disable endpoint 0 +and at the same time enable one or both of the other endpoints, since +a USB device is only usable if it can process the standard control +messages.</P> +</LI> +<LI> +<P> +Endpoint 1 is controlled by +<TT +CLASS="LITERAL"> +CYGPKG_DEVS_USB_SA11X0_EP1</TT> +. By default it is +enabled whenever endpoint 0 is enabled, but it can be disabled +manually when not required.</P> +</LI> +<LI> +<P> +Similarly endpoint 2 is controlled by +<TT +CLASS="LITERAL"> +CYGPKG_DEVS_USB_SA11X0_EP2</TT> +. This is also enabled by +default whenever endpoint 0 is enabled, but it can be disabled manually.</P> +</LI> +</OL> +<P> +The SA11X0 USB device driver implements the interface specified by the +common eCos USB Slave Support package. The documentation for that +package should be consulted for further details. There is only one +major deviation: when there is a peripheral to host transfer on +endpoint 2 which is an exact multiple of the bulk transfer packet size +(usually 64 bytes) the device driver has to pad the transfer with one +extra byte. This is because of a hardware limitation: the UDC is +incapable of transmitting 0-byte packets as required by the USB +specification. Higher-level code, including the host-side device +driver, needs to be aware of this and adapt accordingly.</P> +<P> +The device driver assumes a bulk packet size of 64 bytes, so this +value should be used in the endpoint descriptors in the enumeration +data provided by application code. There is experimental code +for running with <A +HREF="devs-usb-sa11x0.html#AEN58"> +DMA disabled</A> +, +in which case the packet size will be 16 bytes rather than 64.</P> +</DIV> +<DIV +CLASS="REFSECT1"> +<A +NAME="AEN39"> +</A> +<H2> +Devtab Entries</H2> +<P> +In addition to the endpoint data structures the SA11X0 USB device +driver can also provide devtab entries for each endpoint. This allows +higher-level code to use traditional I/O operations such as +<TT +CLASS="FUNCTION"> +open</TT> +/<TT +CLASS="FUNCTION"> +read</TT> +/<TT +CLASS="FUNCTION"> +write</TT> + +rather than the USB-specific non-blocking functions like +<TT +CLASS="FUNCTION"> +usbs_start_rx_buffer</TT> +. These devtab entries are +optional since they are not always required. The relevant +configuration options are +<TT +CLASS="LITERAL"> +CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY</TT> +, +<TT +CLASS="LITERAL"> +CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY</TT> + and +<TT +CLASS="LITERAL"> +CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY</TT> +. By default +these devtab entries are provided if the global preference +<TT +CLASS="LITERAL"> +CYGGLO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES</TT> + is enabled, +which is usually the case. Obviously a devtab entry for a given +endpoint will only be provided if the underlying endpoint is enabled. +For example, there will not be a devtab entry for endpoint 1 if +<TT +CLASS="LITERAL"> +CYGPKG_DEVS_USB_SA11X0_EP1</TT> + is disabled.</P> +<P> +The names for the three devtab entries are determined by using a +configurable base name and appending <TT +CLASS="LITERAL"> +0c</TT> +, +<TT +CLASS="LITERAL"> +1r</TT> + or <TT +CLASS="LITERAL"> +2w</TT> +. The base name is +determined by the configuration option +<TT +CLASS="LITERAL"> +CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME</TT> + and has a +default value of <TT +CLASS="LITERAL"> +/dev/usbs</TT> +, so the devtab entry for +endpoint 1 would default to <TT +CLASS="LITERAL"> +/dev/usbs1r</TT> +. If the +target hardware involves multiple USB devices then application +developers may have to change the base name to prevent a name clash.</P> +</DIV> +<DIV +CLASS="REFSECT1"> +<A +NAME="AEN58"> +</A> +<H2> +DMA Engines</H2> +<P> +The SA11X0 UDC provides only limited fifos for bulk transfers on +endpoints 1 and 2; smaller than the normal 64-byte bulk packet size. +Therefore a typical transfer requires the use of DMA engines. The +SA11x0 provides six DMA engines that can be used for this, and the +endpoints require one each (assuming both endpoints are enabled). At +the time of writing there is no arbitration mechanism to control +access to the DMA engines. By default the device driver will use +DMA engine 4 for endpoint 1 and DMA engine 5 for endpoint 2, and it +assumes that no other code uses these particular engines.</P> +<P> +The exact DMA engines that will be used are determined by the +configuration options +<TT +CLASS="LITERAL"> +CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL</TT> + and +<TT +CLASS="LITERAL"> +CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL</TT> +. These +options have the booldata flavor, allowing the use of DMA to be +disabled completely in addition to controlling which DMA engines are +used. If DMA is disabled then the device driver will attempt to +work purely using the fifos, and the packet size will be limited to +only 16 bytes. This limit should be reflected in the appropriate +endpoint descriptors in the enumeration data. The code for driving the +endpoints without DMA should be considered experimental. At best it +will be suitable only for applications where the amount of data +transferred is relatively small, because four times as many interrupts +will be raised and performance will suffer accordingly.</P> +</DIV> +</BODY> +</HTML> diff --git a/ecos/packages/devs/usb/sa11x0/current/doc/makefile b/ecos/packages/devs/usb/sa11x0/current/doc/makefile new file mode 100644 index 0000000..95cd4e9 --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/doc/makefile @@ -0,0 +1,54 @@ +#============================================================================= +# +# makefile +# +# For building the SA11x0 USB device driver documentation +# +#============================================================================= +## ####ECOSGPLCOPYRIGHTBEGIN#### +## ------------------------------------------- +## This file is part of eCos, the Embedded Configurable Operating System. +## Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +## +## eCos is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free +## Software Foundation; either version 2 or (at your option) any later +## version. +## +## eCos is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with eCos; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## As a special exception, if other files instantiate templates or use +## macros or inline functions from this file, or you compile this file +## and link it with other works to produce a work based on this file, +## this file does not by itself cause the resulting work to be covered by +## the GNU General Public License. However the source code for this file +## must still be made available in accordance with section (3) of the GNU +## General Public License v2. +## +## This exception does not invalidate any other reasons why a work based +## on this file might be covered by the GNU General Public License. +## ------------------------------------------- +## ####ECOSGPLCOPYRIGHTEND#### +#============================================================================= +#####DESCRIPTIONBEGIN#### +# +# Author(s): bartv +# Date: 2001-01-11 +#####DESCRIPTIONEND#### +#============================================================================= + +TOPLEVEL := ../../../../.. +MAIN_SGML := usbs_sa11x0.sgml +MAIN_HTML := devs-usb-sa11x0.html +MAIN_PDF := devs-usb-sa11x0.pdf +OTHER_SGML := +PICTURES := + +include $(TOPLEVEL)/pkgconf/rules.doc diff --git a/ecos/packages/devs/usb/sa11x0/current/doc/usbs_sa11x0.sgml b/ecos/packages/devs/usb/sa11x0/current/doc/usbs_sa11x0.sgml new file mode 100644 index 0000000..cb6432c --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/doc/usbs_sa11x0.sgml @@ -0,0 +1,234 @@ +<!-- DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" --> + +<!-- {{{ Banner --> + +<!-- =============================================================== --> +<!-- --> +<!-- usbs_sa11x0.sgml --> +<!-- --> +<!-- Documentation for the SA11x0 USB Device Driver. --> +<!-- --> +<!-- =============================================================== --> +<!-- ####ECOSDOCCOPYRIGHTBEGIN#### --> +<!-- =============================================================== --> +<!-- Copyright (C) 2001, 2002 Free Software Foundation, Inc. --> +<!-- This material may be distributed only subject to the terms --> +<!-- and conditions set forth in the Open Publication License, v1.0 --> +<!-- or later (the latest version is presently available at --> +<!-- http://www.opencontent.org/openpub/) --> +<!-- Distribution of the work or derivative of the work in any --> +<!-- standard (paper) book form is prohibited unless prior --> +<!-- permission obtained from the copyright holder --> +<!-- =============================================================== --> +<!-- ####ECOSDOCCOPYRIGHTEND#### --> +<!-- =============================================================== --> +<!-- #####DESCRIPTIONBEGIN#### --> +<!-- --> +<!-- Author(s): bartv --> +<!-- Contact(s): bartv --> +<!-- Date: 2001/01/11 --> +<!-- Version: 0.01 --> +<!-- --> +<!-- ####DESCRIPTIONEND#### --> +<!-- =============================================================== --> + +<!-- }}} --> + +<part id="devs-usb-sa11x0-ref"> +<!-- reference id="devs-usb-sa11x0-ref" --> + <title>SA11X0 USB Device Driver</title> + +<refentry id="devs-usb-sa11x0"> +<refmeta> +<refentrytitle>SA11X0 USB Device Driver</refentrytitle> +</refmeta> +<refnamediv> +<refname>SA11X0 USB Support</refname> +<refpurpose>Device driver for the on-chip SA11X0 USB device</refpurpose> +</refnamediv> + +<refsect1><title>SA11X0 USB Hardware</title> +<para> +The Intel StrongARM SA11x0 family of processors is supplied with an +on-chip USB slave device, the UDC (USB Device Controller). This +supports three endpoints. Endpoint 0 can only be used for control +messages. Endpoint 1 can only be used for bulk transfers from host to +peripheral. Endpoint 2 can only be used for bulk transfers from +peripheral to host. Isochronous and interrupt transfers are not +supported. +</para> +<caution> +<para> +Different revisions of the SA11x0 silicon have had various problems +with the USB support. The device driver has been tested primarily +against stepping B4 of the SA1110 processor, and may not function as +expected with other revisions. Application developers should obtain +the manufacturer's current errata sheets and specification updates. +The B4 stepping still has a number of problems, but the device driver +can work around these. However there is a penalty in terms of extra +code, extra cpu cycles, and increased dispatch latency because extra +processing is needed at DSR level. Interrupt latency should not be +affected. +</para> +<para> +There is one specific problem inherent in the UDC design of which +application developers should be aware: the hardware cannot fully +implement the USB standard for bulk transfers. A bulk transfer +typically consists of some number of full-size 64-byte packets and is +terminated by a packet less than the full size. If the amount of data +transferred is an exact multiple of 64 bytes then this requires a +terminating packet of 0 bytes of data (plus header and checksum). The +SA11x0 USB hardware does not allow a 0-byte packet to be transmitted, +so the device driver is forced to substitute a 1-byte packet and the +host receives more data than expected. Protocol support is needed so +that the appropriate host-side device driver can allow buffer space +for the extra byte, detect when it gets sent, and discard it. +Consequently certain standard USB class protocols cannot be +implemented using the SA11x0, and therefore custom host-side device +drivers will generally have to be provided, rather than re-using +existing ones that understand the standard protocol. +</para> +</caution> +</refsect1> + +<refsect1><title>Endpoint Data Structures</title> +<para> +The SA11x0 USB device driver can provide up to three data structures +corresponding to the three endpoints: a +<structname>usbs_control_endpoint</structname> structure +<literal>usbs_sa11x0_ep0</literal>; a +<structname>usbs_rx_endpoint</structname> +<literal>usbs_sa11x0_ep1</literal>; and a +<structname>usbs_tx_endpoint</structname> +<literal>usbs_sa11x0_ep2</literal>. The header file +<filename class="headerfile">cyg/io/usb/usbs_sa11x0.h</filename> +provides declarations for these. +</para> +<para> +Not all applications will require support for all the endpoints. For +example, if the intended use of the UDC only involves peripheral to +host transfers then <literal>usbs_sa11x0_ep1</literal> is redundant. +The device driver provides configuration options to control the +presence of each endpoint: +</para> +<orderedlist> +<listitem> +<para> +Endpoint 0 is controlled by +<literal>CYGFUN_DEVS_USB_SA11X0_EP0</literal>. This defaults to +enabled if there are any higher-level packages that require USB +hardware or if the global preference +<literal>CYGGLO_IO_USB_SLAVE_APPLICATION</literal> is enabled, +otherwise it is disabled. Usually this has the desired effect. It may +be necessary to override this in special circumstances, for example if +the target board uses an external USB chip in preference to the UDC +and it is that external chip's device driver that should be used +rather than the on-chip UDC. It is not possible to disable endpoint 0 +and at the same time enable one or both of the other endpoints, since +a USB device is only usable if it can process the standard control +messages. +</para> +</listitem> +<listitem> +<para> +Endpoint 1 is controlled by +<literal>CYGPKG_DEVS_USB_SA11X0_EP1</literal>. By default it is +enabled whenever endpoint 0 is enabled, but it can be disabled +manually when not required. +</para> +</listitem> +<listitem> +<para> +Similarly endpoint 2 is controlled by +<literal>CYGPKG_DEVS_USB_SA11X0_EP2</literal>. This is also enabled by +default whenever endpoint 0 is enabled, but it can be disabled manually. +</para> +</listitem> +</orderedlist> +<para> +The SA11X0 USB device driver implements the interface specified by the +common eCos USB Slave Support package. The documentation for that +package should be consulted for further details. There is only one +major deviation: when there is a peripheral to host transfer on +endpoint 2 which is an exact multiple of the bulk transfer packet size +(usually 64 bytes) the device driver has to pad the transfer with one +extra byte. This is because of a hardware limitation: the UDC is +incapable of transmitting 0-byte packets as required by the USB +specification. Higher-level code, including the host-side device +driver, needs to be aware of this and adapt accordingly. +</para> +<para> +The device driver assumes a bulk packet size of 64 bytes, so this +value should be used in the endpoint descriptors in the enumeration +data provided by application code. There is experimental code +for running with <link linkend="usbs-sa11x0-dma">DMA disabled</link>, +in which case the packet size will be 16 bytes rather than 64. +</para> +</refsect1> + +<refsect1><title>Devtab Entries</title> +<para> +In addition to the endpoint data structures the SA11X0 USB device +driver can also provide devtab entries for each endpoint. This allows +higher-level code to use traditional I/O operations such as +<function>open</function>/<function>read</function>/<function>write</function> +rather than the USB-specific non-blocking functions like +<function>usbs_start_rx_buffer</function>. These devtab entries are +optional since they are not always required. The relevant +configuration options are +<literal>CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY</literal>, +<literal>CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY</literal> and +<literal>CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY</literal>. By default +these devtab entries are provided if the global preference +<literal>CYGGLO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES</literal> is enabled, +which is usually the case. Obviously a devtab entry for a given +endpoint will only be provided if the underlying endpoint is enabled. +For example, there will not be a devtab entry for endpoint 1 if +<literal>CYGPKG_DEVS_USB_SA11X0_EP1</literal> is disabled. +</para> +<para> +The names for the three devtab entries are determined by using a +configurable base name and appending <literal>0c</literal>, +<literal>1r</literal> or <literal>2w</literal>. The base name is +determined by the configuration option +<literal>CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME</literal> and has a +default value of <literal>/dev/usbs</literal>, so the devtab entry for +endpoint 1 would default to <literal>/dev/usbs1r</literal>. If the +target hardware involves multiple USB devices then application +developers may have to change the base name to prevent a name clash. +</para> +</refsect1> + +<refsect1><title id="usbs-sa11x0-dma">DMA Engines</title> +<para> +The SA11X0 UDC provides only limited fifos for bulk transfers on +endpoints 1 and 2; smaller than the normal 64-byte bulk packet size. +Therefore a typical transfer requires the use of DMA engines. The +SA11x0 provides six DMA engines that can be used for this, and the +endpoints require one each (assuming both endpoints are enabled). At +the time of writing there is no arbitration mechanism to control +access to the DMA engines. By default the device driver will use +DMA engine 4 for endpoint 1 and DMA engine 5 for endpoint 2, and it +assumes that no other code uses these particular engines. +</para> +<para> +The exact DMA engines that will be used are determined by the +configuration options +<literal>CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL</literal> and +<literal>CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL</literal>. These +options have the booldata flavor, allowing the use of DMA to be +disabled completely in addition to controlling which DMA engines are +used. If DMA is disabled then the device driver will attempt to +work purely using the fifos, and the packet size will be limited to +only 16 bytes. This limit should be reflected in the appropriate +endpoint descriptors in the enumeration data. The code for driving the +endpoints without DMA should be considered experimental. At best it +will be suitable only for applications where the amount of data +transferred is relatively small, because four times as many interrupts +will be raised and performance will suffer accordingly. +</para> +</refsect1> + +</refentry> +</part> +<!-- /reference --> diff --git a/ecos/packages/devs/usb/sa11x0/current/include/usbs_sa11x0.h b/ecos/packages/devs/usb/sa11x0/current/include/usbs_sa11x0.h new file mode 100644 index 0000000..f2288cc --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/include/usbs_sa11x0.h @@ -0,0 +1,73 @@ +#ifndef CYGONCE_USBS_SA11X0_H +# define CYGONCE_USBS_SA11X0_H +//========================================================================== +// +// include/usbs_sa11x0.h +// +// The interface exported by the SA11X0 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2000-10-04 +// Purpose: +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/usb/usbs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The SA11x0 family comes with on-chip USB slave support. This + * provides three endpoints. Endpoint 0 can only be used for control + * messages. Endpoints 1 and 2 can only be used for bulk transfers, + * host->slave for endpoint 1 and slave->host for endpoint 2. + */ +extern usbs_control_endpoint usbs_sa11x0_ep0; +extern usbs_rx_endpoint usbs_sa11x0_ep1; +extern usbs_tx_endpoint usbs_sa11x0_ep2; + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + + +#endif /* CYGONCE_USBS_SA11X0_H */ diff --git a/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0.c b/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0.c new file mode 100644 index 0000000..56b105f --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0.c @@ -0,0 +1,2550 @@ +//========================================================================== +// +// usbs_sa11x0.c +// +// Device driver for the SA11x0 USB port. +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2000-10-04 +// +// This code implements support for the on-chip USB port on the SA11x0 +// family of processors. The code has been developed on the SA1110 and +// may or may not work on other members of the SA11x0 family. There +// have problems with the USB support on certain revisions of the silicon, +// so the errata sheet appropriate to the specific processor being used +// should be consulted. There also appear to be problems which do not +// appear on any errata, which this code attempts to work around. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/infra/cyg_type.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/cyg_trac.h> +#include <cyg/infra/diag.h> + +#include <pkgconf/hal_arm.h> +#include <pkgconf/devs_usb_sa11x0.h> + +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_cache.h> +#include <cyg/hal/hal_sa11x0.h> +#include <cyg/error/codes.h> + +#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> + +// Debugging support. By default this driver operates mostly at +// DSR level, with the ISR doing a minimal amount of processing. +// However is also possible to run most of the code at thread-level, +// This is subject to some restrictions because the USB standard +// imposes timing constraints, e.g. some control operations such +// as SET-ADDRESS have to complete within 50ms. However it is +// very useful for debugging, specifically it allows you to put +// printf()'s in various places. +// +// Right now these configuration options are not exported to the +// user because running at DSR level is likely to be good enough +// for everybody not actively debugging this code. The options +// could be exported if necessary. +//#define CYGPKG_DEVS_USB_SA11X0_THREAD +#undef CYGPKG_DEVS_USB_SA11X0_THREAD +#ifdef CYGPKG_DEVS_USB_SA11X0_THREAD + // Default stack size should be CYGNUM_HAL_STACK_SIZE_TYPICAL +# define CYGNUM_DEVS_USB_SA11X0_THREAD_STACK_SIZE 4096 +# define CYGNUM_DEVS_USB_SA11X0_THREAD_PRIORITY 7 +# include <cyg/kernel/kapi.h> +#endif + +#if 0 +# define DBG(a) diag_printf a +#else +# define DBG(a) +#endif + +#undef FAILURES +#ifdef FAILURES +static volatile int ep1_failure = 7; +#endif + +#undef STATS +#ifdef STATS +int ep1_receives = 0; +int ep1_errors = 0; +int ep2_transmits = 0; +int ep2_errors = 0; +# define INCR_STAT(a) (a) += 1 +# define SET_STAT(a, b) (a) = (b) +#else +# define INCR_STAT(a) +# define SET_STAT(a, b) +#endif + +// ---------------------------------------------------------------------------- +// Serial port 0 on the SA11x0 provides a USB slave connection (aka a +// USB device controller or UDC). The functionality is somewhat +// limited, there are just three endpoints. +// +// Endpoint 0 can only be used for control messages. It has an 8 byte +// fifo which cannot be connected to a DMA engine. Hence incoming +// control packets have to be limited to 8 bytes by the enumeration +// data. The endpoint has to be managed at a low-level, i.e. the +// incoming request has to be extracted from the fifo, processed, and +// any response put back into the fifo within the permitted USB +// response times. +// +// Endpoint 1 can only be used for host->slave bulk OUT transfers. It +// has a 20 byte receive fifo, and it can be hooked up to any of the +// six DMA engines. Since bulk transfers will typically involve 64 +// byte packets, most applications will require the use of DMA. +// +// Endpoint 2 can only be used for slave-host bulk IN transfers. There +// is a 16 byte transmit fifo so small messages can be transferred in +// software. The fifo can also be hooked up to DMA, which is a more +// likely scenario. +// +// Start with definitions of the hardware. The use of a structure and +// a const base pointer should allow the compiler to do base/offset +// addressing and keep the hardware base address in a register. This +// is better than defining each hardware register via a separate +// address. Although the registers are only a byte wide, the peripheral +// bus only supports word accesses. +// +// The USBS_CONTROL etc. macros allow for an alternative way of +// accessing the hardware if a better approach is presented, without +// having to rewrite all the code. Macros that correspond to registers +// are actually addresses, making it easier in the code to distinguish +// them from bit values: the & and * operators will just cancel out. + +typedef struct usbs_sa11x0_hardware { + volatile int control; + volatile int address; + volatile int out_size; + volatile int in_size; + volatile int ep0_control; + volatile int ep1_control; + volatile int ep2_control; + volatile int ep0_data; + volatile int ep0_write_count; + int dummy1; + volatile int fifo; + int dummy2; + volatile int status; +} usbs_sa11x0_hardware; + +static usbs_sa11x0_hardware* const usbs_sa11x0_base = (usbs_sa11x0_hardware* const) 0x80000000; +#define USBS_CONTROL (&(usbs_sa11x0_base->control)) +#define USBS_ADDRESS (&(usbs_sa11x0_base->address)) +#define USBS_OUT_SIZE (&(usbs_sa11x0_base->out_size)) +#define USBS_IN_SIZE (&(usbs_sa11x0_base->in_size)) +#define EP0_CONTROL (&(usbs_sa11x0_base->ep0_control)) +#define EP1_CONTROL (&(usbs_sa11x0_base->ep1_control)) +#define EP2_CONTROL (&(usbs_sa11x0_base->ep2_control)) +#define EP0_DATA (&(usbs_sa11x0_base->ep0_data)) +#define EP0_WRITE_COUNT (&(usbs_sa11x0_base->ep0_write_count)) +#define EP1_DATA (&(usbs_sa11x0_base->fifo)) +#define EP2_DATA (&(usbs_sa11x0_base->fifo)) +#define USBS_STATUS (&(usbs_sa11x0_base->status)) + +#define CONTROL_DISABLE (1 << 0) +#define CONTROL_ACTIVE (1 << 1) +// The meaning of bit 2 changed, see errata +#define CONTROL_RESUME_INTR (1 << 2) +#define CONTROL_EP0_INTR (1 << 3) +#define CONTROL_EP1_INTR (1 << 4) +#define CONTROL_EP2_INTR (1 << 5) +// The meaning of bit 6 also changed, see errata +#define CONTROL_SUSPEND_INTR (1 << 6) +#define CONTROL_RESET_INTR (1 << 7) + +// Getting the control register settings right is a little bit tricky. +// Bit 0 is the disable bit so touching that is dangerous, and the +// other bits have inverted meanings i.e. 0 enables interrupts. The +// following macro encapsulates this. +#define CONTROL_ALL_INTR 0x00FC +#define CONTROL_INTR_ENABLE(bits) ((~(bits)) & CONTROL_ALL_INTR) +#define CONTROL_INTR_CLEAR(bits) ((bits) & CONTROL_ALL_INTR) + +// All the endpoint interrupt numbers can be handled en masse, +// but some of the endpoints may be disabled. +#if defined(CYGPKG_DEVS_USB_SA11X0_EP1) && defined(CYGPKG_DEVS_USB_SA11X0_EP2) +# define CONTROL_EP_INTR_BITS (CONTROL_EP0_INTR | CONTROL_EP1_INTR | CONTROL_EP2_INTR) +#elif defined(CYGPKG_DEVS_USB_SA11X0_EP1) +# define CONTROL_EP_INTR_BITS (CONTROL_EP0_INTR | CONTROL_EP1_INTR) +#elif defined(CYGPKG_DEVS_USB_SA11X0_EP2) +# define CONTROL_EP_INTR_BITS (CONTROL_EP0_INTR | CONTROL_EP2_INTR) +#else +# define CONTROL_EP_INTR_BITS (CONTROL_EP0_INTR) +#endif + +#define EP0_OUT_READY (1 << 0) +#define EP0_IN_READY (1 << 1) +#define EP0_SENT_STALL (1 << 2) +#define EP0_FORCE_STALL (1 << 3) +#define EP0_DATA_END (1 << 4) +#define EP0_SETUP_END (1 << 5) +#define EP0_SERVICED_OPR (1 << 6) +#define EP0_SERVICED_SETUP_END (1 << 7) + +#define EP1_FIFO_SERVICE (1 << 0) +#define EP1_PACKET_COMPLETE (1 << 1) +#define EP1_PACKET_ERROR (1 << 2) +#define EP1_SENT_STALL (1 << 3) +#define EP1_FORCE_STALL (1 << 4) +#define EP1_FIFO_NOT_EMPTY (1 << 5) + +#define EP2_FIFO_SERVICE (1 << 0) +#define EP2_PACKET_COMPLETE (1 << 1) +#define EP2_PACKET_ERROR (1 << 2) +#define EP2_PACKET_UNDERRUN (1 << 3) +#define EP2_SENT_STALL (1 << 4) +#define EP2_FORCE_STALL (1 << 5) + +#define STATUS_EP0_INTR (1 << 0) +#define STATUS_EP1_INTR (1 << 1) +#define STATUS_EP2_INTR (1 << 2) +#define STATUS_SUSPEND_INTR (1 << 3) +#define STATUS_RESUME_INTR (1 << 4) +#define STATUS_RESET_INTR (1 << 5) + +#define EP0_FIFO_SIZE 8 +#define EP0_MTU 8 + +#define EP1_FIFO_SIZE 20 +#ifdef CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL +# define EP1_MTU 64 +#else +# define EP1_MTU 16 +#endif + +#define EP2_FIFO_SIZE 16 +#ifdef CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL +# define EP2_MTU 64 +#else +# define EP2_MTU 16 +#endif + +#if defined(CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL) || defined(CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL) +typedef struct usbs_sa11x0_dma { + volatile int address; + volatile int control_set; + volatile int control_clear; + volatile int status; + volatile int buf_a_address; // Absolute, not remapped + volatile int buf_a_size; + volatile int buf_b_address; // Absolute, not remapped + volatile int buf_b_size; +} usbs_sa11x0_dma; + +#define DMA_CONTROL_RUN (1 << 0) +#define DMA_CONTROL_INTR_ENABLE (1 << 1) +#define DMA_STATUS_ERROR (1 << 2) +#define DMA_STATUS_DONE_A (1 << 3) +#define DMA_CONTROL_START_A (1 << 4) +#define DMA_STATUS_DONE_B (1 << 5) +#define DMA_CONTROL_START_B (1 << 6) +#define DMA_STATUS_BUFFER_IN_USE (1 << 7) +// All the bits that are useful to clear. BUFFER_IN_USE is read-only. +#define DMA_CONTROL_CLEAR_ALL (DMA_CONTROL_RUN | DMA_CONTROL_INTR_ENABLE | DMA_STATUS_ERROR | \ + DMA_STATUS_DONE_A | DMA_CONTROL_START_A | DMA_STATUS_DONE_B | DMA_CONTROL_START_B) + +// The DMA engines operate eight-bytes at a time. This affects issues +// such as alignment. +#define DMA_BURST_SIZE 8 + +// The DMA engines bypass the cache and MMU, accessing physical +// memory directly. Newer HALS should provide appropriate macros. +#ifndef HAL_VIRT_TO_PHYS_ADDRESS +# error HAL macros for translating between virtual and physical memory are required. +#endif + +// Make absolutely sure that the two endpoints use different +// DMA channels. Right now this check cannot be done easily +// at the CDL level. +# if defined(CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL) && defined(CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL) +# if (CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL == CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL) +# error Different DMA channels must be selected for the two endpoints. +# endif +# endif + +# ifdef CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL +static usbs_sa11x0_dma* const ep1_dma_base = (usbs_sa11x0_dma* const)(0xB0000000 | (0x20 * CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL)); +# define EP1_DMA_ADDRESS (&(ep1_dma_base->address)) +# define EP1_DMA_CONTROL_SET (&(ep1_dma_base->control_set)) +# define EP1_DMA_CONTROL_CLEAR (&(ep1_dma_base->control_clear)) +# define EP1_DMA_STATUS (&(ep1_dma_base->status)) +# define EP1_DMA_BUF_A_ADDRESS (&(ep1_dma_base->buf_a_address)) +# define EP1_DMA_BUF_A_SIZE (&(ep1_dma_base->buf_a_size)) +# define EP1_DMA_BUF_B_ADDRESS (&(ep1_dma_base->buf_b_address)) +# define EP1_DMA_BUF_B_SIZE (&(ep1_dma_base->buf_b_size)) + +// The correct value for the DMA address register is fixed for USB transfers +// See table 11.6 of the SA1110 Advanced Developer's Manual +// Device datum width == 1 byte +// Device burst size == 8 bytes +// Device transfer direction == read (device->memory) +// Endianness is controlled by the ARM architectural HAL package +# ifdef CYGHWR_HAL_ARM_BIGENDIAN +# define EP1_DMA_ADDRESS_VALUE (0x80000A00 | 0x10 | 0x0 | 0x4 | 0x2 | 0x1) +# else +# define EP1_DMA_ADDRESS_VALUE (0x80000A00 | 0x10 | 0x0 | 0x4 | 0x0 | 0x1) +# endif +# endif // EP1_DMA + +# ifdef CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL + +static usbs_sa11x0_dma* const ep2_dma_base = (usbs_sa11x0_dma* const)(0xB0000000 | (0x20 * CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL)); +# define EP2_DMA_ADDRESS (&(ep2_dma_base->address)) +# define EP2_DMA_CONTROL_SET (&(ep2_dma_base->control_set)) +# define EP2_DMA_CONTROL_CLEAR (&(ep2_dma_base->control_clear)) +# define EP2_DMA_STATUS (&(ep2_dma_base->status)) +# define EP2_DMA_BUF_A_ADDRESS (&(ep2_dma_base->buf_a_address)) +# define EP2_DMA_BUF_A_SIZE (&(ep2_dma_base->buf_a_size)) +# define EP2_DMA_BUF_B_ADDRESS (&(ep2_dma_base->buf_b_address)) +# define EP2_DMA_BUF_B_SIZE (&(ep2_dma_base->buf_b_size)) + +# ifdef CYGHWR_HAL_ARM_BIGENDIAN +# define EP2_DMA_ADDRESS_VALUE (0x80000A00 | 0x00 | 0x0 | 0x4 | 0x2 | 0x0) +# else +# define EP2_DMA_ADDRESS_VALUE (0x80000A00 | 0x00 | 0x0 | 0x4 | 0x0 | 0x0) +# endif +# endif // EP2_DMA + +#endif // EP1_DMA || EP2_DMA + +// ---------------------------------------------------------------------------- +// Static data. There is a data structure for each endpoint. The +// implementation is essentially a private class that inherits from +// common classes for control and data endpoints, but device drivers +// are supposed to be written in C so some ugliness is required. +// +// Devtab entries are defined in usbs_sa11x0_data.cxx to make sure +// that the linker does not garbage-collect them. + +// Support for the interrupt handling code. +static cyg_interrupt usbs_sa11x0_intr_data; +static cyg_handle_t usbs_sa11x0_intr_handle; +static volatile int isr_status_bits = 0; + +// Endpoint 0 is always present, this module would not get compiled +// otherwise. +static void usbs_sa11x0_ep0_start(usbs_control_endpoint*); +static void usbs_sa11x0_poll(usbs_control_endpoint*); + +typedef enum ep0_state { + EP0_STATE_IDLE = 0, + EP0_STATE_IN = 1, + EP0_STATE_OUT = 2 +} ep0_state; + +typedef struct ep0_impl { + usbs_control_endpoint common; + ep0_state ep_state; + int length; + int transmitted; +} ep0_impl; + +static ep0_impl ep0 = { + common: + { + state: USBS_STATE_POWERED, // The hardware does not distinguish between detached, attached and powered. + enumeration_data: (usbs_enumeration_data*) 0, + start_fn: &usbs_sa11x0_ep0_start, + poll_fn: &usbs_sa11x0_poll, + interrupt_vector: SA11X0_IRQ_USB_SERVICE_REQUEST, + control_buffer: { 0, 0, 0, 0, 0, 0, 0, 0 }, + state_change_fn: (void (*)(usbs_control_endpoint*, void*, usbs_state_change, int)) 0, + state_change_data: (void*) 0, + standard_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + standard_control_data: (void*) 0, + class_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + class_control_data: (void*) 0, + vendor_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + vendor_control_data: (void*) 0, + reserved_control_fn: (usbs_control_return (*)(usbs_control_endpoint*, void*)) 0, + reserved_control_data: (void*) 0, + buffer: (unsigned char*) 0, + buffer_size: 0, + fill_buffer_fn: (void (*)(usbs_control_endpoint*)) 0, + fill_data: (void*) 0, + fill_index: 0, + complete_fn: (usbs_control_return (*)(usbs_control_endpoint*, int)) 0 + }, + ep_state: EP0_STATE_IDLE, + length: 0, + transmitted: 0 +}; + +extern usbs_control_endpoint usbs_sa11x0_ep0 __attribute__((alias ("ep0"))); + +// Endpoint 1 is optional. If the application only involves control +// messages or only slave->host transfers then the endpoint 1 +// support can be disabled. +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + +typedef struct ep1_impl { + usbs_rx_endpoint common; + int fetched; + cyg_bool using_buf_a; +} ep1_impl; + +static void ep1_start_rx(usbs_rx_endpoint*); +static void ep1_set_halted(usbs_rx_endpoint*, cyg_bool); + +static ep1_impl ep1 = { + common: { + start_rx_fn: &ep1_start_rx, + set_halted_fn: &ep1_set_halted, + complete_fn: (void (*)(void*, int)) 0, + complete_data: (void*) 0, + buffer: (unsigned char*) 0, + buffer_size: 0, + halted: 0, + }, + fetched: 0, + using_buf_a: 0 +}; + +extern usbs_rx_endpoint usbs_sa11x0_ep1 __attribute__((alias ("ep1"))); +#endif + +// Endpoint 2 is optional. If the application only involves control +// messages or only host->slave transfers then the endpoint 2 support +// can be disabled. +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + +typedef struct ep2_impl { + usbs_tx_endpoint common; + int transmitted; + int pkt_size; +} ep2_impl; + +static void ep2_start_tx(usbs_tx_endpoint*); +static void ep2_set_halted(usbs_tx_endpoint*, cyg_bool); + +static ep2_impl ep2 = { + common: { + start_tx_fn: &ep2_start_tx, + set_halted_fn: &ep2_set_halted, + complete_fn: (void (*)(void*, int)) 0, + complete_data: (void*) 0, + buffer: (const unsigned char*) 0, + buffer_size: 0, + halted: 0, + }, + transmitted: 0, + pkt_size: 0 +}; + +extern usbs_tx_endpoint usbs_sa11x0_ep2 __attribute__ ((alias ("ep2"))); + +#endif + +// ---------------------------------------------------------------------------- +// Hardware problem: experiments indicate that manipulating the USB +// controller registers does not always work as expected. The control +// fifo is especially badly affected, with e.g. writes just being lost +// completely. It is necessary to work around these problems using +// retry loops. MAX_RETRIES controls the total number of attempts to +// access a register. MAX_CHECKS controls the number of times a +// register is checked to determine whether or not the attempt has +// been succesful. These constants are used to access the data fifo, +// so MAX_RETRIES has to be > 20 bytes. +#define MAX_RETRIES 32 +#define MAX_CHECKS 8 + +// Write one or more bits to a register. This should result in some +// bits ending up set and other bits ending up clear. Some register +// bits are write-1-to-clear or may have side effects. +static cyg_bool +usbs_sa11x0_poke(volatile int* addr, int value, int should_be_set, int should_be_clear) +{ + cyg_bool result = false; + int retries, checks; + + for (retries = 0; !result && (retries < MAX_RETRIES); retries++) { + *addr = value; + (void) *addr; // The first read is always invalid. + for (checks = 0; !result && (checks < MAX_CHECKS); checks++) { + int current_value = *addr; + if (should_be_set != (should_be_set & current_value)) { + continue; + } + if ((0 != should_be_clear) && (0 != (should_be_clear & current_value))) { + continue; + } + result = true; + } + } + if (!result) { + DBG(("usbs_sa11x0_poke failed: addr %x, value %x, should_be_set %x, should_be_clear %x, actual %x\n", \ + (int) addr, value, should_be_set, should_be_clear, *addr)); + } + return result; +} + +// Write a whole value to a register, rather than just manipulating +// individual bits. +static cyg_bool +usbs_sa11x0_poke_value(volatile int* addr, int value) +{ + cyg_bool result = false; + int retries, checks; + + for (retries = 0; !result && (retries < MAX_RETRIES); retries++) { + *addr = value; + (void) *addr; // The first read is always invalid. + for (checks = 0; !result && (checks < MAX_CHECKS); checks++) { + if (value == *addr) { + result = true; + } + } + } + if (!result) { + DBG(("usbs_sa11x0_poke_value failed: addr %x, value %x, actual %x\n", (int) addr, value, *addr)); + } + return result; +} + + +// ---------------------------------------------------------------------------- +// Control transfers +// +// Endpoint 0 is rather more complicated than the others. This is +// partly due to the nature of the control protocol, for example it is +// bidirectional and transfer sizes are unpredictable. +// +// The USB standard imposes some timing constraints on endpoint 0, see +// section 9.2.6 of the spec. For example the set-address operation is +// supposed to take at most 50ms. In general the timings are reasonably +// generous so no special action is taken here. There could be problems +// when debugging, but that is pretty much inevitable. +// +// It is necessary to maintain a state for the control endpoint, the +// default state being idle. Control operations involve roughly the +// following sequence of events: +// +// 1) the host transmits a special setup token, indicating the start +// of a control operation and possibly cancelling any existing control +// operation that may be in progress. USB peripherals cannot NAK this +// even if they are busy. +// +// 2) the setup operation is followed by an eight-byte packet from the host +// that describes the specific control operation. This fits into the +// SA11X0's eight-byte control fifo. There will be an endpoint 0 +// interrupt with the out-packet-ready bit set. If the setup token +// was sent while a previous control operation was also in progress +// then the setup-end bit will be set as well. +// +// 3) the eight-byte packet is described in section 9.3 of the USB spec. +// The first byte holds three fields, with the top bit indicating the +// direction of subsequent data transfer. There are also two bytes +// specifying the size of the subsequent transfer. Obviously the +// packet also contains information such as the request type. +// +// If the specified size is zero then the endpoint will remain in +// its idle state. Otherwise the endpoint will switch to either +// IN or OUT state, depending on the direction of subsequent +// transfers. +// +// 4) some standard control operations can be handled by the code +// here. Set-address involves poking the address register and +// a change of state. Set-feature and clear-feature on the +// data endpoints can be used in conjunction with endpoint-halt. +// Get-status on the data endpoints tests the halt condition. +// It is also possible for the hardware-specific code to +// implement set-feature, clear-feature and get-status +// for the device as a whole since the SA11x0 always has to +// be self-powered and is incapable of initiating a remote +// wakeup. +// +// Other standard control operations will be handled by the +// application-specific installed handler, if any, or by the +// default handler usbs_handle_standard_control(). Class-specific +// and vendor-specific functions require appropriate handlers to be +// installed as well, If a particular request is not recognized +// then a stall condition should be raised. This will not prevent +// subsequent control operations, just the current one. +// +// Data transfers on endpoint 0 involve at most eight bytes at +// a time. More data will only be accepted if the out-packet-ready +// bit has been cleared via the serviced-opr bit, with the +// hardware nak'ing OUT requests. To send data back to the host +// the FIFO should be filled and then the in-packet-ready bit +// should be set. +// +// It looks like processing all control packets at DSR level should be +// sufficient. During the data phase the hardware will NAK IN and +// OUT requests if the fifo is still empty/full, so timing is not +// an issue. Timing after receipt of the initial control message +// may be more important, e.g. the 50ms upper limit on processing +// the set-address control message, but this should still be ok. +// This decision may have to be re-examined in the light of +// experience. + +// Init may get called during system startup or following a reset. +// During startup no work is needed since the hardware will +// have been reset and everything should be fine. After a reset +// the hardware will also be ok but there may be state information +// in ep0 that needs to be reset. +static void +usbs_sa11x0_ep0_init(void) +{ + if ((EP0_STATE_IDLE != ep0.ep_state) && + ((usbs_control_return (*)(usbs_control_endpoint*, int)) 0 != ep0.common.complete_fn)) { + (*ep0.common.complete_fn)(&ep0.common, -EPIPE); + } + ep0.common.state = USBS_STATE_POWERED; + memset(ep0.common.control_buffer, 0, 8); + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = (void (*)(usbs_control_endpoint*)) 0; + ep0.common.fill_data = (void*) 0; + ep0.common.fill_index = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + ep0.ep_state = EP0_STATE_IDLE; + ep0.length = 0; + ep0.transmitted = 0; +} + +// The start function is called by higher-level code when things have +// been set up, i.e. the enumeration data is available, appropriate +// handlers have been installed for the different types of control +// messages, and communication with the host is allowed to start. The +// next event that should happen is a reset operation from the host, +// so all other interrupts should be blocked. However it is likely +// that the hardware will detect a suspend state before the reset +// arrives, and hence the reset will act as a resume as well as a +// reset. +static void +usbs_sa11x0_ep0_start(usbs_control_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep0.common, "USB startup involves the wrong endpoint"); + + // Activate the hardware. Write a 0 to the enable/disable bit 0. + // Bit 1 is read-only. The other bits are set to 1 to disable + // the corresponding interrupt source. + usbs_sa11x0_poke(USBS_CONTROL, CONTROL_ALL_INTR, CONTROL_ALL_INTR, 0); + + // If there is additional platform-specific initialization to + // perform, do it now. This macro can come from the platform HAL. +#ifdef SA11X0_USB_PLATFORM_INIT + SA11X0_USB_PLATFORM_INIT; +#endif + + // Clear any pending interrupts. There should not be any, but just + // in case. Note: passing 0x00FF as the should_be_clear argument + // is a race condition, an external event can happen at any time, + // so we may loop unnecessarily and lose an interrupt. However + // the initial reset should last for 10ms. + usbs_sa11x0_poke(USBS_STATUS, 0x00FF, 0x00, 0x00FF); + + // The only interrupt really of interest right now is reset, but + // it is likely to be preceded by a resume. + usbs_sa11x0_poke(USBS_CONTROL, + CONTROL_INTR_ENABLE(CONTROL_RESET_INTR | CONTROL_RESUME_INTR), + 0, + CONTROL_INTR_CLEAR(CONTROL_RESET_INTR | CONTROL_RESUME_INTR)); +} + + +// Filling the fifo with a reply to the host. This can be called +// immediately at the end of a control message, to prepare for +// the next IN token. It will also get called after each subsequent +// IN operation when the fifo has been emptied. +// +// Experiments have indicated serious problems with the control fifo: +// some writes to the fifo just get lost completely. The failure rate +// is sufficiently high that more often than not the host will be +// unable to read all the enumeration data. However, the write-count +// register appears to give a valid indication of the current fifo +// contents. This means the code can retry stuffing a particular byte +// into the fifo until the write-count goes up. + +static void +usbs_sa11x0_ep0_fill_fifo(void) +{ + cyg_bool ok = true; + int filled = 0; + int max; + int fifo_count = *EP0_WRITE_COUNT; + int bits_to_set = 0; + + // The host can interrupt the current control message at any time + // with a new one. In practice this is unlikely, things could get + // rather confused on the host side. However if a control message + // has been received then the fifo should obviously not be filled. + // A new control message is indicated by the SETUP_END bit. + // + // The hardware design means that there is a race condition: the + // new control message can come in at any time, even in the middle + // of filling the fifo. Checking the SETUP_END more often would + // reduce the probability of things getting messed up, but not + // eliminate it. + // + // There is a check for SETUP_END at the start of the DSR, so + // the setting of this bit should have resulted in another ISR + // and another DSR being scheduled. Hence there is no need for + // special action here. + if (0 != (*EP0_CONTROL & EP0_SETUP_END)) { + DBG(("EP0_fill_fifo(), interrupted by SETUP_END\n")); + return; + } + + // There should never be any data in the fifo. Any such data could + // be the remnant of a previous transfer to the host, but that + // should all have gone out already. Alternatively it could be + // incoming data, but that means a new control message. + if (0 != fifo_count) { + DBG(("EP0_fill_fifo(), fifo already contains %d bytes", fifo_count)); + return; + } + + // The IN_READY bit should never be set on entry. It can only get + // set by a previous call to fill_fifo(), and the data should + // have gone out before we get back here. + if (0 != (*EP0_CONTROL & EP0_IN_READY)) { + DBG(("EP0 fill_fifo(), in-packet-ready bit already set, state %x\n", *EP0_CONTROL)); + return; + } + + // Now put up to another eight bytes into the fifo. + max = ((ep0.length - ep0.transmitted) > EP0_FIFO_SIZE) ? EP0_FIFO_SIZE : (ep0.length - ep0.transmitted); + while (ok && (filled < max)) { + if (0 != ep0.common.buffer_size) { + int datum; + int retries, checks; + cyg_bool written; + + datum = *ep0.common.buffer++; + ep0.common.buffer_size--; + written = false; + + for (retries = 0; ok && !written && (retries < MAX_RETRIES); retries++) { + if (filled != *EP0_WRITE_COUNT) { + DBG(("EP0 fill_fifo, inconsistency, written %d but write count %d\n", filled, *EP0_WRITE_COUNT)); + ok = false; + } + *EP0_DATA = datum; + // The write-count may take a few cycles to settle down. + for (checks = 0; !written && (checks < MAX_CHECKS); checks++) { + if (filled < *EP0_WRITE_COUNT) { + filled++; + written = true; + // DBG(("Transferred %d byte (%x) after %d checks, %d retries\n", filled - 1, datum, checks, retries)); + } + } + } + } else if ((void (*)(usbs_control_endpoint*))0 != ep0.common.fill_buffer_fn) { + (*ep0.common.fill_buffer_fn)(&ep0.common); + } else { + break; + } + } + + // At this point either it has proved impossible to fill the fifo, + // e.g. because of a new control message, or up to another eight + // bytes have been sent. + if (!ok) { + if (0 == (EP0_SETUP_END & *EP0_CONTROL)) { + // There is something seriously wrong. + DBG(("ep0_fill_fifo(), failed, only filled %d bytes, status %x\n", filled, *EP0_CONTROL)); + usbs_sa11x0_poke(EP0_CONTROL, EP0_FORCE_STALL, EP0_FORCE_STALL, 0); + } + return; + } + + // The following conditions are possible: + // 1) amount transferred == amount requested, transfer complete. + // 2) amount transferred < amount requested, this fill involved + // <eight bytes, transfer complete by definition of the protocol. + // 3) amount transferred < amount requested but exactly eight + // bytes were sent this time. It will be necessary to send + // another packet of zero bytes to complete the transfer. + ep0.transmitted += filled; + if ((ep0.transmitted == ep0.length) || (filled < EP0_FIFO_SIZE)) { + + ep0.ep_state = EP0_STATE_IDLE; + if ((usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn) { + (void) (*ep0.common.complete_fn)(&ep0.common, 0); + } + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = (void (*)(usbs_control_endpoint*)) 0; + + // This informs the hardware that the control message has been + // handled. + bits_to_set = EP0_DATA_END; + } + + // This allows another IN operation to empty the fifo. + bits_to_set |= EP0_IN_READY; + usbs_sa11x0_poke(EP0_CONTROL, bits_to_set, bits_to_set, 0); +} + +// Another utility function to empty the fifo. This involves similar +// hardware problems to writing, it is possible to read a byte without +// changing the fifo state so that next time the same byte would be +// read again. Again there is a possible race condition if another +// control message arrives while emptying the fifo. +static int +usbs_sa11x0_ep0_empty_fifo(unsigned char* buf) +{ + int count = *EP0_WRITE_COUNT & 0x00FF; + int emptied = 0; + cyg_bool ok = true; + + CYG_ASSERT( (count >= 0) & (count <= 8), "EP0 write count must be in range"); + + while (ok && (emptied < count)) { + int retries, checks; + cyg_bool read = false; + + for (retries = 0; !read && (retries < MAX_RETRIES); retries++) { + if ((count - emptied) != *EP0_WRITE_COUNT) { + DBG(("EP0_empty_fifo, inconsistency, read %d bytes of %d, but fifo count %d\n", emptied, count, *EP0_WRITE_COUNT)); + ok = false; + } else { + buf[emptied] = *EP0_DATA; + for (checks = 0; !read && (checks < MAX_CHECKS); checks++) { + if ((count - emptied) > *EP0_WRITE_COUNT) { + //DBG(("Read %d byte (%x) after %d checks, %d retries\n", emptied, buf[emptied], checks, retries)); + read = true; + emptied++; + } + } + } + } + if (ok && !read) { + DBG(("EP0 empty fifo, failed to read byte from fifo\n")); + ok = false; + } + } + + return emptied; +} + +// This is where all the hard work happens. It is a very large routine +// for a DSR, but in practice nearly all of it is nested if's and very +// little code actually gets executed. Note that there may be +// invocations of callback functions and the driver has no control +// over how much time those will take, but those callbacks should be +// simple. +static void +usbs_sa11x0_ep0_dsr(void) +{ + int hw_state = *EP0_CONTROL; + + // Handle the stall bits. + // + // Force-stall should not be a problem. It is set by the code here + // if the host needs to be told that the control message was + // unacceptable and is cleared automatically by the hardware after + // the stall is sent. + // NOTE: it is not clear the hardware actually works in this + // respect. The FORCE_STALL bit has been observed still set during + // the next interrupt, and the host appears to receive spurious + // data back in response to the next control packet. + // + // Sent-stall is set by the hardware following a protocol + // violation, e.g. if there is an IN token when a new control + // message is expected. There is nothing the software can do about + // this. However if we are in the middle of an IN or OUT transfer + // then those are not going to complete successfully. + if (0 != (hw_state & EP0_SENT_STALL)) { + if (EP0_STATE_IDLE != ep0.ep_state) { + if ((usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn) { + (*ep0.common.complete_fn)(&ep0.common, -EIO); + } + ep0.ep_state = EP0_STATE_IDLE; + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + } + usbs_sa11x0_poke(EP0_CONTROL, EP0_SENT_STALL, 0, EP0_SENT_STALL); + } // STALL condition + + // Next, check whether we have received a new control message + // while still busy processing an old one. + if (0 != (hw_state & EP0_SETUP_END)) { + if (EP0_STATE_IDLE != ep0.ep_state) { + if ((usbs_control_return (*)(usbs_control_endpoint*, int)) 0 != ep0.common.complete_fn) { + (*ep0.common.complete_fn)(&ep0.common, -EIO); + } + ep0.ep_state = EP0_STATE_IDLE; + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.fill_buffer_fn = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + } + // We are now back in idle state so the control message will be + // extracted and processed. + usbs_sa11x0_poke(EP0_CONTROL, EP0_SERVICED_SETUP_END, 0, EP0_SETUP_END); + } // Interrupted control transaction + + // The endpoint can be in one of three states: IN, OUT, or IDLE. + // For the first two it should mean that there is more data to be + // transferred, which is pretty straightforward. IDLE means + // that a new control message has arrived. + if ((EP0_STATE_IN == ep0.ep_state) && (0 == (EP0_IN_READY & hw_state))) { + + usbs_sa11x0_ep0_fill_fifo(); + + } else if ((EP0_STATE_OUT == ep0.ep_state) && (0 != (EP0_OUT_READY & hw_state))) { + + // A host->device transfer. Higher level code must have + // provided a suitable buffer. + CYG_ASSERT( (unsigned char*)0 != ep0.common.buffer, "A receive buffer should have been provided" ); + + ep0.transmitted += usbs_sa11x0_ep0_empty_fifo(ep0.common.buffer + ep0.transmitted); + + if (ep0.transmitted != ep0.length) { + // The host is not allowed to send more data than it + // indicated in the original control message, and all + // messages until the last one should be full size. + CYG_ASSERT( ep0.transmitted < ep0.length, "The host must not send more data than expected"); + CYG_ASSERT( 0 == (ep0.transmitted % EP0_FIFO_SIZE), "All OUT packets until the last one should be full-size"); + + usbs_sa11x0_poke(EP0_CONTROL, EP0_SERVICED_OPR, 0, EP0_OUT_READY); + } else { + // The whole transfer is now complete. Invoke the + // completion function, and based on its return value + // either generate a stall or complete the message. + usbs_control_return result; + + CYG_ASSERT( (usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn, \ + "A completion function should be provided for OUT control messages"); + + result = (*ep0.common.complete_fn)(&ep0.common, 0); + ep0.common.buffer = (unsigned char*) 0; + ep0.common.buffer_size = 0; + ep0.common.complete_fn = (usbs_control_return (*)(usbs_control_endpoint*, int)) 0; + + if (USBS_CONTROL_RETURN_HANDLED == result) { + usbs_sa11x0_poke(EP0_CONTROL, + EP0_SERVICED_OPR | EP0_DATA_END, + EP0_DATA_END, + EP0_OUT_READY); + } else { + usbs_sa11x0_poke(EP0_CONTROL, + EP0_SERVICED_OPR | EP0_DATA_END | EP0_FORCE_STALL, + EP0_FORCE_STALL, + EP0_OUT_READY); + } + // Also remember to switch back to IDLE state + ep0.ep_state = EP0_STATE_IDLE; + } + + } else if (0 != (EP0_OUT_READY & hw_state)) { + + int emptied = usbs_sa11x0_ep0_empty_fifo(ep0.common.control_buffer); + + if (8 != emptied) { + // This indicates a serious problem somewhere. Respond by + // stalling. Hopefully the host will take some action that + // sorts out the mess. + usbs_sa11x0_poke(EP0_CONTROL, + EP0_SERVICED_OPR | EP0_DATA_END | EP0_FORCE_STALL, + EP0_FORCE_STALL, + EP0_OUT_READY); + + } else { + usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN; + usb_devreq* req = (usb_devreq*) ep0.common.control_buffer; + int length, direction, protocol, recipient; + + // Now we need to do some decoding of the data. A non-zero + // length field indicates that there will be a subsequent + // IN or OUT phase. The direction is controlled by the + // top bit of the first byte. The protocol is determined + // by other bits of the top byte. + length = (req->length_hi << 8) | req->length_lo; + direction = req->type & USB_DEVREQ_DIRECTION_MASK; + protocol = req->type & USB_DEVREQ_TYPE_MASK; + recipient = req->type & USB_DEVREQ_RECIPIENT_MASK; + +#if 0 + DBG(("ep0, new control request: type %x, code %x\n", req->type, req->request)); + DBG((" %s, length %d, value hi %x lo %x, index hi %x lo %x\n", + (USB_DEVREQ_DIRECTION_OUT == direction) ? "out" : "in", + length, req->value_hi, req->value_lo, req->index_hi, req->index_lo)); +#endif + if (0 != length){ + // Clear the fifo straightaway. There is no harm in + // doing this here. It may or may not do some good. + usbs_sa11x0_poke(EP0_CONTROL, EP0_SERVICED_OPR, 0, EP0_OUT_READY); + } + + if (USB_DEVREQ_TYPE_STANDARD == protocol) { + + // First see if the request can be handled entirely in + // this module. + if (USB_DEVREQ_SET_ADDRESS == req->request) { + // The USB device address should be in value_lo. + // No more data is expected. + int address = req->value_lo; + if ((0 != length) || (address > 127)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + // poke_value() cannot be used here because + // setting the address does not take effect + // until the status phase. + *USBS_ADDRESS = address; + result = USBS_CONTROL_RETURN_HANDLED; + } + } else if (USB_DEVREQ_GET_STATUS == req->request) { + // GET_STATUS on the device as a whole is used to + // check the remote-wakeup and self-powered bits. + // GET_STATUS on an endpoint is used to determine + // the halted condition. + // GET_STATUS on anything else has to be left to + // other code. + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + // The host should expect two bytes back. + if ((2 == length) && (USB_DEVREQ_DIRECTION_IN == direction)) { + ep0.common.control_buffer[0] = 0; // Not self-powered, no remote wakeup + ep0.common.control_buffer[1] = 0; + ep0.common.buffer = ep0.common.control_buffer; + ep0.common.buffer_size = 2; + result = USBS_CONTROL_RETURN_HANDLED; + } else { + result = USBS_CONTROL_RETURN_STALL; + } + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + if ((2 == length) && (USB_DEVREQ_DIRECTION_IN == direction)) { + int endpoint = req->index_lo; + if (0 == endpoint) { + // get-status on endpoint 0 is either undefined or always valid. + // endpoint 0 is always up. + ep0.common.control_buffer[0] = 0; + result = USBS_CONTROL_RETURN_HANDLED; + } +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + else if (((USB_DEVREQ_INDEX_DIRECTION_OUT | 1) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + + ep0.common.control_buffer[0] = ep1.common.halted; + result = USBS_CONTROL_RETURN_HANDLED; + + } +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 2) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + + ep0.common.control_buffer[0] = ep2.common.halted; + result = USBS_CONTROL_RETURN_HANDLED; + + } +#endif + else { + // An invalid endpoint has been specified or the + // endpoint can only be examined in configured state. + result = USBS_CONTROL_RETURN_STALL; + } + if (USBS_CONTROL_RETURN_HANDLED == result) { + ep0.common.control_buffer[1] = 0; + ep0.common.buffer = ep0.common.control_buffer; + ep0.common.buffer_size = 2; + } + } else { + result = USBS_CONTROL_RETURN_STALL; + } + } // Endpoint or device get-status + + } else if (USB_DEVREQ_CLEAR_FEATURE == req->request) { + + // CLEAR_FEATURE operates in much the same way as + // GET_STATUS + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + + // No data should be transferred, and only remote-wakeup can be cleared. + if ((0 != length) || (USB_DEVREQ_FEATURE_DEVICE_REMOTE_WAKEUP != req->value_lo)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + // Clearing remote-wakeup is a no-op. + result = USBS_CONTROL_RETURN_HANDLED; + } + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + // The only feature that can be cleared is endpoint-halt, no data should be transferred. + if ((0 != length) || (USB_DEVREQ_FEATURE_ENDPOINT_HALT != req->value_lo)) { + result = USBS_CONTROL_RETURN_STALL; + } else { + int endpoint = req->index_lo; + if (0 == endpoint) { + // Clearing halt on endpoint 0 is always a no-op since that endpoint cannot be halted + } +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + else if (((USB_DEVREQ_INDEX_DIRECTION_OUT | 1) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep1_set_halted(&ep1.common, false); + result = USBS_CONTROL_RETURN_HANDLED; + + } +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + else if (((USB_DEVREQ_INDEX_DIRECTION_IN | 2) == endpoint) && + (USBS_STATE_CONFIGURED == (ep0.common.state & USBS_STATE_MASK))) { + ep2_set_halted(&ep2.common, false); + result = USBS_CONTROL_RETURN_HANDLED; + + } +#endif + else { + // Invalid endpoint or not in configured state. + result = USBS_CONTROL_RETURN_STALL; + } + } + } // Endpoing or device clear-feature + + } else if (USB_DEVREQ_SET_FEATURE == req->request) { + + // SET_FEATURE also operates in much the same way as + // GET_STATUS + if (USB_DEVREQ_RECIPIENT_DEVICE == recipient) { + + // The only valid feature that can be set is remote-wakeup, + // which is not supported by the hardware. + result = USBS_CONTROL_RETURN_STALL; + + } else if (USB_DEVREQ_RECIPIENT_ENDPOINT == recipient) { + + // Only the halt condition can be set, and no data should be transferred. + // Halting endpoint 0 should probably be disallowed although the + // standard does not explicitly say so. + if ((0 != length) || + (USB_DEVREQ_FEATURE_ENDPOINT_HALT != req->value_lo) || + (USBS_STATE_CONFIGURED != (ep0.common.state & USBS_STATE_MASK))) { + + result = USBS_CONTROL_RETURN_STALL; + + } else { + int endpoint = req->index_lo; + if (0) { + } +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + else if ((USB_DEVREQ_INDEX_DIRECTION_OUT | 1) == endpoint) { + ep1_set_halted(&ep1.common, true); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + else if ((USB_DEVREQ_INDEX_DIRECTION_IN | 2) == endpoint) { + ep2_set_halted(&ep2.common, true); + result = USBS_CONTROL_RETURN_HANDLED; + } +#endif + else { + result = USBS_CONTROL_RETURN_STALL; + } + } + } // Endpoint or device set-feature + } + + // If the result has not been handled yet, pass it to + // the installed callback function (if any). + if (USBS_CONTROL_RETURN_UNKNOWN == result) { + if ((usbs_control_return (*)(usbs_control_endpoint*, void*))0 != ep0.common.standard_control_fn) { + result = (*ep0.common.standard_control_fn)(&ep0.common, ep0.common.standard_control_data); + } + } + +#if 1 + if ((USBS_CONTROL_RETURN_UNKNOWN == result) && + (USB_DEVREQ_SET_INTERFACE == req->request)) { + + // This code should not be necessary. For + // non-trivial applications which involve + // alternate interfaces and the like, this request + // should be handled by the application itself. + // For other applications, the default handler + // will ignore this request so we end up falling + // through without actually handling the request + // and hence returning a stall condition. That + // is legitimate behaviour according to the standard. + // + // However, there are appear to be problems with + // the SA1110 USB hardware when it comes to stall + // conditions: they appear to affect some + // subsequent messages from target to host as + // well. Hence rather than returning a stall + // condition this code instead generates a dummy + // reply, which is also valid according to the + // standard. This avoids complications with certain + // USB compliance testers. + if ((0 != length) || + (0 != req->value_hi) || (0 != req->index_hi) || + (USBS_STATE_CONFIGURED != (ep0.common.state & USBS_STATE_MASK))) { + + result = USBS_CONTROL_RETURN_STALL; + } else { + int interface_id; + int alternate; + + CYG_ASSERT( (1 == ep0.common.enumeration_data->device.number_configurations) && \ + (1 == ep0.common.enumeration_data->total_number_interfaces), \ + "Higher level code should have handled this request"); + + interface_id = req->index_lo; + alternate = req->value_lo; + if ((interface_id != ep0.common.enumeration_data->interfaces[0].interface_id) || + (alternate != ep0.common.enumeration_data->interfaces[0].alternate_setting)) { + + result = USBS_CONTROL_RETURN_STALL; + } else { + result = USBS_CONTROL_RETURN_HANDLED; + } + + } + } +#endif + + // If the result has still not been handled, leave it to + // the default implementation in the USB slave common package + if (USBS_CONTROL_RETURN_UNKNOWN == result) { + result = usbs_handle_standard_control(&ep0.common); + } + + } else { + // The other three types of control message can be + // handled by similar code. + usbs_control_return (*callback_fn)(usbs_control_endpoint*, void*); + void* callback_arg; + //DBG(("non-standard control request %x", req->request)); + + if (USB_DEVREQ_TYPE_CLASS == protocol) { + callback_fn = ep0.common.class_control_fn; + callback_arg = ep0.common.class_control_data; + } else if (USB_DEVREQ_TYPE_VENDOR == protocol) { + callback_fn = ep0.common.vendor_control_fn; + callback_arg = ep0.common.vendor_control_data; + } else { + callback_fn = ep0.common.reserved_control_fn; + callback_arg = ep0.common.reserved_control_data; + } + + if ((usbs_control_return (*)(usbs_control_endpoint*, void*)) 0 == callback_fn) { + result = USBS_CONTROL_RETURN_STALL; + } else { + result = (*callback_fn)(&ep0.common, callback_arg); + } + } + //DBG(("Control request done, %d\n", result)); + + if (USBS_CONTROL_RETURN_HANDLED != result) { + // This control request cannot be handled. Generate a stall. + usbs_sa11x0_poke(EP0_CONTROL, + EP0_FORCE_STALL | EP0_SERVICED_OPR | EP0_DATA_END, + EP0_FORCE_STALL, + EP0_OUT_READY); + } else { + // The control request has been handled. Is there any more + // data to be transferred? + if (0 == length) { + usbs_sa11x0_poke(EP0_CONTROL, + EP0_SERVICED_OPR | EP0_DATA_END, + EP0_DATA_END, + EP0_OUT_READY); + } else { + // The endpoint should now go into IN or OUT mode while the + // remaining data is transferred. + ep0.transmitted = 0; + ep0.length = length; + if (USB_DEVREQ_DIRECTION_OUT == direction) { + // Wait for the next packet from the host. + ep0.ep_state = EP0_STATE_OUT; + CYG_ASSERT( (unsigned char*) 0 != ep0.common.buffer, "A receive buffer should have been provided"); + CYG_ASSERT( (usbs_control_return (*)(usbs_control_endpoint*, int))0 != ep0.common.complete_fn, \ + "A completion function should be provided for OUT control messages"); + } else { + ep0.ep_state = EP0_STATE_IN; + usbs_sa11x0_ep0_fill_fifo(); + } + } + } // Control message handled + } // Received 8-byte control message + } // Idle state, i.e. control message +} // ep0_dsr + +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 +// ---------------------------------------------------------------------------- +// Endpoint 1 is used for OUT transfers, i.e. receive operations. Only +// the bulk protocol is supported by the hardware. The semantics allow +// for two different modes of operation: higher-level code can ask for +// exactly one or more bulk packets of 64 bytes each, allowing buffer +// requirements to be determined from a header; alternatively the +// rx request can just supply a large buffer. Processing the first +// packet of a larger transfer separately does not introduce any +// special problems at the protocol level. +// +// It is not legal to receive just part of a packet and expect the +// hardware or the driver to buffer the rest. Not all hardware will +// be capable of doing this buffering, and there should not be +// a driver design requirement to provide buffering space. +// +// +// The hardware design for endpoint 1 is flawed in a number of +// respects. The receive fifo is only 20 bytes, less than the packet +// size, so it is essential to use DMA (there is a configuration +// option to allow for communication protocols where packets will +// never exceed 16 bytes, but that is not the normal case). The DMA +// engine is triggered by a receive-fifo-service high-water mark +// bit. DMA transfers operate in bursts of eight bytes. Therefore +// it would make sense if the high-water mark was set when the +// receive fifo contained eight bytes or more. +// +// Instead the high-water mark is set when the fifo contains twelve +// bytes or more. Worse, there is no way of measuring how many bytes +// there are left in the fifo without actually extracting those bytes. +// +// For a full-size 64-byte packet, the first 56 bytes will be +// transferred by DMA and the remainder will remain in the fifo. For a +// partial packet of between 56 and 63 bytes, the first 56 bytes will +// be transferred by DMA and the remainder will remain in the fifo. There +// is no way to distinguish between these scenarios without emptying +// the fifo. +// +// The result is that there is never any point in attempting a DMA +// transfer of more than 56 bytes, and for every endpoint 1 interrupt +// it is necessary to read the remainder from the fifo. This adds +// a lot of software overhead, and it is not clear that DMA is +// particularly useful. It is still necessary because of the limited +// fifo size. +// +// +// Because DMA involves the use of physical rather than virtual +// memory, there are also cache interaction problems. Specifically it +// would be necessary to invalidate cache lines after a DMA transfer +// has completed, but that only works sensibly if the buffer is +// aligned to a cache-line boundary and is a multiple of the +// cache-line size. Imposing such restrictions on higher-level code +// is undesirable. Also the DMA engines have an apparently undocumented +// restriction that the buffer must be eight-byte aligned. +// +// To work around all these problems, the receive code works in terms +// of a small private buffer. After a packet has been received, data +// will be copied from this private buffer to the destination. Obviously +// this copy operation is overhead and, because the code is expected +// to run at DSR level, However the copy operation is limited to at +// most 64 bytes, which is not good but not disastrous either. +// +// For data transfers the entry points are: +// +// 1) ep1_start_rx_packet() - prepare to receive another packet from +// the host. +// 2) ep1_clear_error() - an error condition has occurred (CRC, +// bit-stuffing, fifo overrun). It appears that the only way +// to clear this is to clear the receive-packet-complete bit, +// which unfortunately allows in another packet from the host +// before we are ready for it. Doing anything else while +// the error bit is set does not work, for example it is not +// possible to empty the fifo by hand. +// 3) ep1_process_packet() - a whole packet has been received +// and now needs to be moved into application space. +// +// These three routines are called by the start_rx() routine and +// by the DSR. There are different implementations for DMA and +// non-DMA. +// +// There is another hardware problem: the receive-packet-complete bit +// comes up with the wrong default value, allowing the host to start +// transmitting before the target is ready to receive. Unfortunately +// there is not much that can be done about this: the +// receive-packet-complete bit cannot be set by software and the OUT +// max register has a minimum size of eight bytes. Fortunately for +// many protocols the target-side code has a chance to start a receive +// before the host is allowed to send, so this problem is mostly +// ignored for now. +// +// Another potential problem arises if the host sends more data than +// is expected for a given transfer. It would be possible to address +// this by manipulating the OUT max packet register and getting the +// hardware to generate protocol violation stalls. This would also +// eliminate the need to test for buffer overflows. For now it is +// left to higher-level code to sort it all out. + +#ifdef CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL + +// DMA needs an area of physical memory. To avoid conflicts with +// the cached shadow of this memory, this area needs to start at +// a cache line boundary and there must be padding at the end +// to the next cache line boundary, thus ensuring that the +// processor will not accidentally overwrite the physical +// memory because it is manipulating some other variable. +// +// NOTE: at the time of writing the toolchain has a problem with +// the aligned attribute, so instead the start alignment has +// to be handled in software. + +# define EP1_DMA_MTU 56 +# define EP1_DMA_BUFSIZE ((EP1_DMA_MTU + HAL_DCACHE_LINE_SIZE - 1) - \ + ((EP1_DMA_MTU + HAL_DCACHE_LINE_SIZE - 1) % HAL_DCACHE_LINE_SIZE)) +# define EP1_DMA_ALLOCSIZE (EP1_DMA_BUFSIZE + HAL_DCACHE_LINE_SIZE - 1) + +static unsigned char ep1_dma_data[EP1_DMA_ALLOCSIZE]; + +// This variable cannot be initialized statically, instead it is +// set by ep1_init(). It corresponds to the physical address +// for the buffer. +static unsigned char* ep1_dma_buf; + +static void +ep1_start_rx_packet(void) +{ + int dma_size = EP1_DMA_MTU; + + // This assertion does not always hold: clearing an error condition + // involves the packet-complete bit so another message may have + // started to arrive. + // CYG_ASSERT( 0 == (EP1_FIFO_NOT_EMPTY & *EP1_CONTROL), "The receive fifo should be empty"); + + CYG_ASSERT( 0 == ((DMA_CONTROL_RUN | DMA_CONTROL_START_A) & *EP1_DMA_STATUS), "EP1 DMA should be inactive"); + +#ifdef FAILURES + ep1_failure = (ep1_failure + 1) % 32; + if (0 == ep1_failure) { + dma_size = 8; + } +#endif + + // The full flexibility of the DMA engines is not required here, + // specifically the automatic chaining between buffers A and B. + // Instead always using buffer A is sufficient. To avoid the + // However the hardware still requires the software to alternate + // between A and B. To avoid switching between buffers during a + // transfer an excessive size field is used, EP1_MTU rather than + // EP1_DMA_MTU, and hence the DMA transfer will never complete. + // + // With some silicon revisions writing to the DMA registers does + // not always work either, so a retry is in order. Possibly + // some short delays immediately after the clear and before the + // set would be sufficient. + *EP1_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + if (0 == (DMA_STATUS_BUFFER_IN_USE & *EP1_DMA_STATUS)) { + ep1.using_buf_a = true; + usbs_sa11x0_poke_value(EP1_DMA_BUF_A_ADDRESS, (unsigned int) ep1_dma_buf); + usbs_sa11x0_poke_value(EP1_DMA_BUF_A_SIZE, dma_size); + *EP1_DMA_CONTROL_SET = DMA_CONTROL_RUN | DMA_CONTROL_START_A; + } else { + ep1.using_buf_a = false; + usbs_sa11x0_poke_value(EP1_DMA_BUF_B_ADDRESS, (unsigned int) ep1_dma_buf); + usbs_sa11x0_poke_value(EP1_DMA_BUF_B_SIZE, dma_size); + *EP1_DMA_CONTROL_SET = DMA_CONTROL_RUN | DMA_CONTROL_START_B; + } + + // This should not be necessary, but occasionally the equivalent + // operation during ep1_init() fails. Strictly speaking it should + // be calling poke_value(), but the added overheads for that are + // not worthwhile. + *USBS_OUT_SIZE = EP1_MTU - 1; + + // Now allow the host to send the packet. + usbs_sa11x0_poke(EP1_CONTROL, EP1_PACKET_COMPLETE | EP1_SENT_STALL, 0, + EP1_PACKET_COMPLETE | EP1_SENT_STALL | EP1_FORCE_STALL); +} + +// Clear an error condition following a CRC, bit stuffing or overrun +// error. The only reliable way to do this is to halt DMA and clear +// the packet-complete bit. Unfortunately this allows the host to send +// another packet immediately, before start_rx_packet can be called, +// introducing another race condition. The hardware does not appear +// to offer any alternatives. +static void +ep1_clear_error(void) +{ + *EP1_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + usbs_sa11x0_poke(EP1_CONTROL, EP1_PACKET_COMPLETE | EP1_SENT_STALL, 0, + EP1_PACKET_COMPLETE | EP1_PACKET_ERROR | EP1_SENT_STALL | EP1_FORCE_STALL | EP1_FIFO_NOT_EMPTY); + + // Clearing the packet-complete bit may cause the host to send + // another packet, immediately causing another error, so this + // assertion does not hold. + // CYG_ASSERT( 0 == (*EP1_CONTROL & (EP1_PACKET_ERROR | EP1_FIFO_NOT_EMPTY)), "Receive error should have been cleared"); +} + +// A packet has been received. Some of it may still be in the fifo +// and must be extracted by hand. The data then has to copied to +// a higher-level buffer. +static int +ep1_process_packet(void) +{ + int pkt_size; + + // First, work out how much data has been processed by the DMA + // engine. This is the amount originally poked into the size + // register minus its current value. + *EP1_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + if (ep1.using_buf_a) { + pkt_size = EP1_DMA_MTU - *EP1_DMA_BUF_A_SIZE; + } else { + pkt_size = EP1_DMA_MTU - *EP1_DMA_BUF_B_SIZE; + } + CYG_ASSERT( 0 == (pkt_size % DMA_BURST_SIZE), "DMA transfers must be in multiples of the burst size"); + + // Move these bytes from physical memory to the target buffer. + if ((pkt_size > 0) && ((ep1.fetched + pkt_size) < ep1.common.buffer_size)) { + memcpy(ep1.common.buffer + ep1.fetched, ep1_dma_buf, pkt_size); + } + + // Copy remaining bytes into the target buffer directly. + // The DMA buffer could be used instead, moving the memcpy() + // down and avoiding the need for a buffer overflow check + // inside the loop, but at the cost of accessing physical + // memory every time. That cost is too high. + while (1) { + int status = *EP1_CONTROL; + if ((EP1_PACKET_COMPLETE | EP1_PACKET_ERROR) == ((EP1_PACKET_COMPLETE | EP1_PACKET_ERROR) & status)) { + break; + } else if (0 == (EP1_FIFO_NOT_EMPTY & status)) { + break; + } else { + int datum = *EP1_DATA; + if (ep1.fetched < ep1.common.buffer_size) { + ep1.common.buffer[ep1.fetched + pkt_size] = datum; + } + pkt_size++; + } + } + ep1.fetched += pkt_size; + return pkt_size; +} + +#else + +// Transfers not involving DMA. Obviously these are much simpler +// but restricted to packets of 16 bytes. +static void +ep1_start_rx_packet(void) +{ + // Nothing to be done, just let the host send a packet and it will + // end up in the fifo. + usbs_sa11x0_poke(EP1_CONTROL, EP1_PACKET_COMPLETE | EP1_SENT_STALL, 0, + EP1_PACKET_COMPLETE | EP1_SENT_STALL | EP1_FORCE_STALL); +} + +static void +ep1_clear_error(void) +{ + usbs_sa11x0_poke(EP1_CONTROL, EP1_PACKET_COMPLETE | EP1_SENT_STALL, 0, + EP1_PACKET_COMPLETE | EP1_SENT_STALL | EP1_FORCE_STALL); +} + +static int +ep1_process_packet(void) +{ + int pkt_size = 0; + while (0 != (*EP1_CONTROL & EP1_FIFO_NOT_EMPTY)) { + int datum = *EP1_DATA; + pkt_size++; + if (ep1.fetched < ep1.common.buffer_size) { + ep1.common.buffer[ep1.fetched + pkt_size] = datum; + } + } + return pkt_size; +} +#endif + +// Complete a transfer. This takes care of invoking the completion +// callback and resetting the buffer. +static void +ep1_rx_complete(int result) +{ + void (*complete_fn)(void*, int) = ep1.common.complete_fn; + void* complete_data = ep1.common.complete_data; + + ep1.common.buffer = (unsigned char*) 0; + ep1.common.buffer_size = 0; + ep1.common.complete_fn = (void (*)(void*, int)) 0; + ep1.common.complete_data = (void*) 0; + + if ((void (*)(void*, int))0 != complete_fn) { + (*complete_fn)(complete_data, result); + } +} + +// Start a transmission. This functionality is overloaded to cope with +// waiting for stalls to complete. +static void +ep1_start_rx(usbs_rx_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep1.common, "USB data transfer involves the wrong endpoint"); + + // Is this endpoint currently stalled? If so then a size of 0 can + // be used to block until the stall condition is clear, anything + // else should result in an immediate callback. + if (ep1.common.halted) { + if (0 != ep1.common.buffer_size) { + ep1_rx_complete(-EAGAIN); + } + } else if (0 == ep1.common.buffer_size) { + // A check to see if the endpoint is halted. It isn't. + ep1_rx_complete(0); + } else { + int status = *EP1_CONTROL; + + CYG_ASSERT((void*) 0 != ep1.common.buffer, "USB receives should not override the interrupt vectors"); + + // This indicates the start of a transfer. + ep1.fetched = 0; + + // The sent-stall bit may get set by hardware because of + // a protocol violation. If so it must be cleared here. + if (0 != (status & EP1_SENT_STALL)) { + usbs_sa11x0_poke(EP1_CONTROL, EP1_SENT_STALL, 0, EP1_SENT_STALL | EP1_FORCE_STALL); + status = *EP1_CONTROL; + } + + // The bogus initial value for the receive-packet-complete + // bit means that we may start off with an error condition. + if ((EP1_PACKET_COMPLETE | EP1_PACKET_ERROR) == (status & (EP1_PACKET_COMPLETE | EP1_PACKET_ERROR))) { + ep1_clear_error(); + ep1_start_rx_packet(); + } else if (0 != (status & EP1_FIFO_NOT_EMPTY)) { + // No error but data in the fifo. This implies a small + // initial packet, all held in the fifo. +#ifdef CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL + *EP1_DMA_BUF_A_SIZE = EP1_MTU; + ep1.using_buf_a = true; +#endif + (void) ep1_process_packet(); + ep1_rx_complete(ep1.fetched); + } else { + // Start a new transfer. + ep1_start_rx_packet(); + } + } +} + +static void +ep1_set_halted(usbs_rx_endpoint* endpoint, cyg_bool new_value) +{ + CYG_ASSERT( endpoint == &ep1.common, "USB set-stall operation involves the wrong endpoint"); + + if (ep1.common.halted == new_value) { + return; + } + if (new_value) { + // The endpoint should be stalled. There is a potential race + // condition here with a current transfer. Updating the + // stalled flag means that the dsr will do nothing. + ep1.common.halted = true; + HAL_REORDER_BARRIER(); + + // Now perform the actual stall. If we are in the middle of a + // transfer then the stall bit may not get set for a while, so + // poke() is inappropriate. + *EP1_CONTROL = EP1_FORCE_STALL; + } else { + // The stall condition should be cleared. First take care of + // things at the hardware level so that a new transfer is + // allowed. + usbs_sa11x0_poke(EP1_CONTROL, EP1_SENT_STALL, 0, EP1_SENT_STALL | EP1_FORCE_STALL); + + // Now allow new transfers to begin. + ep1.common.halted = false; + } +} + +// The DSR is invoked following an interrupt. According to the docs an +// endpoint 1 interrupt can only happen if the receive-packet-complete +// bit is set. +static void +usbs_sa11x0_ep1_dsr(void) +{ + int status = *EP1_CONTROL; + + // This assertion does not always hold. During long runs + // spurious interrupts have been observed. + // CYG_ASSERT( 0 != (status & EP1_PACKET_COMPLETE), "ep1 dsr should only be invoked when there is data"); + if (0 == (status & EP1_PACKET_COMPLETE)) { + return; + } + + if (ep1.common.halted) { + // Do nothing. What may have happened is that a transfer + // was in progress when the stall bit was set. The + // set_halted() call above will have taken care of things. + return; + } + + // The sent-stall bit should never get set, since we always + // accept full-size 64-byte packets. Just in case... + if (0 != (status & EP1_SENT_STALL)) { + DBG(("ep1_dsr(), sent-stall bit\n")); + usbs_sa11x0_poke(EP1_CONTROL, EP1_SENT_STALL, 0, EP1_SENT_STALL | EP1_FORCE_STALL); + } + + // Was there a receive error (CRC, bit-stuffing, fifo-overrun?). + // Whichever bits of the current packet have been received must be + // discarded, and the current packet must be retried. + if (0 != (status & EP1_PACKET_ERROR)) { + INCR_STAT(ep1_errors); + ep1_clear_error(); + ep1_start_rx_packet(); + } else { + // Another packet has been received. Process it, which may + // complete the transfer or it may leave more to be done. + // + // The hardware starts with the wrong default value for + // the receive-packet-complete bit, so a packet may arrive + // even though no rx operation has started yet. The + // packets must be ignored for now. start_rx_packet() + // will detect data in the fifo and do the right thing. + int pkt_size; + + if ((unsigned char*)0 != ep1.common.buffer) { + + pkt_size = ep1_process_packet(); + INCR_STAT(ep1_receives); + if (0 != (EP1_PACKET_ERROR & *EP1_CONTROL)) { + CYG_ASSERT( 0, "an error has occurred inside ep1_process_packet()\n"); + + } else if ((ep1.fetched != ep1.common.buffer_size) && (0 != pkt_size) && (0 == (ep1.fetched % EP1_MTU))) { + ep1_start_rx_packet(); + } else if (ep1.fetched > ep1.common.buffer_size) { + // The host has sent too much data. + ep1_rx_complete(-EMSGSIZE); + } else { +#if 0 + int i; + diag_printf("------------------------------------------------------\n"); + diag_printf("rx: buf %x, total size %d\n", ep1.common.buffer, ep1.fetched); + for (i = 0; (i < ep1.fetched) && (i < 128); i+= 8) { + diag_printf("rx %x %x %x %x %x %x %x %x\n", + ep1.common.buffer[i+0], ep1.common.buffer[i+1], ep1.common.buffer[i+2], ep1.common.buffer[i+3], + ep1.common.buffer[i+4], ep1.common.buffer[i+5], ep1.common.buffer[i+6], ep1.common.buffer[i+7]); + } + diag_printf("------------------------------------------------------\n"); +#endif + ep1_rx_complete(ep1.fetched); + } + } + } +} + +// Initialization. +// +// This may get called during system start-up or following a reset +// from the host. +static void +usbs_sa11x0_ep1_init(void) +{ +#ifdef CYGNUM_DEVS_USB_SA11X0_EP1_DMA_CHANNEL + // What is the physical address that should be used for + // transfers? + unsigned int phys; + HAL_VIRT_TO_PHYS_ADDRESS( ep1_dma_data, phys); + phys += (HAL_DCACHE_LINE_SIZE - 1); + phys -= (phys % HAL_DCACHE_LINE_SIZE); + CYG_ASSERT( 0 == (phys % HAL_DCACHE_LINE_SIZE), "DMA buffer must be aligned to a cache-line boundary"); + ep1_dma_buf = (unsigned char*)phys; + + // Clear the DMA channel and fix the DMA address register. The + // value is determined above. + *EP1_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + *EP1_DMA_ADDRESS = EP1_DMA_ADDRESS_VALUE; +#endif + + // Always allow the host to send full-size packets. If there is a + // protocol problem and the host sends packets that are too large, + // it will have to be handled at a level above the device driver. + // + // With some silicon revisions reading back the register does not + // work, so poke_value() is not applicable. This may be an issue + // with reset timing. + *USBS_OUT_SIZE = EP1_MTU - 1; + + // Endpoints should never be halted during a start-up. + ep1.common.halted = false; + + // If there has been a reset and there was a receive in progress, + // abort it. This also takes care of sorting out the endpoint + // fields ready for the next rx. + ep1_rx_complete(-EPIPE); +} + +#endif // CYGPKG_DEVS_USB_SA11X0_EP1 + + +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 +// ---------------------------------------------------------------------------- +// Endpoint 2 is used for IN transfers, i.e. transmitting data to the +// host. The code is mostly similar to that for endpoint 1, although +// a little bit simpler (e.g. there is no need to worry about +// buffer overflow, that is the host's problem). +// +// There is a flaw in the hardware design. If the transfer involves an +// exact multiple of 64 bytes then according to the USB spec there +// should be a terminating packet of 0 bytes. However the size of the +// current outgoing packet is determined by the IN_SIZE register and +// that only allows for packets between 1 and 256 bytes - even though +// USB bulk transfers can only go up to 64 bytes. This can be worked +// around at this level by transmitting an extra byte, at the risk of +// upsetting host-side device drivers. Both higher-level and host-side +// code need to be aware of this problem. +// +// Again there appear to be problems with the DMA engine. This time it +// appears that the transmit-fifo-service bit does not always work +// correctly. If you set up a DMA transfer for more than the packet +// size than once the packet has gone out the fifo-service bit just +// remains set, the DMA engine continues to fill the fifo, and the +// data gets lost. Instead DMA can only happen one packet at a time. +// The same issues regarding cache line alignment etc. arise, so +// using a small buffer here is convenient. +// +// 1) process_packet moves a packet from the main transmit buffer +// into the dma buffer. +// 2) start_tx_packet() starts a transfer to the host +// 3) clear_error() copes with error conditions. + +#ifdef CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL +// See the equivalent EP1 DMA definitions. +# define EP2_DMA_MTU 64 +# define EP2_DMA_BUFSIZE ((EP2_DMA_MTU + HAL_DCACHE_LINE_SIZE - 1) - \ + ((EP2_DMA_MTU + HAL_DCACHE_LINE_SIZE - 1) % HAL_DCACHE_LINE_SIZE)) +# define EP2_DMA_ALLOCSIZE (EP2_DMA_BUFSIZE + HAL_DCACHE_LINE_SIZE - 1) + +static unsigned char ep2_dma_data[EP2_DMA_ALLOCSIZE]; +static unsigned char* ep2_dma_buf; + +static void +ep2_process_packet(void) +{ + ep2.pkt_size = ep2.common.buffer_size - ep2.transmitted; + if (ep2.pkt_size > EP2_MTU) { + ep2.pkt_size = EP2_MTU; + } + // Work around the hardware's inability to send a zero-byte packet. + if (0 == ep2.pkt_size) { + ep2.pkt_size = 1; + ep2_dma_buf[0] = 0; + } else { + memcpy(ep2_dma_buf, ep2.common.buffer + ep2.transmitted, ep2.pkt_size); + } +} + +static void +ep2_tx_packet(void) +{ + int dma_size, dma_control_settings; + + // CYG_ASSERT( 0 != (*EP2_CONTROL & EP2_FIFO_SERVICE), "Fifo should be empty"); + + // Halt any DMA that may still be going on (there should not + // be any). Then work out the desired DMA settings for the + // current packet. The DMA engine needs to transfer a multiple + // of the burst size. If the packet size is not a multiple of + // the burst size, this presents a minor problem. The chances + // of an interrupt handler running in time to put the + // remaining bytes into the fifo by hand are not good, so + // instead more data is DMA'd in then is absolutely necessary + // and the surplus bytes will be cleared out during the next + // tx_packet. + // + // A possible optimisation is to detect small packets of + // less than the fifo size and byte-stuff those, bypassing + // DMA. It is not clear that would give any performance benefits. + *EP2_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + + dma_size = ep2.pkt_size + DMA_BURST_SIZE - 1; + dma_size -= (dma_size % DMA_BURST_SIZE); + + CYG_ASSERT(dma_size > 0, "DMA calculations should result in a transfer of at least 8 bytes"); + + // Now clear the fifo, after DMA has stopped. + usbs_sa11x0_poke(EP2_CONTROL, EP2_SENT_STALL, 0, EP2_SENT_STALL); + + // Should we be using buf_a or buf_b for this transfer? + // Getting this wrong means that the DMA engine just idles. + if (0 == (*EP2_DMA_STATUS & DMA_STATUS_BUFFER_IN_USE)) { + usbs_sa11x0_poke_value(EP2_DMA_BUF_A_ADDRESS, (int) ep2_dma_buf); + usbs_sa11x0_poke_value(EP2_DMA_BUF_A_SIZE, dma_size); + dma_control_settings = DMA_CONTROL_RUN | DMA_CONTROL_START_A; + } else { + usbs_sa11x0_poke_value(EP2_DMA_BUF_B_ADDRESS, (int) ep2_dma_buf); + usbs_sa11x0_poke_value(EP2_DMA_BUF_B_SIZE, dma_size); + dma_control_settings = DMA_CONTROL_RUN | DMA_CONTROL_START_B; + } + + // Poke the tx size register while the fifo is clearing. + // This operation must be reliable or the host will get + // confused by funny-sized packets. + usbs_sa11x0_poke_value(USBS_IN_SIZE, ep2.pkt_size - 1); + + // The USB hardware must be updated before the DMA engine + // starts filling the fifo. Otherwise ~48% of outgoing + // packets fail with a DMA underrun. When called from + // start_tx() there is a race condition: if the host + // request comes in before the DMA starts then an + // error interrupt will be raised, to be processed by + // the DSR, and then the DMA engine gets updated again. + // Locking the scheduler eliminates this race. + cyg_drv_dsr_lock(); + usbs_sa11x0_poke(EP2_CONTROL, EP2_PACKET_COMPLETE, 0, EP2_PACKET_COMPLETE | EP2_PACKET_ERROR | EP2_PACKET_UNDERRUN); + *EP2_DMA_CONTROL_SET = dma_control_settings; + cyg_drv_dsr_unlock(); + + // CYG_ASSERT(0 == (*EP2_CONTROL & EP2_FIFO_SERVICE), "DMA engine should have filled up the fifo by now"); +} + +// Clearing an error should be a no-op when DMA is involved. +// In practice clearing the packet-complete bit appears to +// have some desirable effects, at the risk of the host +// getting bogus data. This should only happen when there +// is a real transfer in progress: an error early on is +// likely because the PACKET_COMPLETE bit has a bogus initial +// value. +static void +ep2_clear_error(void) +{ + usbs_sa11x0_poke(EP2_CONTROL, EP2_PACKET_COMPLETE, 0, EP2_PACKET_COMPLETE | EP2_PACKET_ERROR | EP2_PACKET_UNDERRUN); +} + +#else // EP2_DMA + +// When not using DMA, process_packet() is responsible for filling the +// fifo and keeping a shadow copy in a static buffer. clear_error() +// refills the fifo using the shadow copy. tx_packet() starts the +// actual transfer. +static unsigned char ep2_tx_buf[EP2_MTU]; + +static void +ep2_process_packet() +{ + int i; + + // Clear the fifo, just in case. + usbs_sa11x0_poke(EP2_CONTROL, EP2_SENT_STALL, 0, EP2_SENT_STALL); + + ep2.pkt_size = ep2.common.buffer_size - ep2.transmitted; + if (ep2.pkt_size > EP2_MTU) { + ep2.pkt_size = EP2_MTU; + } + if (0 == ep2.pkt_size) { + ep2.pkt_size = 1; + ep2_tx_buf[i] = 0; + *EP2_DATA = 0; + } else { + for (i = 0; i < ep2.pkt_size; i++) { + unsigned int datum = ep2.common.buffer[ep2.transmitted + i]; + ep2_tx_buf[i] = datum; + *EP2_DATA = datum; + } + } +} + +static void +ep2_tx_packet() +{ + usbs_sa11x0_poke_value(USBS_IN_SIZE, ep2.pkt_size - 1); + usbs_sa11x0_poke(EP2_CONTROL, EP2_PACKET_COMPLETE, 0, EP2_PACKET_COMPLETE | EP2_PACKET_ERROR | EP2_PACKET_UNDERRUN); +} + +static void +ep2_clear_error() +{ + int i; + // Clear the fifo, just in case. + usbs_sa11x0_poke(EP2_CONTROL, EP2_SENT_STALL, 0, EP2_SENT_STALL); + for (i = 0; i < ep2.pkt_size; i++) { + *EP2_DATA = ep2_tx_buf[i]; + } +} + +#endif // !EP2_DMA + +// A utility routine for completing a transfer. This takes care of the +// callback as well as resetting the buffer. +static void +ep2_tx_complete(int result) +{ + void (*complete_fn)(void*, int) = ep2.common.complete_fn; + void* complete_data = ep2.common.complete_data; + + ep2.common.buffer = (unsigned char*) 0; + ep2.common.buffer_size = 0; + ep2.common.complete_fn = (void (*)(void*, int)) 0; + ep2.common.complete_data = (void*) 0; + + if ((void (*)(void*, int))0 != complete_fn) { + (*complete_fn)(complete_data, result); + } +} + + +// The exported interface to start a transmission. +static void +ep2_start_tx(usbs_tx_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep2.common, "USB data transfer involves the wrong endpoint"); + + // Is this endpoint currently stalled? If so then a size of 0 can + // be used to block until the stall condition is clear, anything + // else should result in an immediate callback. + if (ep2.common.halted) { + if (0 != ep2.common.buffer_size) { + ep2_tx_complete(-EAGAIN); + } + } else if (0 == ep2.common.buffer_size) { + // A check to see if the endpoint is halted. It isn't. + ep2_tx_complete(0); + } else { + // There should not be any errors at the start of a + // transmission, but if there is one then there is no safe way + // to recover. process_packet() and tx_packet() will hopefully + // do the right thing. + CYG_ASSERT((void*) 0 != ep2.common.buffer, "Transmitting the interrupt vectors is unlikely to be useful"); +#if 0 + { + int i; + diag_printf("----------------------------------------------\n"); + diag_printf("ep2_start_tx: buf %x, %d bytes\n", ep2.common.buffer, ep2.common.buffer_size); + for (i = 0; (i < ep2.common.buffer_size) && (i < 128); i+= 8) { + diag_printf("tx: %x %x %x %x %x %x %x %x\n", + ep2.common.buffer[i+0], ep2.common.buffer[i+1], ep2.common.buffer[i+2], ep2.common.buffer[i+3], + ep2.common.buffer[i+4], ep2.common.buffer[i+5], ep2.common.buffer[i+6], ep2.common.buffer[i+7]); + } + diag_printf("----------------------------------------------\n"); + } +#endif + + // Prepare the first packet for transmission, then send it. + ep2.transmitted = 0; + ep2_process_packet(); + ep2_tx_packet(); + } +} + +static void +ep2_set_halted(usbs_tx_endpoint* endpoint, cyg_bool new_value) +{ + CYG_ASSERT(endpoint == &ep2.common, "USB set-stall operation involves the wrong endpoint"); + + if (ep2.common.halted == new_value) { + return; + } + if (new_value) { + // The endpoint should be stalled. There is a potential race + // condition here with the current transfer and DSR invocation. + // Updating the stalled flag means that the DSR will do nothing. + ep2.common.halted = true; + HAL_REORDER_BARRIER(); + + // Now perform the actual stall. This may be delayed by the hardware + // so poke() cannot be used. + *EP2_CONTROL = EP2_FORCE_STALL; + + // If in the middle of a transfer then that cannot be aborted, + // the DMA engines etc. would get very confused. + } else { + // Take care of the hardware so that a new transfer is allowed. + usbs_sa11x0_poke(EP2_CONTROL, EP2_SENT_STALL, 0, EP2_SENT_STALL | EP2_FORCE_STALL); + ep2.common.halted = false; + } +} + +// The dsr will be invoked when the transmit-packet-complete bit is +// set. Typically this happens when a packet has been completed +// (surprise surprise) but it can also happen for error conditions. +static void +usbs_sa11x0_ep2_dsr(void) +{ + int status = *EP2_CONTROL; + // This assertion does not always hold - spurious interrupts have + // been observed if you run for a few hours. + // CYG_ASSERT( 0 != (status & EP2_PACKET_COMPLETE), "ep2 dsr should only be invoked when the packet-complete bit is set"); + + if (0 == (status & EP2_PACKET_COMPLETE)) { + // Spurious interrupt, do nothing. + } else if (ep2.common.halted) { + // There is a possible race condition between a packet + // completing and the stalled condition being set. + // set_halted() above does everything that is needed. + } else if (0 == ep2.pkt_size) { + // This can happen because of the initial value for the + // packet-complete bit, allowing the host to retrieve data + // before the target is ready. The correct action is to do + // nothing. + } else if (0 != (status & (EP2_PACKET_ERROR | EP2_PACKET_UNDERRUN))) { + // A transmit error occurred, the details are not important. + INCR_STAT(ep2_errors); + ep2_clear_error(); + ep2_tx_packet(); + } else { + // Another packet has gone out. + INCR_STAT(ep2_transmits); + ep2.transmitted += ep2.pkt_size; + if ((ep2.transmitted < ep2.common.buffer_size) || + ((ep2.transmitted == ep2.common.buffer_size) && (0 == (ep2.common.buffer_size % EP2_MTU)))) { + ep2_process_packet(); + ep2_tx_packet(); + } else { + ep2_tx_complete(ep2.transmitted); + } + } +} + +// Endpoint 2 initialization. +// +// This may be called during system start-up or following a reset +// from the host. +static void +usbs_sa11x0_ep2_init(void) +{ +#ifdef CYGNUM_DEVS_USB_SA11X0_EP2_DMA_CHANNEL + // What is the physical address that should be used for + // transfers? + unsigned int phys; + HAL_VIRT_TO_PHYS_ADDRESS(ep2_dma_data, phys); + phys += (HAL_DCACHE_LINE_SIZE - 1); + phys -= (phys % HAL_DCACHE_LINE_SIZE); + CYG_ASSERT(0 == (phys % HAL_DCACHE_LINE_SIZE), "DMA buffer must be aligned to a cache-line boundary"); + ep2_dma_buf = (unsigned char*) phys; + + // Clear the DMA channel completely, otherwise it may not be + // possible to write the ADDRESS register. Then set the DMA + // address register. The value is determined above. + *EP2_DMA_CONTROL_CLEAR = DMA_CONTROL_CLEAR_ALL; + *EP2_DMA_ADDRESS = EP2_DMA_ADDRESS_VALUE; +#endif + + // Endpoints should never be halted after a reset + ep2.common.halted = false; + + // If there has been a reset and there was a receive in progress, + // abort it. This also takes care of clearing the endpoint + // structure fields. + ep2_tx_complete(-EPIPE); +} + +#endif // CYGPKG_DEVS_USB_SA11X0_EP2 + +// ---------------------------------------------------------------------------- +// Interrupt handling +// +// As much work as possible is deferred to the DSR (or to the debug +// thread). Interrupts for the endpoints are never a problem: the +// variuos packet-complete etc. bits ensure that the endpoints +// remain quiescent until the relevant interrupt has been serviced. +// Suspend and resume are more complicated. A suspend means that +// there has been no activity for 3ms, which should be enough +// time for the whole thing to be handled. A resume means that there +// has been bus activity after a suspend, and again it is infrequent. +// +// Reset appears to be much more complicated. A reset means that the +// host is holding the USB lines to a specific state for 10ms. This is +// detected by the hardware, causing the USB controller to be reset +// (i.e. any pending transfers are discarded, etc.). The reset bit in +// the status register will be set, and an interrupt will be raised. +// Now, in theory the correct thing to do is to process this +// interrupt, block reset interrupts for the duration of these 10ms, +// and wait for further activity such as the control message to set +// the address. +// +// In practice this does not seem to work. Possibly the USB controller +// gets reset continuously while the external reset signal is applied, +// but I have not been able to confirm this. Messing about with the +// reset interrupt control bit causes the system to go off into +// never-never land. 10ms is too short a time to allow for manual +// debugging of what happens. So for now the interrupt source is +// blocked at the interrupt mask level and the dsr will do the +// right thing. This causes a significant number of spurious interrupts +// for the duration of the reset signal and not a lot else can happen. + + +// Perform reset operations on all endpoints that have been +// configured in. It is convenient to keep this in a separate +// routine to allow for polling, where manipulating the +// interrupt controller mask is a bad idea. +static void +usbs_sa11x0_handle_reset(void) +{ + int old_state = ep0.common.state; + + // Any state change must be reported to higher-level code + ep0.common.state = USBS_STATE_DEFAULT; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_RESET, old_state); + } + + // Reinitialize all the endpoints that have been configured in. + usbs_sa11x0_ep0_init(); +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + usbs_sa11x0_ep1_init(); +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + usbs_sa11x0_ep2_init(); +#endif + + // After a reset we need to handle endpoint interrupts, reset + // interrupts, and suspend interrupts. There should not be a + // resume since we have not suspended, but leaving resume + // interrupts enabled appears to be desirable with some hardware. + // + // With some silicon revisions it appears that a longer delay + // is needed after reset, so this poke() may not work. + if (!usbs_sa11x0_poke(USBS_CONTROL, + CONTROL_INTR_ENABLE(CONTROL_EP_INTR_BITS|CONTROL_RESET_INTR|CONTROL_SUSPEND_INTR|CONTROL_RESUME_INTR), + 0, + CONTROL_INTR_CLEAR(CONTROL_EP_INTR_BITS|CONTROL_RESET_INTR|CONTROL_SUSPEND_INTR|CONTROL_RESUME_INTR))) { + // DBG(("usbs_sa11x0_handle_reset(), update of control register failed, status %x\n", *USBS_STATUS)); + } +} + +// The DSR. This can be invoked directly by poll(), or via the usual +// interrupt subsystem. It acts as per the current value of +// isr_status_bits. If another interrupt goes off while this +// DSR is running, there will be another invocation of the DSR and +// the status bits will be updated. +static void +usbs_sa11x0_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + int status = 0; + + CYG_ASSERT(SA11X0_IRQ_USB_SERVICE_REQUEST == vector, "USB DSR should only be invoked for USB interrupts" ); + CYG_ASSERT(0 == data, "The SA11X0 USB DSR needs no global data pointer"); + + // There is no atomic swap support, so interrupts have to be + // blocked. It might be possible to do this via the USBS_CONTROL + // register, but at the risk of messing up the status register + // if another interrupt comes in. Blocking interrupts at the + // processor level is less intrusive on the USB code. + + cyg_drv_isr_lock(); + status = isr_status_bits; + isr_status_bits = 0; + cyg_drv_isr_unlock(); + + // Reset is special, since it invalidates everything else. + // If the reset is still ongoing then do not attempt any + // further processing, there will just be another interrupt. + // Otherwise handle_reset() does the hard work. Unmasking + // the interrupt means that another interrupt will occur + // immediately if reset is still asserted, i.e. no threads + // will run, but there is no easy way of triggering action + // at the end of reset. + if (0 != (status & STATUS_RESET_INTR)) { + + int new_status = *USBS_STATUS; + if (0 == (new_status & STATUS_RESET_INTR)) { + usbs_sa11x0_handle_reset(); + } + // This unmask is likely to cause another interrupt immediately + cyg_drv_interrupt_unmask(SA11X0_IRQ_USB_SERVICE_REQUEST); + + } else { + // Process resume first. Ignore any resumes when we are not + // actually suspended yet, this happens mainly during system + // startup. If there has been a state change to suspended + // then we need a matching state change to resumed. + if (0 != (status & STATUS_RESUME_INTR)) { + int old_state = ep0.common.state; + if (0 != (old_state & USBS_STATE_SUSPENDED)) { + ep0.common.state &= ~USBS_STATE_SUSPENDED; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_RESUMED, old_state); + } + // After a resume, all interrupts should be enabled. + // In theory there is no need to worry about further + // resume interrupts, but strange hardware behaviour + // has been observed if resume interrupts are left + // disabled. + usbs_sa11x0_poke(USBS_CONTROL, + CONTROL_INTR_ENABLE(CONTROL_EP_INTR_BITS|CONTROL_RESET_INTR|CONTROL_SUSPEND_INTR|CONTROL_RESUME_INTR), + 0, + CONTROL_INTR_CLEAR(CONTROL_EP_INTR_BITS|CONTROL_RESET_INTR|CONTROL_SUSPEND_INTR|CONTROL_RESUME_INTR)); + } + } + + // Now process endpoint interrupts. Control operations on + // endpoint 0 may have side effects on the other endpoints + // so it is better to leave them until last. +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + if (0 != (status & STATUS_EP1_INTR)) { + usbs_sa11x0_ep1_dsr(); + } +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + if (0 != (status & STATUS_EP2_INTR)) { + usbs_sa11x0_ep2_dsr(); + } +#endif + if (0 != (status & STATUS_EP0_INTR)) { + usbs_sa11x0_ep0_dsr(); + } + + // Process suspend last, but only if there has not also been + // a resume. A suspend immediately followed by a resume should + // be ignored. A resume immediately followed by a suspend + // would be unfortunate, but suspend means that there has been + // at least 3ms of inactivity so the DSR latency would have + // to be pretty bad. + // + // Total robustness is possible but requires more work in the ISR. + if ((0 != (status & STATUS_SUSPEND_INTR)) && (0 == (status & STATUS_RESUME_INTR))) { + int old_state = ep0.common.state; + ep0.common.state |= USBS_STATE_SUSPENDED; + if ((void (*)(usbs_control_endpoint*, void*, usbs_state_change, int))0 != ep0.common.state_change_fn) { + (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data, + USBS_STATE_CHANGE_SUSPENDED, old_state); + } + // We are no longer interested in further suspend interrupts, + // which could happen every 3 ms, but resume has become + // very interesting. + usbs_sa11x0_poke(USBS_CONTROL, + CONTROL_INTR_ENABLE(CONTROL_EP_INTR_BITS | CONTROL_RESET_INTR | CONTROL_RESUME_INTR), + 0, + CONTROL_INTR_CLEAR(CONTROL_EP_INTR_BITS | CONTROL_RESET_INTR | CONTROL_RESUME_INTR)); + } + } +} + +// ---------------------------------------------------------------------------- +// Optionally the USB code can do most of its processing in a thread +// rather than in a DSR. +#ifdef CYGPKG_DEVS_USB_SA11X0_THREAD +static unsigned char usbs_sa11x0_thread_stack[CYGNUM_DEVS_USB_SA11X0_THREAD_STACK_SIZE]; +static cyg_thread usbs_sa11x0_thread; +static cyg_handle_t usbs_sa11x0_thread_handle; +static cyg_sem_t usbs_sa11x0_sem; + + +static void +usbs_sa11x0_thread_fn(cyg_addrword_t param) +{ + for (;;) { + cyg_semaphore_wait(&usbs_sa11x0_sem); + usbs_sa11x0_dsr(SA11X0_IRQ_USB_SERVICE_REQUEST, 0, 0); + } + CYG_UNUSED_PARAM(cyg_addrword_t, param); +} + +static void +usbs_sa11x0_thread_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + CYG_ASSERT( 0 != isr_status_bits, "DSR's should only be scheduled when there is work to do"); + cyg_semaphore_post(&usbs_sa11x0_sem); + + CYG_UNUSED_PARAM(cyg_vector_t, vector); + CYG_UNUSED_PARAM(cyg_ucount32, count); + CYG_UNUSED_PARAM(cyg_addrword_t, data); +} + +#endif + +// ---------------------------------------------------------------------------- +// The interrupt handler. This does as little as possible. +static cyg_uint32 +usbs_sa11x0_isr(cyg_vector_t vector, cyg_addrword_t data) +{ + int old_status_bits = isr_status_bits; + int status_bits; + + CYG_ASSERT(SA11X0_IRQ_USB_SERVICE_REQUEST == vector, "USB ISR should only be invoked for USB interrupts" ); + CYG_ASSERT(0 == data, "The SA11X0 USB ISR needs no global data pointer" ); + + // Read the current status. Reset is special, it means that the + // whole chip has been reset apart from the one bit in the status + // register. Nothing should be done about this until the DSR sets + // the endpoints back to a consistent state and re-enables + // interrupts in the control register. + status_bits = *USBS_STATUS; + + if (0 != (status_bits & STATUS_RESET_INTR)) { + isr_status_bits = STATUS_RESET_INTR; + *USBS_STATUS = status_bits; + cyg_drv_interrupt_mask(SA11X0_IRQ_USB_SERVICE_REQUEST); + } else { + *USBS_STATUS = status_bits; + isr_status_bits |= status_bits; + } + + // Now keep the rest of the system happy. + cyg_drv_interrupt_acknowledge(vector); + return (old_status_bits != isr_status_bits) ? CYG_ISR_CALL_DSR : CYG_ISR_HANDLED; +} + +// ---------------------------------------------------------------------------- +// Polling support. This acts mostly like the interrupt handler: it +// sets the isr status bits and causes the dsr to run. Reset has to be +// handled specially: polling does nothing as long as reset is asserted. + +static void +usbs_sa11x0_poll(usbs_control_endpoint* endpoint) +{ + CYG_ASSERT( endpoint == &ep0.common, "USB poll involves the wrong endpoint"); + + if (0 != (isr_status_bits & STATUS_RESET_INTR)) { + // Reset was detected the last time poll() was invoked. If + // reset is still active, do nothing. Once the reset has + // completed things can continue. + if (0 == (STATUS_RESET_INTR & *USBS_STATUS)) { + isr_status_bits = 0; + usbs_sa11x0_handle_reset(); + } + } else { + isr_status_bits = *USBS_STATUS; + if (0 != (STATUS_RESET_INTR & isr_status_bits)) { + // Reset has just been asserted. Do nothing, just continue + // polling for the duration of the reset signal. + } else if (0 != isr_status_bits) { + usbs_sa11x0_dsr(SA11X0_IRQ_USB_SERVICE_REQUEST, 0, (cyg_addrword_t) 0); + } + } +} + + +// ---------------------------------------------------------------------------- +// Initialization. + +void +usbs_sa11x0_init(void) +{ + // Start by disabling/resetting the hardware. This is easy. + *USBS_CONTROL = CONTROL_DISABLE; + *USBS_CONTROL = CONTROL_DISABLE; + *USBS_CONTROL = CONTROL_DISABLE; + + // The USB bus is now tristated, preventing any communications. + // This is a good thing, the situation should change only when + // higher-level code has provided the enumeration data and done an + // explicit start. + usbs_sa11x0_ep0_init(); +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + usbs_sa11x0_ep1_init(); +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + usbs_sa11x0_ep2_init(); +#endif + + // If processing is supposed to happen in a thread rather + // than in DSR, initialize the threads. +#ifdef CYGPKG_DEVS_USB_SA11X0_THREAD + cyg_semaphore_init(&usbs_sa11x0_sem, 0); + cyg_thread_create(CYGNUM_DEVS_USB_SA11X0_THREAD_PRIORITY, + &usbs_sa11x0_thread_fn, + 0, + "SA11X0 USB support", + usbs_sa11x0_thread_stack, + CYGNUM_DEVS_USB_SA11X0_THREAD_STACK_SIZE, + &usbs_sa11x0_thread_handle, + &usbs_sa11x0_thread + ); + cyg_thread_resume(usbs_sa11x0_thread_handle); +#endif + + // It is also possible and desirable to install the interrupt + // handler here, even though there will be no interrupts for a + // while yet. + cyg_drv_interrupt_create(SA11X0_IRQ_USB_SERVICE_REQUEST, + 99, // priority + 0, // data + &usbs_sa11x0_isr, +#ifdef CYGPKG_DEVS_USB_SA11X0_THREAD + &usbs_sa11x0_thread_dsr, +#else + &usbs_sa11x0_dsr, +#endif + &usbs_sa11x0_intr_handle, + &usbs_sa11x0_intr_data); + cyg_drv_interrupt_attach(usbs_sa11x0_intr_handle); + cyg_drv_interrupt_unmask(SA11X0_IRQ_USB_SERVICE_REQUEST); +} + +// ---------------------------------------------------------------------------- +// Testing support. +usbs_testing_endpoint usbs_testing_endpoints[] = { + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, + endpoint_number : 0, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep0.common, +#ifdef CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "0c", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, // zero-byte control transfers are meaningless + max_size : 0x0FFFF, // limit imposed by protocol + max_in_padding : 0, + alignment : 0 + }, +#ifdef CYGPKG_DEVS_USB_SA11X0_EP1 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 1, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, + endpoint : (void*) &ep1.common, +#ifdef CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "1r", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : -1, // No hardware or driver limitation + max_in_padding : 0, + alignment : 0 + }, +#endif +#ifdef CYGPKG_DEVS_USB_SA11X0_EP2 + { + endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + endpoint_number : 2, + endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, + endpoint : (void*) &ep2.common, +#ifdef CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY + devtab_entry : CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "2w", +#else + devtab_entry : (const char*) 0, +#endif + min_size : 1, + max_size : -1, // No hardware or driver limitation + max_in_padding : 1, // hardware limitation + alignment : 0 + }, +#endif + USBS_TESTING_ENDPOINTS_TERMINATOR +}; diff --git a/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0_data.cxx b/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0_data.cxx new file mode 100644 index 0000000..0ce3f3b --- /dev/null +++ b/ecos/packages/devs/usb/sa11x0/current/src/usbs_sa11x0_data.cxx @@ -0,0 +1,173 @@ +//========================================================================== +// +// usbs_sa11x0.c +// +// Static data for the SA11x0 USB device driver +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. +// +// eCos is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 or (at your option) any later +// version. +// +// eCos is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with eCos; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// As a special exception, if other files instantiate templates or use +// macros or inline functions from this file, or you compile this file +// and link it with other works to produce a work based on this file, +// this file does not by itself cause the resulting work to be covered by +// the GNU General Public License. However the source code for this file +// must still be made available in accordance with section (3) of the GNU +// General Public License v2. +// +// This exception does not invalidate any other reasons why a work based +// on this file might be covered by the GNU General Public License. +// ------------------------------------------- +// ####ECOSGPLCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contributors: bartv +// Date: 2000-10-04 +// +// This file contains various objects that should go into extras.o +// rather than libtarget.a, e.g. devtab entries that would normally +// be eliminated by the selective linking. +// +//####DESCRIPTIONEND#### +//========================================================================== + +#include <cyg/io/devtab.h> +#include <cyg/io/usb/usbs_sa11x0.h> +#include <pkgconf/devs_usb_sa11x0.h> + +// ---------------------------------------------------------------------------- +// Initialization. The goal here is to call usbs_sa11x0_init() +// early on during system startup, to take care of things like +// registering interrupt handlers etc. which are best done +// during system init. +// +// If the endpoint 0 devtab entry is available then its init() +// function can be used to take care of this. However the devtab +// entries are optional so an alternative mechanism must be +// provided. Unfortunately although it is possible to give +// a C function the constructor attribute, it cannot be given +// an initpri attribute. Instead it is necessary to define a +// dummy C++ class. + +extern "C" void usbs_sa11x0_init(void); + +#ifndef CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY +class usbs_sa11x0_initialization { + public: + usbs_sa11x0_initialization() { + usbs_sa11x0_init(); + } +}; + +static usbs_sa11x0_initialization usbs_sa11x0_init_object CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO); +#endif + +// ---------------------------------------------------------------------------- +// The devtab entries. Each of these is optional, many applications +// will want to use the lower-level API rather than go via +// open/read/write/ioctl. + +#ifdef CYGVAR_DEVS_USB_SA11X0_EP0_DEVTAB_ENTRY + +// For endpoint 0 the only legal operations are get_config() and +// set_config(), and these are provided by the common package. + +static bool +usbs_sa11x0_devtab_ep0_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + usbs_sa11x0_init(); + return true; +} + +static CHAR_DEVIO_TABLE(usbs_sa11x0_ep0_devtab_functions, + &cyg_devio_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static CHAR_DEVTAB_ENTRY(usbs_sa11x0_ep0_devtab_entry, + CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "0c", + 0, + &usbs_sa11x0_ep0_devtab_functions, + &usbs_sa11x0_devtab_ep0_init, + 0, + (void*) &usbs_sa11x0_ep0); +#endif + +// ---------------------------------------------------------------------------- +// Common routines for ep1 and ep2. +#if defined(CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY) || defined(CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY) +static bool +usbs_sa11x0_devtab_dummy_init(struct cyg_devtab_entry* tab) +{ + CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab); + return true; +} +#endif + +// ---------------------------------------------------------------------------- +// ep1 devtab entry. This can only be used for host->slave, so only the +// cread() function makes sense. + +#ifdef CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY + +static CHAR_DEVIO_TABLE(usbs_sa11x0_ep1_devtab_functions, + &cyg_devio_cwrite, + &usbs_devtab_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static CHAR_DEVTAB_ENTRY(usbs_sa11x0_ep1_devtab_entry, + CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "1r", + 0, + &usbs_sa11x0_ep1_devtab_functions, + &usbs_sa11x0_devtab_dummy_init, + 0, + (void*) &usbs_sa11x0_ep1); +#endif + +// ---------------------------------------------------------------------------- +// ep2 devtab entry. This can only be used for slave->host, so only +// the cwrite() function makes sense. + +#ifdef CYGVAR_DEVS_USB_SA11X0_EP2_DEVTAB_ENTRY + +static CHAR_DEVIO_TABLE(usbs_sa11x0_ep2_devtab_functions, + &usbs_devtab_cwrite, + &cyg_devio_cread, + &cyg_devio_select, + &usbs_devtab_get_config, + &usbs_devtab_set_config); + +static DEVTAB_ENTRY(usbs_sa11x0_ep2_devtab_entry, + CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "2w", + 0, + &usbs_sa11x0_ep2_devtab_functions, + &usbs_sa11x0_devtab_dummy_init, + 0, + (void*) &usbs_sa11x0_ep2); + +#endif + |