blob: 0619ed78735ce31685293cb8f018b0f1de0f7257 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/dif/dif_usbdev.h"
#include "sw/device/lib/base/bitfield.h"
#include "usbdev_regs.h" // Generated.
/**
* Definition in the header file (and probably other places) must be updated if
* there is a hardware change.
*/
_Static_assert(USBDEV_NUM_ENDPOINTS == USBDEV_PARAM_NENDPOINTS,
"Mismatch in number of endpoints");
/**
* Max packet size is equal to the size of device buffers.
*/
#define USBDEV_BUFFER_ENTRY_SIZE_BYTES USBDEV_MAX_PACKET_SIZE
/**
* Constants used to indicate that a buffer pool is full or empty.
*/
#define BUFFER_POOL_FULL (USBDEV_NUM_BUFFERS - 1)
#define BUFFER_POOL_EMPTY -1
/**
* Hardware information for endpoints.
*/
typedef struct endpoint_hw_info {
uint32_t config_in_reg_offset;
uint8_t bit_index;
} endpoint_hw_info_t;
/**
* Helper macro to define an `endpoint_hw_info_t` entry for endpoint N.
*
* Note: This uses the bit indices of `USBDEV_IN_SENT` register for the sake
* of conciseness because other endpoint registers use the same layout.
*/
#define ENDPOINT_HW_INFO_ENTRY(N) \
[N] = {.config_in_reg_offset = USBDEV_CONFIGIN_##N##_REG_OFFSET, \
.bit_index = USBDEV_IN_SENT_SENT_##N##_BIT}
static const endpoint_hw_info_t kEndpointHwInfos[USBDEV_NUM_ENDPOINTS] = {
ENDPOINT_HW_INFO_ENTRY(0), ENDPOINT_HW_INFO_ENTRY(1),
ENDPOINT_HW_INFO_ENTRY(2), ENDPOINT_HW_INFO_ENTRY(3),
ENDPOINT_HW_INFO_ENTRY(4), ENDPOINT_HW_INFO_ENTRY(5),
ENDPOINT_HW_INFO_ENTRY(6), ENDPOINT_HW_INFO_ENTRY(7),
ENDPOINT_HW_INFO_ENTRY(8), ENDPOINT_HW_INFO_ENTRY(9),
ENDPOINT_HW_INFO_ENTRY(10), ENDPOINT_HW_INFO_ENTRY(11),
};
#undef ENDPOINT_HW_INFO_ENTRY
/**
* Mapping from `dif_usbdev_irq_t` to bit indices in interrupt registers.
*/
static const uint8_t kIrqEnumToBitIndex[] = {
[kDifUsbdevIrqPktReceived] = USBDEV_INTR_COMMON_PKT_RECEIVED_BIT,
[kDifUsbdevIrqPktSent] = USBDEV_INTR_COMMON_PKT_SENT_BIT,
[kDifUsbdevIrqDisconnected] = USBDEV_INTR_COMMON_DISCONNECTED_BIT,
[kDifUsbdevIrqHostLost] = USBDEV_INTR_COMMON_HOST_LOST_BIT,
[kDifUsbdevIrqLinkReset] = USBDEV_INTR_COMMON_LINK_RESET_BIT,
[kDifUsbdevIrqLinkSuspend] = USBDEV_INTR_COMMON_LINK_SUSPEND_BIT,
[kDifUsbdevIrqLinkResume] = USBDEV_INTR_COMMON_LINK_RESUME_BIT,
[kDifUsbdevIrqAvEmpty] = USBDEV_INTR_COMMON_AV_EMPTY_BIT,
[kDifUsbdevIrqRxFull] = USBDEV_INTR_COMMON_RX_FULL_BIT,
[kDifUsbdevIrqAvOverflow] = USBDEV_INTR_COMMON_AV_OVERFLOW_BIT,
[kDifUsbdevIrqLinkInError] = USBDEV_INTR_COMMON_LINK_IN_ERR_BIT,
[kDifUsbdevIrqRxCrcError] = USBDEV_INTR_COMMON_RX_CRC_ERR_BIT,
[kDifUsbdevIrqRxPidError] = USBDEV_INTR_COMMON_RX_PID_ERR_BIT,
[kDifUsbdevIrqRxBitstuffError] = USBDEV_INTR_COMMON_RX_BITSTUFF_ERR_BIT,
[kDifUsbdevIrqFrame] = USBDEV_INTR_COMMON_FRAME_BIT,
[kDifUsbdevIrqConnected] = USBDEV_INTR_COMMON_CONNECTED_BIT,
};
/**
* Static functions for the free buffer pool.
*/
/**
* Checks if a buffer pool is full.
*
* A buffer pool is full if it contains `USBDEV_NUM_BUFFERS` buffers.
*
* @param pool A buffer pool.
* @return `true` if the buffer pool if full, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_is_full(dif_usbdev_buffer_pool_t *pool) {
return pool->top == BUFFER_POOL_FULL;
}
/**
* Checks if a buffer pool is empty.
*
* @param pool A buffer pool.
* @return `true` if the buffer pool is empty, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_is_empty(dif_usbdev_buffer_pool_t *pool) {
return pool->top == BUFFER_POOL_EMPTY;
}
/**
* Checks if a buffer id is valid.
*
* A buffer id is valid if it is less than `USBDEV_NUM_BUFFERS`.
*
* @param buffer_id A buffer id.
* @return `true` if `buffer_id` is valid, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_is_valid_buffer_id(uint8_t buffer_id) {
return buffer_id < USBDEV_NUM_BUFFERS;
}
/**
* Adds a buffer to a buffer pool.
*
* @param pool A buffer pool.
* @param buffer_id A buffer id.
* @return `true` if the operation was successful, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_add(dif_usbdev_buffer_pool_t *pool, uint8_t buffer_id) {
if (buffer_pool_is_full(pool) || !buffer_pool_is_valid_buffer_id(buffer_id)) {
return false;
}
++pool->top;
pool->buffers[pool->top] = buffer_id;
return true;
}
/**
* Removes a buffer from a buffer pool.
*
* @param pool A buffer pool.
* @param buffer_id A buffer id.
* @return `true` if the operation was successful, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_remove(dif_usbdev_buffer_pool_t *pool,
uint8_t *buffer_id) {
if (buffer_pool_is_empty(pool) || buffer_id == NULL) {
return false;
}
*buffer_id = pool->buffers[pool->top];
--pool->top;
return true;
}
/**
* Initializes the buffer pool.
*
* At the end of this operation, the buffer pool contains `USBDEV_NUM_BUFFERS`
* buffers.
*
* @param pool A buffer pool.
* @return `true` if the operation was successful, `false` otherwise.
*/
DIF_WARN_UNUSED_RESULT
static bool buffer_pool_init(dif_usbdev_buffer_pool_t *pool) {
// Start with an empty pool
pool->top = -1;
// Add all buffers
for (uint8_t i = 0; i < USBDEV_NUM_BUFFERS; ++i) {
if (!buffer_pool_add(pool, i)) {
return false;
}
}
return true;
}
/**
* Utility functions
*/
/**
* Checks if the given value is a valid `dif_usbdev_toggle_t` variant.
*/
DIF_WARN_UNUSED_RESULT
static bool is_valid_toggle(dif_usbdev_toggle_t val) {
return val == kDifUsbdevToggleEnable || val == kDifUsbdevToggleDisable;
}
/**
* Checks if the given value is a valid `dif_usbdev_power_sense_override_t`
* variant.
*/
DIF_WARN_UNUSED_RESULT
static bool is_valid_power_sense_override(
dif_usbdev_power_sense_override_t val) {
return val == kDifUsbdevPowerSenseOverrideDisabled ||
val == kDifUsbdevPowerSenseOverridePresent ||
val == kDifUsbdevPowerSenseOverrideNotPresent;
}
/**
* Checks if the given value is a valid endpoint number.
*/
DIF_WARN_UNUSED_RESULT
static bool is_valid_endpoint(uint8_t endpoint) {
return endpoint < USBDEV_NUM_ENDPOINTS;
}
/**
* Checks if the given value is a valid `dif_usbdev_irq_t` variant.
*/
DIF_WARN_UNUSED_RESULT
static bool is_valid_irq(dif_usbdev_irq_t irq) {
return irq >= kDifUsbdevIrqFirst && irq <= kDifUsbdevIrqLast;
}
/**
* Enables/disables the functionality controlled by the register at `reg_offset`
* for an endpoint.
*/
DIF_WARN_UNUSED_RESULT
static dif_usbdev_result_t endpoint_functionality_enable(
dif_usbdev_t *usbdev, uint32_t reg_offset, uint8_t endpoint,
dif_usbdev_toggle_t new_state) {
if (usbdev == NULL || !is_valid_endpoint(endpoint) ||
!is_valid_toggle(new_state)) {
return kDifUsbdevBadArg;
}
if (kDifUsbdevToggleEnable) {
mmio_region_nonatomic_set_bit32(usbdev->base_addr, reg_offset,
kEndpointHwInfos[endpoint].bit_index);
} else {
mmio_region_nonatomic_clear_bit32(usbdev->base_addr, reg_offset,
kEndpointHwInfos[endpoint].bit_index);
}
return kDifUsbdevOK;
}
/**
* Returns the address that corresponds to the given buffer and offset
* into that buffer.
*/
DIF_WARN_UNUSED_RESULT
static uint32_t get_buffer_addr(uint8_t buffer_id, size_t offset) {
return USBDEV_BUFFER_REG_OFFSET +
(buffer_id * USBDEV_BUFFER_ENTRY_SIZE_BYTES) + offset;
}
/**
* USBDEV DIF library functions.
*/
dif_usbdev_result_t dif_usbdev_init(dif_usbdev_config_t *config,
dif_usbdev_t *usbdev) {
if (usbdev == NULL || config == NULL) {
return kDifUsbdevBadArg;
}
// Check enum fields
if (!is_valid_toggle(config->differential_rx) ||
!is_valid_toggle(config->differential_tx) ||
!is_valid_toggle(config->single_bit_eop) ||
!is_valid_power_sense_override(config->power_sense_override) ||
!is_valid_toggle(config->pin_flip) ||
!is_valid_toggle(config->clock_sync_signals)) {
return kDifUsbdevBadArg;
}
// Store base address
usbdev->base_addr = config->base_addr;
// Initialize the free buffer pool
if (!buffer_pool_init(&usbdev->buffer_pool)) {
return kDifUsbdevError;
}
// Determine the value of the PHY_CONFIG register.
uint32_t phy_config_val = 0;
if (config->differential_rx == kDifUsbdevToggleEnable) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_RX_DIFFERENTIAL_MODE_BIT,
},
1);
}
if (config->differential_tx == kDifUsbdevToggleEnable) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_TX_DIFFERENTIAL_MODE_BIT,
},
1);
}
if (config->single_bit_eop == kDifUsbdevToggleEnable) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT,
},
1);
}
if (config->power_sense_override == kDifUsbdevPowerSenseOverridePresent) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_OVERRIDE_PWR_SENSE_EN_BIT,
},
1);
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_OVERRIDE_PWR_SENSE_VAL_BIT,
},
1);
} else if (config->power_sense_override ==
kDifUsbdevPowerSenseOverrideNotPresent) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_OVERRIDE_PWR_SENSE_EN_BIT,
},
1);
}
if (config->pin_flip == kDifUsbdevToggleEnable) {
phy_config_val =
bitfield_field32_write(phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_PINFLIP_BIT,
},
1);
}
if (config->clock_sync_signals == kDifUsbdevToggleDisable) {
phy_config_val = bitfield_field32_write(
phy_config_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT,
},
1);
}
// Write configuration to PHY_CONFIG register
mmio_region_write32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET,
phy_config_val);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_fill_available_fifo(dif_usbdev_t *usbdev) {
if (usbdev == NULL) {
return kDifUsbdevBadArg;
}
// Remove buffers from the pool and write them to the AV FIFO until it is full
while (!mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_AV_FULL_BIT) &&
!buffer_pool_is_empty(&usbdev->buffer_pool)) {
uint8_t buffer_id;
if (!buffer_pool_remove(&usbdev->buffer_pool, &buffer_id)) {
return kDifUsbdevError;
}
mmio_region_write_only_set_field32(usbdev->base_addr,
USBDEV_AVBUFFER_REG_OFFSET,
USBDEV_AVBUFFER_BUFFER_FIELD, buffer_id);
}
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_endpoint_setup_enable(
dif_usbdev_t *usbdev, uint8_t endpoint, dif_usbdev_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_SETUP_REG_OFFSET,
endpoint, new_state);
}
dif_usbdev_result_t dif_usbdev_endpoint_out_enable(
dif_usbdev_t *usbdev, uint8_t endpoint, dif_usbdev_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_OUT_REG_OFFSET,
endpoint, new_state);
}
dif_usbdev_result_t dif_usbdev_endpoint_stall_enable(
dif_usbdev_t *usbdev, uint8_t endpoint, dif_usbdev_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_STALL_REG_OFFSET,
endpoint, new_state);
}
dif_usbdev_result_t dif_usbdev_endpoint_stall_get(dif_usbdev_t *usbdev,
uint8_t endpoint,
bool *state) {
if (usbdev == NULL || state == NULL || !is_valid_endpoint(endpoint)) {
return kDifUsbdevBadArg;
}
*state = mmio_region_get_bit32(usbdev->base_addr, USBDEV_STALL_REG_OFFSET,
kEndpointHwInfos[endpoint].bit_index);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_endpoint_iso_enable(
dif_usbdev_t *usbdev, uint8_t endpoint, dif_usbdev_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_ISO_REG_OFFSET, endpoint,
new_state);
}
dif_usbdev_result_t dif_usbdev_interface_enable(dif_usbdev_t *usbdev,
dif_usbdev_toggle_t new_state) {
if (usbdev == NULL || !is_valid_toggle(new_state)) {
return kDifUsbdevBadArg;
}
if (new_state == kDifUsbdevToggleEnable) {
mmio_region_nonatomic_set_bit32(usbdev->base_addr,
USBDEV_USBCTRL_REG_OFFSET,
USBDEV_USBCTRL_ENABLE_BIT);
} else {
mmio_region_nonatomic_clear_bit32(usbdev->base_addr,
USBDEV_USBCTRL_REG_OFFSET,
USBDEV_USBCTRL_ENABLE_BIT);
}
return kDifUsbdevOK;
}
dif_usbdev_recv_result_t dif_usbdev_recv(dif_usbdev_t *usbdev,
dif_usbdev_rx_packet_info_t *info,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || info == NULL || buffer == NULL) {
return kDifUsbdevRecvResultBadArg;
}
// Check if the RX FIFO is empty
if (mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_RX_EMPTY_BIT)) {
return kDifUsbdevRecvResultNoNewPacket;
}
// Read fifo entry
const uint32_t fifo_entry =
mmio_region_read32(usbdev->base_addr, USBDEV_RXFIFO_REG_OFFSET);
// Init packet info
*info = (dif_usbdev_rx_packet_info_t){
.endpoint = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_EP_FIELD),
.is_setup = bitfield_bit32_read(fifo_entry, USBDEV_RXFIFO_SETUP_BIT),
.length = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_SIZE_FIELD),
};
// Init buffer struct
*buffer = (dif_usbdev_buffer_t){
.id = bitfield_field32_read(fifo_entry, USBDEV_RXFIFO_BUFFER_FIELD),
.offset = 0,
.remaining_bytes = info->length,
.type = kDifUsbdevBufferTypeRead,
};
return kDifUsbdevRecvResultOK;
}
dif_usbdev_buffer_request_result_t dif_usbdev_buffer_request(
dif_usbdev_t *usbdev, dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || buffer == NULL) {
return kDifUsbdevBufferRequestResultBadArg;
}
if (buffer_pool_is_empty(&usbdev->buffer_pool)) {
return kDifUsbdevBufferRequestResultNoBuffers;
}
uint8_t buffer_id;
if (!buffer_pool_remove(&usbdev->buffer_pool, &buffer_id)) {
return kDifUsbdevBufferRequestResultError;
}
*buffer = (dif_usbdev_buffer_t){
.id = buffer_id,
.offset = 0,
.remaining_bytes = USBDEV_BUFFER_ENTRY_SIZE_BYTES,
.type = kDifUsbdevBufferTypeWrite,
};
return kDifUsbdevBufferRequestResultOK;
}
dif_usbdev_result_t dif_usbdev_buffer_return(dif_usbdev_t *usbdev,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || buffer == NULL) {
return kDifUsbdevBadArg;
}
switch (buffer->type) {
case kDifUsbdevBufferTypeRead:
case kDifUsbdevBufferTypeWrite:
// Return the buffer to the free buffer pool
if (!buffer_pool_add(&usbdev->buffer_pool, buffer->id)) {
return kDifUsbdevError;
}
// Mark the buffer as stale
buffer->type = kDifUsbdevBufferTypeStale;
return kDifUsbdevOK;
default:
return kDifUsbdevBadArg;
}
}
dif_usbdev_buffer_read_result_t dif_usbdev_buffer_read(
dif_usbdev_t *usbdev, dif_usbdev_buffer_t *buffer, uint8_t *dst,
size_t dst_len, size_t *bytes_written) {
if (usbdev == NULL || buffer == NULL ||
buffer->type != kDifUsbdevBufferTypeRead || dst == NULL) {
return kDifUsbdevBufferReadResultBadArg;
}
// bytes_to_copy is the minimum of remaining_bytes and dst_len
size_t bytes_to_copy = buffer->remaining_bytes;
if (bytes_to_copy > dst_len) {
bytes_to_copy = dst_len;
}
// Copy from buffer to dst
const uint32_t buffer_addr = get_buffer_addr(buffer->id, buffer->offset);
mmio_region_memcpy_from_mmio32(usbdev->base_addr, buffer_addr, dst,
bytes_to_copy);
// Update buffer state
buffer->offset += bytes_to_copy;
buffer->remaining_bytes -= bytes_to_copy;
if (bytes_written != NULL) {
*bytes_written = bytes_to_copy;
}
// Check if there are any remaining bytes
if (buffer->remaining_bytes > 0) {
return kDifUsbdevBufferReadResultContinue;
}
// Return the buffer to the free buffer pool
if (!buffer_pool_add(&usbdev->buffer_pool, buffer->id)) {
return kDifUsbdevBufferReadResultError;
}
// Mark the buffer as stale
buffer->type = kDifUsbdevBufferTypeStale;
return kDifUsbdevBufferReadResultOK;
}
dif_usbdev_buffer_write_result_t dif_usbdev_buffer_write(
dif_usbdev_t *usbdev, dif_usbdev_buffer_t *buffer, uint8_t *src,
size_t src_len, size_t *bytes_written) {
if (usbdev == NULL || buffer == NULL ||
buffer->type != kDifUsbdevBufferTypeWrite || src == NULL) {
return kDifUsbdevBufferWriteResultBadArg;
}
// bytes_to_copy is the minimum of remaining_bytes and src_len.
size_t bytes_to_copy = buffer->remaining_bytes;
if (bytes_to_copy > src_len) {
bytes_to_copy = src_len;
}
// Write bytes to the buffer
uint32_t buffer_addr = get_buffer_addr(buffer->id, buffer->offset);
mmio_region_memcpy_to_mmio32(usbdev->base_addr, buffer_addr, src,
bytes_to_copy);
buffer->offset += bytes_to_copy;
buffer->remaining_bytes -= bytes_to_copy;
if (bytes_written) {
*bytes_written = bytes_to_copy;
}
if (buffer->remaining_bytes == 0 && bytes_to_copy < src_len) {
return kDifUsbdevBufferWriteResultFull;
}
return kDifUsbdevBufferWriteResultOK;
}
dif_usbdev_result_t dif_usbdev_send(dif_usbdev_t *usbdev, uint8_t endpoint,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || !is_valid_endpoint(endpoint) || buffer == NULL ||
buffer->type != kDifUsbdevBufferTypeWrite) {
return kDifUsbdevBadArg;
}
// Get the configin register offset of the endpoint.
const uint32_t config_in_reg_offset =
kEndpointHwInfos[endpoint].config_in_reg_offset;
// Configure USBDEV_CONFIGINX register.
// Note: Using mask and offset values for the USBDEV_CONFIGIN0 register
// for all endpoints because all USBDEV_CONFIGINX registers have the same
// layout.
uint32_t config_in_val =
mmio_region_read32(usbdev->base_addr, config_in_reg_offset);
config_in_val = bitfield_field32_write(
config_in_val, USBDEV_CONFIGIN_0_BUFFER_0_FIELD, buffer->id);
config_in_val = bitfield_field32_write(
config_in_val, USBDEV_CONFIGIN_0_SIZE_0_FIELD, buffer->offset);
mmio_region_write32(usbdev->base_addr, config_in_reg_offset, config_in_val);
// Mark the packet as ready for transmission
mmio_region_nonatomic_set_bit32(usbdev->base_addr, config_in_reg_offset,
USBDEV_CONFIGIN_0_RDY_0_BIT);
// Mark the buffer as stale. It will be returned to the free buffer pool
// in dif_usbdev_get_tx_status once transmission is complete.
buffer->type = kDifUsbdevBufferTypeStale;
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_get_tx_status(dif_usbdev_t *usbdev,
uint8_t endpoint,
dif_usbdev_tx_status_t *status) {
if (usbdev == NULL || status == NULL || !is_valid_endpoint(endpoint)) {
return kDifUsbdevBadArg;
}
// Get the configin register offset and bit index of the endpoint
uint32_t config_in_reg_offset =
kEndpointHwInfos[endpoint].config_in_reg_offset;
uint8_t endpoint_bit_index = kEndpointHwInfos[endpoint].bit_index;
// Read the configin register
uint32_t config_in_val =
mmio_region_read32(usbdev->base_addr, config_in_reg_offset);
// Buffer used by this endpoint
uint8_t buffer =
bitfield_field32_read(config_in_val, USBDEV_CONFIGIN_0_BUFFER_0_FIELD);
// Check the status of the packet
if (bitfield_field32_read(config_in_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_CONFIGIN_0_RDY_0_BIT,
})) {
// Packet is marked as ready to be sent and pending transmission
*status = kDifUsbdevTxStatusPending;
} else if (mmio_region_get_bit32(usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET,
endpoint_bit_index)) {
// Packet was sent successfully
// Clear IN_SENT bit (rw1c)
mmio_region_write_only_set_bit32(
usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET, endpoint_bit_index);
// Return the buffer back to the free buffer pool
if (!buffer_pool_add(&usbdev->buffer_pool, buffer)) {
return kDifUsbdevError;
}
*status = kDifUsbdevTxStatusSent;
} else if (bitfield_field32_read(config_in_val,
(bitfield_field32_t){
.mask = 1,
.index = USBDEV_CONFIGIN_0_PEND_0_BIT,
})) {
// Canceled due to an IN SETUP packet or link reset
// Clear pending bit (rw1c)
mmio_region_write_only_set_bit32(usbdev->base_addr, config_in_reg_offset,
USBDEV_CONFIGIN_0_PEND_0_BIT);
// Return the buffer back to the free buffer pool
if (!buffer_pool_add(&usbdev->buffer_pool, buffer)) {
return kDifUsbdevError;
}
*status = kDifUsbdevTxStatusCancelled;
} else {
// No packet has been queued for this endpoint
*status = kDifUsbdevTxStatusNoPacket;
}
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_address_set(dif_usbdev_t *usbdev, uint8_t addr) {
if (usbdev == NULL) {
return kDifUsbdevBadArg;
}
mmio_region_nonatomic_set_field32(usbdev->base_addr,
USBDEV_USBCTRL_REG_OFFSET,
USBDEV_USBCTRL_DEVICE_ADDRESS_FIELD, addr);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_address_get(dif_usbdev_t *usbdev,
uint8_t *addr) {
if (usbdev == NULL || addr == NULL) {
return kDifUsbdevBadArg;
}
// Note: Size of address is 7 bits.
*addr = mmio_region_read_mask32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET,
USBDEV_USBCTRL_DEVICE_ADDRESS_MASK,
USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_frame(dif_usbdev_t *usbdev,
uint16_t *frame_index) {
if (usbdev == NULL || frame_index == NULL) {
return kDifUsbdevBadArg;
}
// Note: size of frame index is 11 bits.
*frame_index = mmio_region_read_mask32(
usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET, USBDEV_USBSTAT_FRAME_MASK,
USBDEV_USBSTAT_FRAME_OFFSET);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_host_lost(dif_usbdev_t *usbdev,
bool *host_lost) {
if (usbdev == NULL || host_lost == NULL) {
return kDifUsbdevBadArg;
}
*host_lost =
mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_HOST_LOST_BIT);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_link_state(
dif_usbdev_t *usbdev, dif_usbdev_link_state_t *link_state) {
if (usbdev == NULL || link_state == NULL) {
return kDifUsbdevBadArg;
}
uint32_t val = mmio_region_read_mask32(
usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_LINK_STATE_MASK, USBDEV_USBSTAT_LINK_STATE_OFFSET);
switch (val) {
case USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECT:
*link_state = kDifUsbdevLinkStateDisconnected;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED:
*link_state = kDifUsbdevLinkStatePowered;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPEND:
*link_state = kDifUsbdevLinkStatePoweredSuspend;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE:
*link_state = kDifUsbdevLinkStateActive;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPEND:
*link_state = kDifUsbdevLinkStateSuspend;
break;
default:
return kDifUsbdevError;
}
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_sense(dif_usbdev_t *usbdev,
bool *sense) {
if (usbdev == NULL || sense == NULL) {
return kDifUsbdevBadArg;
}
*sense = mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_SENSE_BIT);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_available_fifo_depth(
dif_usbdev_t *usbdev, uint8_t *depth) {
if (usbdev == NULL || depth == NULL) {
return kDifUsbdevBadArg;
}
// Note: Size of available FIFO depth is 3 bits.
*depth = mmio_region_read_mask32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_AV_DEPTH_MASK,
USBDEV_USBSTAT_AV_DEPTH_OFFSET);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_available_fifo_full(
dif_usbdev_t *usbdev, bool *is_full) {
if (usbdev == NULL || is_full == NULL) {
return kDifUsbdevBadArg;
}
*is_full = mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_AV_FULL_BIT);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_rx_fifo_depth(dif_usbdev_t *usbdev,
uint8_t *depth) {
if (usbdev == NULL || depth == NULL) {
return kDifUsbdevBadArg;
}
// Note: Size of RX FIFO depth is 3 bits.
*depth = mmio_region_read_mask32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_RX_DEPTH_MASK,
USBDEV_USBSTAT_RX_DEPTH_OFFSET);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_status_get_rx_fifo_empty(dif_usbdev_t *usbdev,
bool *is_full) {
if (usbdev == NULL || is_full == NULL) {
return kDifUsbdevBadArg;
}
*is_full = mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_RX_EMPTY_BIT);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_enable(dif_usbdev_t *usbdev,
dif_usbdev_irq_t irq,
dif_usbdev_toggle_t state) {
if (usbdev == NULL || !is_valid_irq(irq) || !is_valid_toggle(state)) {
return kDifUsbdevBadArg;
}
if (state == kDifUsbdevToggleEnable) {
mmio_region_nonatomic_set_bit32(usbdev->base_addr,
USBDEV_INTR_ENABLE_REG_OFFSET,
kIrqEnumToBitIndex[irq]);
} else {
mmio_region_nonatomic_clear_bit32(usbdev->base_addr,
USBDEV_INTR_ENABLE_REG_OFFSET,
kIrqEnumToBitIndex[irq]);
}
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_get(dif_usbdev_t *usbdev,
dif_usbdev_irq_t irq, bool *state) {
if (usbdev == NULL || state == NULL || !is_valid_irq(irq)) {
return kDifUsbdevBadArg;
}
*state = mmio_region_get_bit32(
usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET, kIrqEnumToBitIndex[irq]);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_clear(dif_usbdev_t *usbdev,
dif_usbdev_irq_t irq) {
if (usbdev == NULL || !is_valid_irq(irq)) {
return kDifUsbdevBadArg;
}
mmio_region_write_only_set_bit32(
usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET, kIrqEnumToBitIndex[irq]);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_clear_all(dif_usbdev_t *usbdev) {
if (usbdev == NULL) {
return kDifUsbdevBadArg;
}
mmio_region_write32(usbdev->base_addr, USBDEV_INTR_STATE_REG_OFFSET,
UINT32_MAX);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_disable_all(dif_usbdev_t *usbdev,
uint32_t *cur_config) {
if (usbdev == NULL) {
return kDifUsbdevBadArg;
}
if (cur_config != NULL) {
*cur_config =
mmio_region_read32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET);
}
mmio_region_write32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET, 0);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_restore(dif_usbdev_t *usbdev,
uint32_t new_config) {
if (usbdev == NULL) {
return kDifUsbdevBadArg;
}
mmio_region_write32(usbdev->base_addr, USBDEV_INTR_ENABLE_REG_OFFSET,
new_config);
return kDifUsbdevOK;
}
dif_usbdev_result_t dif_usbdev_irq_test(dif_usbdev_t *usbdev,
dif_usbdev_irq_t irq) {
if (usbdev == NULL || !is_valid_irq(irq)) {
return kDifUsbdevBadArg;
}
mmio_region_write_only_set_bit32(
usbdev->base_addr, USBDEV_INTR_TEST_REG_OFFSET, kIrqEnumToBitIndex[irq]);
return kDifUsbdevOK;
}