/***********************license start*************** * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights * reserved. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of Cavium Networks nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * This Software, including technical data, may be subject to U.S. export control * laws, including the U.S. Export Administration Act and its associated * regulations, and may be subject to export or import regulations in other * countries. * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION OR * DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. ***********************license end**************************************/ /** * @file * * "cvmx-usb.c" defines a set of low level USB functions to help * developers create Octeon USB drivers for various operating * systems. These functions provide a generic API to the Octeon * USB blocks, hiding the internal hardware specific * operations. * *
$Revision: 32636 $
*/ #ifdef CVMX_BUILD_FOR_LINUX_KERNEL #include #include #include #include #include #include #include #include #include #if 0 /* Do not use cvmx-error.h for now. When the cvmx-error.h is properly * ported, remove the above #if 0, and all #ifdef __CVMX_ERROR_H__ within * this file */ #include #endif #else #include "cvmx.h" #include "cvmx-clock.h" #include "cvmx-sysinfo.h" #include "cvmx-usb.h" #include "cvmx-helper.h" #include "cvmx-helper-board.h" #include "cvmx-csr-db.h" #include "cvmx-swap.h" #include "cvmx-error.h" #endif #define MAX_RETRIES 3 /* Maximum number of times to retry failed transactions */ #define MAX_PIPES 32 /* Maximum number of pipes that can be open at once */ #define MAX_TRANSACTIONS 256 /* Maximum number of outstanding transactions across all pipes */ #define MAX_CHANNELS 8 /* Maximum number of hardware channels supported by the USB block */ #define MAX_USB_ADDRESS 127 /* The highest valid USB device address */ #define MAX_USB_ENDPOINT 15 /* The highest valid USB endpoint number */ #define MAX_USB_HUB_PORT 15 /* The highest valid port number on a hub */ #define MAX_TRANSFER_BYTES ((1<<19)-1) /* The low level hardware can transfer a maximum of this number of bytes in each transfer. The field is 19 bits wide */ #define MAX_TRANSFER_PACKETS ((1<<10)-1) /* The low level hardware can transfer a maximum of this number of packets in each transfer. The field is 10 bits wide */ #define ALLOW_CSR_DECODES 0 /* CSR decoding when CVMX_USB_INITIALIZE_FLAGS_DEBUG_CSRS is set enlarges the code a lot. This define overrides the ability to do CSR decoding since it isn't necessary 99% of the time. Change this to a one if you need CSR decoding */ /* These defines disable the normal read and write csr. This is so I can add extra debug stuff to the usb specific version and I won't use the normal version by mistake */ #define cvmx_read_csr use_cvmx_usb_read_csr64_instead_of_cvmx_read_csr #define cvmx_write_csr use_cvmx_usb_write_csr64_instead_of_cvmx_write_csr typedef enum { __CVMX_USB_TRANSACTION_FLAGS_IN_USE = 1<<16, } cvmx_usb_transaction_flags_t; /** * Logical transactions may take numerous low level * transactions, especially when splits are concerned. This * enum represents all of the possible stages a transaction can * be in. Note that split completes are always even. This is so * the NAK handler can backup to the previous low level * transaction with a simple clearing of bit 0. */ typedef enum { CVMX_USB_STAGE_NON_CONTROL, CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE, CVMX_USB_STAGE_SETUP, CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE, CVMX_USB_STAGE_DATA, CVMX_USB_STAGE_DATA_SPLIT_COMPLETE, CVMX_USB_STAGE_STATUS, CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE, } cvmx_usb_stage_t; /** * This structure describes each pending USB transaction * regardless of type. These are linked together to form a list * of pending requests for a pipe. */ typedef struct cvmx_usb_transaction { struct cvmx_usb_transaction *prev; /**< Transaction before this one in the pipe */ struct cvmx_usb_transaction *next; /**< Transaction after this one in the pipe */ cvmx_usb_transfer_t type; /**< Type of transaction, duplicated of the pipe */ cvmx_usb_transaction_flags_t flags; /**< State flags for this transaction */ uint64_t buffer; /**< User's physical buffer address to read/write */ int buffer_length; /**< Size of the user's buffer in bytes */ uint64_t control_header; /**< For control transactions, physical address of the 8 byte standard header */ int iso_start_frame; /**< For ISO transactions, the starting frame number */ int iso_number_packets; /**< For ISO transactions, the number of packets in the request */ cvmx_usb_iso_packet_t *iso_packets; /**< For ISO transactions, the sub packets in the request */ int xfersize; int pktcnt; int retries; int actual_bytes; /**< Actual bytes transfer for this transaction */ cvmx_usb_stage_t stage; /**< For control transactions, the current stage */ cvmx_usb_callback_func_t callback; /**< User's callback function when complete */ void *callback_data; /**< User's data */ } cvmx_usb_transaction_t; /** * A pipe represents a virtual connection between Octeon and some * USB device. It contains a list of pending request to the device. */ typedef struct cvmx_usb_pipe { struct cvmx_usb_pipe *prev; /**< Pipe before this one in the list */ struct cvmx_usb_pipe *next; /**< Pipe after this one in the list */ cvmx_usb_transaction_t *head; /**< The first pending transaction */ cvmx_usb_transaction_t *tail; /**< The last pending transaction */ uint64_t interval; /**< For periodic pipes, the interval between packets in frames */ uint64_t next_tx_frame; /**< The next frame this pipe is allowed to transmit on */ cvmx_usb_pipe_flags_t flags; /**< State flags for this pipe */ cvmx_usb_speed_t device_speed; /**< Speed of device connected to this pipe */ cvmx_usb_transfer_t transfer_type; /**< Type of transaction supported by this pipe */ cvmx_usb_direction_t transfer_dir; /**< IN or OUT. Ignored for Control */ int multi_count; /**< Max packet in a row for the device */ uint16_t max_packet; /**< The device's maximum packet size in bytes */ uint8_t device_addr; /**< USB device address at other end of pipe */ uint8_t endpoint_num; /**< USB endpoint number at other end of pipe */ uint8_t hub_device_addr; /**< Hub address this device is connected to */ uint8_t hub_port; /**< Hub port this device is connected to */ uint8_t pid_toggle; /**< This toggles between 0/1 on every packet send to track the data pid needed */ uint8_t channel; /**< Hardware DMA channel for this pipe */ int8_t split_sc_frame; /**< The low order bits of the frame number the split complete should be sent on */ } cvmx_usb_pipe_t; typedef struct { cvmx_usb_pipe_t *head; /**< Head of the list, or NULL if empty */ cvmx_usb_pipe_t *tail; /**< Tail if the list, or NULL if empty */ } cvmx_usb_pipe_list_t; typedef struct { struct { int channel; int size; uint64_t address; } entry[MAX_CHANNELS+1]; int head; int tail; } cvmx_usb_tx_fifo_t; /** * The state of the USB block is stored in this structure */ typedef struct { int init_flags; /**< Flags passed to initialize */ int index; /**< Which USB block this is for */ int idle_hardware_channels; /**< Bit set for every idle hardware channel */ cvmx_usbcx_hprt_t usbcx_hprt; /**< Stored port status so we don't need to read a CSR to determine splits */ cvmx_usb_pipe_t *pipe_for_channel[MAX_CHANNELS]; /**< Map channels to pipes */ cvmx_usb_transaction_t *free_transaction_head; /**< List of free transactions head */ cvmx_usb_transaction_t *free_transaction_tail; /**< List of free transactions tail */ cvmx_usb_pipe_t pipe[MAX_PIPES]; /**< Storage for pipes */ cvmx_usb_transaction_t transaction[MAX_TRANSACTIONS]; /**< Storage for transactions */ cvmx_usb_callback_func_t callback[__CVMX_USB_CALLBACK_END]; /**< User global callbacks */ void *callback_data[__CVMX_USB_CALLBACK_END]; /**< User data for each callback */ int indent; /**< Used by debug output to indent functions */ cvmx_usb_port_status_t port_status; /**< Last port status used for change notification */ cvmx_usb_pipe_list_t free_pipes; /**< List of all pipes that are currently closed */ cvmx_usb_pipe_list_t idle_pipes; /**< List of open pipes that have no transactions */ cvmx_usb_pipe_list_t active_pipes[4]; /**< Active pipes indexed by transfer type */ uint64_t frame_number; /**< Increments every SOF interrupt for time keeping */ cvmx_usb_transaction_t *active_split; /**< Points to the current active split, or NULL */ cvmx_usb_tx_fifo_t periodic; cvmx_usb_tx_fifo_t nonperiodic; } cvmx_usb_internal_state_t; /* This macro logs out whenever a function is called if debugging is on */ #define CVMX_USB_LOG_CALLED() \ if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLS)) \ cvmx_dprintf("%*s%s: called\n", 2*usb->indent++, "", __FUNCTION__); /* This macro logs out each function parameter if debugging is on */ #define CVMX_USB_LOG_PARAM(format, param) \ if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLS)) \ cvmx_dprintf("%*s%s: param %s = " format "\n", 2*usb->indent, "", __FUNCTION__, #param, param); /* This macro logs out when a function returns a value */ #define CVMX_USB_RETURN(v) \ do { \ typeof(v) r = v; \ if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLS)) \ cvmx_dprintf("%*s%s: returned %s(%d)\n", 2*--usb->indent, "", __FUNCTION__, #v, r); \ return r; \ } while (0); /* This macro logs out when a function doesn't return a value */ #define CVMX_USB_RETURN_NOTHING() \ do { \ if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLS)) \ cvmx_dprintf("%*s%s: returned\n", 2*--usb->indent, "", __FUNCTION__); \ return; \ } while (0); /* This macro spins on a field waiting for it to reach a value */ #define CVMX_WAIT_FOR_FIELD32(address, type, field, op, value, timeout_usec)\ ({int result; \ do { \ uint64_t done = cvmx_get_cycle() + (uint64_t)timeout_usec * \ cvmx_clock_get_rate(CVMX_CLOCK_CORE) / 1000000; \ type c; \ while (1) \ { \ c.u32 = __cvmx_usb_read_csr32(usb, address); \ if (c.s.field op (value)) { \ result = 0; \ break; \ } else if (cvmx_get_cycle() > done) { \ result = -1; \ break; \ } else \ cvmx_wait(100); \ } \ } while (0); \ result;}) /* This macro logically sets a single field in a CSR. It does the sequence read, modify, and write */ #define USB_SET_FIELD32(address, type, field, value)\ do { \ type c; \ c.u32 = __cvmx_usb_read_csr32(usb, address);\ c.s.field = value; \ __cvmx_usb_write_csr32(usb, address, c.u32);\ } while (0) /* Returns the IO address to push/pop stuff data from the FIFOs */ #define USB_FIFO_ADDRESS(channel, usb_index) (CVMX_USBCX_GOTGCTL(usb_index) + ((channel)+1)*0x1000) /** * @INTERNAL * Read a USB 32bit CSR. It performs the necessary address swizzle * for 32bit CSRs and logs the value in a readable format if * debugging is on. * * @param usb USB block this access is for * @param address 64bit address to read * * @return Result of the read */ static inline uint32_t __cvmx_usb_read_csr32(cvmx_usb_internal_state_t *usb, uint64_t address) { uint32_t result = cvmx_read64_uint32(address ^ 4); #if ALLOW_CSR_DECODES if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CSRS)) { cvmx_dprintf("Read: "); cvmx_csr_db_decode(cvmx_get_proc_id(), address, result); } #endif return result; } /** * @INTERNAL * Write a USB 32bit CSR. It performs the necessary address * swizzle for 32bit CSRs and logs the value in a readable format * if debugging is on. * * @param usb USB block this access is for * @param address 64bit address to write * @param value Value to write */ static inline void __cvmx_usb_write_csr32(cvmx_usb_internal_state_t *usb, uint64_t address, uint32_t value) { #if ALLOW_CSR_DECODES if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CSRS)) { cvmx_dprintf("Write: "); cvmx_csr_db_decode(cvmx_get_proc_id(), address, value); } #endif cvmx_write64_uint32(address ^ 4, value); cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); } /** * @INTERNAL * Read a USB 64bit CSR. It logs the value in a readable format if * debugging is on. * * @param usb USB block this access is for * @param address 64bit address to read * * @return Result of the read */ static inline uint64_t __cvmx_usb_read_csr64(cvmx_usb_internal_state_t *usb, uint64_t address) { uint64_t result = cvmx_read64_uint64(address); #if ALLOW_CSR_DECODES if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CSRS)) { cvmx_dprintf("Read: "); cvmx_csr_db_decode(cvmx_get_proc_id(), address, result); } #endif return result; } /** * @INTERNAL * Write a USB 64bit CSR. It logs the value in a readable format * if debugging is on. * * @param usb USB block this access is for * @param address 64bit address to write * @param value Value to write */ static inline void __cvmx_usb_write_csr64(cvmx_usb_internal_state_t *usb, uint64_t address, uint64_t value) { #if ALLOW_CSR_DECODES if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CSRS)) { cvmx_dprintf("Write: "); cvmx_csr_db_decode(cvmx_get_proc_id(), address, value); } #endif cvmx_write64_uint64(address, value); } /** * @INTERNAL * Utility function to convert complete codes into strings * * @param complete_code * Code to convert * * @return Human readable string */ static const char *__cvmx_usb_complete_to_string(cvmx_usb_complete_t complete_code) { switch (complete_code) { case CVMX_USB_COMPLETE_SUCCESS: return "SUCCESS"; case CVMX_USB_COMPLETE_SHORT: return "SHORT"; case CVMX_USB_COMPLETE_CANCEL: return "CANCEL"; case CVMX_USB_COMPLETE_ERROR: return "ERROR"; case CVMX_USB_COMPLETE_STALL: return "STALL"; case CVMX_USB_COMPLETE_XACTERR: return "XACTERR"; case CVMX_USB_COMPLETE_DATATGLERR: return "DATATGLERR"; case CVMX_USB_COMPLETE_BABBLEERR: return "BABBLEERR"; case CVMX_USB_COMPLETE_FRAMEERR: return "FRAMEERR"; } return "Update __cvmx_usb_complete_to_string"; } /** * @INTERNAL * Return non zero if this pipe connects to a non HIGH speed * device through a high speed hub. * * @param usb USB block this access is for * @param pipe Pipe to check * * @return Non zero if we need to do split transactions */ static inline int __cvmx_usb_pipe_needs_split(cvmx_usb_internal_state_t *usb, cvmx_usb_pipe_t *pipe) { return ((pipe->device_speed != CVMX_USB_SPEED_HIGH) && (usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_HIGH)); } /** * @INTERNAL * Trivial utility function to return the correct PID for a pipe * * @param pipe pipe to check * * @return PID for pipe */ static inline int __cvmx_usb_get_data_pid(cvmx_usb_pipe_t *pipe) { if (pipe->pid_toggle) return 2; /* Data1 */ else return 0; /* Data0 */ } /** * Return the number of USB ports supported by this Octeon * chip. If the chip doesn't support USB, or is not supported * by this API, a zero will be returned. Most Octeon chips * support one usb port, but some support two ports. * cvmx_usb_initialize() must be called on independent * cvmx_usb_state_t structures. * * This utilizes cvmx_helper_board_usb_get_num_ports() * to get any board specific variations. * * @return Number of port, zero if usb isn't supported */ int cvmx_usb_get_num_ports(void) { int arch_ports = 0; if (OCTEON_IS_MODEL(OCTEON_CN56XX)) arch_ports = 1; else if (OCTEON_IS_MODEL(OCTEON_CN52XX)) arch_ports = 2; else if (OCTEON_IS_MODEL(OCTEON_CN50XX)) arch_ports = 1; else if (OCTEON_IS_MODEL(OCTEON_CN31XX)) arch_ports = 1; else if (OCTEON_IS_MODEL(OCTEON_CN30XX)) arch_ports = 1; else arch_ports = 0; return __cvmx_helper_board_usb_get_num_ports(arch_ports); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_get_num_ports); #endif /** * @INTERNAL * Allocate a usb transaction for use * * @param usb USB device state populated by * cvmx_usb_initialize(). * * @return Transaction or NULL */ static inline cvmx_usb_transaction_t *__cvmx_usb_alloc_transaction(cvmx_usb_internal_state_t *usb) { cvmx_usb_transaction_t *t; t = usb->free_transaction_head; if (t) { usb->free_transaction_head = t->next; if (!usb->free_transaction_head) usb->free_transaction_tail = NULL; } else if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: Failed to allocate a transaction\n", __FUNCTION__); if (t) { memset(t, 0, sizeof(*t)); t->flags = __CVMX_USB_TRANSACTION_FLAGS_IN_USE; } return t; } /** * @INTERNAL * Free a usb transaction * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param transaction * Transaction to free */ static inline void __cvmx_usb_free_transaction(cvmx_usb_internal_state_t *usb, cvmx_usb_transaction_t *transaction) { transaction->flags = 0; transaction->prev = NULL; transaction->next = NULL; if (usb->free_transaction_tail) usb->free_transaction_tail->next = transaction; else usb->free_transaction_head = transaction; usb->free_transaction_tail = transaction; } /** * @INTERNAL * Add a pipe to the tail of a list * @param list List to add pipe to * @param pipe Pipe to add */ static inline void __cvmx_usb_append_pipe(cvmx_usb_pipe_list_t *list, cvmx_usb_pipe_t *pipe) { pipe->next = NULL; pipe->prev = list->tail; if (list->tail) list->tail->next = pipe; else list->head = pipe; list->tail = pipe; } /** * @INTERNAL * Remove a pipe from a list * @param list List to remove pipe from * @param pipe Pipe to remove */ static inline void __cvmx_usb_remove_pipe(cvmx_usb_pipe_list_t *list, cvmx_usb_pipe_t *pipe) { if (list->head == pipe) { list->head = pipe->next; pipe->next = NULL; if (list->head) list->head->prev = NULL; else list->tail = NULL; } else if (list->tail == pipe) { list->tail = pipe->prev; list->tail->next = NULL; pipe->prev = NULL; } else { pipe->prev->next = pipe->next; pipe->next->prev = pipe->prev; pipe->prev = NULL; pipe->next = NULL; } } /** * Initialize a USB port for use. This must be called before any * other access to the Octeon USB port is made. The port starts * off in the disabled state. * * @param state Pointer to an empty cvmx_usb_state_t structure * that will be populated by the initialize call. * This structure is then passed to all other USB * functions. * @param usb_port_number * Which Octeon USB port to initialize. * @param flags Flags to control hardware initialization. See * cvmx_usb_initialize_flags_t for the flag * definitions. Some flags are mandatory. * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_initialize(cvmx_usb_state_t *state, int usb_port_number, cvmx_usb_initialize_flags_t flags) { cvmx_usbnx_clk_ctl_t usbn_clk_ctl; cvmx_usbnx_usbp_ctl_status_t usbn_usbp_ctl_status; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; usb->init_flags = flags; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", usb_port_number); CVMX_USB_LOG_PARAM("0x%x", flags); /* Make sure that state is large enough to store the internal state */ if (sizeof(*state) < sizeof(*usb)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* At first allow 0-1 for the usb port number */ if ((usb_port_number < 0) || (usb_port_number > 1)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* For all chips except 52XX there is only one port */ if (!OCTEON_IS_MODEL(OCTEON_CN52XX) && (usb_port_number > 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Try to determine clock type automatically */ if ((flags & (CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI | CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND)) == 0) { if (__cvmx_helper_board_usb_get_clock_type() == USB_CLOCK_TYPE_CRYSTAL_12) flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI; /* Only 12 MHZ crystals are supported */ else flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND; } if (flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND) { /* Check for auto ref clock frequency */ if (!(flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK)) switch (__cvmx_helper_board_usb_get_clock_type()) { case USB_CLOCK_TYPE_REF_12: flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ; break; case USB_CLOCK_TYPE_REF_24: flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ; break; case USB_CLOCK_TYPE_REF_48: flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ; break; default: CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); break; } } memset(usb, 0, sizeof(usb)); usb->init_flags = flags; /* Initialize the USB state structure */ { int i; usb->index = usb_port_number; /* Initialize the transaction double linked list */ usb->free_transaction_head = NULL; usb->free_transaction_tail = NULL; for (i=0; itransaction + i); for (i=0; ifree_pipes, usb->pipe + i); } /* Power On Reset and PHY Initialization */ /* 1. Wait for DCOK to assert (nothing to do) */ /* 2a. Write USBN0/1_CLK_CTL[POR] = 1 and USBN0/1_CLK_CTL[HRST,PRST,HCLK_RST] = 0 */ usbn_clk_ctl.u64 = __cvmx_usb_read_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index)); usbn_clk_ctl.s.por = 1; usbn_clk_ctl.s.hrst = 0; usbn_clk_ctl.s.prst = 0; usbn_clk_ctl.s.hclk_rst = 0; usbn_clk_ctl.s.enable = 0; /* 2b. Select the USB reference clock/crystal parameters by writing appropriate values to USBN0/1_CLK_CTL[P_C_SEL, P_RTYPE, P_COM_ON] */ if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND) { /* The USB port uses 12/24/48MHz 2.5V board clock source at USB_XO. USB_XI should be tied to GND. Most Octeon evaluation boards require this setting */ if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) { usbn_clk_ctl.cn31xx.p_rclk = 1; /* From CN31XX,CN30XX manual */ usbn_clk_ctl.cn31xx.p_xenbn = 0; } else if (OCTEON_IS_MODEL(OCTEON_CN56XX) || OCTEON_IS_MODEL(OCTEON_CN50XX)) usbn_clk_ctl.cn56xx.p_rtype = 2; /* From CN56XX,CN50XX manual */ else usbn_clk_ctl.cn52xx.p_rtype = 1; /* From CN52XX manual */ switch (flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK) { case CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: usbn_clk_ctl.s.p_c_sel = 0; break; case CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: usbn_clk_ctl.s.p_c_sel = 1; break; case CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: usbn_clk_ctl.s.p_c_sel = 2; break; } } else { /* The USB port uses a 12MHz crystal as clock source at USB_XO and USB_XI */ if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) { usbn_clk_ctl.cn31xx.p_rclk = 1; /* From CN31XX,CN30XX manual */ usbn_clk_ctl.cn31xx.p_xenbn = 1; } else if (OCTEON_IS_MODEL(OCTEON_CN56XX) || OCTEON_IS_MODEL(OCTEON_CN50XX)) usbn_clk_ctl.cn56xx.p_rtype = 0; /* From CN56XX,CN50XX manual */ else usbn_clk_ctl.cn52xx.p_rtype = 0; /* From CN52XX manual */ usbn_clk_ctl.s.p_c_sel = 0; } /* 2c. Select the HCLK via writing USBN0/1_CLK_CTL[DIVIDE, DIVIDE2] and setting USBN0/1_CLK_CTL[ENABLE] = 1. Divide the core clock down such that USB is as close as possible to 125Mhz */ { int divisor = (cvmx_clock_get_rate(CVMX_CLOCK_CORE)+125000000-1)/125000000; if (divisor < 4) /* Lower than 4 doesn't seem to work properly */ divisor = 4; usbn_clk_ctl.s.divide = divisor; usbn_clk_ctl.s.divide2 = 0; } __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); /* 2d. Write USBN0/1_CLK_CTL[HCLK_RST] = 1 */ usbn_clk_ctl.s.hclk_rst = 1; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); /* 2e. Wait 64 core-clock cycles for HCLK to stabilize */ cvmx_wait(64); /* 3. Program the power-on reset field in the USBN clock-control register: USBN_CLK_CTL[POR] = 0 */ usbn_clk_ctl.s.por = 0; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); /* 4. Wait 1 ms for PHY clock to start */ cvmx_wait_usec(1000); /* 5. Program the Reset input from automatic test equipment field in the USBP control and status register: USBN_USBP_CTL_STATUS[ATE_RESET] = 1 */ usbn_usbp_ctl_status.u64 = __cvmx_usb_read_csr64(usb, CVMX_USBNX_USBP_CTL_STATUS(usb->index)); usbn_usbp_ctl_status.s.ate_reset = 1; __cvmx_usb_write_csr64(usb, CVMX_USBNX_USBP_CTL_STATUS(usb->index), usbn_usbp_ctl_status.u64); /* 6. Wait 10 cycles */ cvmx_wait(10); /* 7. Clear ATE_RESET field in the USBN clock-control register: USBN_USBP_CTL_STATUS[ATE_RESET] = 0 */ usbn_usbp_ctl_status.s.ate_reset = 0; __cvmx_usb_write_csr64(usb, CVMX_USBNX_USBP_CTL_STATUS(usb->index), usbn_usbp_ctl_status.u64); /* 8. Program the PHY reset field in the USBN clock-control register: USBN_CLK_CTL[PRST] = 1 */ usbn_clk_ctl.s.prst = 1; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); /* 9. Program the USBP control and status register to select host or device mode. USBN_USBP_CTL_STATUS[HST_MODE] = 0 for host, = 1 for device */ usbn_usbp_ctl_status.s.hst_mode = 0; __cvmx_usb_write_csr64(usb, CVMX_USBNX_USBP_CTL_STATUS(usb->index), usbn_usbp_ctl_status.u64); /* 10. Wait 1 µs */ cvmx_wait_usec(1); /* 11. Program the hreset_n field in the USBN clock-control register: USBN_CLK_CTL[HRST] = 1 */ usbn_clk_ctl.s.hrst = 1; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); /* 12. Proceed to USB core initialization */ usbn_clk_ctl.s.enable = 1; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); cvmx_wait_usec(1); /* USB Core Initialization */ /* 1. Read USBC_GHWCFG1, USBC_GHWCFG2, USBC_GHWCFG3, USBC_GHWCFG4 to determine USB core configuration parameters. */ /* Nothing needed */ /* 2. Program the following fields in the global AHB configuration register (USBC_GAHBCFG) DMA mode, USBC_GAHBCFG[DMAEn]: 1 = DMA mode, 0 = slave mode Burst length, USBC_GAHBCFG[HBSTLEN] = 0 Nonperiodic TxFIFO empty level (slave mode only), USBC_GAHBCFG[NPTXFEMPLVL] Periodic TxFIFO empty level (slave mode only), USBC_GAHBCFG[PTXFEMPLVL] Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 */ { cvmx_usbcx_gahbcfg_t usbcx_gahbcfg; /* Due to an errata, CN31XX doesn't support DMA */ if (OCTEON_IS_MODEL(OCTEON_CN31XX)) usb->init_flags |= CVMX_USB_INITIALIZE_FLAGS_NO_DMA; usbcx_gahbcfg.u32 = 0; usbcx_gahbcfg.s.dmaen = !(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA); if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) usb->idle_hardware_channels = 0x1; /* Only use one channel with non DMA */ else if (OCTEON_IS_MODEL(OCTEON_CN5XXX)) usb->idle_hardware_channels = 0xf7; /* CN5XXX have an errata with channel 3 */ else usb->idle_hardware_channels = 0xff; usbcx_gahbcfg.s.hbstlen = 0; usbcx_gahbcfg.s.nptxfemplvl = 1; usbcx_gahbcfg.s.ptxfemplvl = 1; usbcx_gahbcfg.s.glblintrmsk = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_GAHBCFG(usb->index), usbcx_gahbcfg.u32); } /* 3. Program the following fields in USBC_GUSBCFG register. HS/FS timeout calibration, USBC_GUSBCFG[TOUTCAL] = 0 ULPI DDR select, USBC_GUSBCFG[DDRSEL] = 0 USB turnaround time, USBC_GUSBCFG[USBTRDTIM] = 0x5 PHY low-power clock select, USBC_GUSBCFG[PHYLPWRCLKSEL] = 0 */ { cvmx_usbcx_gusbcfg_t usbcx_gusbcfg; usbcx_gusbcfg.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GUSBCFG(usb->index)); usbcx_gusbcfg.s.toutcal = 0; usbcx_gusbcfg.s.ddrsel = 0; usbcx_gusbcfg.s.usbtrdtim = 0x5; usbcx_gusbcfg.s.phylpwrclksel = 0; __cvmx_usb_write_csr32(usb, CVMX_USBCX_GUSBCFG(usb->index), usbcx_gusbcfg.u32); } /* 4. The software must unmask the following bits in the USBC_GINTMSK register. OTG interrupt mask, USBC_GINTMSK[OTGINTMSK] = 1 Mode mismatch interrupt mask, USBC_GINTMSK[MODEMISMSK] = 1 */ { cvmx_usbcx_gintmsk_t usbcx_gintmsk; int channel; usbcx_gintmsk.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GINTMSK(usb->index)); usbcx_gintmsk.s.otgintmsk = 1; usbcx_gintmsk.s.modemismsk = 1; usbcx_gintmsk.s.hchintmsk = 1; usbcx_gintmsk.s.sofmsk = 0; /* We need RX FIFO interrupts if we don't have DMA */ if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) usbcx_gintmsk.s.rxflvlmsk = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTMSK(usb->index), usbcx_gintmsk.u32); /* Disable all channel interrupts. We'll enable them per channel later */ for (channel=0; channel<8; channel++) __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), 0); } { /* Host Port Initialization */ if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: USB%d is in host mode\n", __FUNCTION__, usb->index); /* 1. Program the host-port interrupt-mask field to unmask, USBC_GINTMSK[PRTINT] = 1 */ USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, prtintmsk, 1); USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, disconnintmsk, 1); /* 2. Program the USBC_HCFG register to select full-speed host or high-speed host. */ { cvmx_usbcx_hcfg_t usbcx_hcfg; usbcx_hcfg.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCFG(usb->index)); usbcx_hcfg.s.fslssupp = 0; usbcx_hcfg.s.fslspclksel = 0; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCFG(usb->index), usbcx_hcfg.u32); } /* 3. Program the port power bit to drive VBUS on the USB, USBC_HPRT[PRTPWR] = 1 */ USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt_t, prtpwr, 1); /* Steps 4-15 from the manual are done later in the port enable */ } #ifdef __CVMX_ERROR_H__ cvmx_error_enable_group(CVMX_ERROR_GROUP_USB, usb->index); #endif CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_initialize); #endif /** * Shutdown a USB port after a call to cvmx_usb_initialize(). * The port should be disabled with all pipes closed when this * function is called. * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_shutdown(cvmx_usb_state_t *state) { cvmx_usbnx_clk_ctl_t usbn_clk_ctl; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); /* Make sure all pipes are closed */ if (usb->idle_pipes.head || usb->active_pipes[CVMX_USB_TRANSFER_ISOCHRONOUS].head || usb->active_pipes[CVMX_USB_TRANSFER_INTERRUPT].head || usb->active_pipes[CVMX_USB_TRANSFER_CONTROL].head || usb->active_pipes[CVMX_USB_TRANSFER_BULK].head) CVMX_USB_RETURN(CVMX_USB_BUSY); #ifdef __CVMX_ERROR_H__ cvmx_error_disable_group(CVMX_ERROR_GROUP_USB, usb->index); #endif /* Disable the clocks and put them in power on reset */ usbn_clk_ctl.u64 = __cvmx_usb_read_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index)); usbn_clk_ctl.s.enable = 1; usbn_clk_ctl.s.por = 1; usbn_clk_ctl.s.hclk_rst = 1; usbn_clk_ctl.s.prst = 0; usbn_clk_ctl.s.hrst = 0; __cvmx_usb_write_csr64(usb, CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_shutdown); #endif /** * Enable a USB port. After this call succeeds, the USB port is * online and servicing requests. * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_enable(cvmx_usb_state_t *state) { cvmx_usbcx_ghwcfg3_t usbcx_ghwcfg3; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); usb->usbcx_hprt.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); /* If the port is already enabled the just return. We don't need to do anything */ if (usb->usbcx_hprt.s.prtena) CVMX_USB_RETURN(CVMX_USB_SUCCESS); /* If there is nothing plugged into the port then fail immediately */ if (!usb->usbcx_hprt.s.prtconnsts) { if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: USB%d Nothing plugged into the port\n", __FUNCTION__, usb->index); CVMX_USB_RETURN(CVMX_USB_TIMEOUT); } /* Program the port reset bit to start the reset process */ USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt_t, prtrst, 1); /* Wait at least 50ms (high speed), or 10ms (full speed) for the reset process to complete. */ cvmx_wait_usec(50000); /* Program the port reset bit to 0, USBC_HPRT[PRTRST] = 0 */ USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt_t, prtrst, 0); /* Wait for the USBC_HPRT[PRTENA]. */ if (CVMX_WAIT_FOR_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt_t, prtena, ==, 1, 100000)) { if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: Timeout waiting for the port to finish reset\n", __FUNCTION__); CVMX_USB_RETURN(CVMX_USB_TIMEOUT); } /* Read the port speed field to get the enumerated speed, USBC_HPRT[PRTSPD]. */ usb->usbcx_hprt.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: USB%d is in %s speed mode\n", __FUNCTION__, usb->index, (usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_HIGH) ? "high" : (usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_FULL) ? "full" : "low"); usbcx_ghwcfg3.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GHWCFG3(usb->index)); /* 13. Program the USBC_GRXFSIZ register to select the size of the receive FIFO (25%). */ USB_SET_FIELD32(CVMX_USBCX_GRXFSIZ(usb->index), cvmx_usbcx_grxfsiz_t, rxfdep, usbcx_ghwcfg3.s.dfifodepth / 4); /* 14. Program the USBC_GNPTXFSIZ register to select the size and the start address of the non- periodic transmit FIFO for nonperiodic transactions (50%). */ { cvmx_usbcx_gnptxfsiz_t siz; siz.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index)); siz.s.nptxfdep = usbcx_ghwcfg3.s.dfifodepth / 2; siz.s.nptxfstaddr = usbcx_ghwcfg3.s.dfifodepth / 4; __cvmx_usb_write_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index), siz.u32); } /* 15. Program the USBC_HPTXFSIZ register to select the size and start address of the periodic transmit FIFO for periodic transactions (25%). */ { cvmx_usbcx_hptxfsiz_t siz; siz.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index)); siz.s.ptxfsize = usbcx_ghwcfg3.s.dfifodepth / 4; siz.s.ptxfstaddr = 3 * usbcx_ghwcfg3.s.dfifodepth / 4; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index), siz.u32); } /* Flush all FIFOs */ USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), cvmx_usbcx_grstctl_t, txfnum, 0x10); USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), cvmx_usbcx_grstctl_t, txfflsh, 1); CVMX_WAIT_FOR_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), cvmx_usbcx_grstctl_t, txfflsh, ==, 0, 100); USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), cvmx_usbcx_grstctl_t, rxfflsh, 1); CVMX_WAIT_FOR_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), cvmx_usbcx_grstctl_t, rxfflsh, ==, 0, 100); CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_enable); #endif /** * Disable a USB port. After this call the USB port will not * generate data transfers and will not generate events. * Transactions in process will fail and call their * associated callbacks. * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_disable(cvmx_usb_state_t *state) { cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); /* Disable the port */ USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt_t, prtena, 1); CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_disable); #endif /** * Get the current state of the USB port. Use this call to * determine if the usb port has anything connected, is enabled, * or has some sort of error condition. The return value of this * call has "changed" bits to signal of the value of some fields * have changed between calls. These "changed" fields are based * on the last call to cvmx_usb_set_status(). In order to clear * them, you must update the status through cvmx_usb_set_status(). * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return Port status information */ cvmx_usb_port_status_t cvmx_usb_get_status(cvmx_usb_state_t *state) { cvmx_usbcx_hprt_t usbc_hprt; cvmx_usb_port_status_t result; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; memset(&result, 0, sizeof(result)); CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); usbc_hprt.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); result.port_enabled = usbc_hprt.s.prtena; result.port_over_current = usbc_hprt.s.prtovrcurract; result.port_powered = usbc_hprt.s.prtpwr; result.port_speed = usbc_hprt.s.prtspd; result.connected = usbc_hprt.s.prtconnsts; result.connect_change = (result.connected != usb->port_status.connected); if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLS)) cvmx_dprintf("%*s%s: returned port enabled=%d, over_current=%d, powered=%d, speed=%d, connected=%d, connect_change=%d\n", 2*(--usb->indent), "", __FUNCTION__, result.port_enabled, result.port_over_current, result.port_powered, result.port_speed, result.connected, result.connect_change); return result; } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_get_status); #endif /** * Set the current state of the USB port. The status is used as * a reference for the "changed" bits returned by * cvmx_usb_get_status(). Other than serving as a reference, the * status passed to this function is not used. No fields can be * changed through this call. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param port_status * Port status to set, most like returned by cvmx_usb_get_status() */ void cvmx_usb_set_status(cvmx_usb_state_t *state, cvmx_usb_port_status_t port_status) { cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); usb->port_status = port_status; CVMX_USB_RETURN_NOTHING(); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_set_status); #endif /** * @INTERNAL * Convert a USB transaction into a handle * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param transaction * Transaction to get handle for * * @return Handle */ static inline int __cvmx_usb_get_submit_handle(cvmx_usb_internal_state_t *usb, cvmx_usb_transaction_t *transaction) { return ((unsigned long)transaction - (unsigned long)usb->transaction) / sizeof(*transaction); } /** * @INTERNAL * Convert a USB pipe into a handle * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param pipe Pipe to get handle for * * @return Handle */ static inline int __cvmx_usb_get_pipe_handle(cvmx_usb_internal_state_t *usb, cvmx_usb_pipe_t *pipe) { return ((unsigned long)pipe - (unsigned long)usb->pipe) / sizeof(*pipe); } /** * Open a virtual pipe between the host and a USB device. A pipe * must be opened before data can be transferred between a device * and Octeon. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param flags Optional pipe flags defined in * cvmx_usb_pipe_flags_t. * @param device_addr * USB device address to open the pipe to * (0-127). * @param endpoint_num * USB endpoint number to open the pipe to * (0-15). * @param device_speed * The speed of the device the pipe is going * to. This must match the device's speed, * which may be different than the port speed. * @param max_packet The maximum packet length the device can * transmit/receive (low speed=0-8, full * speed=0-1023, high speed=0-1024). This value * comes from the standard endpoint descriptor * field wMaxPacketSize bits <10:0>. * @param transfer_type * The type of transfer this pipe is for. * @param transfer_dir * The direction the pipe is in. This is not * used for control pipes. * @param interval For ISOCHRONOUS and INTERRUPT transfers, * this is how often the transfer is scheduled * for. All other transfers should specify * zero. The units are in frames (8000/sec at * high speed, 1000/sec for full speed). * @param multi_count * For high speed devices, this is the maximum * allowed number of packet per microframe. * Specify zero for non high speed devices. This * value comes from the standard endpoint descriptor * field wMaxPacketSize bits <12:11>. * @param hub_device_addr * Hub device address this device is connected * to. Devices connected directly to Octeon * use zero. This is only used when the device * is full/low speed behind a high speed hub. * The address will be of the high speed hub, * not and full speed hubs after it. * @param hub_port Which port on the hub the device is * connected. Use zero for devices connected * directly to Octeon. Like hub_device_addr, * this is only used for full/low speed * devices behind a high speed hub. * * @return A non negative value is a pipe handle. Negative * values are failure codes from cvmx_usb_status_t. */ int cvmx_usb_open_pipe(cvmx_usb_state_t *state, cvmx_usb_pipe_flags_t flags, int device_addr, int endpoint_num, cvmx_usb_speed_t device_speed, int max_packet, cvmx_usb_transfer_t transfer_type, cvmx_usb_direction_t transfer_dir, int interval, int multi_count, int hub_device_addr, int hub_port) { cvmx_usb_pipe_t *pipe; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("0x%x", flags); CVMX_USB_LOG_PARAM("%d", device_addr); CVMX_USB_LOG_PARAM("%d", endpoint_num); CVMX_USB_LOG_PARAM("%d", device_speed); CVMX_USB_LOG_PARAM("%d", max_packet); CVMX_USB_LOG_PARAM("%d", transfer_type); CVMX_USB_LOG_PARAM("%d", transfer_dir); CVMX_USB_LOG_PARAM("%d", interval); CVMX_USB_LOG_PARAM("%d", multi_count); CVMX_USB_LOG_PARAM("%d", hub_device_addr); CVMX_USB_LOG_PARAM("%d", hub_port); if (cvmx_unlikely((device_addr < 0) || (device_addr > MAX_USB_ADDRESS))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((endpoint_num < 0) || (endpoint_num > MAX_USB_ENDPOINT))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(device_speed > CVMX_USB_SPEED_LOW)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((max_packet <= 0) || (max_packet > 1024))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(transfer_type > CVMX_USB_TRANSFER_INTERRUPT)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((transfer_dir != CVMX_USB_DIRECTION_OUT) && (transfer_dir != CVMX_USB_DIRECTION_IN))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(interval < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((transfer_type == CVMX_USB_TRANSFER_CONTROL) && interval)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(multi_count < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((device_speed != CVMX_USB_SPEED_HIGH) && (multi_count != 0))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((hub_device_addr < 0) || (hub_device_addr > MAX_USB_ADDRESS))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((hub_port < 0) || (hub_port > MAX_USB_HUB_PORT))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Find a free pipe */ pipe = usb->free_pipes.head; if (!pipe) CVMX_USB_RETURN(CVMX_USB_NO_MEMORY); __cvmx_usb_remove_pipe(&usb->free_pipes, pipe); pipe->flags = flags | __CVMX_USB_PIPE_FLAGS_OPEN; if ((device_speed == CVMX_USB_SPEED_HIGH) && (transfer_dir == CVMX_USB_DIRECTION_OUT) && (transfer_type == CVMX_USB_TRANSFER_BULK)) pipe->flags |= __CVMX_USB_PIPE_FLAGS_NEED_PING; pipe->device_addr = device_addr; pipe->endpoint_num = endpoint_num; pipe->device_speed = device_speed; pipe->max_packet = max_packet; pipe->transfer_type = transfer_type; pipe->transfer_dir = transfer_dir; /* All pipes use interval to rate limit NAK processing. Force an interval if one wasn't supplied */ if (!interval) interval = 1; if (__cvmx_usb_pipe_needs_split(usb, pipe)) { pipe->interval = interval*8; /* Force start splits to be schedule on uFrame 0 */ pipe->next_tx_frame = ((usb->frame_number+7)&~7) + pipe->interval; } else { pipe->interval = interval; pipe->next_tx_frame = usb->frame_number + pipe->interval; } pipe->multi_count = multi_count; pipe->hub_device_addr = hub_device_addr; pipe->hub_port = hub_port; pipe->pid_toggle = 0; pipe->split_sc_frame = -1; __cvmx_usb_append_pipe(&usb->idle_pipes, pipe); /* We don't need to tell the hardware about this pipe yet since it doesn't have any submitted requests */ CVMX_USB_RETURN(__cvmx_usb_get_pipe_handle(usb, pipe)); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_open_pipe); #endif /** * @INTERNAL * Poll the RX FIFOs and remove data as needed. This function is only used * in non DMA mode. It is very important that this function be called quickly * enough to prevent FIFO overflow. * * @param usb USB device state populated by * cvmx_usb_initialize(). */ static void __cvmx_usb_poll_rx_fifo(cvmx_usb_internal_state_t *usb) { cvmx_usbcx_grxstsph_t rx_status; int channel; int bytes; uint64_t address; uint32_t *ptr; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); rx_status.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GRXSTSPH(usb->index)); /* Only read data if IN data is there */ if (rx_status.s.pktsts != 2) CVMX_USB_RETURN_NOTHING(); /* Check if no data is available */ if (!rx_status.s.bcnt) CVMX_USB_RETURN_NOTHING(); channel = rx_status.s.chnum; bytes = rx_status.s.bcnt; if (!bytes) CVMX_USB_RETURN_NOTHING(); /* Get where the DMA engine would have written this data */ address = __cvmx_usb_read_csr64(usb, CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel*8); ptr = cvmx_phys_to_ptr(address); __cvmx_usb_write_csr64(usb, CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel*8, address + bytes); /* Loop writing the FIFO data for this packet into memory */ while (bytes > 0) { *ptr++ = __cvmx_usb_read_csr32(usb, USB_FIFO_ADDRESS(channel, usb->index)); bytes -= 4; } CVMX_SYNCW; CVMX_USB_RETURN_NOTHING(); } /** * Fill the TX hardware fifo with data out of the software * fifos * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param fifo Software fifo to use * @param available Amount of space in the hardware fifo * * @return Non zero if the hardware fifo was too small and needs * to be serviced again. */ static int __cvmx_usb_fill_tx_hw(cvmx_usb_internal_state_t *usb, cvmx_usb_tx_fifo_t *fifo, int available) { CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%p", fifo); CVMX_USB_LOG_PARAM("%d", available); /* We're done either when there isn't anymore space or the software FIFO is empty */ while (available && (fifo->head != fifo->tail)) { int i = fifo->tail; const uint32_t *ptr = cvmx_phys_to_ptr(fifo->entry[i].address); uint64_t csr_address = USB_FIFO_ADDRESS(fifo->entry[i].channel, usb->index) ^ 4; int words = available; /* Limit the amount of data to waht the SW fifo has */ if (fifo->entry[i].size <= available) { words = fifo->entry[i].size; fifo->tail++; if (fifo->tail > MAX_CHANNELS) fifo->tail = 0; } /* Update the next locations and counts */ available -= words; fifo->entry[i].address += words * 4; fifo->entry[i].size -= words; /* Write the HW fifo data. The read every three writes is due to an errata on CN3XXX chips */ while (words > 3) { cvmx_write64_uint32(csr_address, *ptr++); cvmx_write64_uint32(csr_address, *ptr++); cvmx_write64_uint32(csr_address, *ptr++); cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); words -= 3; } cvmx_write64_uint32(csr_address, *ptr++); if (--words) { cvmx_write64_uint32(csr_address, *ptr++); if (--words) cvmx_write64_uint32(csr_address, *ptr++); } cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); } CVMX_USB_RETURN(fifo->head != fifo->tail); } /** * Check the hardware FIFOs and fill them as needed * * @param usb USB device state populated by * cvmx_usb_initialize(). */ static void __cvmx_usb_poll_tx_fifo(cvmx_usb_internal_state_t *usb) { CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); if (usb->periodic.head != usb->periodic.tail) { cvmx_usbcx_hptxsts_t tx_status; tx_status.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPTXSTS(usb->index)); if (__cvmx_usb_fill_tx_hw(usb, &usb->periodic, tx_status.s.ptxfspcavail)) USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, ptxfempmsk, 1); else USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, ptxfempmsk, 0); } if (usb->nonperiodic.head != usb->nonperiodic.tail) { cvmx_usbcx_gnptxsts_t tx_status; tx_status.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GNPTXSTS(usb->index)); if (__cvmx_usb_fill_tx_hw(usb, &usb->nonperiodic, tx_status.s.nptxfspcavail)) USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, nptxfempmsk, 1); else USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, nptxfempmsk, 0); } CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Fill the TX FIFO with an outgoing packet * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param channel Channel number to get packet from */ static void __cvmx_usb_fill_tx_fifo(cvmx_usb_internal_state_t *usb, int channel) { cvmx_usbcx_hccharx_t hcchar; cvmx_usbcx_hcspltx_t usbc_hcsplt; cvmx_usbcx_hctsizx_t usbc_hctsiz; cvmx_usb_tx_fifo_t *fifo; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%d", channel); /* We only need to fill data on outbound channels */ hcchar.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index)); if (hcchar.s.epdir != CVMX_USB_DIRECTION_OUT) CVMX_USB_RETURN_NOTHING(); /* OUT Splits only have data on the start and not the complete */ usbc_hcsplt.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCSPLTX(channel, usb->index)); if (usbc_hcsplt.s.spltena && usbc_hcsplt.s.compsplt) CVMX_USB_RETURN_NOTHING(); /* Find out how many bytes we need to fill and convert it into 32bit words */ usbc_hctsiz.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index)); if (!usbc_hctsiz.s.xfersize) CVMX_USB_RETURN_NOTHING(); if ((hcchar.s.eptype == CVMX_USB_TRANSFER_INTERRUPT) || (hcchar.s.eptype == CVMX_USB_TRANSFER_ISOCHRONOUS)) fifo = &usb->periodic; else fifo = &usb->nonperiodic; fifo->entry[fifo->head].channel = channel; fifo->entry[fifo->head].address = __cvmx_usb_read_csr64(usb, CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + channel*8); fifo->entry[fifo->head].size = (usbc_hctsiz.s.xfersize+3)>>2; fifo->head++; if (fifo->head > MAX_CHANNELS) fifo->head = 0; __cvmx_usb_poll_tx_fifo(usb); CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Perform channel specific setup for Control transactions. All * the generic stuff will already have been done in * __cvmx_usb_start_channel() * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param channel Channel to setup * @param pipe Pipe for control transaction */ static void __cvmx_usb_start_channel_control(cvmx_usb_internal_state_t *usb, int channel, cvmx_usb_pipe_t *pipe) { cvmx_usb_transaction_t *transaction = pipe->head; cvmx_usb_control_header_t *header = cvmx_phys_to_ptr(transaction->control_header); int bytes_to_transfer = transaction->buffer_length - transaction->actual_bytes; int packets_to_transfer; cvmx_usbcx_hctsizx_t usbc_hctsiz; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%d", channel); CVMX_USB_LOG_PARAM("%p", pipe); usbc_hctsiz.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index)); switch (transaction->stage) { case CVMX_USB_STAGE_NON_CONTROL: case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: cvmx_dprintf("%s: ERROR - Non control stage\n", __FUNCTION__); break; case CVMX_USB_STAGE_SETUP: usbc_hctsiz.s.pid = 3; /* Setup */ bytes_to_transfer = sizeof(*header); /* All Control operations start with a setup going OUT */ USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, CVMX_USB_DIRECTION_OUT); /* Setup send the control header instead of the buffer data. The buffer data will be used in the next stage */ __cvmx_usb_write_csr64(usb, CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + channel*8, transaction->control_header); break; case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: usbc_hctsiz.s.pid = 3; /* Setup */ bytes_to_transfer = 0; /* All Control operations start with a setup going OUT */ USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, CVMX_USB_DIRECTION_OUT); USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), cvmx_usbcx_hcspltx_t, compsplt, 1); break; case CVMX_USB_STAGE_DATA: usbc_hctsiz.s.pid = __cvmx_usb_get_data_pid(pipe); if (__cvmx_usb_pipe_needs_split(usb, pipe)) { if (header->s.request_type & 0x80) bytes_to_transfer = 0; else if (bytes_to_transfer > pipe->max_packet) bytes_to_transfer = pipe->max_packet; } USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, ((header->s.request_type & 0x80) ? CVMX_USB_DIRECTION_IN : CVMX_USB_DIRECTION_OUT)); break; case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: usbc_hctsiz.s.pid = __cvmx_usb_get_data_pid(pipe); if (!(header->s.request_type & 0x80)) bytes_to_transfer = 0; USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, ((header->s.request_type & 0x80) ? CVMX_USB_DIRECTION_IN : CVMX_USB_DIRECTION_OUT)); USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), cvmx_usbcx_hcspltx_t, compsplt, 1); break; case CVMX_USB_STAGE_STATUS: usbc_hctsiz.s.pid = __cvmx_usb_get_data_pid(pipe); bytes_to_transfer = 0; USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, ((header->s.request_type & 0x80) ? CVMX_USB_DIRECTION_OUT : CVMX_USB_DIRECTION_IN)); break; case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: usbc_hctsiz.s.pid = __cvmx_usb_get_data_pid(pipe); bytes_to_transfer = 0; USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, epdir, ((header->s.request_type & 0x80) ? CVMX_USB_DIRECTION_OUT : CVMX_USB_DIRECTION_IN)); USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), cvmx_usbcx_hcspltx_t, compsplt, 1); break; } /* Make sure the transfer never exceeds the byte limit of the hardware. Further bytes will be sent as continued transactions */ if (bytes_to_transfer > MAX_TRANSFER_BYTES) { /* Round MAX_TRANSFER_BYTES to a multiple of out packet size */ bytes_to_transfer = MAX_TRANSFER_BYTES / pipe->max_packet; bytes_to_transfer *= pipe->max_packet; } /* Calculate the number of packets to transfer. If the length is zero we still need to transfer one packet */ packets_to_transfer = (bytes_to_transfer + pipe->max_packet - 1) / pipe->max_packet; if (packets_to_transfer == 0) packets_to_transfer = 1; else if ((packets_to_transfer>1) && (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { /* Limit to one packet when not using DMA. Channels must be restarted between every packet for IN transactions, so there is no reason to do multiple packets in a row */ packets_to_transfer = 1; bytes_to_transfer = packets_to_transfer * pipe->max_packet; } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { /* Limit the number of packet and data transferred to what the hardware can handle */ packets_to_transfer = MAX_TRANSFER_PACKETS; bytes_to_transfer = packets_to_transfer * pipe->max_packet; } usbc_hctsiz.s.xfersize = bytes_to_transfer; usbc_hctsiz.s.pktcnt = packets_to_transfer; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index), usbc_hctsiz.u32); CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Start a channel to perform the pipe's head transaction * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param channel Channel to setup * @param pipe Pipe to start */ static void __cvmx_usb_start_channel(cvmx_usb_internal_state_t *usb, int channel, cvmx_usb_pipe_t *pipe) { cvmx_usb_transaction_t *transaction = pipe->head; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%d", channel); CVMX_USB_LOG_PARAM("%p", pipe); if (cvmx_unlikely((usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_TRANSFERS) || (pipe->flags & CVMX_USB_PIPE_FLAGS_DEBUG_TRANSFERS))) cvmx_dprintf("%s: Channel %d started. Pipe %d transaction %d stage %d\n", __FUNCTION__, channel, __cvmx_usb_get_pipe_handle(usb, pipe), __cvmx_usb_get_submit_handle(usb, transaction), transaction->stage); /* Make sure all writes to the DMA region get flushed */ CVMX_SYNCW; /* Attach the channel to the pipe */ usb->pipe_for_channel[channel] = pipe; pipe->channel = channel; pipe->flags |= __CVMX_USB_PIPE_FLAGS_SCHEDULED; /* Mark this channel as in use */ usb->idle_hardware_channels &= ~(1<index)); __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTX(channel, usb->index), usbc_hcint.u32); usbc_hcintmsk.u32 = 0; usbc_hcintmsk.s.chhltdmsk = 1; if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { /* Channels need these extra interrupts when we aren't in DMA mode */ usbc_hcintmsk.s.datatglerrmsk = 1; usbc_hcintmsk.s.frmovrunmsk = 1; usbc_hcintmsk.s.bblerrmsk = 1; usbc_hcintmsk.s.xacterrmsk = 1; if (__cvmx_usb_pipe_needs_split(usb, pipe)) { /* Splits don't generate xfercompl, so we need ACK and NYET */ usbc_hcintmsk.s.nyetmsk = 1; usbc_hcintmsk.s.ackmsk = 1; } usbc_hcintmsk.s.nakmsk = 1; usbc_hcintmsk.s.stallmsk = 1; usbc_hcintmsk.s.xfercomplmsk = 1; } __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), usbc_hcintmsk.u32); /* Enable the channel interrupt to propagate */ usbc_haintmsk.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HAINTMSK(usb->index)); usbc_haintmsk.s.haintmsk |= 1<index), usbc_haintmsk.u32); } /* Setup the locations the DMA engines use */ { uint64_t dma_address = transaction->buffer + transaction->actual_bytes; if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) dma_address = transaction->buffer + transaction->iso_packets[0].offset + transaction->actual_bytes; __cvmx_usb_write_csr64(usb, CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + channel*8, dma_address); __cvmx_usb_write_csr64(usb, CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel*8, dma_address); } /* Setup both the size of the transfer and the SPLIT characteristics */ { cvmx_usbcx_hcspltx_t usbc_hcsplt = {.u32 = 0}; cvmx_usbcx_hctsizx_t usbc_hctsiz = {.u32 = 0}; int packets_to_transfer; int bytes_to_transfer = transaction->buffer_length - transaction->actual_bytes; /* ISOCHRONOUS transactions store each individual transfer size in the packet structure, not the global buffer_length */ if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) bytes_to_transfer = transaction->iso_packets[0].length - transaction->actual_bytes; /* We need to do split transactions when we are talking to non high speed devices that are behind a high speed hub */ if (__cvmx_usb_pipe_needs_split(usb, pipe)) { /* On the start split phase (stage is even) record the frame number we will need to send the split complete. We only store the lower two bits since the time ahead can only be two frames */ if ((transaction->stage&1) == 0) { if (transaction->type == CVMX_USB_TRANSFER_BULK) pipe->split_sc_frame = (usb->frame_number + 1) & 0x7f; else pipe->split_sc_frame = (usb->frame_number + 2) & 0x7f; } else pipe->split_sc_frame = -1; usbc_hcsplt.s.spltena = 1; usbc_hcsplt.s.hubaddr = pipe->hub_device_addr; usbc_hcsplt.s.prtaddr = pipe->hub_port; usbc_hcsplt.s.compsplt = (transaction->stage == CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE); /* SPLIT transactions can only ever transmit one data packet so limit the transfer size to the max packet size */ if (bytes_to_transfer > pipe->max_packet) bytes_to_transfer = pipe->max_packet; /* ISOCHRONOUS OUT splits are unique in that they limit data transfers to 188 byte chunks representing the begin/middle/end of the data or all */ if (!usbc_hcsplt.s.compsplt && (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && (pipe->transfer_type == CVMX_USB_TRANSFER_ISOCHRONOUS)) { /* Clear the split complete frame number as there isn't going to be a split complete */ pipe->split_sc_frame = -1; /* See if we've started this transfer and sent data */ if (transaction->actual_bytes == 0) { /* Nothing sent yet, this is either a begin or the entire payload */ if (bytes_to_transfer <= 188) usbc_hcsplt.s.xactpos = 3; /* Entire payload in one go */ else usbc_hcsplt.s.xactpos = 2; /* First part of payload */ } else { /* Continuing the previous data, we must either be in the middle or at the end */ if (bytes_to_transfer <= 188) usbc_hcsplt.s.xactpos = 1; /* End of payload */ else usbc_hcsplt.s.xactpos = 0; /* Middle of payload */ } /* Again, the transfer size is limited to 188 bytes */ if (bytes_to_transfer > 188) bytes_to_transfer = 188; } } /* Make sure the transfer never exceeds the byte limit of the hardware. Further bytes will be sent as continued transactions */ if (bytes_to_transfer > MAX_TRANSFER_BYTES) { /* Round MAX_TRANSFER_BYTES to a multiple of out packet size */ bytes_to_transfer = MAX_TRANSFER_BYTES / pipe->max_packet; bytes_to_transfer *= pipe->max_packet; } /* Calculate the number of packets to transfer. If the length is zero we still need to transfer one packet */ packets_to_transfer = (bytes_to_transfer + pipe->max_packet - 1) / pipe->max_packet; if (packets_to_transfer == 0) packets_to_transfer = 1; else if ((packets_to_transfer>1) && (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { /* Limit to one packet when not using DMA. Channels must be restarted between every packet for IN transactions, so there is no reason to do multiple packets in a row */ packets_to_transfer = 1; bytes_to_transfer = packets_to_transfer * pipe->max_packet; } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { /* Limit the number of packet and data transferred to what the hardware can handle */ packets_to_transfer = MAX_TRANSFER_PACKETS; bytes_to_transfer = packets_to_transfer * pipe->max_packet; } usbc_hctsiz.s.xfersize = bytes_to_transfer; usbc_hctsiz.s.pktcnt = packets_to_transfer; /* Update the DATA0/DATA1 toggle */ usbc_hctsiz.s.pid = __cvmx_usb_get_data_pid(pipe); /* High speed pipes may need a hardware ping before they start */ if (pipe->flags & __CVMX_USB_PIPE_FLAGS_NEED_PING) usbc_hctsiz.s.dopng = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCSPLTX(channel, usb->index), usbc_hcsplt.u32); __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index), usbc_hctsiz.u32); } /* Setup the Host Channel Characteristics Register */ { cvmx_usbcx_hccharx_t usbc_hcchar = {.u32 = 0}; /* Set the startframe odd/even properly. This is only used for periodic */ usbc_hcchar.s.oddfrm = usb->frame_number&1; /* Set the number of back to back packets allowed by this endpoint. Split transactions interpret "ec" as the number of immediate retries of failure. These retries happen too quickly, so we disable these entirely for splits */ if (__cvmx_usb_pipe_needs_split(usb, pipe)) usbc_hcchar.s.ec = 1; else if (pipe->multi_count < 1) usbc_hcchar.s.ec = 1; else if (pipe->multi_count > 3) usbc_hcchar.s.ec = 3; else usbc_hcchar.s.ec = pipe->multi_count; /* Set the rest of the endpoint specific settings */ usbc_hcchar.s.devaddr = pipe->device_addr; usbc_hcchar.s.eptype = transaction->type; usbc_hcchar.s.lspddev = (pipe->device_speed == CVMX_USB_SPEED_LOW); usbc_hcchar.s.epdir = pipe->transfer_dir; usbc_hcchar.s.epnum = pipe->endpoint_num; usbc_hcchar.s.mps = pipe->max_packet; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index), usbc_hcchar.u32); } /* Do transaction type specific fixups as needed */ switch (transaction->type) { case CVMX_USB_TRANSFER_CONTROL: __cvmx_usb_start_channel_control(usb, channel, pipe); break; case CVMX_USB_TRANSFER_BULK: case CVMX_USB_TRANSFER_INTERRUPT: break; case CVMX_USB_TRANSFER_ISOCHRONOUS: if (!__cvmx_usb_pipe_needs_split(usb, pipe)) { /* ISO transactions require different PIDs depending on direction and how many packets are needed */ if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { if (pipe->multi_count < 2) /* Need DATA0 */ USB_SET_FIELD32(CVMX_USBCX_HCTSIZX(channel, usb->index), cvmx_usbcx_hctsizx_t, pid, 0); else /* Need MDATA */ USB_SET_FIELD32(CVMX_USBCX_HCTSIZX(channel, usb->index), cvmx_usbcx_hctsizx_t, pid, 3); } } break; } { cvmx_usbcx_hctsizx_t usbc_hctsiz = {.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index))}; transaction->xfersize = usbc_hctsiz.s.xfersize; transaction->pktcnt = usbc_hctsiz.s.pktcnt; } /* Remeber when we start a split transaction */ if (__cvmx_usb_pipe_needs_split(usb, pipe)) usb->active_split = transaction; USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), cvmx_usbcx_hccharx_t, chena, 1); if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) __cvmx_usb_fill_tx_fifo(usb, channel); CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Find a pipe that is ready to be scheduled to hardware. * @param usb USB device state populated by * cvmx_usb_initialize(). * @param list Pipe list to search * @param current_frame * Frame counter to use as a time reference. * * @return Pipe or NULL if none are ready */ static cvmx_usb_pipe_t *__cvmx_usb_find_ready_pipe(cvmx_usb_internal_state_t *usb, cvmx_usb_pipe_list_t *list, uint64_t current_frame) { cvmx_usb_pipe_t *pipe = list->head; while (pipe) { if (!(pipe->flags & __CVMX_USB_PIPE_FLAGS_SCHEDULED) && pipe->head && (pipe->next_tx_frame <= current_frame) && ((pipe->split_sc_frame == -1) || ((((int)current_frame - (int)pipe->split_sc_frame) & 0x7f) < 0x40)) && (!usb->active_split || (usb->active_split == pipe->head))) { CVMX_PREFETCH(pipe, 128); CVMX_PREFETCH(pipe->head, 0); return pipe; } pipe = pipe->next; } return NULL; } /** * @INTERNAL * Called whenever a pipe might need to be scheduled to the * hardware. * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param is_sof True if this schedule was called on a SOF interrupt. */ static void __cvmx_usb_schedule(cvmx_usb_internal_state_t *usb, int is_sof) { int channel; cvmx_usb_pipe_t *pipe; int need_sof; cvmx_usb_transfer_t ttype; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { /* Without DMA we need to be careful to not schedule something at the end of a frame and cause an overrun */ cvmx_usbcx_hfnum_t hfnum = {.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index))}; cvmx_usbcx_hfir_t hfir = {.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HFIR(usb->index))}; if (hfnum.s.frrem < hfir.s.frint/4) goto done; } while (usb->idle_hardware_channels) { /* Find an idle channel */ CVMX_CLZ(channel, usb->idle_hardware_channels); channel = 31 - channel; if (cvmx_unlikely(channel > 7)) { if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_INFO)) cvmx_dprintf("%s: Idle hardware channels has a channel higher than 7. This is wrong\n", __FUNCTION__); break; } /* Find a pipe needing service */ pipe = NULL; if (is_sof) { /* Only process periodic pipes on SOF interrupts. This way we are sure that the periodic data is sent in the beginning of the frame */ pipe = __cvmx_usb_find_ready_pipe(usb, usb->active_pipes + CVMX_USB_TRANSFER_ISOCHRONOUS, usb->frame_number); if (cvmx_likely(!pipe)) pipe = __cvmx_usb_find_ready_pipe(usb, usb->active_pipes + CVMX_USB_TRANSFER_INTERRUPT, usb->frame_number); } if (cvmx_likely(!pipe)) { pipe = __cvmx_usb_find_ready_pipe(usb, usb->active_pipes + CVMX_USB_TRANSFER_CONTROL, usb->frame_number); if (cvmx_likely(!pipe)) pipe = __cvmx_usb_find_ready_pipe(usb, usb->active_pipes + CVMX_USB_TRANSFER_BULK, usb->frame_number); } if (!pipe) break; CVMX_USB_LOG_PARAM("%d", channel); CVMX_USB_LOG_PARAM("%p", pipe); if (cvmx_unlikely((usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_TRANSFERS) || (pipe->flags & CVMX_USB_PIPE_FLAGS_DEBUG_TRANSFERS))) { cvmx_usb_transaction_t *transaction = pipe->head; const cvmx_usb_control_header_t *header = (transaction->control_header) ? cvmx_phys_to_ptr(transaction->control_header) : NULL; const char *dir = (pipe->transfer_dir == CVMX_USB_DIRECTION_IN) ? "IN" : "OUT"; const char *type; switch (pipe->transfer_type) { case CVMX_USB_TRANSFER_CONTROL: type = "SETUP"; dir = (header->s.request_type & 0x80) ? "IN" : "OUT"; break; case CVMX_USB_TRANSFER_ISOCHRONOUS: type = "ISOCHRONOUS"; break; case CVMX_USB_TRANSFER_BULK: type = "BULK"; break; default: /* CVMX_USB_TRANSFER_INTERRUPT */ type = "INTERRUPT"; break; } cvmx_dprintf("%s: Starting pipe %d, transaction %d on channel %d. %s %s len=%d header=0x%llx\n", __FUNCTION__, __cvmx_usb_get_pipe_handle(usb, pipe), __cvmx_usb_get_submit_handle(usb, transaction), channel, type, dir, transaction->buffer_length, (header) ? (unsigned long long)header->u64 : 0ull); } __cvmx_usb_start_channel(usb, channel, pipe); } done: /* Only enable SOF interrupts when we have transactions pending in the future that might need to be scheduled */ need_sof = 0; for (ttype=CVMX_USB_TRANSFER_CONTROL; ttype<=CVMX_USB_TRANSFER_INTERRUPT; ttype++) { pipe = usb->active_pipes[ttype].head; while (pipe) { if (pipe->next_tx_frame > usb->frame_number) { need_sof = 1; break; } pipe=pipe->next; } } USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk_t, sofmsk, need_sof); CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Call a user's callback for a specific reason. * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param pipe Pipe the callback is for or NULL * @param transaction * Transaction the callback is for or NULL * @param reason Reason this callback is being called * @param complete_code * Completion code for the transaction, if any */ static void __cvmx_usb_perform_callback(cvmx_usb_internal_state_t *usb, cvmx_usb_pipe_t *pipe, cvmx_usb_transaction_t *transaction, cvmx_usb_callback_t reason, cvmx_usb_complete_t complete_code) { cvmx_usb_callback_func_t callback = usb->callback[reason]; void *user_data = usb->callback_data[reason]; int submit_handle = -1; int pipe_handle = -1; int bytes_transferred = 0; if (pipe) pipe_handle = __cvmx_usb_get_pipe_handle(usb, pipe); if (transaction) { submit_handle = __cvmx_usb_get_submit_handle(usb, transaction); bytes_transferred = transaction->actual_bytes; /* Transactions are allowed to override the default callback */ if ((reason == CVMX_USB_CALLBACK_TRANSFER_COMPLETE) && transaction->callback) { callback = transaction->callback; user_data = transaction->callback_data; } } if (!callback) return; if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLBACKS)) cvmx_dprintf("%*s%s: calling callback %p(usb=%p, complete_code=%s, " "pipe_handle=%d, submit_handle=%d, bytes_transferred=%d, user_data=%p);\n", 2*usb->indent, "", __FUNCTION__, callback, usb, __cvmx_usb_complete_to_string(complete_code), pipe_handle, submit_handle, bytes_transferred, user_data); callback((cvmx_usb_state_t *)usb, reason, complete_code, pipe_handle, submit_handle, bytes_transferred, user_data); if (cvmx_unlikely(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_CALLBACKS)) cvmx_dprintf("%*s%s: callback %p complete\n", 2*usb->indent, "", __FUNCTION__, callback); } /** * @INTERNAL * Signal the completion of a transaction and free it. The * transaction will be removed from the pipe transaction list. * * @param usb USB device state populated by * cvmx_usb_initialize(). * @param pipe Pipe the transaction is on * @param transaction * Transaction that completed * @param complete_code * Completion code */ static void __cvmx_usb_perform_complete(cvmx_usb_internal_state_t * usb, cvmx_usb_pipe_t *pipe, cvmx_usb_transaction_t *transaction, cvmx_usb_complete_t complete_code) { CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%p", pipe); CVMX_USB_LOG_PARAM("%p", transaction); CVMX_USB_LOG_PARAM("%d", complete_code); /* If this was a split then clear our split in progress marker */ if (usb->active_split == transaction) usb->active_split = NULL; /* Isochronous transactions need extra processing as they might not be done after a single data transfer */ if (cvmx_unlikely(transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS)) { /* Update the number of bytes transferred in this ISO packet */ transaction->iso_packets[0].length = transaction->actual_bytes; transaction->iso_packets[0].status = complete_code; /* If there are more ISOs pending and we succeeded, schedule the next one */ if ((transaction->iso_number_packets > 1) && (complete_code == CVMX_USB_COMPLETE_SUCCESS)) { transaction->actual_bytes = 0; /* No bytes transferred for this packet as of yet */ transaction->iso_number_packets--; /* One less ISO waiting to transfer */ transaction->iso_packets++; /* Increment to the next location in our packet array */ transaction->stage = CVMX_USB_STAGE_NON_CONTROL; goto done; } } /* Remove the transaction from the pipe list */ if (transaction->next) transaction->next->prev = transaction->prev; else pipe->tail = transaction->prev; if (transaction->prev) transaction->prev->next = transaction->next; else pipe->head = transaction->next; if (!pipe->head) { __cvmx_usb_remove_pipe(usb->active_pipes + pipe->transfer_type, pipe); __cvmx_usb_append_pipe(&usb->idle_pipes, pipe); } __cvmx_usb_perform_callback(usb, pipe, transaction, CVMX_USB_CALLBACK_TRANSFER_COMPLETE, complete_code); __cvmx_usb_free_transaction(usb, transaction); done: CVMX_USB_RETURN_NOTHING(); } /** * @INTERNAL * Submit a usb transaction to a pipe. Called for all types * of transactions. * * @param usb * @param pipe_handle * Which pipe to submit to. Will be validated in this function. * @param type Transaction type * @param flags Flags for the transaction * @param buffer User buffer for the transaction * @param buffer_length * User buffer's length in bytes * @param control_header * For control transactions, the 8 byte standard header * @param iso_start_frame * For ISO transactions, the start frame * @param iso_number_packets * For ISO, the number of packet in the transaction. * @param iso_packets * A description of each ISO packet * @param callback User callback to call when the transaction completes * @param user_data User's data for the callback * * @return Submit handle or negative on failure. Matches the result * in the external API. */ static int __cvmx_usb_submit_transaction(cvmx_usb_internal_state_t *usb, int pipe_handle, cvmx_usb_transfer_t type, int flags, uint64_t buffer, int buffer_length, uint64_t control_header, int iso_start_frame, int iso_number_packets, cvmx_usb_iso_packet_t *iso_packets, cvmx_usb_callback_func_t callback, void *user_data) { int submit_handle; cvmx_usb_transaction_t *transaction; cvmx_usb_pipe_t *pipe = usb->pipe + pipe_handle; CVMX_USB_LOG_CALLED(); if (cvmx_unlikely((pipe_handle < 0) || (pipe_handle >= MAX_PIPES))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Fail if the pipe isn't open */ if (cvmx_unlikely((pipe->flags & __CVMX_USB_PIPE_FLAGS_OPEN) == 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(pipe->transfer_type != type)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); transaction = __cvmx_usb_alloc_transaction(usb); if (cvmx_unlikely(!transaction)) CVMX_USB_RETURN(CVMX_USB_NO_MEMORY); transaction->type = type; transaction->flags |= flags; transaction->buffer = buffer; transaction->buffer_length = buffer_length; transaction->control_header = control_header; transaction->iso_start_frame = iso_start_frame; // FIXME: This is not used, implement it transaction->iso_number_packets = iso_number_packets; transaction->iso_packets = iso_packets; transaction->callback = callback; transaction->callback_data = user_data; if (transaction->type == CVMX_USB_TRANSFER_CONTROL) transaction->stage = CVMX_USB_STAGE_SETUP; else transaction->stage = CVMX_USB_STAGE_NON_CONTROL; transaction->next = NULL; if (pipe->tail) { transaction->prev = pipe->tail; transaction->prev->next = transaction; } else { if (pipe->next_tx_frame < usb->frame_number) pipe->next_tx_frame = usb->frame_number + pipe->interval - (usb->frame_number - pipe->next_tx_frame) % pipe->interval; transaction->prev = NULL; pipe->head = transaction; __cvmx_usb_remove_pipe(&usb->idle_pipes, pipe); __cvmx_usb_append_pipe(usb->active_pipes + pipe->transfer_type, pipe); } pipe->tail = transaction; submit_handle = __cvmx_usb_get_submit_handle(usb, transaction); /* We may need to schedule the pipe if this was the head of the pipe */ if (!transaction->prev) __cvmx_usb_schedule(usb, 0); CVMX_USB_RETURN(submit_handle); } /** * Call to submit a USB Bulk transfer to a pipe. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Handle to the pipe for the transfer. * @param buffer Physical address of the data buffer in * memory. Note that this is NOT A POINTER, but * the full 64bit physical address of the * buffer. This may be zero if buffer_length is * zero. * @param buffer_length * Length of buffer in bytes. * @param callback Function to call when this transaction * completes. If the return value of this * function isn't an error, then this function * is guaranteed to be called when the * transaction completes. If this parameter is * NULL, then the generic callback registered * through cvmx_usb_register_callback is * called. If both are NULL, then there is no * way to know when a transaction completes. * @param user_data User supplied data returned when the * callback is called. This is only used if * callback in not NULL. * * @return A submitted transaction handle or negative on * failure. Negative values are failure codes from * cvmx_usb_status_t. */ int cvmx_usb_submit_bulk(cvmx_usb_state_t *state, int pipe_handle, uint64_t buffer, int buffer_length, cvmx_usb_callback_func_t callback, void *user_data) { int submit_handle; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); CVMX_USB_LOG_PARAM("0x%llx", (unsigned long long)buffer); CVMX_USB_LOG_PARAM("%d", buffer_length); /* Pipe handle checking is done later in a common place */ if (cvmx_unlikely(!buffer)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(buffer_length < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); submit_handle = __cvmx_usb_submit_transaction(usb, pipe_handle, CVMX_USB_TRANSFER_BULK, 0, /* flags */ buffer, buffer_length, 0, /* control_header */ 0, /* iso_start_frame */ 0, /* iso_number_packets */ NULL, /* iso_packets */ callback, user_data); CVMX_USB_RETURN(submit_handle); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_submit_bulk); #endif /** * Call to submit a USB Interrupt transfer to a pipe. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Handle to the pipe for the transfer. * @param buffer Physical address of the data buffer in * memory. Note that this is NOT A POINTER, but * the full 64bit physical address of the * buffer. This may be zero if buffer_length is * zero. * @param buffer_length * Length of buffer in bytes. * @param callback Function to call when this transaction * completes. If the return value of this * function isn't an error, then this function * is guaranteed to be called when the * transaction completes. If this parameter is * NULL, then the generic callback registered * through cvmx_usb_register_callback is * called. If both are NULL, then there is no * way to know when a transaction completes. * @param user_data User supplied data returned when the * callback is called. This is only used if * callback in not NULL. * * @return A submitted transaction handle or negative on * failure. Negative values are failure codes from * cvmx_usb_status_t. */ int cvmx_usb_submit_interrupt(cvmx_usb_state_t *state, int pipe_handle, uint64_t buffer, int buffer_length, cvmx_usb_callback_func_t callback, void *user_data) { int submit_handle; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); CVMX_USB_LOG_PARAM("0x%llx", (unsigned long long)buffer); CVMX_USB_LOG_PARAM("%d", buffer_length); /* Pipe handle checking is done later in a common place */ if (cvmx_unlikely(!buffer)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(buffer_length < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); submit_handle = __cvmx_usb_submit_transaction(usb, pipe_handle, CVMX_USB_TRANSFER_INTERRUPT, 0, /* flags */ buffer, buffer_length, 0, /* control_header */ 0, /* iso_start_frame */ 0, /* iso_number_packets */ NULL, /* iso_packets */ callback, user_data); CVMX_USB_RETURN(submit_handle); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_submit_interrupt); #endif /** * Call to submit a USB Control transfer to a pipe. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Handle to the pipe for the transfer. * @param control_header * USB 8 byte control header physical address. * Note that this is NOT A POINTER, but the * full 64bit physical address of the buffer. * @param buffer Physical address of the data buffer in * memory. Note that this is NOT A POINTER, but * the full 64bit physical address of the * buffer. This may be zero if buffer_length is * zero. * @param buffer_length * Length of buffer in bytes. * @param callback Function to call when this transaction * completes. If the return value of this * function isn't an error, then this function * is guaranteed to be called when the * transaction completes. If this parameter is * NULL, then the generic callback registered * through cvmx_usb_register_callback is * called. If both are NULL, then there is no * way to know when a transaction completes. * @param user_data User supplied data returned when the * callback is called. This is only used if * callback in not NULL. * * @return A submitted transaction handle or negative on * failure. Negative values are failure codes from * cvmx_usb_status_t. */ int cvmx_usb_submit_control(cvmx_usb_state_t *state, int pipe_handle, uint64_t control_header, uint64_t buffer, int buffer_length, cvmx_usb_callback_func_t callback, void *user_data) { int submit_handle; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; cvmx_usb_control_header_t *header = cvmx_phys_to_ptr(control_header); CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); CVMX_USB_LOG_PARAM("0x%llx", (unsigned long long)control_header); CVMX_USB_LOG_PARAM("0x%llx", (unsigned long long)buffer); CVMX_USB_LOG_PARAM("%d", buffer_length); /* Pipe handle checking is done later in a common place */ if (cvmx_unlikely(!control_header)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Some drivers send a buffer with a zero length. God only knows why */ if (cvmx_unlikely(buffer && (buffer_length < 0))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(!buffer && (buffer_length != 0))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if ((header->s.request_type & 0x80) == 0) buffer_length = cvmx_le16_to_cpu(header->s.length); submit_handle = __cvmx_usb_submit_transaction(usb, pipe_handle, CVMX_USB_TRANSFER_CONTROL, 0, /* flags */ buffer, buffer_length, control_header, 0, /* iso_start_frame */ 0, /* iso_number_packets */ NULL, /* iso_packets */ callback, user_data); CVMX_USB_RETURN(submit_handle); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_submit_control); #endif /** * Call to submit a USB Isochronous transfer to a pipe. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Handle to the pipe for the transfer. * @param start_frame * Number of frames into the future to schedule * this transaction. * @param flags Flags to control the transfer. See * cvmx_usb_isochronous_flags_t for the flag * definitions. * @param number_packets * Number of sequential packets to transfer. * "packets" is a pointer to an array of this * many packet structures. * @param packets Description of each transfer packet as * defined by cvmx_usb_iso_packet_t. The array * pointed to here must stay valid until the * complete callback is called. * @param buffer Physical address of the data buffer in * memory. Note that this is NOT A POINTER, but * the full 64bit physical address of the * buffer. This may be zero if buffer_length is * zero. * @param buffer_length * Length of buffer in bytes. * @param callback Function to call when this transaction * completes. If the return value of this * function isn't an error, then this function * is guaranteed to be called when the * transaction completes. If this parameter is * NULL, then the generic callback registered * through cvmx_usb_register_callback is * called. If both are NULL, then there is no * way to know when a transaction completes. * @param user_data User supplied data returned when the * callback is called. This is only used if * callback in not NULL. * * @return A submitted transaction handle or negative on * failure. Negative values are failure codes from * cvmx_usb_status_t. */ int cvmx_usb_submit_isochronous(cvmx_usb_state_t *state, int pipe_handle, int start_frame, int flags, int number_packets, cvmx_usb_iso_packet_t packets[], uint64_t buffer, int buffer_length, cvmx_usb_callback_func_t callback, void *user_data) { int submit_handle; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); CVMX_USB_LOG_PARAM("%d", start_frame); CVMX_USB_LOG_PARAM("0x%x", flags); CVMX_USB_LOG_PARAM("%d", number_packets); CVMX_USB_LOG_PARAM("%p", packets); CVMX_USB_LOG_PARAM("0x%llx", (unsigned long long)buffer); CVMX_USB_LOG_PARAM("%d", buffer_length); /* Pipe handle checking is done later in a common place */ if (cvmx_unlikely(start_frame < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(flags & ~(CVMX_USB_ISOCHRONOUS_FLAGS_ALLOW_SHORT | CVMX_USB_ISOCHRONOUS_FLAGS_ASAP))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(number_packets < 1)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(!packets)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(!buffer)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(buffer_length < 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); submit_handle = __cvmx_usb_submit_transaction(usb, pipe_handle, CVMX_USB_TRANSFER_ISOCHRONOUS, flags, buffer, buffer_length, 0, /* control_header */ start_frame, number_packets, packets, callback, user_data); CVMX_USB_RETURN(submit_handle); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_submit_isochronous); #endif /** * Cancel one outstanding request in a pipe. Canceling a request * can fail if the transaction has already completed before cancel * is called. Even after a successful cancel call, it may take * a frame or two for the cvmx_usb_poll() function to call the * associated callback. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Pipe handle to cancel requests in. * @param submit_handle * Handle to transaction to cancel, returned by the submit function. * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_cancel(cvmx_usb_state_t *state, int pipe_handle, int submit_handle) { cvmx_usb_transaction_t *transaction; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; cvmx_usb_pipe_t *pipe = usb->pipe + pipe_handle; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); CVMX_USB_LOG_PARAM("%d", submit_handle); if (cvmx_unlikely((pipe_handle < 0) || (pipe_handle >= MAX_PIPES))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely((submit_handle < 0) || (submit_handle >= MAX_TRANSACTIONS))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Fail if the pipe isn't open */ if (cvmx_unlikely((pipe->flags & __CVMX_USB_PIPE_FLAGS_OPEN) == 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); transaction = usb->transaction + submit_handle; /* Fail if this transaction already completed */ if (cvmx_unlikely((transaction->flags & __CVMX_USB_TRANSACTION_FLAGS_IN_USE) == 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* If the transaction is the HEAD of the queue and scheduled. We need to treat it special */ if ((pipe->head == transaction) && (pipe->flags & __CVMX_USB_PIPE_FLAGS_SCHEDULED)) { cvmx_usbcx_hccharx_t usbc_hcchar; usb->pipe_for_channel[pipe->channel] = NULL; pipe->flags &= ~__CVMX_USB_PIPE_FLAGS_SCHEDULED; CVMX_SYNCW; usbc_hcchar.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCCHARX(pipe->channel, usb->index)); /* If the channel isn't enabled then the transaction already completed */ if (usbc_hcchar.s.chena) { usbc_hcchar.s.chdis = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCCHARX(pipe->channel, usb->index), usbc_hcchar.u32); } } __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_CANCEL); CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_cancel); #endif /** * Cancel all outstanding requests in a pipe. Logically all this * does is call cvmx_usb_cancel() in a loop. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Pipe handle to cancel requests in. * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_cancel_all(cvmx_usb_state_t *state, int pipe_handle) { cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; cvmx_usb_pipe_t *pipe = usb->pipe + pipe_handle; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); if (cvmx_unlikely((pipe_handle < 0) || (pipe_handle >= MAX_PIPES))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Fail if the pipe isn't open */ if (cvmx_unlikely((pipe->flags & __CVMX_USB_PIPE_FLAGS_OPEN) == 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Simply loop through and attempt to cancel each transaction */ while (pipe->head) { cvmx_usb_status_t result = cvmx_usb_cancel(state, pipe_handle, __cvmx_usb_get_submit_handle(usb, pipe->head)); if (cvmx_unlikely(result != CVMX_USB_SUCCESS)) CVMX_USB_RETURN(result); } CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_cancel_all); #endif /** * Close a pipe created with cvmx_usb_open_pipe(). * * @param state USB device state populated by * cvmx_usb_initialize(). * @param pipe_handle * Pipe handle to close. * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. CVMX_USB_BUSY is returned if the * pipe has outstanding transfers. */ cvmx_usb_status_t cvmx_usb_close_pipe(cvmx_usb_state_t *state, int pipe_handle) { cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; cvmx_usb_pipe_t *pipe = usb->pipe + pipe_handle; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", pipe_handle); if (cvmx_unlikely((pipe_handle < 0) || (pipe_handle >= MAX_PIPES))) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Fail if the pipe isn't open */ if (cvmx_unlikely((pipe->flags & __CVMX_USB_PIPE_FLAGS_OPEN) == 0)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); /* Fail if the pipe has pending transactions */ if (cvmx_unlikely(pipe->head)) CVMX_USB_RETURN(CVMX_USB_BUSY); pipe->flags = 0; __cvmx_usb_remove_pipe(&usb->idle_pipes, pipe); __cvmx_usb_append_pipe(&usb->free_pipes, pipe); CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_close_pipe); #endif /** * Register a function to be called when various USB events occur. * * @param state USB device state populated by * cvmx_usb_initialize(). * @param reason Which event to register for. * @param callback Function to call when the event occurs. * @param user_data User data parameter to the function. * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_register_callback(cvmx_usb_state_t *state, cvmx_usb_callback_t reason, cvmx_usb_callback_func_t callback, void *user_data) { cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); CVMX_USB_LOG_PARAM("%d", reason); CVMX_USB_LOG_PARAM("%p", callback); CVMX_USB_LOG_PARAM("%p", user_data); if (cvmx_unlikely(reason >= __CVMX_USB_CALLBACK_END)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); if (cvmx_unlikely(!callback)) CVMX_USB_RETURN(CVMX_USB_INVALID_PARAM); usb->callback[reason] = callback; usb->callback_data[reason] = user_data; CVMX_USB_RETURN(CVMX_USB_SUCCESS); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_register_callback); #endif /** * Get the current USB protocol level frame number. The frame * number is always in the range of 0-0x7ff. * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return USB frame number */ int cvmx_usb_get_frame_number(cvmx_usb_state_t *state) { int frame_number; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; cvmx_usbcx_hfnum_t usbc_hfnum; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); usbc_hfnum.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); frame_number = usbc_hfnum.s.frnum; CVMX_USB_RETURN(frame_number); } #ifdef CVMX_BUILD_FOR_LINUX_KERNEL EXPORT_SYMBOL(cvmx_usb_get_frame_number); #endif /** * @INTERNAL * Poll a channel for status * * @param usb USB device * @param channel Channel to poll * * @return Zero on success */ static int __cvmx_usb_poll_channel(cvmx_usb_internal_state_t *usb, int channel) { cvmx_usbcx_hcintx_t usbc_hcint; cvmx_usbcx_hctsizx_t usbc_hctsiz; cvmx_usbcx_hccharx_t usbc_hcchar; cvmx_usb_pipe_t *pipe; cvmx_usb_transaction_t *transaction; int bytes_this_transfer; int bytes_in_last_packet; int packets_processed; int buffer_space_left; CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", usb); CVMX_USB_LOG_PARAM("%d", channel); /* Read the interrupt status bits for the channel */ usbc_hcint.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCINTX(channel, usb->index)); #if 0 cvmx_dprintf("Channel %d%s%s%s%s%s%s%s%s%s%s%s\n", channel, (usbc_hcint.s.datatglerr) ? " DATATGLERR" : "", (usbc_hcint.s.frmovrun) ? " FRMOVRUN" : "", (usbc_hcint.s.bblerr) ? " BBLERR" : "", (usbc_hcint.s.xacterr) ? " XACTERR" : "", (usbc_hcint.s.nyet) ? " NYET" : "", (usbc_hcint.s.ack) ? " ACK" : "", (usbc_hcint.s.nak) ? " NAK" : "", (usbc_hcint.s.stall) ? " STALL" : "", (usbc_hcint.s.ahberr) ? " AHBERR" : "", (usbc_hcint.s.chhltd) ? " CHHLTD" : "", (usbc_hcint.s.xfercompl) ? " XFERCOMPL" : ""); #endif if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { usbc_hcchar.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index)); if (usbc_hcchar.s.chena && usbc_hcchar.s.chdis) { /* There seems to be a bug in CN31XX which can cause interrupt IN transfers to get stuck until we do a write of HCCHARX without changing things */ __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index), usbc_hcchar.u32); CVMX_USB_RETURN(0); } /* In non DMA mode the channels don't halt themselves. We need to manually disable channels that are left running */ if (!usbc_hcint.s.chhltd) { if (usbc_hcchar.s.chena) { cvmx_usbcx_hcintmskx_t hcintmsk; /* Disable all interrupts except CHHLTD */ hcintmsk.u32 = 0; hcintmsk.s.chhltdmsk = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), hcintmsk.u32); usbc_hcchar.s.chdis = 1; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index), usbc_hcchar.u32); CVMX_USB_RETURN(0); } else if (usbc_hcint.s.xfercompl) { /* Successful IN/OUT with transfer complete. Channel halt isn't needed */ } else { cvmx_dprintf("USB%d: Channel %d interrupt without halt\n", usb->index, channel); CVMX_USB_RETURN(0); } } } else { /* There is are no interrupts that we need to process when the channel is still running */ if (!usbc_hcint.s.chhltd) CVMX_USB_RETURN(0); } /* Disable the channel interrupts now that it is done */ __cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), 0); usb->idle_hardware_channels |= (1<pipe_for_channel[channel]; CVMX_PREFETCH(pipe, 0); CVMX_PREFETCH(pipe, 128); if (!pipe) CVMX_USB_RETURN(0); transaction = pipe->head; CVMX_PREFETCH0(transaction); /* Disconnect this pipe from the HW channel. Later the schedule function will figure out which pipe needs to go */ usb->pipe_for_channel[channel] = NULL; pipe->flags &= ~__CVMX_USB_PIPE_FLAGS_SCHEDULED; /* Read the channel config info so we can figure out how much data transfered */ usbc_hcchar.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCCHARX(channel, usb->index)); usbc_hctsiz.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index)); /* Calculating the number of bytes successfully transferred is dependent on the transfer direction */ packets_processed = transaction->pktcnt - usbc_hctsiz.s.pktcnt; if (usbc_hcchar.s.epdir) { /* IN transactions are easy. For every byte received the hardware decrements xfersize. All we need to do is subtract the current value of xfersize from its starting value and we know how many bytes were written to the buffer */ bytes_this_transfer = transaction->xfersize - usbc_hctsiz.s.xfersize; } else { /* OUT transaction don't decrement xfersize. Instead pktcnt is decremented on every successful packet send. The hardware does this when it receives an ACK, or NYET. If it doesn't receive one of these responses pktcnt doesn't change */ bytes_this_transfer = packets_processed * usbc_hcchar.s.mps; /* The last packet may not be a full transfer if we didn't have enough data */ if (bytes_this_transfer > transaction->xfersize) bytes_this_transfer = transaction->xfersize; } /* Figure out how many bytes were in the last packet of the transfer */ if (packets_processed) bytes_in_last_packet = bytes_this_transfer - (packets_processed-1) * usbc_hcchar.s.mps; else bytes_in_last_packet = bytes_this_transfer; /* As a special case, setup transactions output the setup header, not the user's data. For this reason we don't count setup data as bytes transferred */ if ((transaction->stage == CVMX_USB_STAGE_SETUP) || (transaction->stage == CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE)) bytes_this_transfer = 0; /* Optional debug output */ if (cvmx_unlikely((usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_DEBUG_TRANSFERS) || (pipe->flags & CVMX_USB_PIPE_FLAGS_DEBUG_TRANSFERS))) cvmx_dprintf("%s: Channel %d halted. Pipe %d transaction %d stage %d bytes=%d\n", __FUNCTION__, channel, __cvmx_usb_get_pipe_handle(usb, pipe), __cvmx_usb_get_submit_handle(usb, transaction), transaction->stage, bytes_this_transfer); /* Add the bytes transferred to the running total. It is important that bytes_this_transfer doesn't count any data that needs to be retransmitted */ transaction->actual_bytes += bytes_this_transfer; if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) buffer_space_left = transaction->iso_packets[0].length - transaction->actual_bytes; else buffer_space_left = transaction->buffer_length - transaction->actual_bytes; /* We need to remember the PID toggle state for the next transaction. The hardware already updated it for the next transaction */ pipe->pid_toggle = !(usbc_hctsiz.s.pid == 0); /* For high speed bulk out, assume the next transaction will need to do a ping before proceeding. If this isn't true the ACK processing below will clear this flag */ if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && (pipe->transfer_type == CVMX_USB_TRANSFER_BULK) && (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT)) pipe->flags |= __CVMX_USB_PIPE_FLAGS_NEED_PING; if (usbc_hcint.s.stall) { /* STALL as a response means this transaction cannot be completed because the device can't process transactions. Tell the user. Any data that was transferred will be counted on the actual bytes transferred */ pipe->pid_toggle = 0; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_STALL); } else if (usbc_hcint.s.xacterr) { /* We know at least one packet worked if we get a ACK or NAK. Reset the retry counter */ if (usbc_hcint.s.nak || usbc_hcint.s.ack) transaction->retries = 0; transaction->retries++; if (transaction->retries > MAX_RETRIES) { /* XactErr as a response means the device signaled something wrong with the transfer. For example, PID toggle errors cause these */ __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_XACTERR); } else { /* If this was a split then clear our split in progress marker */ if (usb->active_split == transaction) usb->active_split = NULL; /* Rewind to the beginning of the transaction by anding off the split complete bit */ transaction->stage &= ~1; pipe->split_sc_frame = -1; pipe->next_tx_frame += pipe->interval; if (pipe->next_tx_frame < usb->frame_number) pipe->next_tx_frame = usb->frame_number + pipe->interval - (usb->frame_number - pipe->next_tx_frame) % pipe->interval; } } else if (usbc_hcint.s.bblerr) { /* Babble Error (BblErr) */ __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_BABBLEERR); } else if (usbc_hcint.s.datatglerr) { /* We'll retry the exact same transaction again */ transaction->retries++; } else if (usbc_hcint.s.nyet) { /* NYET as a response is only allowed in three cases: as a response to a ping, as a response to a split transaction, and as a response to a bulk out. The ping case is handled by hardware, so we only have splits and bulk out */ if (!__cvmx_usb_pipe_needs_split(usb, pipe)) { transaction->retries = 0; /* If there is more data to go then we need to try again. Otherwise this transaction is complete */ if ((buffer_space_left == 0) || (bytes_in_last_packet < pipe->max_packet)) __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } else { /* Split transactions retry the split complete 4 times then rewind to the start split and do the entire transactions again */ transaction->retries++; if ((transaction->retries & 0x3) == 0) { /* Rewind to the beginning of the transaction by anding off the split complete bit */ transaction->stage &= ~1; pipe->split_sc_frame = -1; } } } else if (usbc_hcint.s.ack) { transaction->retries = 0; /* The ACK bit can only be checked after the other error bits. This is because a multi packet transfer may succeed in a number of packets and then get a different response on the last packet. In this case both ACK and the last response bit will be set. If none of the other response bits is set, then the last packet must have been an ACK */ /* Since we got an ACK, we know we don't need to do a ping on this pipe */ pipe->flags &= ~__CVMX_USB_PIPE_FLAGS_NEED_PING; switch (transaction->type) { case CVMX_USB_TRANSFER_CONTROL: switch (transaction->stage) { case CVMX_USB_STAGE_NON_CONTROL: case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: /* This should be impossible */ __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_ERROR); break; case CVMX_USB_STAGE_SETUP: pipe->pid_toggle = 1; if (__cvmx_usb_pipe_needs_split(usb, pipe)) transaction->stage = CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE; else { cvmx_usb_control_header_t *header = cvmx_phys_to_ptr(transaction->control_header); if (header->s.length) transaction->stage = CVMX_USB_STAGE_DATA; else transaction->stage = CVMX_USB_STAGE_STATUS; } break; case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: { cvmx_usb_control_header_t *header = cvmx_phys_to_ptr(transaction->control_header); if (header->s.length) transaction->stage = CVMX_USB_STAGE_DATA; else transaction->stage = CVMX_USB_STAGE_STATUS; } break; case CVMX_USB_STAGE_DATA: if (__cvmx_usb_pipe_needs_split(usb, pipe)) { transaction->stage = CVMX_USB_STAGE_DATA_SPLIT_COMPLETE; /* For setup OUT data that are splits, the hardware doesn't appear to count transferred data. Here we manually update the data transferred */ if (!usbc_hcchar.s.epdir) { if (buffer_space_left < pipe->max_packet) transaction->actual_bytes += buffer_space_left; else transaction->actual_bytes += pipe->max_packet; } } else if ((buffer_space_left == 0) || (bytes_in_last_packet < pipe->max_packet)) { pipe->pid_toggle = 1; transaction->stage = CVMX_USB_STAGE_STATUS; } break; case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: if ((buffer_space_left == 0) || (bytes_in_last_packet < pipe->max_packet)) { pipe->pid_toggle = 1; transaction->stage = CVMX_USB_STAGE_STATUS; } else { transaction->stage = CVMX_USB_STAGE_DATA; } break; case CVMX_USB_STAGE_STATUS: if (__cvmx_usb_pipe_needs_split(usb, pipe)) transaction->stage = CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE; else __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); break; case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); break; } break; case CVMX_USB_TRANSFER_BULK: case CVMX_USB_TRANSFER_INTERRUPT: /* The only time a bulk transfer isn't complete when it finishes with an ACK is during a split transaction. For splits we need to continue the transfer if more data is needed */ if (__cvmx_usb_pipe_needs_split(usb, pipe)) { if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) transaction->stage = CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; else { if (buffer_space_left && (bytes_in_last_packet == pipe->max_packet)) transaction->stage = CVMX_USB_STAGE_NON_CONTROL; else { if (transaction->type == CVMX_USB_TRANSFER_INTERRUPT) pipe->next_tx_frame += pipe->interval; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } } } else { if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && (pipe->transfer_type == CVMX_USB_TRANSFER_BULK) && (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && (usbc_hcint.s.nak)) pipe->flags |= __CVMX_USB_PIPE_FLAGS_NEED_PING; if (!buffer_space_left || (bytes_in_last_packet < pipe->max_packet)) { if (transaction->type == CVMX_USB_TRANSFER_INTERRUPT) pipe->next_tx_frame += pipe->interval; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } } break; case CVMX_USB_TRANSFER_ISOCHRONOUS: if (__cvmx_usb_pipe_needs_split(usb, pipe)) { /* ISOCHRONOUS OUT splits don't require a complete split stage. Instead they use a sequence of begin OUT splits to transfer the data 188 bytes at a time. Once the transfer is complete, the pipe sleeps until the next schedule interval */ if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { /* If no space left or this wasn't a max size packet then this transfer is complete. Otherwise start it again to send the next 188 bytes */ if (!buffer_space_left || (bytes_this_transfer < 188)) { pipe->next_tx_frame += pipe->interval; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } } else { if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE) { /* We are in the incoming data phase. Keep getting data until we run out of space or get a small packet */ if ((buffer_space_left == 0) || (bytes_in_last_packet < pipe->max_packet)) { pipe->next_tx_frame += pipe->interval; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } } else transaction->stage = CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; } } else { pipe->next_tx_frame += pipe->interval; __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_SUCCESS); } break; } } else if (usbc_hcint.s.nak) { /* If this was a split then clear our split in progress marker */ if (usb->active_split == transaction) usb->active_split = NULL; /* NAK as a response means the device couldn't accept the transaction, but it should be retried in the future. Rewind to the beginning of the transaction by anding off the split complete bit. Retry in the next interval */ transaction->retries = 0; transaction->stage &= ~1; pipe->next_tx_frame += pipe->interval; if (pipe->next_tx_frame < usb->frame_number) pipe->next_tx_frame = usb->frame_number + pipe->interval - (usb->frame_number - pipe->next_tx_frame) % pipe->interval; } else { cvmx_usb_port_status_t port; port = cvmx_usb_get_status((cvmx_usb_state_t *)usb); if (port.port_enabled) { /* We'll retry the exact same transaction again */ transaction->retries++; } else { /* We get channel halted interrupts with no result bits sets when the cable is unplugged */ __cvmx_usb_perform_complete(usb, pipe, transaction, CVMX_USB_COMPLETE_ERROR); } } CVMX_USB_RETURN(0); } /** * Poll the USB block for status and call all needed callback * handlers. This function is meant to be called in the interrupt * handler for the USB controller. It can also be called * periodically in a loop for non-interrupt based operation. * * @param state USB device state populated by * cvmx_usb_initialize(). * * @return CVMX_USB_SUCCESS or a negative error code defined in * cvmx_usb_status_t. */ cvmx_usb_status_t cvmx_usb_poll(cvmx_usb_state_t *state) { cvmx_usbcx_hfnum_t usbc_hfnum; cvmx_usbcx_gintsts_t usbc_gintsts; cvmx_usb_internal_state_t *usb = (cvmx_usb_internal_state_t*)state; CVMX_PREFETCH(usb, 0); CVMX_PREFETCH(usb, 1*128); CVMX_PREFETCH(usb, 2*128); CVMX_PREFETCH(usb, 3*128); CVMX_PREFETCH(usb, 4*128); CVMX_USB_LOG_CALLED(); CVMX_USB_LOG_PARAM("%p", state); /* Update the frame counter */ usbc_hfnum.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); if ((usb->frame_number&0x3fff) > usbc_hfnum.s.frnum) usb->frame_number += 0x4000; usb->frame_number &= ~0x3fffull; usb->frame_number |= usbc_hfnum.s.frnum; /* Read the pending interrupts */ usbc_gintsts.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_GINTSTS(usb->index)); /* Clear the interrupts now that we know about them */ __cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), usbc_gintsts.u32); if (usbc_gintsts.s.rxflvl) { /* RxFIFO Non-Empty (RxFLvl) Indicates that there is at least one packet pending to be read from the RxFIFO. */ /* In DMA mode this is handled by hardware */ if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) __cvmx_usb_poll_rx_fifo(usb); } if (usbc_gintsts.s.ptxfemp || usbc_gintsts.s.nptxfemp) { /* Fill the Tx FIFOs when not in DMA mode */ if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) __cvmx_usb_poll_tx_fifo(usb); } if (usbc_gintsts.s.disconnint || usbc_gintsts.s.prtint) { cvmx_usbcx_hprt_t usbc_hprt; /* Disconnect Detected Interrupt (DisconnInt) Asserted when a device disconnect is detected. */ /* Host Port Interrupt (PrtInt) The core sets this bit to indicate a change in port status of one of the O2P USB core ports in Host mode. The application must read the Host Port Control and Status (HPRT) register to determine the exact event that caused this interrupt. The application must clear the appropriate status bit in the Host Port Control and Status register to clear this bit. */ /* Call the user's port callback */ __cvmx_usb_perform_callback(usb, NULL, NULL, CVMX_USB_CALLBACK_PORT_CHANGED, CVMX_USB_COMPLETE_SUCCESS); /* Clear the port change bits */ usbc_hprt.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); usbc_hprt.s.prtena = 0; __cvmx_usb_write_csr32(usb, CVMX_USBCX_HPRT(usb->index), usbc_hprt.u32); } if (usbc_gintsts.s.hchint) { /* Host Channels Interrupt (HChInt) The core sets this bit to indicate that an interrupt is pending on one of the channels of the core (in Host mode). The application must read the Host All Channels Interrupt (HAINT) register to determine the exact number of the channel on which the interrupt occurred, and then read the corresponding Host Channel-n Interrupt (HCINTn) register to determine the exact cause of the interrupt. The application must clear the appropriate status bit in the HCINTn register to clear this bit. */ cvmx_usbcx_haint_t usbc_haint; usbc_haint.u32 = __cvmx_usb_read_csr32(usb, CVMX_USBCX_HAINT(usb->index)); while (usbc_haint.u32) { int channel; CVMX_CLZ(channel, usbc_haint.u32); channel = 31 - channel; __cvmx_usb_poll_channel(usb, channel); usbc_haint.u32 ^= 1<