blob: 3e5105ff7a36963c5bdbdfbd72f44958c8781409 [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 <assert.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_N_ENDPOINTS,
"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
/**
* 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.
*/
OT_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.
*/
OT_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.
*/
OT_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.
*/
OT_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.
*/
OT_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.
*/
OT_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 endpoint number.
*/
OT_WARN_UNUSED_RESULT
static bool is_valid_endpoint(uint8_t endpoint_number) {
return endpoint_number < USBDEV_NUM_ENDPOINTS;
}
/**
* Enables/disables the functionality controlled by the register at `reg_offset`
* for an endpoint.
*/
OT_WARN_UNUSED_RESULT
static dif_result_t endpoint_functionality_enable(const dif_usbdev_t *usbdev,
uint32_t reg_offset,
uint8_t endpoint,
dif_toggle_t new_state) {
if (usbdev == NULL || !is_valid_endpoint(endpoint) ||
!dif_is_valid_toggle(new_state)) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(usbdev->base_addr, reg_offset);
reg_val = bitfield_bit32_write(reg_val, kEndpointHwInfos[endpoint].bit_index,
dif_toggle_to_bool(new_state));
mmio_region_write32(usbdev->base_addr, reg_offset, reg_val);
return kDifOk;
}
/**
* Returns the address that corresponds to the given buffer and offset
* into that buffer.
*/
OT_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_result_t dif_usbdev_configure(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_pool_t *buffer_pool,
dif_usbdev_config_t config) {
if (usbdev == NULL || buffer_pool == NULL) {
return kDifBadArg;
}
// Configure the free buffer pool.
if (!buffer_pool_init(buffer_pool)) {
return kDifError;
}
// Check enum fields.
if (!dif_is_valid_toggle(config.have_differential_receiver) ||
!dif_is_valid_toggle(config.use_tx_d_se0) ||
!dif_is_valid_toggle(config.single_bit_eop) ||
!dif_is_valid_toggle(config.pin_flip) ||
!dif_is_valid_toggle(config.clock_sync_signals)) {
return kDifBadArg;
}
// Determine the value of the PHY_CONFIG register.
uint32_t phy_config_val = 0;
phy_config_val = bitfield_bit32_write(
phy_config_val, USBDEV_PHY_CONFIG_USE_DIFF_RCVR_BIT,
dif_toggle_to_bool(config.have_differential_receiver));
phy_config_val =
bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_TX_USE_D_SE0_BIT,
dif_toggle_to_bool(config.use_tx_d_se0));
phy_config_val =
bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT,
dif_toggle_to_bool(config.single_bit_eop));
phy_config_val =
bitfield_bit32_write(phy_config_val, USBDEV_PHY_CONFIG_PINFLIP_BIT,
dif_toggle_to_bool(config.pin_flip));
phy_config_val = bitfield_bit32_write(
phy_config_val, USBDEV_PHY_CONFIG_USB_REF_DISABLE_BIT,
!dif_toggle_to_bool(config.clock_sync_signals));
// Write configuration to PHY_CONFIG register
mmio_region_write32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET,
phy_config_val);
return kDifOk;
}
dif_result_t dif_usbdev_fill_available_fifo(
const dif_usbdev_t *usbdev, dif_usbdev_buffer_pool_t *buffer_pool) {
if (usbdev == NULL || buffer_pool == NULL) {
return kDifBadArg;
}
// Remove buffers from the pool and write them to the AV FIFO until it is full
while (true) {
uint32_t status =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
bool av_full = bitfield_bit32_read(status, USBDEV_USBSTAT_AV_FULL_BIT);
if (av_full || buffer_pool_is_empty(buffer_pool)) {
break;
}
uint8_t buffer_id;
if (!buffer_pool_remove(buffer_pool, &buffer_id)) {
return kDifError;
}
uint32_t reg_val =
bitfield_field32_write(0, USBDEV_AVBUFFER_BUFFER_FIELD, buffer_id);
mmio_region_write32(usbdev->base_addr, USBDEV_AVBUFFER_REG_OFFSET, reg_val);
}
return kDifOk;
}
dif_result_t dif_usbdev_endpoint_setup_enable(const dif_usbdev_t *usbdev,
uint8_t endpoint,
dif_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_SETUP_REG_OFFSET,
endpoint, new_state);
}
dif_result_t dif_usbdev_endpoint_out_enable(const dif_usbdev_t *usbdev,
uint8_t endpoint,
dif_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_RXENABLE_OUT_REG_OFFSET,
endpoint, new_state);
}
dif_result_t dif_usbdev_endpoint_set_nak_out_enable(const dif_usbdev_t *usbdev,
uint8_t endpoint,
dif_toggle_t new_state) {
return endpoint_functionality_enable(usbdev, USBDEV_SET_NAK_OUT_REG_OFFSET,
endpoint, new_state);
}
dif_result_t dif_usbdev_endpoint_stall_enable(const dif_usbdev_t *usbdev,
dif_usbdev_endpoint_id_t endpoint,
dif_toggle_t new_state) {
if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) {
return endpoint_functionality_enable(usbdev, USBDEV_IN_STALL_REG_OFFSET,
endpoint.number, new_state);
} else {
return endpoint_functionality_enable(usbdev, USBDEV_OUT_STALL_REG_OFFSET,
endpoint.number, new_state);
}
}
dif_result_t dif_usbdev_endpoint_stall_get(const dif_usbdev_t *usbdev,
dif_usbdev_endpoint_id_t endpoint,
bool *state) {
if (usbdev == NULL || state == NULL || !is_valid_endpoint(endpoint.number)) {
return kDifBadArg;
}
ptrdiff_t reg_offset = endpoint.direction == USBDEV_ENDPOINT_DIR_IN
? USBDEV_IN_STALL_REG_OFFSET
: USBDEV_OUT_STALL_REG_OFFSET;
uint32_t reg_val = mmio_region_read32(usbdev->base_addr, reg_offset);
*state =
bitfield_bit32_read(reg_val, kEndpointHwInfos[endpoint.number].bit_index);
return kDifOk;
}
dif_result_t dif_usbdev_endpoint_iso_enable(const dif_usbdev_t *usbdev,
dif_usbdev_endpoint_id_t endpoint,
dif_toggle_t new_state) {
if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) {
return endpoint_functionality_enable(usbdev, USBDEV_IN_ISO_REG_OFFSET,
endpoint.number, new_state);
} else {
return endpoint_functionality_enable(usbdev, USBDEV_OUT_ISO_REG_OFFSET,
endpoint.number, new_state);
}
}
dif_result_t dif_usbdev_endpoint_enable(const dif_usbdev_t *usbdev,
dif_usbdev_endpoint_id_t endpoint,
dif_toggle_t new_state) {
if (endpoint.direction == USBDEV_ENDPOINT_DIR_IN) {
return endpoint_functionality_enable(usbdev, USBDEV_EP_IN_ENABLE_REG_OFFSET,
endpoint.number, new_state);
} else {
return endpoint_functionality_enable(
usbdev, USBDEV_EP_OUT_ENABLE_REG_OFFSET, endpoint.number, new_state);
}
return kDifOk;
}
dif_result_t dif_usbdev_interface_enable(const dif_usbdev_t *usbdev,
dif_toggle_t new_state) {
if (usbdev == NULL || !dif_is_valid_toggle(new_state)) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET);
reg_val = bitfield_bit32_write(reg_val, USBDEV_USBCTRL_ENABLE_BIT,
dif_toggle_to_bool(new_state));
mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val);
return kDifOk;
}
dif_result_t dif_usbdev_recv(const dif_usbdev_t *usbdev,
dif_usbdev_rx_packet_info_t *info,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || info == NULL || buffer == NULL) {
return kDifBadArg;
}
// Check if the RX FIFO is empty
uint32_t fifo_status =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
if (bitfield_bit32_read(fifo_status, USBDEV_USBSTAT_RX_EMPTY_BIT)) {
return kDifUnavailable;
}
// 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 kDifOk;
}
dif_result_t dif_usbdev_buffer_request(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_pool_t *buffer_pool,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || buffer_pool == NULL || buffer == NULL) {
return kDifBadArg;
}
if (buffer_pool_is_empty(buffer_pool)) {
return kDifUnavailable;
}
uint8_t buffer_id;
if (!buffer_pool_remove(buffer_pool, &buffer_id)) {
return kDifError;
}
*buffer = (dif_usbdev_buffer_t){
.id = buffer_id,
.offset = 0,
.remaining_bytes = USBDEV_BUFFER_ENTRY_SIZE_BYTES,
.type = kDifUsbdevBufferTypeWrite,
};
return kDifOk;
}
dif_result_t dif_usbdev_buffer_return(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_pool_t *buffer_pool,
dif_usbdev_buffer_t *buffer) {
if (usbdev == NULL || buffer_pool == NULL || buffer == NULL) {
return kDifBadArg;
}
switch (buffer->type) {
case kDifUsbdevBufferTypeRead:
case kDifUsbdevBufferTypeWrite:
// Return the buffer to the free buffer pool
if (!buffer_pool_add(buffer_pool, buffer->id)) {
return kDifError;
}
// Mark the buffer as stale
buffer->type = kDifUsbdevBufferTypeStale;
return kDifOk;
default:
return kDifBadArg;
}
}
dif_result_t dif_usbdev_buffer_read(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_pool_t *buffer_pool,
dif_usbdev_buffer_t *buffer, uint8_t *dst,
size_t dst_len, size_t *bytes_written) {
if (usbdev == NULL || buffer_pool == NULL || buffer == NULL ||
buffer->type != kDifUsbdevBufferTypeRead || dst == NULL) {
return kDifBadArg;
}
// 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 kDifOk;
}
// Return the buffer to the free buffer pool
if (!buffer_pool_add(buffer_pool, buffer->id)) {
return kDifError;
}
// Mark the buffer as stale
buffer->type = kDifUsbdevBufferTypeStale;
return kDifOk;
}
dif_result_t dif_usbdev_buffer_write(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_t *buffer,
const uint8_t *src, size_t src_len,
size_t *bytes_written) {
if (usbdev == NULL || buffer == NULL ||
buffer->type != kDifUsbdevBufferTypeWrite || src == NULL) {
return kDifBadArg;
}
// 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 kDifError;
}
return kDifOk;
}
dif_result_t dif_usbdev_send(const 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 kDifBadArg;
}
// 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 = 0;
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
config_in_val =
bitfield_bit32_write(config_in_val, USBDEV_CONFIGIN_0_RDY_0_BIT, true);
mmio_region_write32(usbdev->base_addr, config_in_reg_offset, config_in_val);
// 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 kDifOk;
}
dif_result_t dif_usbdev_get_tx_sent(const dif_usbdev_t *usbdev,
uint16_t *sent) {
if (usbdev == NULL || sent == NULL) {
return kDifBadArg;
}
*sent = mmio_region_read32(usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET);
return kDifOk;
}
dif_result_t dif_usbdev_clear_tx_status(const dif_usbdev_t *usbdev,
dif_usbdev_buffer_pool_t *buffer_pool,
uint8_t endpoint) {
if (usbdev == NULL || buffer_pool == NULL || !is_valid_endpoint(endpoint)) {
return kDifBadArg;
}
// Get the configin register offset and bit index of the endpoint.
uint32_t config_in_reg_offset =
kEndpointHwInfos[endpoint].config_in_reg_offset;
uint32_t config_in_reg_val =
mmio_region_read32(usbdev->base_addr, config_in_reg_offset);
uint8_t buffer = bitfield_field32_read(config_in_reg_val,
USBDEV_CONFIGIN_0_BUFFER_0_FIELD);
mmio_region_write32(usbdev->base_addr, config_in_reg_offset,
1u << USBDEV_CONFIGIN_0_PEND_0_BIT);
// Clear IN_SENT bit (rw1c).
mmio_region_write32(usbdev->base_addr, USBDEV_IN_SENT_REG_OFFSET,
1u << endpoint);
// Return the buffer back to the free buffer pool.
if (!buffer_pool_add(buffer_pool, buffer)) {
return kDifError;
}
return kDifOk;
}
dif_result_t dif_usbdev_get_tx_status(const dif_usbdev_t *usbdev,
uint8_t endpoint,
dif_usbdev_tx_status_t *status) {
if (usbdev == NULL || status == NULL || !is_valid_endpoint(endpoint)) {
return kDifBadArg;
}
// 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);
// Check the status of the packet.
if (bitfield_bit32_read(config_in_val, USBDEV_CONFIGIN_0_RDY_0_BIT)) {
// Packet is marked as ready to be sent and pending transmission.
*status = kDifUsbdevTxStatusPending;
} else if (bitfield_bit32_read(mmio_region_read32(usbdev->base_addr,
USBDEV_IN_SENT_REG_OFFSET),
endpoint_bit_index)) {
// Packet was sent successfully.
*status = kDifUsbdevTxStatusSent;
} else if (bitfield_bit32_read(config_in_val, USBDEV_CONFIGIN_0_PEND_0_BIT)) {
// Canceled due to an IN SETUP packet or link reset.
*status = kDifUsbdevTxStatusCancelled;
} else {
// No packet has been queued for this endpoint.
*status = kDifUsbdevTxStatusNoPacket;
}
return kDifOk;
}
dif_result_t dif_usbdev_address_set(const dif_usbdev_t *usbdev, uint8_t addr) {
if (usbdev == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET);
reg_val = bitfield_field32_write(reg_val, USBDEV_USBCTRL_DEVICE_ADDRESS_FIELD,
addr);
mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val);
return kDifOk;
}
dif_result_t dif_usbdev_address_get(const dif_usbdev_t *usbdev, uint8_t *addr) {
if (usbdev == NULL || addr == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET);
// Note: Size of address is 7 bits.
*addr = bitfield_field32_read(reg_val, USBDEV_USBCTRL_DEVICE_ADDRESS_FIELD);
return kDifOk;
}
dif_result_t dif_usbdev_clear_data_toggle(const dif_usbdev_t *usbdev,
uint8_t endpoint) {
if (usbdev == NULL) {
return kDifBadArg;
}
mmio_region_write32(usbdev->base_addr, USBDEV_DATA_TOGGLE_CLEAR_REG_OFFSET,
1u << endpoint);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_frame(const dif_usbdev_t *usbdev,
uint16_t *frame_index) {
if (usbdev == NULL || frame_index == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
// Note: size of frame index is 11 bits.
*frame_index = bitfield_field32_read(reg_val, USBDEV_USBSTAT_FRAME_FIELD);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_host_lost(const dif_usbdev_t *usbdev,
bool *host_lost) {
if (usbdev == NULL || host_lost == NULL) {
return kDifBadArg;
}
*host_lost =
mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_HOST_LOST_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_link_state(
const dif_usbdev_t *usbdev, dif_usbdev_link_state_t *link_state) {
if (usbdev == NULL || link_state == NULL) {
return kDifBadArg;
}
uint32_t val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
val = bitfield_field32_read(val, USBDEV_USBSTAT_LINK_STATE_FIELD);
switch (val) {
case USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECTED:
*link_state = kDifUsbdevLinkStateDisconnected;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED:
*link_state = kDifUsbdevLinkStatePowered;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPENDED:
*link_state = kDifUsbdevLinkStatePoweredSuspended;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE:
*link_state = kDifUsbdevLinkStateActive;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPENDED:
*link_state = kDifUsbdevLinkStateSuspended;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF:
*link_state = kDifUsbdevLinkStateActiveNoSof;
break;
case USBDEV_USBSTAT_LINK_STATE_VALUE_RESUMING:
*link_state = kDifUsbdevLinkStateResuming;
break;
default:
return kDifError;
}
return kDifOk;
}
dif_result_t dif_usbdev_status_get_sense(const dif_usbdev_t *usbdev,
bool *sense) {
if (usbdev == NULL || sense == NULL) {
return kDifBadArg;
}
*sense = mmio_region_get_bit32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET,
USBDEV_USBSTAT_SENSE_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_available_fifo_depth(
const dif_usbdev_t *usbdev, uint8_t *depth) {
if (usbdev == NULL || depth == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
// Note: Size of available FIFO depth is 3 bits.
*depth = bitfield_field32_read(reg_val, USBDEV_USBSTAT_AV_DEPTH_FIELD);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_available_fifo_full(
const dif_usbdev_t *usbdev, bool *is_full) {
if (usbdev == NULL || is_full == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
*is_full = bitfield_bit32_read(reg_val, USBDEV_USBSTAT_AV_FULL_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_rx_fifo_depth(const dif_usbdev_t *usbdev,
uint8_t *depth) {
if (usbdev == NULL || depth == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
// Note: Size of RX FIFO depth is 3 bits.
*depth = bitfield_field32_read(reg_val, USBDEV_USBSTAT_RX_DEPTH_FIELD);
return kDifOk;
}
dif_result_t dif_usbdev_status_get_rx_fifo_empty(const dif_usbdev_t *usbdev,
bool *is_empty) {
if (usbdev == NULL || is_empty == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBSTAT_REG_OFFSET);
*is_empty = bitfield_bit32_read(reg_val, USBDEV_USBSTAT_RX_EMPTY_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_set_osc_test_mode(const dif_usbdev_t *usbdev,
dif_toggle_t enable) {
if (usbdev == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
bool set_tx_osc_mode = dif_toggle_to_bool(enable);
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET);
reg_val = bitfield_bit32_write(
reg_val, USBDEV_PHY_CONFIG_TX_OSC_TEST_MODE_BIT, set_tx_osc_mode);
mmio_region_write32(usbdev->base_addr, USBDEV_PHY_CONFIG_REG_OFFSET, reg_val);
return kDifOk;
}
dif_result_t dif_usbdev_set_wake_enable(const dif_usbdev_t *usbdev,
dif_toggle_t enable) {
if (usbdev == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
uint32_t reg_val;
if (dif_toggle_to_bool(enable)) {
reg_val =
bitfield_bit32_write(0, USBDEV_WAKE_CONTROL_SUSPEND_REQ_BIT, true);
} else {
reg_val = bitfield_bit32_write(0, USBDEV_WAKE_CONTROL_WAKE_ACK_BIT, true);
}
mmio_region_write32(usbdev->base_addr, USBDEV_WAKE_CONTROL_REG_OFFSET,
reg_val);
return kDifOk;
}
dif_result_t dif_usbdev_get_wake_status(const dif_usbdev_t *usbdev,
dif_usbdev_wake_status_t *status) {
if (usbdev == NULL || status == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_WAKE_EVENTS_REG_OFFSET);
status->active =
bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_MODULE_ACTIVE_BIT);
status->disconnected =
bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_DISCONNECTED_BIT);
status->bus_reset =
bitfield_bit32_read(reg_val, USBDEV_WAKE_EVENTS_BUS_RESET_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_resume_link_to_active(const dif_usbdev_t *usbdev) {
if (usbdev == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET);
reg_val = bitfield_bit32_write(reg_val, USBDEV_USBCTRL_RESUME_LINK_ACTIVE_BIT,
true);
mmio_region_write32(usbdev->base_addr, USBDEV_USBCTRL_REG_OFFSET, reg_val);
return kDifOk;
}
dif_result_t dif_usbdev_get_phy_pins_status(
const dif_usbdev_t *usbdev, dif_usbdev_phy_pins_sense_t *status) {
if (usbdev == NULL || status == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(usbdev->base_addr, USBDEV_PHY_PINS_SENSE_REG_OFFSET);
status->rx_dp =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_DP_I_BIT);
status->rx_dn =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_DN_I_BIT);
status->rx_d = bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_RX_D_I_BIT);
status->tx_dp =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_DP_O_BIT);
status->tx_dn =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_DN_O_BIT);
status->tx_d = bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_D_O_BIT);
status->tx_se0 =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_SE0_O_BIT);
status->output_enable =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_TX_OE_O_BIT);
status->vbus_sense =
bitfield_bit32_read(reg_val, USBDEV_PHY_PINS_SENSE_PWR_SENSE_BIT);
return kDifOk;
}
dif_result_t dif_usbdev_set_phy_pins_state(
const dif_usbdev_t *usbdev, dif_toggle_t override_enable,
dif_usbdev_phy_pins_drive_t overrides) {
if (usbdev == NULL || !dif_is_valid_toggle(override_enable)) {
return kDifBadArg;
}
bool drive_en = dif_toggle_to_bool(override_enable);
uint32_t reg_val =
bitfield_bit32_write(0, USBDEV_PHY_PINS_DRIVE_EN_BIT, drive_en);
if (drive_en) {
reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DP_O_BIT,
overrides.dp);
reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DN_O_BIT,
overrides.dn);
reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_D_O_BIT,
overrides.data);
reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_SE0_O_BIT,
overrides.se0);
reg_val = bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_OE_O_BIT,
overrides.output_enable);
reg_val =
bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_RX_ENABLE_O_BIT,
overrides.diff_receiver_enable);
reg_val =
bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DP_PULLUP_EN_O_BIT,
overrides.dp_pullup_en);
reg_val =
bitfield_bit32_write(reg_val, USBDEV_PHY_PINS_DRIVE_DN_PULLUP_EN_O_BIT,
overrides.dn_pullup_en);
}
mmio_region_write32(usbdev->base_addr, USBDEV_PHY_PINS_DRIVE_REG_OFFSET,
reg_val);
return kDifOk;
}