diff options
Diffstat (limited to 'c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src')
4 files changed, 8611 insertions, 0 deletions
diff --git a/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_16550_uart.c b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_16550_uart.c new file mode 100644 index 0000000000..a5dfc5f22c --- /dev/null +++ b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_16550_uart.c @@ -0,0 +1,1179 @@ +/****************************************************************************** + * + * Copyright 2013 Altera Corporation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include "alt_16550_uart.h" +#include "alt_clock_manager.h" +#include "socal/alt_rstmgr.h" +#include "socal/alt_uart.h" +#include "socal/hps.h" +#include "socal/socal.h" + +///// + +#define ALT_16550_HANDLE_DATA_UART_ENABLED_MSK (1UL << 31) +#define ALT_16550_HANDLE_DATA_DIVISOR_VALUE_GET(value) (value & 0xffff) + +#define ALT_ALTERA_16550_CPR_OFST (0xF4) +#define ALT_ALTERA_16550_CPR_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_ALTERA_16550_CPR_OFST)) +#define ALT_ALTERA_16550_CPR_FIFO_MODE_GET(value) (((value) >> 16) & 0xff) +#define ALT_ALTERA_16550_CPR_AFCE_MODE_SET_MSK (1 << 4) + +///// + +// Remove these macros as part of case:123835. +#define ALT_UART_IER_DLH_VALUE_SET(value) ((value) & 0xff) +#define ALT_UART_IER_DLH_ETBEI_DLH1_SET_MSK ALT_UART_IER_DLH_ETBEI_DLHL_SET_MSK + +///// + +// +// Helper function which resets the UART and if requested, initializes the UART +// to the default settings. Currently the default settings are: +// - 8 databits +// - no parity +// - 1 stopbit +// - 57600 baudrate +// The reset routines depends on the hardware implementation of the UART. +// + +// This helper is needed because the regular alt_read_word(src) essentially +// resolves to "*(volatile uint32_t *)src". As there is no assignment, this +// could potentially be optimized away. With the helper, the actual register +// read should occur and be returned (and subsequently discarded). +static inline uint32_t alt_read_word_helper(const void * addr) +{ + return alt_read_word(addr); +} + +// +// Helper function write the divisor in hardware. +// +static ALT_STATUS_CODE alt_16550_write_divisor_helper(ALT_16550_HANDLE_t * handle, + uint32_t divisor) +{ + // Validate the divisor parameter. + if (divisor > 0xffff) + { + // This should never happen as it is verified in divisor_set. + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Set LCR::DLAB (Line Control Register :: Divisor Latch Access Bit) + alt_setbits_word(ALT_UART_LCR_ADDR(handle->location), ALT_UART_LCR_DLAB_SET_MSK); + + // Write DLL (Divisor Latch Low). + alt_write_word(ALT_UART_RBR_THR_DLL_ADDR(handle->location), ALT_UART_RBR_THR_DLL_VALUE_SET(divisor)); + + // Write DLH (Divisor Latch High). + alt_write_word(ALT_UART_IER_DLH_ADDR(handle->location), ALT_UART_IER_DLH_VALUE_SET(divisor >> 8)); + + // Clear LCR::DLAB (Line Control Register :: Divisor Latch Access Bit) + alt_clrbits_word(ALT_UART_LCR_ADDR(handle->location), ALT_UART_LCR_DLAB_SET_MSK); + + break; + + default: + return ALT_E_ERROR; + } + + // Update the enabled state in the handle data. + if (divisor != 0) + { + handle->data |= ALT_16550_HANDLE_DATA_UART_ENABLED_MSK; + } + else + { + handle->data &= ~ALT_16550_HANDLE_DATA_UART_ENABLED_MSK; + } + + return ALT_E_SUCCESS; +} + +// +// Helper function to reset the UART. +// +static ALT_STATUS_CODE alt_16550_reset_helper(ALT_16550_HANDLE_t * handle, bool enable_init) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Write SRR::UR (Shadow Reset Register :: UART Reset) + alt_write_word(ALT_UART_SRR_ADDR(handle->location), ALT_UART_SRR_UR_SET_MSK); + + // Read the MSR to work around case:119085. + alt_read_word_helper(ALT_UART_MSR_ADDR(handle->location)); + break; + + case ALT_16550_DEVICE_ALTERA_16550_UART: + alt_16550_write_divisor_helper(handle, 0); // Disable UART + alt_16550_int_disable_all(handle); // Disable interrupts + alt_16550_fifo_disable(handle); // Disable FIFOs + alt_write_word(ALT_UART_MCR_ADDR(handle->location), 0); // 0 -> MCR (AFCE, LP, OUT2, OUT1, RTS, DTR) + break; + + default: + return ALT_E_ERROR; + } + + // If we are initializing (as opposed to just uninitializing) + if (enable_init) + { + ALT_STATUS_CODE status; + + // Set bit IER::PTIME (Interrupt Enable Register :: Programmable THRE Mode Enable) + alt_setbits_word(ALT_UART_IER_DLH_ADDR(handle->location), ALT_UART_IER_DLH_PTIME_DLH7_SET_MSK); + + // Set the line configuration to use 8-N-1. + status = alt_16550_line_config_set(handle, ALT_16550_DATABITS_8, + ALT_16550_PARITY_DISABLE, + ALT_16550_STOPBITS_1); + if (status != ALT_E_SUCCESS) + { + return status; + } + + uint32_t divisor = ALT_16550_HANDLE_DATA_DIVISOR_VALUE_GET(handle->data); + if (divisor == 0) + { + // Set the default baudrate to 57600. + status = alt_16550_baudrate_set(handle, ALT_16550_BAUDRATE_57600); + if (status != ALT_E_SUCCESS) + { + return status; + } + } + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_init(ALT_16550_DEVICE_t device, + void * location, + alt_freq_t clock_freq, + ALT_16550_HANDLE_t * handle) +{ + handle->device = device; + handle->data = 0; + handle->fcr = 0; + + switch (device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // The ALT_CLK_L4_SP is required for all SoCFPGA UARTs. Check that it's enabled. + if (alt_clk_is_enabled(ALT_CLK_L4_SP) != ALT_E_TRUE) + { + return ALT_E_BAD_CLK; + } + else + { + ALT_STATUS_CODE status; + status = alt_clk_freq_get(ALT_CLK_L4_SP, &handle->clock_freq); + if (status != ALT_E_SUCCESS) + { + return status; + } + + if (device == ALT_16550_DEVICE_SOCFPGA_UART0) + { + handle->location = ALT_UART0_ADDR; + + // Bring UART0 out of reset. + alt_clrbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_UART0_SET_MSK); + } + else // device == ALT_16550_DEVICE_SOCFPGA_UART1 + { + handle->location = ALT_UART1_ADDR; + + // Bring UART1 out of reset. + alt_clrbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_UART1_SET_MSK); + } + + // Verify the UCR (UART Component Version) + uint32_t ucr = alt_read_word(ALT_UART_UCV_ADDR(handle->location)); + if (ucr != ALT_UART_UCV_UART_COMPONENT_VER_RESET) + { + return ALT_E_ERROR; + } + } + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + handle->location = location; + handle->clock_freq = clock_freq; + break; + default: + return ALT_E_BAD_ARG; + } + + return alt_16550_reset_helper(handle, true); +} + +ALT_STATUS_CODE alt_16550_uninit(ALT_16550_HANDLE_t * handle) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + alt_setbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_UART0_SET_MSK); + return ALT_E_SUCCESS; + case ALT_16550_DEVICE_SOCFPGA_UART1: + alt_setbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_UART1_SET_MSK); + return ALT_E_SUCCESS; + case ALT_16550_DEVICE_ALTERA_16550_UART: + default: + return alt_16550_reset_helper(handle, false); + } +} + +ALT_STATUS_CODE alt_16550_reset(ALT_16550_HANDLE_t * handle) +{ + return alt_16550_reset_helper(handle, true); +} + +ALT_STATUS_CODE alt_16550_enable(ALT_16550_HANDLE_t * handle) +{ + // Write the divisor cached in the handle data to the divisor registers. + // This will effectively enable the UART. + return alt_16550_write_divisor_helper(handle, + ALT_16550_HANDLE_DATA_DIVISOR_VALUE_GET(handle->data)); +} + +ALT_STATUS_CODE alt_16550_disable(ALT_16550_HANDLE_t * handle) +{ + // Write 0 to the divisor the divisor registers. This will effectively + // disable the UART. + return alt_16550_write_divisor_helper(handle, 0); +} + +ALT_STATUS_CODE alt_16550_read(ALT_16550_HANDLE_t * handle, + char * item) +{ + // Verify that the UART is enabled + if (!(handle->data & ALT_16550_HANDLE_DATA_UART_ENABLED_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that the FIFO is disabled + if (handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Read the RBR (Receive Buffer Register) into *item. + *item = ALT_UART_RBR_THR_DLL_VALUE_GET(alt_read_word(ALT_UART_RBR_THR_DLL_ADDR(handle->location))); + break; + default: + return ALT_E_ERROR; + } + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_write(ALT_16550_HANDLE_t * handle, + char item) +{ + // Verify that the UART is enabled + if (!(handle->data & ALT_16550_HANDLE_DATA_UART_ENABLED_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that the FIFO is disabled + if (handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Write the buffer into the THR (Transmit Holding Register) + alt_write_word(ALT_UART_RBR_THR_DLL_ADDR(handle->location), item); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +///// + +ALT_STATUS_CODE alt_16550_fifo_enable(ALT_16550_HANDLE_t * handle) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Set FCR::FIFOE (FIFO Control Register :: FIFO Enable) bit. + handle->fcr |= ALT_UART_FCR_FIFOE_SET_MSK; + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr); + break; + default: + return ALT_E_ERROR; + } + + // No need to reset / clear the FIFOs. This is done automatically when + // FCR::FIFOE is changed. + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_disable(ALT_16550_HANDLE_t * handle) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Clear FCR::FIFOE (FIFO Control Register :: FIFO Enable) bit. + handle->fcr &= ~ALT_UART_FCR_FIFOE_SET_MSK; + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_read(ALT_16550_HANDLE_t * handle, + char * buffer, + size_t count) +{ + // Verify that the UART is enabled + if (!(handle->data & ALT_16550_HANDLE_DATA_UART_ENABLED_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Read the RBR (Receive Buffer Register) into the buffer + for (size_t i = 0; i < count; ++i) + { + buffer[i] = ALT_UART_RBR_THR_DLL_VALUE_GET(alt_read_word(ALT_UART_RBR_THR_DLL_ADDR(handle->location))); + } + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_write(ALT_16550_HANDLE_t * handle, + const char * buffer, + size_t count) +{ + // Verify that the UART is enabled + if (!(handle->data & ALT_16550_HANDLE_DATA_UART_ENABLED_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Write the buffer into the THR (Transmit Holding Register) + for (size_t i = 0; i < count; ++i) + { + alt_write_word(ALT_UART_RBR_THR_DLL_ADDR(handle->location), buffer[i]); + } + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_clear_rx(ALT_16550_HANDLE_t * handle) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Write SRR::RFR (Shadow Reset Register :: Receiver FIFO Reset) bit. + alt_write_word(ALT_UART_SRR_ADDR(handle->location), ALT_UART_SRR_RFR_SET_MSK); + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Write FCR::RFIFOR (FIFO Control Register :: Receiver FIFO Reset) bit. + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr | ALT_UART_FCR_RFIFOR_SET_MSK); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_clear_tx(ALT_16550_HANDLE_t * handle) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Write SRR::XFR (Shadow Reset Register :: Xmitter FIFO Reset) bit. + alt_write_word(ALT_UART_SRR_ADDR(handle->location), ALT_UART_SRR_XFR_SET_MSK); + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Write FCR::XFIFOR (FIFO Control Register :: Xmitter FIFO Reset) bit. + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr | ALT_UART_FCR_XFIFOR_SET_MSK); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_clear_all(ALT_16550_HANDLE_t * handle) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Write SRR::(RFR | XFR) + // (Shadow Reset Register :: (Receiver FIFO Reset | Xmitter FIFO Reset)) bits. + alt_write_word(ALT_UART_SRR_ADDR(handle->location), + ALT_UART_SRR_RFR_SET_MSK | ALT_UART_SRR_XFR_SET_MSK); + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Write FCR::(RFIFOR |XFIFOR) + // (FIFO Control Register :: (Receiver FIFO Reset | Xmitter FIFO Reset)) bits. + alt_write_word(ALT_UART_FCR_ADDR(handle->location), + handle->fcr | ALT_UART_FCR_RFIFOR_SET_MSK | ALT_UART_FCR_XFIFOR_SET_MSK); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_size_get_rx(ALT_16550_HANDLE_t * handle, + uint32_t * size) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Read the CPR::FIFO_Mod (Component Parameter Register :: FIFO Mode). + // The FIFO size is 16x this value. + *size = ALT_UART_CPR_FIFO_MOD_GET(alt_read_word(ALT_UART_CPR_ADDR(handle->location))) << 4; + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Altera 16550 Compatible Soft UARTs have a configurable size and is + // stored in the CPR::FIFO_Mode (Component Parameter Register :: FIFO Depth). + *size = ALT_ALTERA_16550_CPR_FIFO_MODE_GET(alt_read_word(ALT_ALTERA_16550_CPR_ADDR(handle->location))) << 4; + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_size_get_tx(ALT_16550_HANDLE_t * handle, + uint32_t * size) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Read the CPR::FIFO_Mod (Component Parameter Register :: FIFO Mode). + // The FIFO size is 16x this value. + *size = ALT_UART_CPR_FIFO_MOD_GET(alt_read_word(ALT_UART_CPR_ADDR(handle->location))) << 4; + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Altera 16550 Compatible Soft UARTs have a configurable size and is + // stored in the CPR::FIFO_Mode (Component Parameter Register :: FIFO Depth). + // The FIFO size is 16x this value. + *size = ALT_ALTERA_16550_CPR_FIFO_MODE_GET(alt_read_word(ALT_ALTERA_16550_CPR_ADDR(handle->location))) << 4; + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_level_get_rx(ALT_16550_HANDLE_t * handle, + uint32_t * level) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Read RFL (Receive FIFO Level). + *level = alt_read_word(ALT_UART_RFL_ADDR(handle->location)); + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // RFL not implemented. Return 0. + *level = 0; + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_level_get_tx(ALT_16550_HANDLE_t * handle, + uint32_t * level) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + // Read TFL (Transmit FIFO Level). + *level = alt_read_word(ALT_UART_TFL_ADDR(handle->location)); + break; + case ALT_16550_DEVICE_ALTERA_16550_UART: + // TFL not implemented. Return 0. + *level = 0; + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_trigger_set_rx(ALT_16550_HANDLE_t * handle, + ALT_16550_FIFO_TRIGGER_RX_t trigger) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + // Verify triggering parameter + switch (trigger) + { + case ALT_16550_FIFO_TRIGGER_RX_ANY: + case ALT_16550_FIFO_TRIGGER_RX_QUARTER_FULL: + case ALT_16550_FIFO_TRIGGER_RX_HALF_FULL: + case ALT_16550_FIFO_TRIGGER_RX_ALMOST_FULL: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Update FCR::RT (FIFO Control Register :: Receiver Trigger) + handle->fcr &= ~ALT_UART_FCR_RT_SET_MSK; + handle->fcr |= ALT_UART_FCR_RT_SET(trigger); + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_fifo_trigger_set_tx(ALT_16550_HANDLE_t * handle, + ALT_16550_FIFO_TRIGGER_TX_t trigger) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + // Verify triggering parameter + switch (trigger) + { + case ALT_16550_FIFO_TRIGGER_TX_EMPTY: + case ALT_16550_FIFO_TRIGGER_TX_ALMOST_EMPTY: + case ALT_16550_FIFO_TRIGGER_TX_QUARTER_FULL: + case ALT_16550_FIFO_TRIGGER_TX_HALF_FULL: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Update FCR::TET (FIFO Control Register :: Transmit Empty Trigger) + handle->fcr &= ~ALT_UART_FCR_TET_SET_MSK; + handle->fcr |= ALT_UART_FCR_TET_SET(trigger); + alt_write_word(ALT_UART_FCR_ADDR(handle->location), handle->fcr); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +///// + +ALT_STATUS_CODE alt_16550_baudrate_get(ALT_16550_HANDLE_t * handle, + uint32_t * baudrate) +{ + // Query the divisor cached in the handle data + uint32_t divisor = ALT_16550_HANDLE_DATA_DIVISOR_VALUE_GET(handle->data); + + // The divisor should never be zero. It is set to allow for a baud of 57600 + // on initialization and a valid value is checked at + // alt_16550_divisor_set(). We do not check for users altering the data in + // the handle structure. + + // Formula for calculating the baudrate: + // baudrate = clock / (16 * divisor) + + *baudrate = (handle->clock_freq >> 4) / divisor; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_baudrate_set(ALT_16550_HANDLE_t * handle, + uint32_t baudrate) +{ + if (baudrate == 0) + { + return ALT_E_ARG_RANGE; + } + + // Formula for calculating the divisor: + // baudrate = clock / (16 * divisor) + // => baudrate * 16 * divisor = clock + // => divisor = clock / (baudrate * 16) + // => divisor = (clock / 16) / baudrate + + // Add half of the denominator to address rounding errors. + uint32_t divisor = ((handle->clock_freq + (8 * baudrate)) / (16 * baudrate)); + + // Check for divisor range is in alt_16550_divisor_set(). + return alt_16550_divisor_set(handle, divisor); +} + +ALT_STATUS_CODE alt_16550_divisor_get(ALT_16550_HANDLE_t * handle, + uint32_t * divisor) +{ + // Just read the divisor portion of the handle data. + *divisor = ALT_16550_HANDLE_DATA_DIVISOR_VALUE_GET(handle->data); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_divisor_set(ALT_16550_HANDLE_t * handle, + uint32_t divisor) +{ + // Verify divisor value is in range. + if ((divisor > 0xffff) || (divisor == 0)) + { + return ALT_E_ARG_RANGE; + } + + // Set the divisor portion of the handle data. + handle->data &= ~(0xffff); + handle->data |= divisor; + + // Even if the UART is enabled, don't do anything. It is documented that + // the change will take effect when the UART move to the enabled state. + + return ALT_E_SUCCESS; +} + +///// + +static ALT_STATUS_CODE alt_16550_ier_mask_set_helper(ALT_16550_HANDLE_t * handle, uint32_t setmask) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Set bit in IER (Interrupt Enable Register) + alt_setbits_word(ALT_UART_IER_DLH_ADDR(handle->location), setmask); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +static ALT_STATUS_CODE alt_16550_ier_mask_clr_helper(ALT_16550_HANDLE_t * handle, uint32_t setmask) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Clear bit in IER (Interrupt Enable Register) + alt_clrbits_word(ALT_UART_IER_DLH_ADDR(handle->location), setmask); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_int_enable_rx(ALT_16550_HANDLE_t * handle) +{ + // Set the IER::ERBFI (Interrupt Enable Register :: Enable Receive Buffer Full Interrupt) bit. + return alt_16550_ier_mask_set_helper(handle, ALT_UART_IER_DLH_ERBFI_DLH0_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_disable_rx(ALT_16550_HANDLE_t * handle) +{ + // Clear the IER::ERBFI (Interrupt Enable Register :: Enable Receive Buffer Full Interrupt) bit. + return alt_16550_ier_mask_clr_helper(handle, ALT_UART_IER_DLH_ERBFI_DLH0_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_enable_tx(ALT_16550_HANDLE_t * handle) +{ + // Set the IER::ETBEI (Interrupt Enable Register :: Enable Transmit Buffer Empty Interrupt) bit. + return alt_16550_ier_mask_set_helper(handle, ALT_UART_IER_DLH_ETBEI_DLH1_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_disable_tx(ALT_16550_HANDLE_t * handle) +{ + // Clear the IER::ETBEI (Interrupt Enable Register :: Enable Transmit Buffer Empty Interrupt) bit. + return alt_16550_ier_mask_clr_helper(handle, ALT_UART_IER_DLH_ETBEI_DLH1_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_enable_line(ALT_16550_HANDLE_t * handle) +{ + // Set the IER::ELSI (Interrupt Enable Register :: Enable Line Status Interrupt) bit. + return alt_16550_ier_mask_set_helper(handle, ALT_UART_IER_DLH_ELSI_DHL2_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_disable_line(ALT_16550_HANDLE_t * handle) +{ + // Clear the IER::ELSI (Interrupt Enable Register :: Enable Line Status Interrupt) bit. + return alt_16550_ier_mask_clr_helper(handle, ALT_UART_IER_DLH_ELSI_DHL2_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_enable_modem(ALT_16550_HANDLE_t * handle) +{ + // Set the IER::EDSSI (Interrupt Enable Register :: Enable Modem Status Interrupt) bit. + return alt_16550_ier_mask_set_helper(handle, ALT_UART_IER_DLH_EDSSI_DHL3_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_disable_modem(ALT_16550_HANDLE_t * handle) +{ + // Clear the IER::EDSSI (Interrupt Enable Register :: Enable Modem Status Interrupt) bit. + return alt_16550_ier_mask_clr_helper(handle, ALT_UART_IER_DLH_EDSSI_DHL3_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_disable_all(ALT_16550_HANDLE_t * handle) +{ + // Clear the IER::(ERBFI | ETBEI | ELSI | EDSSI) + // (Interrupt Enable Register :: (Enable Receive Buffer Full Interrupt | + // Enable Transmit Buffer Empty Interrupt | + // Enable Line Status Interrupt | + // Enable Modem Status Interrupt)) bits + return alt_16550_ier_mask_clr_helper(handle, ALT_UART_IER_DLH_ERBFI_DLH0_SET_MSK | + ALT_UART_IER_DLH_ETBEI_DLH1_SET_MSK | + ALT_UART_IER_DLH_ELSI_DHL2_SET_MSK | + ALT_UART_IER_DLH_EDSSI_DHL3_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_int_status_get(ALT_16550_HANDLE_t * handle, + ALT_16550_INT_STATUS_t * status) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Read IIR::IID (Interrupt Identity Register :: Interrupt ID) + *status = (ALT_16550_INT_STATUS_t) ALT_UART_IIR_ID_GET(alt_read_word(ALT_UART_IIR_ADDR(handle->location))); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +///// + +ALT_STATUS_CODE alt_16550_line_config_set(ALT_16550_HANDLE_t * handle, + ALT_16550_DATABITS_t databits, + ALT_16550_PARITY_t parity, + ALT_16550_STOPBITS_t stopbits) +{ + // Validate the databits parameter. + switch (databits) + { + case ALT_16550_DATABITS_5: + case ALT_16550_DATABITS_6: + case ALT_16550_DATABITS_7: + case ALT_16550_DATABITS_8: + break; + default: + return ALT_E_ERROR; + } + + // Validate the parity parameter. + switch (parity) + { + case ALT_16550_PARITY_DISABLE: + case ALT_16550_PARITY_ODD: + case ALT_16550_PARITY_EVEN: + break; + default: + return ALT_E_ERROR; + } + + // Validate the stopbits parameter. + switch (stopbits) + { + case ALT_16550_STOPBITS_1: + case ALT_16550_STOPBITS_2: + break; + default: + return ALT_E_ERROR; + } + + // LCR (Line Control Register) cache. + uint32_t lcr = 0; + + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + + // Configure the number of databits + lcr |= ALT_UART_LCR_DLS_SET(databits); + + // Configure the number of stopbits + lcr |= ALT_UART_LCR_STOP_SET(stopbits); + + // Configure the parity + if (parity != ALT_16550_PARITY_DISABLE) + { + // Enable parity in LCR + lcr |= ALT_UART_LCR_PEN_SET_MSK; + + if (parity == ALT_16550_PARITY_EVEN) + { + // Enable even parity in LCR; otherwise it's odd parity. + lcr |= ALT_UART_LCR_EPS_SET_MSK; + } + } + + // Update LCR (Line Control Register) + alt_replbits_word(ALT_UART_LCR_ADDR(handle->location), + ALT_UART_LCR_DLS_SET_MSK + | ALT_UART_LCR_STOP_SET_MSK + | ALT_UART_LCR_PEN_SET_MSK + | ALT_UART_LCR_EPS_SET_MSK, + lcr); + + break; + + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_line_break_enable(ALT_16550_HANDLE_t * handle) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Set the LCR::Break (Line Control Register :: Break) bit. + alt_setbits_word(ALT_UART_LCR_ADDR(handle->location), ALT_UART_LCR_BREAK_SET_MSK); + break; + + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_line_break_disable(ALT_16550_HANDLE_t * handle) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Clear the LCR::Break (Line Control Register :: Break) bit. + alt_clrbits_word(ALT_UART_LCR_ADDR(handle->location), ALT_UART_LCR_BREAK_SET_MSK); + break; + + default: + return ALT_E_ERROR; + } + + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_line_status_get(ALT_16550_HANDLE_t * handle, + uint32_t * status) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Read the LSR (Line Status Register). + *status = alt_read_word(ALT_UART_LSR_ADDR(handle->location)); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +///// + +static ALT_STATUS_CODE alt_16550_mcr_mask_set_helper(ALT_16550_HANDLE_t * handle, + uint32_t setmask) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Set the bit in MCR (Modem Control Register). + alt_setbits_word(ALT_UART_MCR_ADDR(handle->location), setmask); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +static ALT_STATUS_CODE alt_16550_mcr_mask_clr_helper(ALT_16550_HANDLE_t * handle, uint32_t setmask) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Clear the bit in MCR (Modem Control Register). + alt_clrbits_word(ALT_UART_MCR_ADDR(handle->location), setmask); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_16550_flowcontrol_enable(ALT_16550_HANDLE_t * handle) +{ + // Verify that the FIFO is enabled + if (!(handle->fcr & ALT_UART_FCR_FIFOE_SET_MSK)) + { + return ALT_E_ERROR; + } + + // For the Altera 16550 Compatible Soft UART, check that Hardware Flowcontrol is enabled. + if (handle->device == ALT_16550_DEVICE_ALTERA_16550_UART) + { + // Read the CPR::AFCE_Mode (Component Parameter Register :: Auto Flow Control mode) bit. + uint32_t cpr = alt_read_word(ALT_ALTERA_16550_CPR_ADDR(handle->location)); + if (!(ALT_ALTERA_16550_CPR_AFCE_MODE_SET_MSK & cpr)) + { + return ALT_E_ERROR; + } + } + + // Set MCR::AFCE (Modem Control Register :: Automatic FlowControl Enable) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_AFCE_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_flowcontrol_disable(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::AFCE (Modem Control Register :: Automatic FlowControl Enable) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_AFCE_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_loopback_enable(ALT_16550_HANDLE_t * handle) +{ + // Loopback is not implemented in the Altera 16550 Compatible Soft UART. + if (handle->device == ALT_16550_DEVICE_ALTERA_16550_UART) + { + return ALT_E_ERROR; + } + + // Set MCR::Loopback (Modem Control Register :: Loopback) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_LOOPBACK_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_loopback_disable(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::Loopback (Modem Control Register :: Loopback) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_LOOPBACK_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_enable_out1(ALT_16550_HANDLE_t * handle) +{ + // Set MCR::Out1 (Modem Control Register :: Out1) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_OUT1_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_disable_out1(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::Out1 (Modem Control Register :: Out1) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_OUT1_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_enable_out2(ALT_16550_HANDLE_t * handle) +{ + // Set MCR::Out2 (Modem Control Register :: Out2) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_OUT2_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_disable_out2(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::Out2 (Modem Control Register :: Out2) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_OUT2_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_enable_rts(ALT_16550_HANDLE_t * handle) +{ + // Set MCR::RTS (Modem Control Register :: Request To Send) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_RTS_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_disable_rts(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::RTS (Modem Control Register :: Request To Send) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_RTS_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_enable_dtr(ALT_16550_HANDLE_t * handle) +{ + // Set MCR::DTR (Modem Control Register :: Data Terminal Ready) bit. + return alt_16550_mcr_mask_set_helper(handle, ALT_UART_MCR_DTR_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_disable_dtr(ALT_16550_HANDLE_t * handle) +{ + // Clear MCR::DTR (Modem Control Register :: Data Terminal Ready) bit. + return alt_16550_mcr_mask_clr_helper(handle, ALT_UART_MCR_DTR_SET_MSK); +} + +ALT_STATUS_CODE alt_16550_modem_status_get(ALT_16550_HANDLE_t * handle, + uint32_t * status) +{ + switch (handle->device) + { + case ALT_16550_DEVICE_SOCFPGA_UART0: + case ALT_16550_DEVICE_SOCFPGA_UART1: + case ALT_16550_DEVICE_ALTERA_16550_UART: + // Read the MSR (Modem Status Register). + *status = alt_read_word(ALT_UART_MSR_ADDR(handle->location)); + break; + default: + return ALT_E_ERROR; + } + + return ALT_E_SUCCESS; +} diff --git a/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma.c b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma.c new file mode 100644 index 0000000000..2bdc519eee --- /dev/null +++ b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma.c @@ -0,0 +1,3749 @@ +/****************************************************************************** + * + * Copyright 2013 Altera Corporation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include <stdio.h> +#include "alt_dma.h" +#include "socal/socal.h" +#include "socal/hps.h" +#include "socal/alt_rstmgr.h" +#include "socal/alt_sysmgr.h" + +#if ALT_DMA_PERIPH_PROVISION_16550_SUPPORT +#include "alt_16550_uart.h" +#include "socal/alt_uart.h" +#endif + +#if ALT_DMA_PERIPH_PROVISION_QSPI_SUPPORT +#include "socal/alt_qspi.h" +#endif + +///// + +#ifndef MIN +#define MIN(a, b) ((a) > (b) ? (b) : (a)) +#endif // MIN + +#ifndef ARRAY_COUNT +#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) +#endif + +// NOTE: To enable debugging output, delete the next line and uncomment the +// line after. +#define dprintf(...) +// #define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) + +///// + +// +// SoCAL stand in for DMA Controller registers +// +// The base can be one of the following: +// - ALT_DMANONSECURE_ADDR +// - ALT_DMASECURE_ADDR +// +// Macros which have a channel parameter does no validation. +// + +// DMA Manager Status Register +#define ALT_DMA_DSR_OFST 0x0 +#define ALT_DMA_DSR_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DSR_OFST)) +#define ALT_DMA_DSR_DMASTATUS_SET_MSK 0x0000000f +#define ALT_DMA_DSR_DMASTATUS_GET(value) ((value) & 0x0000000f) + +// DMA Program Counter Register +#define ALT_DMA_DPC_OFST 0x4 +#define ALT_DMA_DPC_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DPC_OFST)) + +// Interrupt Enable Register +#define ALT_DMA_INTEN_OFST 0x20 +#define ALT_DMA_INTEN_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_INTEN_OFST)) + +// Event-Interrupt Raw Status Register +#define ALT_DMA_INT_EVENT_RIS_OFST 0x24 +#define ALT_DMA_INT_EVENT_RIS_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_INT_EVENT_RIS_OFST)) + +// Interrupt Status Register +#define ALT_DMA_INTMIS_OFST 0x28 +#define ALT_DMA_INTMIS_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_INTMIS_OFST)) + +// Interrupt Clear Register +#define ALT_DMA_INTCLR_OFST 0x2c +#define ALT_DMA_INTCLR_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_INTCLR_OFST)) + +// Fault Status DMA Manager Register +#define ALT_DMA_FSRD_OFST 0x30 +#define ALT_DMA_FSRD_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_FSRD_OFST)) + +// Fault Status DMA Channel Register +#define ALT_DMA_FSRC_OFST 0x34 +#define ALT_DMA_FSRC_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_FSRC_OFST)) + +// Fault Type DMA Manager Register +#define ALT_DMA_FTRD_OFST 0x38 +#define ALT_DMA_FTRD_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_FSRD_OFST)) + +// Fault Type DMA Channel Registers +#define ALT_DMA_FTRx_OFST(channel) (0x40 + 0x4 * (channel)) +#define ALT_DMA_FTRx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_FTRx_OFST(channel))) + +// Channel Status Registers +#define ALT_DMA_CSRx_OFST(channel) (0x100 + 0x8 * (channel)) +#define ALT_DMA_CSRx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CSRx_OFST(channel))) +#define ALT_DMA_CSRx_CHANNELSTATUS_SET_MSK 0x0000000f +#define ALT_DMA_CSRx_CHANNELSTATUS_GET(value) ((value) & 0x0000000f) + +// Channel Program Counter Registers +#define ALT_DMA_CPCx_OFST(channel) (0x104 + 0x8 * (channel)) +#define ALT_DMA_CPCx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CPCx_OFST(channel))) + +// Source Address Registers +#define ALT_DMA_SARx_OFST(channel) (0x400 + 0x20 * (channel)) +#define ALT_DMA_SARx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_SARx_OFST(channel))) + +// Destination Address Registers +#define ALT_DMA_DARx_OFST(channel) (0x404 + 0x20 * (channel)) +#define ALT_DMA_DARx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DARx_OFST(channel))) + +// Channel Control Registers +#define ALT_DMA_CCRx_OFST(channel) (0x408 + 0x20 * (channel)) +#define ALT_DMA_CCRx_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CCRx_OFST(channel))) + +// Loop Counter 0 Registers +#define ALT_DMA_LC0_x_OFST(channel) (0x40c + 0x20 * (channel)) +#define ALT_DMA_LC0_x_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_LC0_x_OFST(channel))) + +// Loop Counter 1 Registers +#define ALT_DMA_LC1_x_OFST(channel) (0x410 + 0x20 * (channel)) +#define ALT_DMA_LC1_x_ADDR(base, channel) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_LC1_x_OFST(channel))) + +// Debug Status Register +#define ALT_DMA_DBGSTATUS_OFST 0xd00 +#define ALT_DMA_DBGSTATUS_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DBGSTATUS_OFST)) + +// Debug Command Register +#define ALT_DMA_DBGCMD_OFST 0xd04 +#define ALT_DMA_DBGCMD_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DBGCMD_OFST)) + +// Debug Instruction-0 Register +#define ALT_DMA_DBGINST0_OFST 0xd08 +#define ALT_DMA_DBGINST0_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DBGINST0_OFST)) +#define ALT_DMA_DBGINST0_CHANNELNUMBER_SET(value) (((value) & 0x7) << 8) +#define ALT_DMA_DBGINST0_DEBUGTHREAD_SET(value) ((value) & 0x1) +#define ALT_DMA_DBGINST0_DEBUGTHREAD_E_MANAGER 0 +#define ALT_DMA_DBGINST0_DEBUGTHREAD_E_CHANNEL 1 +#define ALT_DMA_DBGINST0_INSTRUCTIONBYTE0_SET(value) (((value) & 0xff) << 16) +#define ALT_DMA_DBGINST0_INSTRUCTIONBYTE1_SET(value) (((value) & 0xff) << 24) + +// Debug Instruction-1 Register +#define ALT_DMA_DBGINST1_OFST 0xd0c +#define ALT_DMA_DBGINST1_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_DBGINST1_OFST)) + +// Configuration Registers 0 - 4 +#define ALT_DMA_CR0_OFST 0xe00 +#define ALT_DMA_CR1_OFST 0xe04 +#define ALT_DMA_CR2_OFST 0xe08 +#define ALT_DMA_CR3_OFST 0xe0c +#define ALT_DMA_CR4_OFST 0xe10 +#define ALT_DMA_CR0_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CR0_OFST)) +#define ALT_DMA_CR1_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CR1_OFST)) +#define ALT_DMA_CR2_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CR2_OFST)) +#define ALT_DMA_CR3_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CR3_OFST)) +#define ALT_DMA_CR4_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CR4_OFST)) + +// DMA Configuration Register +#define ALT_DMA_CRD_OFST 0xe14 +#define ALT_DMA_CRD_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_CRD_OFST)) + +// Watchdog Register +#define ALT_DMA_WD_OFST 0xe80 +#define ALT_DMA_WD_ADDR(base) ALT_CAST(void *, (ALT_CAST(char *, (base)) + ALT_DMA_WD_OFST)) + +///// + +// +// Internal Data structures +// + +// This flag marks the channel as being allocated. +#define ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED (1 << 0) + +typedef struct ALT_DMA_CHANNEL_INFO_s +{ + uint8_t flag; +} +ALT_DMA_CHANNEL_INFO_t; + +static ALT_DMA_CHANNEL_INFO_t channel_info_array[8]; + +///// + +ALT_STATUS_CODE alt_dma_init(const ALT_DMA_CFG_t * dma_cfg) +{ + // Initialize the channel information array + for (int i = 0; i < 8; ++i) + { + channel_info_array[i].flag = 0; + } + + // Update the System Manager DMA configuration items + + uint32_t dmactrl = 0; + + // Handle FPGA / CAN muxing + for (int i = 0; i < 4; ++i) + { + // The default is FPGA. + switch (dma_cfg->periph_mux[i]) + { + case ALT_DMA_PERIPH_MUX_DEFAULT: + case ALT_DMA_PERIPH_MUX_FPGA: + break; + case ALT_DMA_PERIPH_MUX_CAN: + dmactrl |= (ALT_SYSMGR_DMA_CTL_CHANSEL_0_SET_MSK << i); + break; + default: + return ALT_E_ERROR; + } + } + + // Handle Manager security + // Default is Secure state. + switch (dma_cfg->manager_sec) + { + case ALT_DMA_SECURITY_DEFAULT: + case ALT_DMA_SECURITY_SECURE: + break; + case ALT_DMA_SECURITY_NONSECURE: + dmactrl |= ALT_SYSMGR_DMA_CTL_MGRNONSECURE_SET_MSK; + break; + default: + return ALT_E_ERROR; + } + + // Handle IRQ security + for (int i = 0; i < ALT_SYSMGR_DMA_CTL_IRQNONSECURE_WIDTH; ++i) + { + // Default is Secure state. + switch (dma_cfg->irq_sec[i]) + { + case ALT_DMA_SECURITY_DEFAULT: + case ALT_DMA_SECURITY_SECURE: + break; + case ALT_DMA_SECURITY_NONSECURE: + dmactrl |= (1 << (i + ALT_SYSMGR_DMA_CTL_IRQNONSECURE_LSB)); + break; + default: + return ALT_E_ERROR; + } + } + + alt_write_word(ALT_SYSMGR_DMA_CTL_ADDR, dmactrl); + + // Update the System Manager DMA peripheral security items + + uint32_t dmapersecurity = 0; + + for (int i = 0; i < 32; ++i) + { + // Default is Secure state. + switch (dma_cfg->periph_sec[i]) + { + case ALT_DMA_SECURITY_DEFAULT: + case ALT_DMA_SECURITY_SECURE: + break; + case ALT_DMA_SECURITY_NONSECURE: + dmapersecurity |= (1 << i); + break; + default: + return ALT_E_ERROR; + } + } + + alt_write_word(ALT_SYSMGR_DMA_PERSECURITY_ADDR, dmapersecurity); + + // Take DMA out of reset. + + alt_clrbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_DMA_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_uninit(void) +{ + // DMAKILL all channel and free all allocated channels. + for (int i = 0; i < 8; ++i) + { + if (channel_info_array[i].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED) + { + alt_dma_channel_kill((ALT_DMA_CHANNEL_t)i); + alt_dma_channel_free((ALT_DMA_CHANNEL_t)i); + } + } + + // Put DMA into reset. + + alt_setbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_DMA_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_alloc(ALT_DMA_CHANNEL_t channel) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify channel is unallocated + + if (channel_info_array[channel].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED) + { + return ALT_E_ERROR; + } + + // Mark channel as allocated + + channel_info_array[channel].flag |= ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_alloc_any(ALT_DMA_CHANNEL_t * allocated) +{ + // Sweep channel array for unallocated channel + + for (int i = 0; i < 8; ++i) + { + if (!(channel_info_array[i].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED)) + { + // Allocate that free channel. + + ALT_STATUS_CODE status = alt_dma_channel_alloc((ALT_DMA_CHANNEL_t)i); + if (status == ALT_E_SUCCESS) + { + *allocated = (ALT_DMA_CHANNEL_t)i; + } + return status; + } + } + + // No free channels found. + + return ALT_E_ERROR; +} + +ALT_STATUS_CODE alt_dma_channel_free(ALT_DMA_CHANNEL_t channel) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify channel is allocated + + if (!(channel_info_array[channel].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED)) + { + return ALT_E_ERROR; + } + + // Verify channel is stopped + + ALT_DMA_CHANNEL_STATE_t state; + ALT_STATUS_CODE status = alt_dma_channel_state_get(channel, &state); + if (status != ALT_E_SUCCESS) + { + return status; + } + if (state != ALT_DMA_CHANNEL_STATE_STOPPED) + { + return ALT_E_ERROR; + } + + // Mark channel as unallocated. + + channel_info_array[channel].flag &= ~ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_exec(ALT_DMA_CHANNEL_t channel, ALT_DMA_PROGRAM_t * pgm) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify channel is allocated + + if (!(channel_info_array[channel].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED)) + { + return ALT_E_ERROR; + } + + // Verify channel is stopped + + ALT_DMA_CHANNEL_STATE_t state; + ALT_STATUS_CODE status = alt_dma_channel_state_get(channel, &state); + if (status != ALT_E_SUCCESS) + { + return status; + } + if (state != ALT_DMA_CHANNEL_STATE_STOPPED) + { + return ALT_E_ERROR; + } + + // Validate the program + + if (alt_dma_program_validate(pgm) != ALT_E_SUCCESS) + { + return ALT_E_ERROR; + } + + // + // Execute the program + // + + // Get the start address + + uint32_t start = (uint32_t) &pgm->program[pgm->buffer_start]; + + dprintf("DMA[exec]: pgm->program = %p.\n", pgm->program); + dprintf("DMA[exec]: start = %p.\n", (void *)start); + + // Configure DBGINST0 and DBGINST1 to execute DMAGO targetting the requested channel. + + // For information on APB Interface, see PL330, section 2.5.1. + // For information on DBGINSTx, see PL330, section 3.3.20 - 3.3.21. + // For information on DMAGO, see PL330, section 4.3.5. + + alt_write_word(ALT_DMA_DBGINST0_ADDR(ALT_DMASECURE_ADDR), + ALT_DMA_DBGINST0_INSTRUCTIONBYTE0_SET(0xa0) | + ALT_DMA_DBGINST0_INSTRUCTIONBYTE1_SET(channel)); + + alt_write_word(ALT_DMA_DBGINST1_ADDR(ALT_DMASECURE_ADDR), start); + + // Execute the instruction held in DBGINST{0,1} + + // For information on DBGCMD, see PL330, section 3.3.19. + + alt_write_word(ALT_DMA_DBGCMD_ADDR(ALT_DMASECURE_ADDR), 0); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_kill(ALT_DMA_CHANNEL_t channel) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify channel is allocated + + if (!(channel_info_array[channel].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED)) + { + return ALT_E_ERROR; + } + + // NOTE: Don't worry about the current channel state. Just issue DMAKILL + // instruction. The channel state cannot move from from Stopped back to + // Killing. + + // Configure DBGINST0 to execute DMAKILL on the requested channel thread. + // DMAKILL is short enough not to use DBGINST1 register. + + // For information on APB Interface, see PL330, section 2.5.1. + // For information on DBGINSTx, see PL330, section 3.3.20 - 3.3.21. + // For information on DMAKILL, see PL330, section 4.3.6. + + alt_write_word(ALT_DMA_DBGINST0_ADDR(ALT_DMASECURE_ADDR), + ALT_DMA_DBGINST0_INSTRUCTIONBYTE0_SET(0x1) | + ALT_DMA_DBGINST0_CHANNELNUMBER_SET(channel) | + ALT_DMA_DBGINST0_DEBUGTHREAD_SET(ALT_DMA_DBGINST0_DEBUGTHREAD_E_CHANNEL)); + + // Execute the instruction held in DBGINST0 + + // For information on DBGCMD, see PL330, section 3.3.19. + + alt_write_word(ALT_DMA_DBGCMD_ADDR(ALT_DMASECURE_ADDR), 0); + + // Wait for channel to move to KILLING or STOPPED state. Do not wait for + // the STOPPED only. If the AXI transaction hangs permanently, it can be + // waiting indefinately. + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + ALT_DMA_CHANNEL_STATE_t current; + uint32_t i = 20000; + + while (--i) + { + status = alt_dma_channel_state_get(channel, ¤t); + if (status != ALT_E_SUCCESS) + { + break; + } + if ( (current == ALT_DMA_CHANNEL_STATE_KILLING) + || (current == ALT_DMA_CHANNEL_STATE_STOPPED)) + { + break; + } + } + + if (i == 0) + { + status = ALT_E_TMO; + } + + return status; +} + +ALT_STATUS_CODE alt_dma_channel_reg_get(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_REG_t reg, uint32_t * val) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on SAR, see PL330, section 3.3.13. + // For information on DAR, see PL330, section 3.3.14. + // For information on CCR, see PL330, section 3.3.15. + + switch (reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + *val = alt_read_word(ALT_DMA_SARx_ADDR(ALT_DMASECURE_ADDR, channel)); + break; + case ALT_DMA_PROGRAM_REG_DAR: + *val = alt_read_word(ALT_DMA_DARx_ADDR(ALT_DMASECURE_ADDR, channel)); + break; + case ALT_DMA_PROGRAM_REG_CCR: + *val = alt_read_word(ALT_DMA_CCRx_ADDR(ALT_DMASECURE_ADDR, channel)); + break; + default: + return ALT_E_BAD_ARG; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_send_event(ALT_DMA_EVENT_t evt_num) +{ + // Validate evt_num + + switch (evt_num) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // Issue the DMASEV on the DMA manager thread. + // DMASEV is short enough not to use DBGINST1 register. + + // For information on APB Interface, see PL330, section 2.5.1. + // For information on DBGINSTx, see PL330, section 3.3.20 - 3.3.21. + // For information on DMASEV, see PL330, section 4.3.15. + + alt_write_word(ALT_DMA_DBGINST0_ADDR(ALT_DMASECURE_ADDR), + ALT_DMA_DBGINST0_INSTRUCTIONBYTE0_SET(0x34) | // opcode for DMASEV + ALT_DMA_DBGINST0_INSTRUCTIONBYTE1_SET(evt_num << 3) | + ALT_DMA_DBGINST0_DEBUGTHREAD_SET(ALT_DMA_DBGINST0_DEBUGTHREAD_E_MANAGER) + ); + + // Execute the instruction held in DBGINST0 + + // For information on DBGCMD, see PL330, section 3.3.19. + + alt_write_word(ALT_DMA_DBGCMD_ADDR(ALT_DMASECURE_ADDR), 0); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_manager_state_get(ALT_DMA_MANAGER_STATE_t * state) +{ + // For information on DSR, see PL330, section 3.3.1. + + uint32_t raw_state = alt_read_word(ALT_DMA_DSR_ADDR(ALT_DMASECURE_ADDR)); + + *state = (ALT_DMA_MANAGER_STATE_t)ALT_DMA_DSR_DMASTATUS_GET(raw_state); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_state_get(ALT_DMA_CHANNEL_t channel, + ALT_DMA_CHANNEL_STATE_t * state) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on CSR, see PL330, section 3.3.11. + + uint32_t raw_state = alt_read_word(ALT_DMA_CSRx_ADDR(ALT_DMASECURE_ADDR, channel)); + + *state = (ALT_DMA_CHANNEL_STATE_t)ALT_DMA_CSRx_CHANNELSTATUS_GET(raw_state); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_manager_fault_status_get(ALT_DMA_MANAGER_FAULT_t * fault) +{ + // For information on FTRD, see PL330, section 3.3.9. + + *fault = (ALT_DMA_MANAGER_FAULT_t)alt_read_word(ALT_DMA_FTRD_ADDR(ALT_DMASECURE_ADDR)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_channel_fault_status_get(ALT_DMA_CHANNEL_t channel, + ALT_DMA_CHANNEL_FAULT_t * fault) +{ + // Validate channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on FTR, see PL330, section 3.3.10. + + *fault = (ALT_DMA_CHANNEL_FAULT_t)alt_read_word(ALT_DMA_FTRx_ADDR(ALT_DMASECURE_ADDR, channel)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_event_int_select(ALT_DMA_EVENT_t evt_num, + ALT_DMA_EVENT_SELECT_t opt) +{ + // Validate evt_num + switch (evt_num) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on INTEN, see PL330, section 3.3.3. + + switch (opt) + { + case ALT_DMA_EVENT_SELECT_SEND_EVT: + alt_clrbits_word(ALT_DMA_INTEN_ADDR(ALT_DMASECURE_ADDR), 1 << evt_num); + break; + case ALT_DMA_EVENT_SELECT_SIG_IRQ: + alt_setbits_word(ALT_DMA_INTEN_ADDR(ALT_DMASECURE_ADDR), 1 << evt_num); + break; + default: + return ALT_E_BAD_ARG; + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_event_int_status_get_raw(ALT_DMA_EVENT_t evt_num) +{ + // Validate evt_num + switch (evt_num) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on INT_EVENT_RIS, see PL330, section 3.3.4. + + uint32_t status_raw = alt_read_word(ALT_DMA_INT_EVENT_RIS_ADDR(ALT_DMASECURE_ADDR)); + + if (status_raw & (1 << evt_num)) + { + return ALT_E_TRUE; + } + else + { + return ALT_E_FALSE; + } +} + +ALT_STATUS_CODE alt_dma_int_status_get(ALT_DMA_EVENT_t irq_num) +{ + // Validate evt_num + switch (irq_num) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on INTMIS, see PL330, section 3.3.5. + + uint32_t int_status = alt_read_word(ALT_DMA_INTMIS_ADDR(ALT_DMASECURE_ADDR)); + + if (int_status & (1 << irq_num)) + { + return ALT_E_TRUE; + } + else + { + return ALT_E_FALSE; + } +} + +ALT_STATUS_CODE alt_dma_int_clear(ALT_DMA_EVENT_t irq_num) +{ + // Validate evt_num + switch (irq_num) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // For information on INTCLR, see PL330, section 3.3.6. + + alt_write_word(ALT_DMA_INTCLR_ADDR(ALT_DMASECURE_ADDR), 1 << irq_num); + + return ALT_E_SUCCESS; +} + +///// + +ALT_STATUS_CODE alt_dma_memory_to_memory(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + void * dst, + const void * src, + size_t size, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // If the size is zero, and no event is requested, just return success. + if ((size == 0) && (send_evt == false)) + { + return status; + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_init(program); + } + + if (size != 0) + { + uintptr_t udst = (uintptr_t)dst; + uintptr_t usrc = (uintptr_t)src; + + dprintf("DMA[M->M]: dst = %p.\n", dst); + dprintf("DMA[M->M]: src = %p.\n", src); + dprintf("DMA[M->M]: size = 0x%x.\n", size); + + // Detect if memory regions overshoots the address space. + + if (udst + size - 1 < udst) + { + return ALT_E_BAD_ARG; + } + if (usrc + size - 1 < usrc) + { + return ALT_E_BAD_ARG; + } + + // Detect if memory regions overlaps. + + if (udst > usrc) + { + if (usrc + size - 1 > udst) + { + return ALT_E_BAD_ARG; + } + } + else + { + if (udst + size - 1 > usrc) + { + return ALT_E_BAD_ARG; + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, usrc); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, udst); + } + + size_t sizeleft = size; + + // + // The algorithm uses the strategy described in PL330 B.3.1. + // It is extended for 2-byte and 1-byte unaligned cases. + // + + // First see how many byte(s) we need to transfer to get src to be 8 byte aligned + if (usrc & 0x7) + { + uint32_t aligncount = MIN(8 - (usrc & 0x7), sizeleft); + sizeleft -= aligncount; + + dprintf("DMA[M->M]: Total pre-alignment 1-byte burst size tranfer(s): %lu.\n", aligncount); + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SBx (Source burst length of [aligncount] transfers) + // - DBx (Destination burst length of [aligncount] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ((aligncount - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((aligncount - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + // This is the number of 8-byte bursts + uint32_t burstcount = sizeleft >> 3; + + bool correction = (burstcount != 0); + + // Update the size left to transfer + sizeleft &= 0x7; + + dprintf("DMA[M->M]: Total Main 8-byte burst size transfer(s): %lu.\n", burstcount); + dprintf("DMA[M->M]: Total Main 1-byte burst size transfer(s): %u.\n", sizeleft); + + // Determine how many 16 length bursts can be done + + if (burstcount >> 4) + { + uint32_t length16burstcount = burstcount >> 4; + burstcount &= 0xf; + + dprintf("DMA[M->M]: Number of 16 burst length 8-byte transfer(s): %lu.\n", length16burstcount); + dprintf("DMA[M->M]: Number of remaining 8-byte transfer(s): %lu.\n", burstcount); + + // Program in the following parameters: + // - SS64 (Source burst size of 8-byte) + // - DS64 (Destination burst size of 8-byte) + // - SB16 (Source burst length of 16 transfers) + // - DB16 (Destination burst length of 16 transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB16 + | ALT_DMA_CCR_OPT_SS64 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DS64 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + while (length16burstcount > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(length16burstcount, 256); + length16burstcount -= loopcount; + + dprintf("DMA[M->M]: Looping %lux 16 burst length 8-byte transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // At this point, we should have [burstcount] 8-byte transfer(s) + // remaining. [burstcount] should be less than 16. + + // Do one more burst with a SB / DB of length [burstcount]. + + if (burstcount) + { + // Program in the following parameters: + // - SS64 (Source burst size of 8-byte) + // - DS64 (Destination burst size of 8-byte) + // - SBx (Source burst length of [burstlength] transfers) + // - DBx (Destination burst length of [burstlength] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ((burstcount - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SS64 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((burstcount - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS64 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + // This is where the last DMAMOV CCR and DMAST is done if an + // alignment correction required. + + if ( (correction == true) + && ((usrc & 0x7) != (udst & 0x7)) // If src and dst are mod-8 congruent, no correction is needed. + ) + { + if (status == ALT_E_SUCCESS) + { + // Determine what type of correction. + + // Set the source parameters to match that of the destination + // parameters. This way the SAR is increment in the same fashion as + // DAR. This will allow the non 8-byte transfers to copy correctly. + + uint32_t ccr; + + if ((usrc & 0x3) == (udst & 0x3)) + { + dprintf("DMA[M->M]: Single correction 4-byte burst size tranfer.\n"); + + // Program in the following parameters: + // - SS32 (Source burst size of 4-byte) + // - DS32 (Destination burst size of 4-byte) + // - SB1 (Source burst length of 1 transfer) + // - DB1 (Destination burst length of 1 transfer) + // - All other options default. + + ccr = ( ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SS32 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DS32 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ); + } + else if ((usrc & 0x1) == (udst & 0x1)) + { + dprintf("DMA[M->M]: Single correction 2-byte burst size tranfer.\n"); + + // Program in the following parameters: + // - SS16 (Source burst size of 2-byte) + // - DS16 (Destination burst size of 2-byte) + // - SB1 (Source burst length of 1 transfer) + // - DB1 (Destination burst length of 1 transfer) + // - All other options default. + + ccr = ( ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SS16 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DS16 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ); + } + else + { + dprintf("DMA[M->M]: Single correction 1-byte burst size tranfer.\n"); + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SB1 (Source burst length of 1 transfer) + // - DB1 (Destination burst length of 1 transfer) + // - All other options default. + + ccr = ( ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ); + } + + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ccr); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + // At this point, there should be 0 - 7 1-byte transfers remaining. + + if (sizeleft) + { + dprintf("DMA[M->M]: Total post 1-byte burst size tranfer(s): %u.\n", sizeleft); + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SBx (Source burst length of [sizeleft] transfers) + // - DBx (Destination burst length of [sizeleft] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ((sizeleft - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((sizeleft - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } // if (size != 0) + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->M]: Adding event ...\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + return alt_dma_channel_exec(channel, program); +} + +ALT_STATUS_CODE alt_dma_zero_to_memory(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + void * buf, + size_t size, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // If the size is zero, and no event is requested, just return success. + if ((size == 0) && (send_evt == false)) + { + return status; + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_init(program); + } + + if (size != 0) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, (uint32_t)buf); + } + + dprintf("DMA[Z->M]: buf = %p.\n", buf); + dprintf("DMA[Z->M]: size = 0x%x.\n", size); + + size_t sizeleft = size; + + // First see how many byte(s) we need to transfer to get dst to be 8 byte aligned. + if ((uint32_t)buf & 0x7) + { + uint32_t aligncount = MIN(8 - ((uint32_t)buf & 0x7), sizeleft); + sizeleft -= aligncount; + + dprintf("DMA[Z->M]: Total pre-alignment 1-byte burst size tranfer(s): %lu.\n", aligncount); + + // Program in the following parameters: + // - DS8 (Destination burst size of 1-byte) + // - DBx (Destination burst length of [aligncount] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB_DEFAULT + | ALT_DMA_CCR_OPT_SS_DEFAULT + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((aligncount - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMASTZ(program); + } + } + + // This is the number of 8-byte bursts left + uint32_t burstcount = sizeleft >> 3; + + // Update the size left to transfer + sizeleft &= 0x7; + + dprintf("DMA[Z->M]: Total Main 8-byte burst size transfer(s): %lu.\n", burstcount); + dprintf("DMA[Z->M]: Total Main 1-byte burst size transfer(s): %u.\n", sizeleft); + + // Determine how many 16 length bursts can be done + if (burstcount >> 4) + { + uint32_t length16burstcount = burstcount >> 4; + burstcount &= 0xf; + + dprintf("DMA[Z->M]: Number of 16 burst length 8-byte transfer(s): %lu.\n", length16burstcount); + dprintf("DMA[Z->M]: Number of remaining 8-byte transfer(s): %lu.\n", burstcount); + + // Program in the following parameters: + // - DS64 (Destination burst size of 8-byte) + // - DB16 (Destination burst length of 16 transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB_DEFAULT + | ALT_DMA_CCR_OPT_SS_DEFAULT + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DS64 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + while (length16burstcount > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(length16burstcount, 256); + length16burstcount -= loopcount; + + dprintf("DMA[Z->M]: Looping %lux 16 burst length 8-byte transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMASTZ(program); + } + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // At this point, we should have [burstcount] 8-byte transfer(s) + // remaining. [burstcount] should be less than 16. + + // Do one more burst with a SB / DB of length [burstcount]. + + if (burstcount) + { + // Program in the following parameters: + // - DS64 (Destination burst size of 8-byte) + // - DBx (Destination burst length of [burstlength] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB_DEFAULT + | ALT_DMA_CCR_OPT_SS_DEFAULT + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((burstcount - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS64 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMASTZ(program); + } + } + + // At this point, there should be 0 - 7 1-byte transfers remaining. + + if (sizeleft) + { + dprintf("DMA[Z->M]: Total post 1-byte burst size tranfer(s): %u.\n", sizeleft); + + // Program in the following parameters: + // - DS8 (Destination burst size of 1-byte) + // - DBx (Destination burst length of [sizeleft] transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB_DEFAULT + | ALT_DMA_CCR_OPT_SS_DEFAULT + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((sizeleft - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMASTZ(program); + } + } + } // if (size != 0) + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[Z->M]: Adding event ...\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + return alt_dma_channel_exec(channel, program); +} + +ALT_STATUS_CODE alt_dma_memory_to_register(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + void * dst_reg, + const void * src_buf, + size_t count, + uint32_t register_width_bits, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // If the count is zero, and no event is requested, just return success. + if ((count == 0) && (send_evt == false)) + { + return status; + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_init(program); + } + + if (count != 0) + { + // Verify valid register_width_bits and construct the CCR SS and DS parameters. + uint32_t ccr_ss_ds_mask = 0; + + if (status == ALT_E_SUCCESS) + { + switch (register_width_bits) + { + case 8: + // Program in the following parameters: + // - SS8 (Source burst size of 8 bits) + // - DS8 (Destination burst size of 8 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS8 | ALT_DMA_CCR_OPT_DS8; + break; + case 16: + // Program in the following parameters: + // - SS16 (Source burst size of 16 bits) + // - DS16 (Destination burst size of 16 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS16 | ALT_DMA_CCR_OPT_DS16; + break; + case 32: + // Program in the following parameters: + // - SS32 (Source burst size of 32 bits) + // - DS32 (Destination burst size of 32 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS32 | ALT_DMA_CCR_OPT_DS32; + break; + case 64: + // Program in the following parameters: + // - SS64 (Source burst size of 64 bits) + // - DS64 (Destination burst size of 64 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS64 | ALT_DMA_CCR_OPT_DS64; + break; + default: + status = ALT_E_BAD_ARG; + break; + } + } + + // Verify that the dst_reg and src_buf are aligned to the register width + if (status == ALT_E_SUCCESS) + { + if (((uintptr_t)dst_reg & ((register_width_bits >> 3) - 1)) != 0) + { + status = ALT_E_BAD_ARG; + } + else if (((uintptr_t)src_buf & ((register_width_bits >> 3) - 1)) != 0) + { + status = ALT_E_BAD_ARG; + } + else + { + dprintf("DMA[M->R]: dst_reg = %p.\n", dst_reg); + dprintf("DMA[M->R]: src_buf = %p.\n", src_buf); + dprintf("DMA[M->R]: count = 0x%x.\n", count); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, (uint32_t)src_buf); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, (uint32_t)dst_reg); + } + + // This is the remaining count left to process. + uint32_t countleft = count; + + // See how many 16-length bursts we can use + if (countleft >> 4) + { + // Program in the following parameters: + // - SSx (Source burst size of [ccr_ss_ds_mask]) + // - DSx (Destination burst size of [ccr_ss_ds_mask]) + // - DAF (Destination address fixed) + // - SB16 (Source burst length of 16 transfers) + // - DB16 (Destination burst length of 16 transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ccr_ss_ds_mask + | ALT_DMA_CCR_OPT_SB16 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + uint32_t length16burst = countleft >> 4; + countleft &= 0xf; + + dprintf("DMA[M->R]: Number of 16 burst length transfer(s): %lu.\n", length16burst); + dprintf("DMA[M->R]: Number of remaining transfer(s): %lu.\n", countleft); + + // See how many 256x 16-length bursts we can use + if (length16burst >> 8) + { + uint32_t loop256length16burst = length16burst >> 8; + length16burst &= ((1 << 8) - 1); + + dprintf("DMA[M->R]: Number of 256-looped 16 burst length transfer(s): %lu.\n", loop256length16burst); + dprintf("DMA[M->R]: Number of remaining 16 burst length transfer(s): %lu.\n", length16burst); + + while (loop256length16burst > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(loop256length16burst, 256); + loop256length16burst -= loopcount; + + dprintf("DMA[M->R]: Looping %lux super loop transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, 256); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // The super loop above ensures that the length16burst is below 256. + if (length16burst > 0) + { + uint32_t loopcount = length16burst; + length16burst = 0; + + dprintf("DMA[M->R]: Looping %lux 16 burst length transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // At this point, we should have [countleft] transfer(s) remaining. + // [countleft] should be less than 16. + + if (countleft) + { + // Program in the following parameters: + // - SSx (Source burst size of [ccr_ss_ds_mask]) + // - DSx (Destination burst size of [ccr_ss_ds_mask]) + // - DAF (Destination address fixed) + // - SBx (Source burst length of [countleft] transfer(s)) + // - DBx (Destination burst length of [countleft] transfer(s)) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->R]: Tail end %lux transfer(s).\n", countleft); + + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ccr_ss_ds_mask + | ((countleft - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((countleft - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + } // if (count != 0) + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->R]: Adding event ...\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->R]: DMAEND program.\n"); + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + return alt_dma_channel_exec(channel, program); +} + +ALT_STATUS_CODE alt_dma_register_to_memory(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + void * dst_buf, + const void * src_reg, + size_t count, + uint32_t register_width_bits, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // If the count is zero, and no event is requested, just return success. + if ((count == 0) && (send_evt == false)) + { + return status; + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_init(program); + } + + if (count != 0) + { + // Verify valid register_width_bits and construct the CCR SS and DS parameters. + uint32_t ccr_ss_ds_mask = 0; + + if (status == ALT_E_SUCCESS) + { + switch (register_width_bits) + { + case 8: + // Program in the following parameters: + // - SS8 (Source burst size of 8 bits) + // - DS8 (Destination burst size of 8 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS8 | ALT_DMA_CCR_OPT_DS8; + break; + case 16: + // Program in the following parameters: + // - SS16 (Source burst size of 16 bits) + // - DS16 (Destination burst size of 16 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS16 | ALT_DMA_CCR_OPT_DS16; + break; + case 32: + // Program in the following parameters: + // - SS32 (Source burst size of 32 bits) + // - DS32 (Destination burst size of 32 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS32 | ALT_DMA_CCR_OPT_DS32; + break; + case 64: + // Program in the following parameters: + // - SS64 (Source burst size of 64 bits) + // - DS64 (Destination burst size of 64 bits) + ccr_ss_ds_mask = ALT_DMA_CCR_OPT_SS64 | ALT_DMA_CCR_OPT_DS64; + break; + default: + dprintf("DMA[R->M]: Invalid register width.\n"); + status = ALT_E_BAD_ARG; + break; + } + } + + // Verify that the dst_buf and src_reg are aligned to the register width + if (status == ALT_E_SUCCESS) + { + if (((uintptr_t)dst_buf & ((register_width_bits >> 3) - 1)) != 0) + { + status = ALT_E_BAD_ARG; + } + else if (((uintptr_t)src_reg & ((register_width_bits >> 3) - 1)) != 0) + { + status = ALT_E_BAD_ARG; + } + else + { + dprintf("DMA[R->M]: dst_reg = %p.\n", dst_buf); + dprintf("DMA[R->M]: src_buf = %p.\n", src_reg); + dprintf("DMA[R->M]: count = 0x%x.\n", count); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, (uint32_t)src_reg); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, (uint32_t)dst_buf); + } + + // This is the remaining count left to process. + uint32_t countleft = count; + + // See how many 16-length bursts we can use + if (countleft >> 4) + { + uint32_t length16burst = countleft >> 4; + countleft &= 0xf; + + dprintf("DMA[R->M]: Number of 16 burst length transfer(s): %lu.\n", length16burst); + dprintf("DMA[R->M]: Number of remaining transfer(s): %lu.\n", countleft); + + // + // The algorithm uses the strategy described in PL330 B.2.3. + // Not sure if registers will accept burst transfers so read the register in its own transfer. + // + + // Program in the following parameters: + // - SAF (Source address fixed) + // - SSx (Source burst size of [ccr_ss_ds_mask]) + // - DSx (Destination burst size of [ccr_ss_ds_mask]) + // - SB16 (Source burst length of 16 transfers) + // - DB16 (Destination burst length of 16 transfers) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ccr_ss_ds_mask + | ALT_DMA_CCR_OPT_SB16 + | ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + // See how many 256x 16-length bursts we can do + if (length16burst >> 8) + { + uint32_t loop256length16burst = length16burst >> 8; + length16burst &= ((1 << 8) - 1); + + dprintf("DMA[R->M]: Number of 256-looped 16 burst length transfer(s): %lu.\n", loop256length16burst); + dprintf("DMA[R->M]: Number of remaining 16 burst length transfer(s): %lu.\n", length16burst); + + while (loop256length16burst > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(loop256length16burst, 256); + loop256length16burst -= loopcount; + + dprintf("DMA[R->M]: Looping %lux super loop transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, 256); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // The super loop above ensures that the length16burst is below 256. + if (length16burst > 0) + { + uint32_t loopcount = length16burst; + length16burst = 0; + + dprintf("DMA[R->M]: Looping %lux 16 burst length transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + + // At this point, we should have [countleft] transfer(s) remaining. + // [countleft] should be less than 16. + + if (countleft) + { + dprintf("DMA[R->M]: Tail end %lux transfer(s).\n", countleft); + + // Program in the following parameters: + // - SAF (Source address fixed) + // - SSx (Source burst size of [ccr_ss_ds_mask]) + // - DSx (Destination burst size of [ccr_ss_ds_mask]) + // - SBx (Source burst length of [countleft] transfer(s)) + // - DBx (Destination burst length of [countleft] transfer(s)) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ccr_ss_ds_mask + | ((countleft - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ((countleft - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + } // if (count != 0) + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[R->M]: Adding event ...\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + return alt_dma_channel_exec(channel, program); +} + +#if ALT_DMA_PERIPH_PROVISION_QSPI_SUPPORT +static ALT_STATUS_CODE alt_dma_memory_to_qspi(ALT_DMA_PROGRAM_t * program, + const char * src, + size_t size) +{ + if ((uintptr_t)src & 0x3) + { + return ALT_E_ERROR; + } + + if (size & 0x3) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, + (uint32_t)ALT_QSPIDATA_ADDR); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, + (uint32_t)src); + } + + ///// + + uint32_t dmaper = alt_read_word(ALT_QSPI_DMAPER_ADDR); + uint32_t qspi_single_size_log2 = ALT_QSPI_DMAPER_NUMSGLREQBYTES_GET(dmaper); + uint32_t qspi_burst_size_log2 = ALT_QSPI_DMAPER_NUMBURSTREQBYTES_GET(dmaper); + uint32_t qspi_single_size = 1 << qspi_single_size_log2; + uint32_t qspi_burst_size = 1 << qspi_burst_size_log2; + + dprintf("DMA[M->P][QSPI]: QSPI Single = %lu; Burst = %lu.\n", qspi_single_size, qspi_burst_size); + + // Because single transfers are equal or smaller than burst (and in the + // smaller case, it is always a clean multiple), only the single size + // check is needed for transfer composability. + if (size & (qspi_single_size - 1)) + { + dprintf("DMA[M->P][QSPI]: QSPI DMA size configuration not suitable for transfer request.\n"); + return ALT_E_ERROR; + } + + ///// + + if ((uintptr_t)src & 0x7) + { + // Source address is not 8-byte aligned. Do 1x 32-bit transfer to get it 8-byte aligned. + + dprintf("DMA[M->P][QSPI]: Creating 1x 4-byte aligning transfer.\n"); + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAI + | ALT_DMA_CCR_OPT_SS32 + | ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DS32 + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + size -= sizeof(uint32_t); + } + + uint32_t qspi_single_count = 0; + uint32_t qspi_burst_count = size >> qspi_burst_size_log2; + + // Use QSPI burst transfers if: + // - QSPI bursts are larger than QSPI singles [AND] + // - Size is large enough that at least 1 burst will be used. + + if ( (qspi_burst_size_log2 > qspi_single_size_log2) + && (qspi_burst_count != 0) + ) + { + // qspi_burst_count = size >> qspi_burst_size_log2; + qspi_single_count = (size & (qspi_burst_size - 1)) >> qspi_single_size_log2; + + dprintf("DMA[M->P][QSPI][B]: Burst size = %lu bytes, count = %lu.\n", qspi_burst_size, qspi_burst_count); + + // 1 << 3 => 8 bytes => 64 bits, which is the width of the AXI bus. + uint32_t src_size_log2 = MIN(3, qspi_burst_size_log2); + + uint32_t src_length = 0; + uint32_t src_multiple = 0; + + if ((qspi_burst_size >> src_size_log2) <= 16) + { + src_length = qspi_burst_size >> src_size_log2; + src_multiple = 1; + } + else + { + src_length = 16; + src_multiple = (qspi_burst_size >> src_size_log2) >> 4; // divide by 16 + + if (src_multiple == 0) + { + dprintf("DEBUG[QSPI][B]: src_multiple is 0.\n"); + status = ALT_E_ERROR; + } + } + + // uint32_t dst_length = 1; // dst_length is always 1 because the address is fixed. + uint32_t dst_multiple = qspi_burst_size >> 2; // divide by sizeof(uint32_t) + + dprintf("DMA[M->P][QSPI][B]: dst_size = %u bits, dst_length = %u, dst_multiple = %lu.\n", + 32, 1, dst_multiple); + dprintf("DMA[M->P][QSPI][B]: src_size = %u bits, src_length = %lu, src_multiple = %lu.\n", + (1 << src_size_log2) * 8, src_length, src_multiple); + + ///// + + // Program in the following parameters: + // - SAI (Source address increment) + // - SSx (Source burst size of [1 << src_size_log2]-bytes) + // - SBx (Source burst length of [src_length] transfer(s)) + // - DAF (Destination address fixed) + // - DS32 (Destination burst size of 4-bytes) + // - DB1 (Destination burst length of 1 transfer) + // - All other parameters default + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAI + | (src_size_log2 << 1) // SS + | ((src_length - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DS32 + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + // NOTE: We do not do the 256x bursts for M->P case because we only + // write up to 256 B at a time. + + while (qspi_burst_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_burst_count, 256); + qspi_burst_count -= loopcount; + + dprintf("DMA[M->P][QSPI][B]: Creating %lu burst-type transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + else + { + qspi_single_count = size >> qspi_single_size_log2; + } + + // Assemble the single portion of the DMA program. + if (qspi_single_count) + { + dprintf("DMA[M->P][QSPI][S]: Single size = %lu bytes, count = %lu.\n", qspi_single_size, qspi_single_count); + + // 1 << 3 => 8 bytes => 64 bits, which is the width of the AXI bus. + uint32_t src_size_log2 = MIN(3, qspi_single_size_log2); + + uint32_t src_length = 0; + uint32_t src_multiple = 0; + + if ((qspi_single_size >> src_size_log2) <= 16) + { + src_length = qspi_single_size >> src_size_log2; + src_multiple = 1; + } + else + { + src_length = 16; + src_multiple = (qspi_single_size >> src_size_log2) >> 4; // divide by 16 + + if (src_multiple == 0) + { + dprintf("DEBUG[QSPI][S]: src_multiple is 0.\n"); + status = ALT_E_ERROR; + } + } + + // uint32_t dst_length = 1; // dst_length is always 1 becaus the address is fixed. + uint32_t dst_multiple = qspi_single_size >> 2; // divide by sizeof(uint32_t) + + dprintf("DMA[M->P][QSPI][S]: dst_size = %u bits, dst_length = %u, dst_multiple = %lu.\n", + 32, 1, dst_multiple); + dprintf("DMA[M->P][QSPI][S]: src_size = %u bits, src_length = %lu, src_multiple = %lu.\n", + (1 <<src_size_log2) * 8, src_length, src_multiple); + + ///// + + // Program in the following parameters: + // - SAI (Source address increment) + // - SSx (Source burst size of [1 << src_size_log2]-bytes) + // - SBx (Source burst length of [src_length] transfer(s)) + // - DAF (Destination address fixed) + // - DS32 (Destination burst size of 4-bytes) + // - DB1 (Destination burst length of 1 transfer) + // - All other parameters default + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAI + | (src_size_log2 << 1) // SS + | ((src_length - 1) << 4) // SB + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DS32 + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + // NOTE: We do not do the 256x bursts for M->P case because we only + // write up to 256 B at a time. + + while (qspi_single_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_single_count, 256); + qspi_single_count -= loopcount; + + dprintf("DMA[M->P][QSPI][S]: Creating %lu single-type transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_TX, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + } // if (qspi_single_count != 0) + + return status; +} + +static ALT_STATUS_CODE alt_dma_qspi_to_memory(ALT_DMA_PROGRAM_t * program, + char * dst, + size_t size) +{ + if ((uintptr_t)dst & 0x3) + { + return ALT_E_ERROR; + } + + if (size & 0x3) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, + (uint32_t)dst); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, + (uint32_t)ALT_QSPIDATA_ADDR); + } + + ///// + + uint32_t dmaper = alt_read_word(ALT_QSPI_DMAPER_ADDR); + uint32_t qspi_single_size_log2 = ALT_QSPI_DMAPER_NUMSGLREQBYTES_GET(dmaper); + uint32_t qspi_burst_size_log2 = ALT_QSPI_DMAPER_NUMBURSTREQBYTES_GET(dmaper); + uint32_t qspi_single_size = 1 << qspi_single_size_log2; + uint32_t qspi_burst_size = 1 << qspi_burst_size_log2; + + dprintf("DMA[P->M][QSPI]: QSPI Single = %lu; Burst = %lu.\n", qspi_single_size, qspi_burst_size); + + // Because single transfers are equal or smaller than burst (and in the + // smaller case, it is always a clean multiple), only the single size + // check is needed for transfer composability. + if (size & (qspi_single_size - 1)) + { + dprintf("DMA[P->M][QSPI]: QSPI DMA size configuration not suitable for transfer request.\n"); + return ALT_E_ERROR; + } + + ///// + + if ((uintptr_t)dst & 0x7) + { + // Destination address is not 8-byte aligned. Do 1x 32-bit transfer to get it 8-byte aligned. + + dprintf("DMA[P->M][QSPI]: Creating 1x 4-byte aligning transfer.\n"); + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SS32 + | ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAI + | ALT_DMA_CCR_OPT_DS32 + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + size -= sizeof(uint32_t); + } + + uint32_t qspi_single_count = 0; + uint32_t qspi_burst_count = size >> qspi_burst_size_log2; + + // Use QSPI burst transfers if: + // - QSPI bursts are larger than QSPI singles [AND] + // - Size is large enough that at least 1 burst will be used. + + if ( (qspi_burst_size_log2 > qspi_single_size_log2) + && (qspi_burst_count != 0) + ) + { + // qspi_burst_count = size >> qspi_burst_size_log2; + qspi_single_count = (size & (qspi_burst_size - 1)) >> qspi_single_size_log2; + + dprintf("DMA[P->M][QSPI][B]: Burst size = %lu bytes, count = %lu.\n", qspi_burst_size, qspi_burst_count); + + // 1 << 3 => 8 bytes => 64 bits, which is the width of the AXI bus. + uint32_t dst_size_log2 = MIN(3, qspi_burst_size_log2); + + uint32_t dst_length = 0; + uint32_t dst_multiple = 0; + + if ((qspi_burst_size >> dst_size_log2) <= 16) + { + dst_length = qspi_burst_size >> dst_size_log2; + dst_multiple = 1; + } + else + { + dst_length = 16; + dst_multiple = (qspi_burst_size >> dst_size_log2) >> 4; // divide by 16 + + if (dst_multiple == 0) + { + dprintf("DEBUG[QSPI][B]: dst_multiple is 0.\n"); + status = ALT_E_ERROR; + } + } + + // uint32_t src_length = 1; // src_length is always 1 because the address is fixed. + uint32_t src_multiple = qspi_burst_size >> 2; // divide by sizeof(uint32_t) + + dprintf("DMA[P->M][QSPI][B]: dst_size = %u bits, dst_length = %lu, dst_multiple = %lu.\n", + (1 << dst_size_log2) * 8, dst_length, dst_multiple); + dprintf("DMA[P->M][QSPI][B]: src_size = %u bits, src_length = %u, src_multiple = %lu.\n", + 32, 1, src_multiple); + + ///// + + // Program in the following parameters: + // - SAF (Source address fixed) + // - SS32 (Source burst size of 4-bytes) + // - SB1 (Source burst length of 1 transfer) + // - DAI (Destination address increment) + // - DSx (Destination burst size of [1 << dst_size_log2]-bytes]) + // - DBx (Destination burst length of [dst_length] transfer(s)) + // - All other parameters default + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SS32 + | ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAI + | (dst_size_log2 << 15) // DS + | ((dst_length - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + // See how many 256x bursts we can construct. This will allow for extremely large requests. + + if (qspi_burst_count >> 8) + { + uint32_t qspi_burst256_count = qspi_burst_count >> 8; + qspi_burst_count &= (1 << 8) - 1; + + while (qspi_burst256_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_burst256_count, 256); + qspi_burst256_count -= loopcount; + + dprintf("DMA[P->M][QSPI][B]: Creating %lu 256x burst-type transfer(s).\n", loopcount); + + // Outer loop { + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + // Inner loop { + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, 256); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + // } Inner loop + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + // } Outer loop + } + } + + while (qspi_burst_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_burst_count, 256); + qspi_burst_count -= loopcount; + + dprintf("DMA[P->M][QSPI][B]: Creating %lu burst-type transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + } + else + { + qspi_single_count = size >> qspi_single_size_log2; + } + + // Assemble the single portion of the DMA program. + if (qspi_single_count) + { + dprintf("DMA[P->M][QSPI][S]: Single size = %lu bytes, count = %lu.\n", qspi_single_size, qspi_single_count); + + // 1 << 3 => 8 bytes => 64 bits, which is the width of the AXI bus. + uint32_t dst_size_log2 = MIN(3, qspi_single_size_log2); + + uint32_t dst_length = 0; + uint32_t dst_multiple = 0; + + if ((qspi_single_size >> dst_size_log2) <= 16) + { + dst_length = qspi_single_size >> dst_size_log2; + dst_multiple = 1; + } + else + { + dst_length = 16; + dst_multiple = (qspi_single_size >> dst_size_log2) >> 4; // divide by 16 + + if (dst_multiple == 0) + { + dprintf("DEBUG[QSPI][S]: dst_multiple is 0.\n"); + status = ALT_E_ERROR; + } + } + + // uint32_t src_length = 1; // src_length is always 1 because the address is fixed. + uint32_t src_multiple = qspi_single_size >> 2; // divide by sizeof(uint32_t) + + dprintf("DMA[P->M][QSPI][S]: dst_size = %u bits, dst_length = %lu, dst_multiple = %lu.\n", + (1 << dst_size_log2) * 8, dst_length, dst_multiple); + dprintf("DMA[P->M][QSPI][S]: src_size = %u bits, src_length = %u, src_multiple = %lu.\n", + 32, 1, src_multiple); + + ///// + + // Program in the following parameters: + // - SAF (Source address fixed) + // - SS32 (Source burst size of 4-bytes) + // - SB1 (Source burst length of 1 transfer) + // - DAI (Destination address increment) + // - DSx (Destination burst size of [1 << dst_size_log2]-bytes]) + // - DBx (Destination burst length of [dst_length] transfer(s)) + // - All other parameters default + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SS32 + | ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DAI + | (dst_size_log2 << 15) // DS + | ((dst_length - 1) << 18) // DB + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + // See how many 256x bursts we can construct. This will allow for extremely large requests. + + if (qspi_single_count >> 8) + { + uint32_t qspi_single256_count = qspi_single_count >> 8; + qspi_single_count &= (1 << 8) - 1; + + while (qspi_single256_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_single256_count, 256); + qspi_single256_count -= loopcount; + + dprintf("DMA[P->M][QSPI][S]: Creating %lu 256x single-type transfer(s).\n", loopcount); + + // Outer loop { + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + // Inner loop { + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, 256); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + // } Inner loop + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + + // } Outer loop + } + } + + while (qspi_single_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(qspi_single_count, 256); + qspi_single_count -= loopcount; + + dprintf("DMA[P->M][QSPI][S]: Creating %lu single-type transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, ALT_DMA_PERIPH_QSPI_FLASH_RX, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + for (uint32_t j = 0; j < src_multiple; ++j) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + for (uint32_t k = 0; k < dst_multiple; ++k) + { + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_NONE); + } + } + + } // if (qspi_single_count != 0) + + return status; +} +#endif // ALT_DMA_PERIPH_PROVISION_QSPI_SUPPORT + +#if ALT_DMA_PERIPH_PROVISION_16550_SUPPORT +static ALT_STATUS_CODE alt_dma_memory_to_16550_single(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SB1 (Source burst length of 1 transfer) + // - DB1 (Destination burst length of 1 transfer) + // - DAF (Destination address fixed) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + uint32_t sizeleft = size; + + while (sizeleft > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(sizeleft, 256); + sizeleft -= loopcount; + + dprintf("DMA[M->P][16550][S]: Creating %lu transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, periph); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, periph, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + + return status; +} + +static ALT_STATUS_CODE alt_dma_memory_to_16550_burst(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + size_t burst_size, + size_t burst_count) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SB16 (Source burst length of 16 transfers) + // - DB16 (Destination burst length of 16 transfers) + // - DAF (Source address fixed) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB16 + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SA_DEFAULT + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DAF + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + while (burst_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(burst_count, 256); + burst_count -= loopcount; + + dprintf("DMA[M->P][16550][B]: Creating outer %lu inner loop(s).\n", loopcount); + + // Outer loop { + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, periph); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, periph, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // Inner loop { + + // Loop [burst_size / 16] times. The burst_size was trimmed to the + // nearest multiple of 16 by the caller. Each burst does 16 transfers + // hence the need for the divide. + + dprintf("DMA[M->P][16550][B]: Creating inner %u transfer(s).\n", burst_size >> 4); + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, burst_size >> 4); // divide by 16. + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // } Inner loop + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // } Outer loop + } + + return status; +} + +static ALT_STATUS_CODE alt_dma_memory_to_16550(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + ALT_16550_HANDLE_t * handle, + const void * src, + size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, + (uint32_t)ALT_UART_RBR_THR_DLL_ADDR(handle->location)); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, + (uint32_t)src); + } + + // Determine if FIFOs are enabled from the FCR cache + + if (ALT_UART_FCR_FIFOE_GET(handle->fcr) != 0) + { + dprintf("DMA[M->P][16550]: FIFOs enabled.\n"); + + // + // FIFOs are enabled. + // + + uint32_t tx_size; + uint32_t burst_size; + ALT_16550_FIFO_TRIGGER_TX_t trig_tx; + + // Get the TX FIFO Size + // Use the register interface to avoid coupling the 16550 and DMA. + tx_size = ALT_UART_CPR_FIFO_MOD_GET(alt_read_word(ALT_UART_CPR_ADDR(handle->location))) << 4; + + // Get the TX FIFO Trigger Level from the FCR cache + trig_tx = (ALT_16550_FIFO_TRIGGER_TX_t)ALT_UART_FCR_TET_GET(handle->fcr); + + switch (trig_tx) + { + case ALT_16550_FIFO_TRIGGER_TX_EMPTY: + burst_size = tx_size; + break; + case ALT_16550_FIFO_TRIGGER_TX_ALMOST_EMPTY: + burst_size = tx_size - 2; + break; + case ALT_16550_FIFO_TRIGGER_TX_QUARTER_FULL: + burst_size = 3 * (tx_size >> 2); + break; + case ALT_16550_FIFO_TRIGGER_TX_HALF_FULL: + burst_size = tx_size >> 1; + break; + default: + // This case should never happen. + return ALT_E_ERROR; + } + + if (burst_size < 16) + { + // There's no point bursting 1 byte at a time per notify, so just do single transfers. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_memory_to_16550_single(program, + periph, + size); + } + } + else + { + uint32_t sizeleft = size; + + // Now trip the burst size to a multiple of 16. + // This will optimize the bursting in the fewest possible commands. + dprintf("DMA[M->P][16550]: Untrimmed burst size = %lu.\n", burst_size); + burst_size &= ~0xf; + dprintf("DMA[M->P][16550]: Trimmed burst size = %lu.\n", burst_size); + + // Determine how many burst transfers can be done + uint32_t burst_count = 0; + + burst_count = sizeleft / burst_size; + sizeleft -= burst_count * burst_size; + + if (burst_count == 0) + { + // Do the transfer + if (status == ALT_E_SUCCESS) + { + status = alt_dma_memory_to_16550_single(program, + periph, + sizeleft); + } + } + else + { + // Do the burst transfers + if (status == ALT_E_SUCCESS) + { + status = alt_dma_memory_to_16550_burst(program, + periph, + burst_size, + burst_count); + } + + // Program the DMA engine to transfer the non-burstable items in single tranfers + if (status == ALT_E_SUCCESS) + { + status = alt_dma_memory_to_16550_single(program, + periph, + sizeleft); + } + + } // else if (burst_count == 0) + } + } + else + { + dprintf("DMA[M->P][16550]: FIFOs disabled.\n"); + + // + // FIFOs are disabled. + // + + status = alt_dma_memory_to_16550_single(program, + periph, + size); + } + + return status; +} + +static ALT_STATUS_CODE alt_dma_16550_to_memory_single(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SB1 (Source burst length of 1 transfer) + // - DB1 (Destination burst length of 1 transfer) + // - SAF (Source address fixed) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB1 + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB1 + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + uint32_t sizeleft = size; + + while (sizeleft > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(sizeleft, 256); + sizeleft -= loopcount; + + dprintf("DMA[P->M][16550][S]: Creating %lu transfer(s).\n", loopcount); + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, periph); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, periph, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_SINGLE); + } + } + + return status; +} + +static ALT_STATUS_CODE alt_dma_16550_to_memory_burst(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + size_t burst_size, + size_t burst_count) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // Program in the following parameters: + // - SS8 (Source burst size of 1-byte) + // - DS8 (Destination burst size of 1-byte) + // - SB16 (Source burst length of 16 transfers) + // - DB16 (Destination burst length of 16 transfers) + // - SAF (Source address fixed) + // - All other options default. + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_CCR, + ( ALT_DMA_CCR_OPT_SB16 + | ALT_DMA_CCR_OPT_SS8 + | ALT_DMA_CCR_OPT_SAF + | ALT_DMA_CCR_OPT_SP_DEFAULT + | ALT_DMA_CCR_OPT_SC_DEFAULT + | ALT_DMA_CCR_OPT_DB16 + | ALT_DMA_CCR_OPT_DS8 + | ALT_DMA_CCR_OPT_DA_DEFAULT + | ALT_DMA_CCR_OPT_DP_DEFAULT + | ALT_DMA_CCR_OPT_DC_DEFAULT + | ALT_DMA_CCR_OPT_ES_DEFAULT + ) + ); + } + + while (burst_count > 0) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + uint32_t loopcount = MIN(burst_count, 256); + burst_count -= loopcount; + + dprintf("DMA[P->M][16550][B]: Creating outer %lu inner loop(s).\n", loopcount); + + // Outer loop { + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALP(program, loopcount); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAFLUSHP(program, periph); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAWFP(program, periph, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // Inner loop { + + // Loop [burst_size / 16] times. The burst_size was trimmed to the + // nearest multiple of 16 by the caller. Each burst does 16 transfers + // hence the need for the divide. + + dprintf("DMA[P->M][16550][B]: Creating inner %u transfer(s).\n", burst_size >> 4); + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALP(program, burst_size >> 4); // divide by 16. + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALD(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAST(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // } Inner loop + + if ((status == ALT_E_SUCCESS) && (loopcount > 1)) + { + status = alt_dma_program_DMALPEND(program, ALT_DMA_PROGRAM_INST_MOD_BURST); + } + + // } Outer loop + } + + return status; +} + +static ALT_STATUS_CODE alt_dma_16550_to_memory(ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t periph, + ALT_16550_HANDLE_t * handle, + void * dst, + size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_DAR, (uint32_t)dst); + } + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAMOV(program, ALT_DMA_PROGRAM_REG_SAR, (uint32_t)ALT_UART_RBR_THR_DLL_ADDR(handle->location)); + } + + // Determine if FIFOs are enabled from the FCR cache + + if (ALT_UART_FCR_FIFOE_GET(handle->fcr) != 0) + { + dprintf("DMA[P->M][16550]: FIFOs enabled.\n"); + + // + // FIFOs are enabled. + // + + uint32_t rx_size; + uint32_t burst_size; + ALT_16550_FIFO_TRIGGER_RX_t trig_rx; + + // Get the RX FIFO Size + // Use the register interface to avoid coupling the 16550 and DMA. + rx_size = ALT_UART_CPR_FIFO_MOD_GET(alt_read_word(ALT_UART_CPR_ADDR(handle->location))) << 4; + + // Get the RX FIFO Trigger Level from the FCR cache + trig_rx = (ALT_16550_FIFO_TRIGGER_RX_t)ALT_UART_FCR_RT_GET(handle->fcr); + + switch (trig_rx) + { + case ALT_16550_FIFO_TRIGGER_RX_ANY: + burst_size = 1; + break; + case ALT_16550_FIFO_TRIGGER_RX_QUARTER_FULL: + burst_size = rx_size >> 2; // divide by 4 + break; + case ALT_16550_FIFO_TRIGGER_RX_HALF_FULL: + burst_size = rx_size >> 1; // divide by 2 + break; + case ALT_16550_FIFO_TRIGGER_RX_ALMOST_FULL: + burst_size = rx_size - 2; + break; + default: + // This case should never happen. + return ALT_E_ERROR; + } + + if (burst_size < 16) + { + // There's no point bursting 1 byte at a time per notify, so just do single transfers. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_16550_to_memory_single(program, + periph, + size); + } + } + else + { + uint32_t sizeleft = size; + + // Now trim the burst size to a multiple of 16. + // This will optimize the bursting in the fewest possible commands. + dprintf("DMA[P->M][16550]: Untrimmed burst size = %lu.\n", burst_size); + burst_size &= ~0xf; + dprintf("DMA[P->M][16550]: Trimmed burst size = %lu.\n", burst_size); + + // Determine how many burst transfers can be done + uint32_t burst_count = 0; + + burst_count = sizeleft / burst_size; + sizeleft -= burst_count * burst_size; + + if (burst_count == 0) + { + // Do the transfer. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_16550_to_memory_single(program, + periph, + sizeleft); + } + } + else + { + // Do the burst transfers + if (status == ALT_E_SUCCESS) + { + status = alt_dma_16550_to_memory_burst(program, + periph, + burst_size, + burst_count); + } + + // Program the DMA engine to transfer the non-burstable items in single transfers. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_16550_to_memory_single(program, + periph, + sizeleft); + } + + } // if (burst_count == 0) + } + } + else + { + dprintf("DMA[P->M][16550]: FIFOs disabled.\n"); + + // + // FIFOs are disabled. + // + + status = alt_dma_16550_to_memory_single(program, + periph, + size); + } + + return status; +} +#endif // ALT_DMA_PERIPH_PROVISION_16550_SUPPORT + +ALT_STATUS_CODE alt_dma_memory_to_periph(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + ALT_DMA_PERIPH_t dstp, + const void * src, + size_t size, + void * periph_info, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if ((size == 0) && (send_evt == false)) + { + return status; + } + + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->P]: Init Program.\n"); + status = alt_dma_program_init(program); + } + + if ((status == ALT_E_SUCCESS) && (size != 0)) + { + switch (dstp) + { +#if ALT_DMA_PERIPH_PROVISION_QSPI_SUPPORT + case ALT_DMA_PERIPH_QSPI_FLASH_TX: + status = alt_dma_memory_to_qspi(program, src, size); + break; +#endif + +#if ALT_DMA_PERIPH_PROVISION_16550_SUPPORT + case ALT_DMA_PERIPH_UART0_TX: + case ALT_DMA_PERIPH_UART1_TX: + status = alt_dma_memory_to_16550(program, dstp, + (ALT_16550_HANDLE_t *)periph_info, src, size); + break; +#endif + + case ALT_DMA_PERIPH_FPGA_0: + case ALT_DMA_PERIPH_FPGA_1: + case ALT_DMA_PERIPH_FPGA_2: + case ALT_DMA_PERIPH_FPGA_3: + case ALT_DMA_PERIPH_FPGA_4: + case ALT_DMA_PERIPH_FPGA_5: + case ALT_DMA_PERIPH_FPGA_6: + case ALT_DMA_PERIPH_FPGA_7: + case ALT_DMA_PERIPH_I2C0_TX: + case ALT_DMA_PERIPH_I2C1_TX: + case ALT_DMA_PERIPH_I2C2_TX: + case ALT_DMA_PERIPH_I2C3_TX: + case ALT_DMA_PERIPH_SPI0_MASTER_TX: + case ALT_DMA_PERIPH_SPI0_SLAVE_TX: + case ALT_DMA_PERIPH_SPI1_MASTER_TX: + case ALT_DMA_PERIPH_SPI1_SLAVE_TX: + + default: + status = ALT_E_BAD_ARG; + break; + } + } + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[M->P]: Adding event.\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + + return alt_dma_channel_exec(channel, program); +} + +ALT_STATUS_CODE alt_dma_periph_to_memory(ALT_DMA_CHANNEL_t channel, + ALT_DMA_PROGRAM_t * program, + void * dst, + ALT_DMA_PERIPH_t srcp, + size_t size, + void * periph_info, + bool send_evt, + ALT_DMA_EVENT_t evt) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if ((size == 0) && (send_evt == false)) + { + return ALT_E_SUCCESS; + } + + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[P->M]: Init Program.\n"); + status = alt_dma_program_init(program); + } + + if ((status == ALT_E_SUCCESS) && (size != 0)) + { + switch (srcp) + { +#if ALT_DMA_PERIPH_PROVISION_QSPI_SUPPORT + case ALT_DMA_PERIPH_QSPI_FLASH_RX: + status = alt_dma_qspi_to_memory(program, dst, size); + break; +#endif + +#if ALT_DMA_PERIPH_PROVISION_16550_SUPPORT + case ALT_DMA_PERIPH_UART0_RX: + case ALT_DMA_PERIPH_UART1_RX: + status = alt_dma_16550_to_memory(program, srcp, + (ALT_16550_HANDLE_t *)periph_info, dst, size); + break; +#endif + + case ALT_DMA_PERIPH_FPGA_0: + case ALT_DMA_PERIPH_FPGA_1: + case ALT_DMA_PERIPH_FPGA_2: + case ALT_DMA_PERIPH_FPGA_3: + case ALT_DMA_PERIPH_FPGA_4: + case ALT_DMA_PERIPH_FPGA_5: + case ALT_DMA_PERIPH_FPGA_6: + case ALT_DMA_PERIPH_FPGA_7: + case ALT_DMA_PERIPH_I2C0_RX: + case ALT_DMA_PERIPH_I2C1_RX: + case ALT_DMA_PERIPH_I2C2_RX: + case ALT_DMA_PERIPH_I2C3_RX: + case ALT_DMA_PERIPH_SPI0_MASTER_RX: + case ALT_DMA_PERIPH_SPI0_SLAVE_RX: + case ALT_DMA_PERIPH_SPI1_MASTER_RX: + case ALT_DMA_PERIPH_SPI1_SLAVE_RX: + + default: + status = ALT_E_BAD_ARG; + break; + } + } + + // Send event if requested. + if (send_evt) + { + if (status == ALT_E_SUCCESS) + { + dprintf("DMA[P->M]: Adding event.\n"); + status = alt_dma_program_DMASEV(program, evt); + } + } + + // Now that everything is done, end the program. + if (status == ALT_E_SUCCESS) + { + status = alt_dma_program_DMAEND(program); + } + + // If there was a problem assembling the program, clean up the buffer and exit. + if (status != ALT_E_SUCCESS) + { + // Do not report the status for the clear operation. A failure should be + // reported regardless of if the clear is successful. + alt_dma_program_clear(program); + return status; + } + + // Execute the program on the given channel. + + return alt_dma_channel_exec(channel, program); +} + +///// + +static bool alt_dma_is_init(void) +{ + uint32_t permodrst = alt_read_word(ALT_RSTMGR_PERMODRST_ADDR); + + if (permodrst & ALT_RSTMGR_PERMODRST_DMA_SET_MSK) + { + return false; + } + else + { + return true; + } +} + +ALT_STATUS_CODE alt_dma_ecc_start(void * block, size_t size) +{ + if (alt_dma_is_init() == false) + { + return ALT_E_ERROR; + } + + if ((uintptr_t)block & (sizeof(uint64_t) - 1)) + { + return ALT_E_ERROR; + } + + // Verify that all channels are either unallocated or allocated and idle. + + for (int i = 0; i < ARRAY_COUNT(channel_info_array); ++i) + { + if (channel_info_array[i].flag & ALT_DMA_CHANNEL_INFO_FLAG_ALLOCED) + { + ALT_DMA_CHANNEL_STATE_t state; + alt_dma_channel_state_get((ALT_DMA_CHANNEL_t)i, &state); + + if (state != ALT_DMA_CHANNEL_STATE_STOPPED) + { + dprintf("DMA[ECC]: Error: Channel %d state is non-stopped (%d).\n", i, (int)state); + return ALT_E_ERROR; + } + } + } + + ///// + + // Enable ECC for DMA RAM + + dprintf("DEBUG[DMA][ECC]: Enable ECC in SysMgr.\n"); + alt_write_word(ALT_SYSMGR_ECC_DMA_ADDR, ALT_SYSMGR_ECC_DMA_EN_SET_MSK); + + // Clear any pending spurious DMA ECC interrupts. + + dprintf("DEBUG[DMA][ECC]: Clear any pending spurious ECC status in SysMgr.\n"); + alt_write_word(ALT_SYSMGR_ECC_DMA_ADDR, + ALT_SYSMGR_ECC_DMA_EN_SET_MSK + | ALT_SYSMGR_ECC_DMA_SERR_SET_MSK + | ALT_SYSMGR_ECC_DMA_DERR_SET_MSK); + + return ALT_E_SUCCESS; +} diff --git a/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma_program.c b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma_program.c new file mode 100644 index 0000000000..26de4c7f0c --- /dev/null +++ b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_dma_program.c @@ -0,0 +1,1064 @@ +/****************************************************************************** + * + * Copyright 2013 Altera Corporation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include "alt_dma_program.h" +#include "alt_cache.h" +#include <stdio.h> + +///// + +// NOTE: To enable debugging output, delete the next line and uncomment the +// line after. +#define dprintf(...) +// #define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) + +///// + +// +// The following section describes how the bits are used in the "flag" field: +// + +// [17:16] Which loop registers (LOOP0, LOOP1) are currently being used by a +// partially assembled program. LOOP0 is always used before LOOP1. LOOP1 is +// always ended before LOOP0. +#define ALT_DMA_PROGRAM_FLAG_LOOP0 (1UL << 16) +#define ALT_DMA_PROGRAM_FLAG_LOOP1 (1UL << 17) +#define ALT_DMA_PROGRAM_FLAG_LOOP_ALL (ALT_DMA_PROGRAM_FLAG_LOOP0 | ALT_DMA_PROGRAM_FLAG_LOOP1) + +// [18] Flag that marks LOOP0 as a forever loop. Said another way, LOOP0 is +// being used to execute the DMALPFE directive. +#define ALT_DMA_PROGRAM_FLAG_LOOP0_IS_FE (1UL << 18) +// [19] Flag that marks LOOP1 as a forever loop. Said another way, LOOP1 is +// being used to execute the DMALPFE directive. +#define ALT_DMA_PROGRAM_FLAG_LOOP1_IS_FE (1UL << 19) + +// [24] Flag that the first SAR has been programmed. The SAR field is valid and +// is the offset from the start of the buffer where SAR is located. +#define ALT_DMA_PROGRAM_FLAG_SAR (1UL << 24) +// [25] Flag that the first DAR has been programmed. The DAR field is valid and +// is the offset from the start of the buffer where DAR is located. +#define ALT_DMA_PROGRAM_FLAG_DAR (1UL << 25) + +// [31] Flag that marks the last assembled instruction as DMAEND. +#define ALT_DMA_PROGRAM_FLAG_ENDED (1UL << 31) + +///// + +ALT_STATUS_CODE alt_dma_program_init(ALT_DMA_PROGRAM_t * pgm) +{ + // Clear the variables that matter. + pgm->flag = 0; + pgm->code_size = 0; + + // Calculate the cache aligned start location of the buffer. + size_t buffer = (size_t)pgm->program; + size_t offset = ((buffer + ALT_DMA_PROGRAM_CACHE_LINE_SIZE - 1) & ~(ALT_DMA_PROGRAM_CACHE_LINE_SIZE - 1)) - buffer; + + // It is safe to cast to uint16_t because the extra offset can only be up to + // (ALT_DMA_PROGRAM_CACHE_LINE_SIZE - 1) or 31, which is within range of the + // uint16_t. + pgm->buffer_start = (uint16_t)offset; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_uninit(ALT_DMA_PROGRAM_t * pgm) +{ + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_clear(ALT_DMA_PROGRAM_t * pgm) +{ + // Clear the variables that matter + pgm->flag = 0; + pgm->code_size = 0; + + return ALT_E_SUCCESS; +} + +__attribute__((weak)) ALT_STATUS_CODE alt_cache_system_clean(void * address, size_t length) +{ + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_validate(const ALT_DMA_PROGRAM_t * pgm) +{ + // Verify that at least one instruction is in the buffer + if (pgm->code_size == 0) + { + return ALT_E_ERROR; + } + + // Verify all loops are completed. + if (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP_ALL) + { + return ALT_E_ERROR; + } + + // Verify last item is DMAEND + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_ENDED)) + { + return ALT_E_ERROR; + } + + // Sync the DMA program to RAM. + void * vaddr = (void *)((uintptr_t)(pgm->program + pgm->buffer_start) & ~(ALT_CACHE_LINE_SIZE - 1)); + size_t length = (pgm->code_size + ALT_CACHE_LINE_SIZE) & ~(ALT_CACHE_LINE_SIZE - 1); + + dprintf("DEBUG[DMAP]: Program (real) @ %p, length = 0x%x.\n", pgm->program + pgm->buffer_start, pgm->code_size); + dprintf("DEBUG[DMAP]: Clean: addr = %p, length = 0x%x.\n", vaddr, length); + + return alt_cache_system_clean(vaddr, length); +} + +ALT_STATUS_CODE alt_dma_program_progress_reg(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_REG_t reg, + uint32_t current, uint32_t * progress) +{ + // Pointer to where the register is initialized in the program buffer. + uint8_t * buffer = NULL; + + switch (reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_SAR)) + { + return ALT_E_BAD_ARG; + } + buffer = pgm->program + pgm->buffer_start + pgm->sar; + break; + + case ALT_DMA_PROGRAM_REG_DAR: + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_DAR)) + { + return ALT_E_BAD_ARG; + } + buffer = pgm->program + pgm->buffer_start + pgm->dar; + break; + + default: + return ALT_E_BAD_ARG; + } + + uint32_t initial = + (buffer[3] << 24) | + (buffer[2] << 16) | + (buffer[1] << 8) | + (buffer[0] << 0); + + *progress = current - initial; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_update_reg(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_REG_t reg, uint32_t val) +{ + uint8_t * buffer = NULL; + + switch (reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_SAR)) + { + return ALT_E_BAD_ARG; + } + buffer = pgm->program + pgm->buffer_start + pgm->sar; + break; + + case ALT_DMA_PROGRAM_REG_DAR: + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_DAR)) + { + return ALT_E_BAD_ARG; + } + buffer = pgm->program + pgm->buffer_start + pgm->dar; + break; + + default: + return ALT_E_BAD_ARG; + } + + buffer[0] = (uint8_t)((val >> 0) & 0xff); + buffer[1] = (uint8_t)((val >> 8) & 0xff); + buffer[2] = (uint8_t)((val >> 16) & 0xff); + buffer[3] = (uint8_t)((val >> 24) & 0xff); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAADDH(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_REG_t addr_reg, uint16_t val) +{ + // For information on DMAADDH, see PL330, section 4.3.1. + + // Check for sufficient space in buffer + if ((pgm->code_size + 3) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify valid register; construct instruction modifier. + uint8_t ra_mask = 0; + switch (addr_reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + ra_mask = 0x0; + break; + case ALT_DMA_PROGRAM_REG_DAR: + ra_mask = 0x2; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAADDH + buffer[0] = 0x54 | ra_mask; + buffer[1] = (uint8_t)(val & 0xff); + buffer[2] = (uint8_t)(val >> 8); + + // Update the code size. + pgm->code_size += 3; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAADNH(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_REG_t addr_reg, uint16_t val) +{ + // For information on DMAADNH, see PL330, section 4.3.2. + + // Check for sufficient space in buffer + if ((pgm->code_size + 3) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify valid register; construct instruction modifier. + uint8_t ra_mask = 0; + switch (addr_reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + ra_mask = 0x0; + break; + case ALT_DMA_PROGRAM_REG_DAR: + ra_mask = 0x2; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAADNH + buffer[0] = 0x5c | ra_mask; + buffer[1] = (uint8_t)(val & 0xff); + buffer[2] = (uint8_t)(val >> 8); + + // Update the code size. + pgm->code_size += 3; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAEND(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMAEND, see PL330, section 4.3.3. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAEND + buffer[0] = 0x00; + + // Update the code size. + pgm->code_size += 1; + + // Mark program as ended. + pgm->flag |= ALT_DMA_PROGRAM_FLAG_ENDED; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAFLUSHP(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PERIPH_t periph) +{ + // For information on DMAFLUSHP, see PL330, section 4.3.4. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify valid peripheral identifier. + if (periph > ((1 << 5) - 1)) + { + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAFLUSHP + buffer[0] = 0x35; + buffer[1] = (uint8_t)(periph) << 3; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAGO(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_CHANNEL_t channel, uint32_t val, + ALT_DMA_SECURITY_t sec) +{ + // For information on DMAGO, see PL330, section 4.3.5. + + // Check for sufficient space in buffer + if ((pgm->code_size + 6) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify channel + switch (channel) + { + case ALT_DMA_CHANNEL_0: + case ALT_DMA_CHANNEL_1: + case ALT_DMA_CHANNEL_2: + case ALT_DMA_CHANNEL_3: + case ALT_DMA_CHANNEL_4: + case ALT_DMA_CHANNEL_5: + case ALT_DMA_CHANNEL_6: + case ALT_DMA_CHANNEL_7: + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify security; construct ns mask value + uint8_t ns_mask = 0; + switch (sec) + { + case ALT_DMA_SECURITY_DEFAULT: + case ALT_DMA_SECURITY_SECURE: + ns_mask = 0x0; + break; + case ALT_DMA_SECURITY_NONSECURE: + ns_mask = 0x2; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAGO + buffer[0] = 0xa0 | ns_mask; + buffer[1] = (uint8_t)channel; + buffer[2] = (uint8_t)((val >> 0) & 0xff); + buffer[3] = (uint8_t)((val >> 8) & 0xff); + buffer[4] = (uint8_t)((val >> 16) & 0xff); + buffer[5] = (uint8_t)((val >> 24) & 0xff); + + // Update the code size. + pgm->code_size += 6; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAKILL(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMAKILL, see PL330, section 4.3.6. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAKILL + buffer[0] = 0x01; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMALD(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_INST_MOD_t mod) +{ + // For information on DMALD, see PL330, section 4.3.7. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify instruction modifier; construct bs, x mask value. + uint8_t bsx_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_NONE: + bsx_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bsx_mask = 0x1; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bsx_mask = 0x3; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMALD + buffer[0] = 0x04 | bsx_mask; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMALDP(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_INST_MOD_t mod, ALT_DMA_PERIPH_t periph) +{ + // For information on DMALDP, see PL330, section 4.3.8. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify instruction modifier; construct bs mask value. + uint8_t bs_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bs_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bs_mask = 0x2; + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify valid peripheral identifier. + if (periph > ((1 << 5) - 1)) + { + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMALDP + buffer[0] = 0x25 | bs_mask; + buffer[1] = (uint8_t)(periph) << 3; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMALP(ALT_DMA_PROGRAM_t * pgm, + uint32_t iterations) +{ + // For information on DMALP, see PL330, section 4.3.9. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify iterations in range + if ((iterations == 0) || (iterations > 256)) + { + return ALT_E_BAD_ARG; + } + + // Find suitable LOOPx register to use; construct lc mask value. + uint8_t lc_mask = 0; + switch (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP_ALL) + { + case 0: // No LOOPx in use. Use LOOP0. + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP0; + pgm->loop0 = pgm->code_size + 2; // This is the first instruction after the DMALP + lc_mask = 0x0; + break; + + case ALT_DMA_PROGRAM_FLAG_LOOP0: // LOOP0 in use. Use LOOP1. + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP1; + pgm->loop1 = pgm->code_size + 2; // This is the first instruction after the DMALP + lc_mask = 0x2; + break; + + case ALT_DMA_PROGRAM_FLAG_LOOP_ALL: // All LOOPx in use. Report error. + return ALT_E_BAD_OPERATION; + + default: // Catastrophic error !!! + return ALT_E_ERROR; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMALP + buffer[0] = 0x20 | lc_mask; + buffer[1] = (uint8_t)(iterations - 1); + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMALPEND(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_INST_MOD_t mod) +{ + // For information on DMALPEND, see PL330, section 4.3.10. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify instruction modifier; construct bs, x mask value. + uint8_t bsx_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_NONE: + bsx_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bsx_mask = 0x1; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bsx_mask = 0x3; + break; + default: + return ALT_E_BAD_ARG; + } + + // Determine the loop to end, if it is a forever loop; construct lc mask, nf mask, and backwards jump value. + uint8_t lc_mask = 0; + uint8_t nf_mask = 0; + uint16_t backwards_jump = 0; + switch (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP_ALL) + { + case ALT_DMA_PROGRAM_FLAG_LOOP0: // LOOP0 in use. End LOOP0. + + backwards_jump = pgm->code_size - pgm->loop0; + + pgm->flag &= ~ALT_DMA_PROGRAM_FLAG_LOOP0; + pgm->loop0 = 0; + + lc_mask = 0x0; + + if (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP0_IS_FE) + { + pgm->flag &= ~ALT_DMA_PROGRAM_FLAG_LOOP0_IS_FE; + } + else + { + nf_mask = 0x10; + } + break; + + case ALT_DMA_PROGRAM_FLAG_LOOP_ALL: // All LOOPx in use. End LOOP1. + + backwards_jump = pgm->code_size - pgm->loop1; + + pgm->flag &= ~ALT_DMA_PROGRAM_FLAG_LOOP1; + pgm->loop1 = 0; + + lc_mask = 0x4; + + if (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP1_IS_FE) + { + pgm->flag &= ~ALT_DMA_PROGRAM_FLAG_LOOP1_IS_FE; + } + else + { + nf_mask = 0x10; + } + break; + + case 0: // No LOOPx in use. Report error! + return ALT_E_BAD_OPERATION; + + default: // Catastrophic error !!! + return ALT_E_ERROR; + } + + // Verify that the jump size is suitable + if (backwards_jump > 255) + { + return ALT_E_ARG_RANGE; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMALPEND + buffer[0] = 0x28 | nf_mask | lc_mask | bsx_mask; + buffer[1] = (uint8_t)(backwards_jump); + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMALPFE(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMALPFE, see PL330, section 4.3.11. + + // Find suitable LOOPx register to use; + switch (pgm->flag & ALT_DMA_PROGRAM_FLAG_LOOP_ALL) + { + case 0: // No LOOPx in use. Use LOOP0. + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP0; + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP0_IS_FE; + pgm->loop0 = pgm->code_size; + break; + + case ALT_DMA_PROGRAM_FLAG_LOOP0: // LOOP0 in use. Use LOOP1. + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP1; + pgm->flag |= ALT_DMA_PROGRAM_FLAG_LOOP1_IS_FE; + pgm->loop1 = pgm->code_size; + break; + + case ALT_DMA_PROGRAM_FLAG_LOOP_ALL: // All LOOPx in use. Report error. + return ALT_E_BAD_OPERATION; + + default: // Catastrophic error !!! + return ALT_E_ERROR; + } + + // Nothing to assemble. + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAMOV(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_REG_t chan_reg, uint32_t val) +{ + // For information on DMAMOV, see PL330, section 4.3.12. + + // Check for sufficient space in buffer + if ((pgm->code_size + 6) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify channel register; construct rd mask value + uint8_t rd_mask = 0; + switch (chan_reg) + { + case ALT_DMA_PROGRAM_REG_SAR: + rd_mask = 0; + // If SAR has not been set before, mark the location of where SAR is in the buffer. + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_SAR)) + { + pgm->flag |= ALT_DMA_PROGRAM_FLAG_SAR; + pgm->sar = pgm->code_size + 2; + } + break; + + case ALT_DMA_PROGRAM_REG_CCR: + rd_mask = 1; + break; + + case ALT_DMA_PROGRAM_REG_DAR: + rd_mask = 2; + // If DAR has not been set before, mark the location of where DAR is in the buffer. + if (!(pgm->flag & ALT_DMA_PROGRAM_FLAG_DAR)) + { + pgm->flag |= ALT_DMA_PROGRAM_FLAG_DAR; + pgm->dar = pgm->code_size + 2; + } + break; + + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAMOV + buffer[0] = 0xbc;; + buffer[1] = rd_mask; + buffer[2] = (uint8_t)((val >> 0) & 0xff); + buffer[3] = (uint8_t)((val >> 8) & 0xff); + buffer[4] = (uint8_t)((val >> 16) & 0xff); + buffer[5] = (uint8_t)((val >> 24) & 0xff); + + // Update the code size. + pgm->code_size += 6; + + return ALT_E_SUCCESS; + +} + +ALT_STATUS_CODE alt_dma_program_DMANOP(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMANOP, see PL330, section 4.3.13. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMANOP + buffer[0] = 0x18; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMARMB(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMARMB, see PL330, section 4.3.14. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMARMB + buffer[0] = 0x12; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMASEV(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_EVENT_t evt) +{ + // For information on DMA, see PL330, section 4.3.15. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Validate evt selection + switch (evt) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMASEV + buffer[0] = 0x34; + buffer[1] = (uint8_t)(evt) << 3; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAST(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_INST_MOD_t mod) +{ + // For information on DMAST, see PL330, section 4.3.16. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify instruction modifier; construct bs, x mask value. + uint8_t bsx_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_NONE: + bsx_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bsx_mask = 0x1; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bsx_mask = 0x3; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAST + buffer[0] = 0x08 | bsx_mask; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMASTP(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PROGRAM_INST_MOD_t mod, ALT_DMA_PERIPH_t periph) +{ + // For information on DMASTP, see PL330, section 4.3.17. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify instruction modifier; construct bs mask value. + uint8_t bs_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bs_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bs_mask = 0x2; + break; + default: + return ALT_E_BAD_ARG; + } + + // Verify valid peripheral identifier. + if (periph > ((1 << 5) - 1)) + { + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMASTP + buffer[0] = 0x29 | bs_mask; + buffer[1] = (uint8_t)(periph) << 3; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMASTZ(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMASTZ, see PL330, section 4.3.18. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMASTZ + buffer[0] = 0x0c; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAWFE(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_EVENT_t evt, bool invalid) +{ + // For information on DMAWFE, see PL330, section 4.3.19. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Validate evt selection + switch (evt) + { + case ALT_DMA_EVENT_0: + case ALT_DMA_EVENT_1: + case ALT_DMA_EVENT_2: + case ALT_DMA_EVENT_3: + case ALT_DMA_EVENT_4: + case ALT_DMA_EVENT_5: + case ALT_DMA_EVENT_6: + case ALT_DMA_EVENT_7: + case ALT_DMA_EVENT_ABORT: + break; + default: + return ALT_E_BAD_ARG; + } + + // Construct i mask value + uint8_t i_mask = 0; + if (invalid) + { + i_mask = 0x2; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAWFE + buffer[0] = 0x36; + buffer[1] = ((uint8_t)(evt) << 3) | i_mask; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAWFP(ALT_DMA_PROGRAM_t * pgm, + ALT_DMA_PERIPH_t periph, ALT_DMA_PROGRAM_INST_MOD_t mod) +{ + // For information on DMAWFP, see PL330, section 4.3.20. + + // Check for sufficient space in buffer + if ((pgm->code_size + 2) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Verify valid peripheral identifier. + if (periph > ((1 << 5) - 1)) + { + return ALT_E_BAD_ARG; + } + + // Verify instruction modifier; construct bs, p mask value. + uint8_t bsp_mask = 0; + switch (mod) + { + case ALT_DMA_PROGRAM_INST_MOD_SINGLE: + bsp_mask = 0x0; + break; + case ALT_DMA_PROGRAM_INST_MOD_BURST: + bsp_mask = 0x2; + break; + case ALT_DMA_PROGRAM_INST_MOD_PERIPH: + bsp_mask = 0x1; + break; + default: + return ALT_E_BAD_ARG; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAWFP + buffer[0] = 0x30 | bsp_mask; + buffer[1] = (uint8_t)(periph) << 3; + + // Update the code size. + pgm->code_size += 2; + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_dma_program_DMAWMB(ALT_DMA_PROGRAM_t * pgm) +{ + // For information on DMAWMB, see PL330, section 4.3.21. + + // Check for sufficient space in buffer + if ((pgm->code_size + 1) > ALT_DMA_PROGRAM_PROVISION_BUFFER_SIZE) + { + return ALT_E_BUF_OVF; + } + + // Buffer of where to assemble the instruction. + uint8_t * buffer = pgm->program + pgm->buffer_start + pgm->code_size; + + // Assemble DMAWMB + buffer[0] = 0x13; + + // Update the code size. + pgm->code_size += 1; + + return ALT_E_SUCCESS; +} diff --git a/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_qspi.c b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_qspi.c new file mode 100644 index 0000000000..458ef71f46 --- /dev/null +++ b/c/src/lib/libbsp/arm/altera-cyclone-v/hwlib/src/hwmgr/alt_qspi.c @@ -0,0 +1,2619 @@ +/****************************************************************************** +* +* alt_qspi.c - API for the Altera SoC FPGA QSPI device. +* +******************************************************************************/ + +/****************************************************************************** + * + * Copyright 2013 Altera Corporation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include <string.h> +#include <stdio.h> +#include <inttypes.h> +#include "hwlib.h" +#include "alt_clock_manager.h" +#include "alt_qspi.h" +#include "alt_qspi_private.h" +#include "socal/alt_qspi.h" +#include "socal/alt_rstmgr.h" +#include "socal/alt_sysmgr.h" +#include "socal/hps.h" +#include "socal/socal.h" + +///// + +// NOTE: To enable debugging output, delete the next line and uncomment the +// line after. +#define dprintf(...) +// #define dprintf printf + +///// + +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +// qspi_clk operating frequency range. +#define ALT_QSPI_CLK_FREQ_MIN ((alt_freq_t)0) +#define ALT_QSPI_CLK_FREQ_MAX ((alt_freq_t)432000000) + +// The set of all valid QSPI controller interrupt status mask values. +#define ALT_QSPI_INT_STATUS_ALL ( \ + ALT_QSPI_INT_STATUS_MODE_FAIL | \ + ALT_QSPI_INT_STATUS_UFL | \ + ALT_QSPI_INT_STATUS_IDAC_OP_COMPLETE | \ + ALT_QSPI_INT_STATUS_IDAC_OP_REJECT | \ + ALT_QSPI_INT_STATUS_WR_PROT_VIOL | \ + ALT_QSPI_INT_STATUS_ILL_AHB_ACCESS | \ + ALT_QSPI_INT_STATUS_IDAC_WTRMK_TRIG | \ + ALT_QSPI_INT_STATUS_RX_OVF | \ + ALT_QSPI_INT_STATUS_TX_FIFO_NOT_FULL | \ + ALT_QSPI_INT_STATUS_TX_FIFO_FULL | \ + ALT_QSPI_INT_STATUS_RX_FIFO_NOT_EMPTY | \ + ALT_QSPI_INT_STATUS_RX_FIFO_FULL | \ + ALT_QSPI_INT_STATUS_IDAC_RD_FULL \ + ) + +static uint32_t qspi_device_size = 0; + +///// + +static ALT_STATUS_CODE alt_qspi_device_status(uint32_t * status) +{ + // Read flag status register through STIG + return alt_qspi_stig_rd_cmd(ALT_QSPI_STIG_OPCODE_RDSR, 0, 1, status, 10000); +} + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT +static ALT_STATUS_CODE alt_qspi_N25Q_device_flag(uint32_t * flagsr) +{ + if (qspi_device_size < 0x4000000) + { + return ALT_E_SUCCESS; + } + + // Read flag status register through STIG + return alt_qspi_stig_rd_cmd(ALT_QSPI_STIG_OPCODE_RDFLGSR, 0, 1, flagsr, 10000); +} + +// NOTE: This must be called after QSPI has been enabled. Communications with +// the device will not happen until QSPI is enabled. +static inline ALT_STATUS_CODE alt_qspi_N25Q_enable(void) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // Reset the volatile memory on the N25Q + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_cmd(ALT_QSPI_STIG_OPCODE_RESET_EN, 0, 10000); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_cmd(ALT_QSPI_STIG_OPCODE_RESET_MEM, 0, 10000); + } + + ///// + + if (status == ALT_E_SUCCESS) + { + ALT_QSPI_DEV_INST_CONFIG_t cfg = + { + .op_code = ALT_QSPI_STIG_OPCODE_FASTREAD_QUAD_IO, + .inst_type = ALT_QSPI_MODE_SINGLE, // RDID does not support QUAD. + .addr_xfer_type = ALT_QSPI_MODE_QUAD, + .data_xfer_type = ALT_QSPI_MODE_QUAD, + .dummy_cycles = 10 + }; + + status = alt_qspi_device_read_config_set(&cfg); + } + +/* + // CASE 157096: Investigate using QUAD for writes. + if (status == ALT_E_SUCCESS) + { + ALT_QSPI_DEV_INST_CONFIG_t cfg = + { + .op_code = ALT_QSPI_STIG_OPCODE_PP, + .inst_type = ALT_QSPI_MODE_SINGLE, + .addr_xfer_type = ALT_QSPI_MODE_QUAD, + .data_xfer_type = ALT_QSPI_MODE_QUAD, + .dummy_cycles = 0 + }; + + status = alt_qspi_device_write_config_set(&cfg); + } +*/ + + return status; +} + +static ALT_STATUS_CODE alt_qspi_N25Q_flag_wait_for_program(uint32_t timeout) +{ + // The flag status register is only available on the 512 Mib and 1 Gib + // (64 MiB and 128 MiB) Micron parts. + if (qspi_device_size < 0x4000000) + { + return ALT_E_SUCCESS; + } + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + uint32_t time_out = timeout; + uint32_t stat = 0; + bool infinite = (timeout == ALT_QSPI_TIMEOUT_INFINITE); + + do + { + status = alt_qspi_device_status(&stat); + if (status != ALT_E_SUCCESS) + { + break; + } + if (!ALT_QSPI_STIG_SR_BUSY_GET(stat)) + { + break; + } + } + while (time_out-- || infinite); + + if (time_out == (uint32_t)-1 && !infinite) + { + status = ALT_E_TMO; + } + + if (status == ALT_E_SUCCESS) + { + uint32_t flagsr = 0; + + do + { + status = alt_qspi_N25Q_device_flag(&flagsr); + if (status != ALT_E_SUCCESS) + { + break; + } + if (ALT_QSPI_STIG_FLAGSR_PROGRAMREADY_GET(flagsr)) + { + break; + } + } + while (timeout-- || infinite); + + if (timeout == (uint32_t)-1 && !infinite) + { + status = ALT_E_TMO; + } + + if (status == ALT_E_SUCCESS) + { + if (ALT_QSPI_STIG_FLAGSR_PROGRAMERROR_GET(flagsr)) + { + status = ALT_E_ERROR; + } + } + } + return status; +} + +static ALT_STATUS_CODE alt_qspi_N25Q_flag_wait_for_erase(uint32_t timeout) +{ + // The flag status register is only available on the 512 Mib and 1 Gib + // (64 MiB and 128 MiB) Micron parts. + if (qspi_device_size < 0x4000000) + { + return ALT_E_SUCCESS; + } + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + uint32_t time_out = timeout; + uint32_t stat = 0; + bool infinite = (timeout == ALT_QSPI_TIMEOUT_INFINITE); + + do + { + status = alt_qspi_device_status(&stat); + if (status != ALT_E_SUCCESS) + { + break; + } + if (!ALT_QSPI_STIG_SR_BUSY_GET(stat)) + { + break; + } + } + while (time_out-- || infinite); + + if (time_out == (uint32_t)-1 && !infinite) + { + status = ALT_E_TMO; + } + + if (status == ALT_E_SUCCESS) + { + + uint32_t flagsr = 0; + + do + { + status = alt_qspi_N25Q_device_flag(&flagsr); + if (status != ALT_E_SUCCESS) + { + break; + } + if (ALT_QSPI_STIG_FLAGSR_ERASEREADY_GET(flagsr)) + { + break; + } + } + while (timeout-- || infinite); + + if (timeout == (uint32_t)-1 && !infinite) + { + status = ALT_E_TMO; + } + + if (status == ALT_E_SUCCESS) + { + if (ALT_QSPI_STIG_FLAGSR_ERASEERROR_GET(flagsr)) + { + status = ALT_E_ERROR; + } + } + } + + return status; +} +#endif + +// +// A helper function which converts a ns interval into a delay interval for a given MHz. +// The +999 is there to round up the result. +// +static inline int alt_qspi_ns_to_multiplier(int ns, int mhz) +{ + return ((ns * mhz) + 999) / 1000; +} + +ALT_STATUS_CODE alt_qspi_init(void) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + alt_freq_t qspi_clk_freq = 0; + + // Validate QSPI module input clocks. + // - pclk - l4_mp_clk + // - hclk - l4_mp_clk + // - ref_clk - qspi_clk + + // Check and validate the QSPI ref_clk which is connected to the HPS qspi_clk. + if (status == ALT_E_SUCCESS) + { + if (alt_clk_is_enabled(ALT_CLK_QSPI) != ALT_E_TRUE) + { + status = ALT_E_BAD_CLK; + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_clk_freq_get(ALT_CLK_QSPI, &qspi_clk_freq); + if (status == ALT_E_SUCCESS) + { + if (qspi_clk_freq > ALT_QSPI_CLK_FREQ_MAX) + { + return ALT_E_BAD_CLK; + } + } + } + + int qspi_clk_mhz = qspi_clk_freq / 1000000; + + ///// + + // Take QSPI controller out of reset. + alt_clrbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_QSPI_SET_MSK); + + ///// + + // Configure the device timing + + if (status == ALT_E_SUCCESS) + { + ALT_QSPI_TIMING_CONFIG_t timing_cfg = + { + .clk_phase = (ALT_QSPI_CLK_PHASE_t)ALT_QSPI_CFG_SELCLKPHASE_RESET, + .clk_pol = (ALT_QSPI_CLK_POLARITY_t)ALT_QSPI_CFG_SELCLKPOL_RESET, + .cs_da = alt_qspi_ns_to_multiplier(ALT_QSPI_TSHSL_NS_DEF, qspi_clk_mhz), + .cs_dads = alt_qspi_ns_to_multiplier(ALT_QSPI_TSD2D_NS_DEF, qspi_clk_mhz), + .cs_eot = alt_qspi_ns_to_multiplier(ALT_QSPI_TCHSH_NS_DEF, qspi_clk_mhz), + .cs_sot = alt_qspi_ns_to_multiplier(ALT_QSPI_TSLCH_NS_DEF, qspi_clk_mhz), + .rd_datacap = 1 + }; + + dprintf("DEBUG[QSPI]: cs_da = %" PRIu32 ".\n", timing_cfg.cs_da); + dprintf("DEBUG[QSPI]: cs_dads = %" PRIu32 ".\n", timing_cfg.cs_dads); + dprintf("DEBUG[QSPI]: cs_eot = %" PRIu32 ".\n", timing_cfg.cs_eot); + dprintf("DEBUG[QSPI]: cs_sot = %" PRIu32 ".\n", timing_cfg.cs_sot); + + status = alt_qspi_timing_config_set(&timing_cfg); + } + + ///// + + // Configure the remap address register, no remap + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_ahb_remap_address_set(0); + } + + // Configure the interrupt mask register, disabled all first + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_int_disable(ALT_QSPI_INT_STATUS_ALL); + } + + // Configure the baud rate divisor + // CASE 157095: Investigate using 108 MHz, and tweaking the rd_datacap param. + + if (status == ALT_E_SUCCESS) + { + uint32_t device_sclk_mhz = 54; + uint32_t div_actual = (qspi_clk_mhz + (device_sclk_mhz - 1)) / device_sclk_mhz; + dprintf("DEBUG[QSPI]: div_actual = %" PRIu32 ".\n", div_actual); + + ALT_QSPI_BAUD_DIV_t div_bits = (ALT_QSPI_BAUD_DIV_t)(((div_actual + 1) / 2) - 1); + status = alt_qspi_baud_rate_div_set(div_bits); + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_uninit(void) +{ + // Put QSPI controller into reset. + alt_setbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_QSPI_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_disable(void) +{ + alt_clrbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_EN_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_enable(void) +{ + alt_setbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_EN_SET_MSK); + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + ///// + + // Device specific configuration + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_enable(); + } +#endif + + uint32_t rdid = 0; + + // Query device capabilities + // This requires QSPI to be enabled. + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_rdid(&rdid); + } + + if (status == ALT_E_SUCCESS) + { + // NOTE: The size code seems to be a form of BCD (binary coded decimal). + // The first nibble is the 10's digit and the second nibble is the 1's + // digit in the number of bytes. + + // Capacity ID samples: + // 0x15 : 16 Mb => 2 MiB => 1 << 21 ; BCD=15 + // 0x16 : 32 Mb => 4 MiB => 1 << 22 ; BCD=16 + // 0x17 : 64 Mb => 8 MiB => 1 << 23 ; BCD=17 + // 0x18 : 128 Mb => 16 MiB => 1 << 24 ; BCD=18 + // 0x19 : 256 Mb => 32 MiB => 1 << 25 ; BCD=19 + // 0x1a + // 0x1b + // 0x1c + // 0x1d + // 0x1e + // 0x1f + // 0x20 : 512 Mb => 64 MiB => 1 << 26 ; BCD=20 + // 0x21 : 1024 Mb => 128 MiB => 1 << 27 ; BCD=21 + + int cap_code = ALT_QSPI_STIG_RDID_CAPACITYID_GET(rdid); + + if ( ((cap_code >> 4) > 0x9) || ((cap_code & 0xf) > 0x9)) + { + // If a non-valid BCD value is detected at the top or bottom nibble, chances + // are that the chip has a problem. + + dprintf("DEBUG[QSPI]: Invalid CapacityID encountered: 0x%02x.\n", cap_code); + status = ALT_E_ERROR; + } + else + { + int cap_decoded = ((cap_code >> 4) * 10) + (cap_code & 0xf); + + qspi_device_size = 1 << (cap_decoded + 6); + + dprintf("DEBUG[QSPI]: Device size = 0x%" PRIx32 ".\n", qspi_device_size); + } + } + + // Configure the device size and address bytes + + if (status == ALT_E_SUCCESS) + { + ALT_QSPI_DEV_SIZE_CONFIG_t size_cfg = + { + .block_size = ALT_QSPI_DEVSZ_BYTESPERSUBSECTOR_RESET, // 0x10 => 2^16 = 64 KiB + .page_size = ALT_QSPI_DEVSZ_BYTESPERDEVICEPAGE_RESET, // 0x100 => 256 B + .addr_size = ALT_QSPI_DEVSZ_NUMADDRBYTES_RESET, // 0x2 => 3 bytes or 0x00ffffff mask. + .lower_wrprot_block = 0, + .upper_wrprot_block = (qspi_device_size - 1) >> 16, + .wrprot_enable = ALT_QSPI_WRPROT_EN_RESET + }; + + status = alt_qspi_device_size_config_set(&size_cfg); + } + + ///// + + // Configure the DMA parameters + + // This will allow DMA to work well without much intervention by users. + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_dma_config_set(4, 32); + } + + ///// + + return status; +} + +///// + +uint32_t alt_qspi_int_status_get(void) +{ + // Read and return the value of the QSPI controller Interrupt Status + // Register (irqstat). + return alt_read_word(ALT_QSPI_IRQSTAT_ADDR); +} + +ALT_STATUS_CODE alt_qspi_int_clear(const uint32_t mask) +{ + // Check that the [mask] contains valid interrupt status conditions values. + if ((ALT_QSPI_INT_STATUS_ALL & mask) == 0) + { + return ALT_E_BAD_ARG; + } + + // Write 1's to clear the desired interrupt status condition(s). + alt_write_word(ALT_QSPI_IRQSTAT_ADDR, mask); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_int_disable(const uint32_t mask) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Check that the [mask] contains valid interrupt status conditions values. + if ((ALT_QSPI_INT_STATUS_ALL & mask) == 0) + { + return ALT_E_BAD_ARG; + } + + // Write 0's to disable the desired interrupt status condition(s). + alt_clrbits_word(ALT_QSPI_IRQMSK_ADDR, mask); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_int_enable(const uint32_t mask) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Check that the [mask] contains valid interrupt status conditions values. + if ((ALT_QSPI_INT_STATUS_ALL & mask) == 0) + { + return ALT_E_BAD_ARG; + } + + // Write 1's to enable the desired interrupt status condition(s). + alt_setbits_word(ALT_QSPI_IRQMSK_ADDR, mask); + + return ALT_E_SUCCESS; +} + +///// + +bool alt_qspi_is_idle(void) +{ + // If the idle field of the QSPI configuration register is 1 then the serial + // interface and QSPI pipeline is idle. + return ALT_QSPI_CFG_IDLE_GET(alt_read_word(ALT_QSPI_CFG_ADDR)) == 1; +} + +///// + +static ALT_STATUS_CODE alt_qspi_indirect_write_start_bank(uint32_t dst, size_t length); + +static ALT_STATUS_CODE alt_qspi_indirect_page_bound_write_helper(uint32_t dst, const char * src, size_t length) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_indirect_write_start_bank(dst, length); + } + + if (status == ALT_E_SUCCESS) + { + uint32_t write_count = 0; + uint32_t write_capacity = ALT_QSPI_SRAM_FIFO_ENTRY_COUNT - alt_qspi_sram_partition_get(); + + while (write_count < length) + { + uint32_t space = write_capacity - alt_qspi_indirect_write_fill_level(); + space = MIN(space, (length - write_count)/ sizeof(uint32_t)); + + const uint32_t * data = (const uint32_t *)(src + write_count); + for (uint32_t i = 0; i < space; ++i) + { + alt_write_word(ALT_QSPIDATA_ADDR, *data++); + } + + write_count += space * sizeof(uint32_t); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_indirect_write_finish(); + } + + return status; +} + +static ALT_STATUS_CODE alt_qspi_indirect_subsector_aligned_write_helper(const char * data, uint32_t subsec_addr) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + for (int i = 0; i < ALT_QSPI_SUBSECTOR_SIZE / ALT_QSPI_PAGE_SIZE; i++) + { + int offset = i * ALT_QSPI_PAGE_SIZE; + + status = alt_qspi_indirect_page_bound_write_helper(subsec_addr + offset, data + offset, ALT_QSPI_PAGE_SIZE); + if (status != ALT_E_SUCCESS) + { + break; + } + } + + return status; +} + +static ALT_STATUS_CODE alt_qspi_indirect_read_start_bank(uint32_t src, size_t size); + +// +// This helper function reads a segment of data, which is limited to 1 bank +// (24 bits of addressing). +// +static ALT_STATUS_CODE alt_qspi_read_bank(char * dst, uint32_t src, size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_indirect_read_start_bank(src, size); + } + + if (status == ALT_E_SUCCESS) + { + uint32_t read_count = 0; + + while (!alt_qspi_indirect_read_is_complete()) + { + uint32_t level = alt_qspi_indirect_read_fill_level(); +// level = MIN(level, (size - read_count) / sizeof(uint32_t)); + + uint32_t * data = (uint32_t *)(dst + read_count); + for (uint32_t i = 0; i < level; ++i) + { + *data++ = alt_read_word(ALT_QSPIDATA_ADDR); + } + + read_count += level * sizeof(uint32_t); + } + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_indirect_read_finish(); + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_read(void * dst, uint32_t src, size_t size) +{ + if (src >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (src + size - 1 >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (size == 0) + { + return ALT_E_SUCCESS; + } + + if ((uintptr_t)dst & 0x3) + { + return ALT_E_ERROR; + } + + if (src & 0x3) + { + return ALT_E_ERROR; + } + + if (size & 0x3) + { + return ALT_E_ERROR; + } + + ///// + + // Verify that there is not already a read in progress. + if (ALT_QSPI_INDRD_RD_STAT_GET(alt_read_word(ALT_QSPI_INDRD_ADDR))) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // + // bank_count : The number of bank(s) affected, including partial banks. + // bank_addr : The aligned address of the first affected bank, including partial bank(s). + // bank_ofst : The offset of the bank to read. Only used when reading the first bank. + // + uint32_t bank_count = ((src + size - 1) >> 24) - (src >> 24) + 1; + uint32_t bank_addr = src & ALT_QSPI_BANK_ADDR_MSK; + uint32_t bank_ofst = src & (ALT_QSPI_BANK_SIZE - 1); + + char * data = (char *)dst; + + uint32_t copy_length = MIN(size, ALT_QSPI_BANK_SIZE - bank_ofst); + + dprintf("DEBUG[QSPI]: read(): bulk: mem_addr = %p; flash_addr = 0x%" PRIx32 ".\n", data, src); + dprintf("DEBUG[QSPI]: read(): bulk: bank_count = 0x%" PRIx32 ", bank_ofst = 0x%" PRIx32 ".\n", bank_count, bank_ofst); + + for (uint32_t i = 0; i < bank_count; ++i) + { + dprintf("DEBUG[QSPI]: read(): bank 0x%" PRIx32 "; copy_length = 0x%" PRIx32 ".\n", bank_addr >> 24, copy_length); + + status = alt_qspi_device_bank_select(bank_addr >> 24); + if (status != ALT_E_SUCCESS) + { + break; + } + + status = alt_qspi_read_bank(dst, bank_ofst, copy_length); + if (status != ALT_E_SUCCESS) + { + break; + } + + bank_addr += ALT_QSPI_BANK_SIZE; + data += copy_length; + size -= copy_length; + + copy_length = MIN(size, ALT_QSPI_BANK_SIZE); + } + + return status; +} + +static ALT_STATUS_CODE alt_qspi_write_bank(uint32_t dst, const char * src, size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + ///// + + uint32_t page_ofst = dst & (ALT_QSPI_PAGE_SIZE - 1); + uint32_t write_size = MIN(size, ALT_QSPI_PAGE_SIZE - page_ofst); + + while (size) + { + dprintf("DEBUG[QSPI]: write(): flash dst = 0x%" PRIx32 ", mem src = %p, write size = 0x%" PRIx32 ", size left = 0x%x.\n", dst, src, write_size, size); + + status = alt_qspi_indirect_page_bound_write_helper(dst, src, write_size); + if (status != ALT_E_SUCCESS) + { + break; + } + + dst += write_size; + src += write_size; + size -= write_size; + + write_size = MIN(size, ALT_QSPI_PAGE_SIZE); + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_write(uint32_t dst, const void * src, size_t size) +{ + if (dst >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (dst + size - 1 >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (size == 0) + { + return ALT_E_SUCCESS; + } + + if ((uintptr_t)src & 0x3) + { + return ALT_E_ERROR; + } + + if (dst & 0x3) + { + return ALT_E_ERROR; + } + + if (size & 0x3) + { + return ALT_E_ERROR; + } + + ///// + + // Verify that there is not already a write in progress. + if (ALT_QSPI_INDWR_RDSTAT_GET(alt_read_word(ALT_QSPI_INDWR_ADDR))) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + uint32_t bank_count = ((dst + size - 1) >> 24) - (dst >> 24) + 1; + uint32_t bank_addr = dst & ALT_QSPI_BANK_ADDR_MSK; + uint32_t bank_ofst = dst & (ALT_QSPI_BANK_SIZE - 1); + + const char * data = src; + + uint32_t copy_length = MIN(size, ALT_QSPI_BANK_SIZE - bank_ofst); + + dprintf("DEBUG[QSPI]: write(): bulk: flash_addr = 0x%" PRIx32 "; mem_addr = %p.\n", dst, data); + dprintf("DEBUG[QSPI]: write(): bulk: bank_count = 0x%" PRIx32 ", bank_ofst = 0x%" PRIx32 ".\n", bank_count, bank_ofst); + + for (uint32_t i = 0; i < bank_count; ++i) + { + dprintf("DEBUG[QSPI]: write(): bank 0x%" PRIx32 "; copy_length = 0x%" PRIx32 ".\n", bank_addr >> 24, copy_length); + + status = alt_qspi_device_bank_select(bank_addr >> 24); + if (status != ALT_E_SUCCESS) + { + break; + } + + status = alt_qspi_write_bank(bank_ofst, data, copy_length); + if (status != ALT_E_SUCCESS) + { + break; + } + + bank_addr += ALT_QSPI_BANK_SIZE; + data += copy_length; + size -= copy_length; + + copy_length = MIN(size, ALT_QSPI_BANK_SIZE); + } + + return status; +} + +static ALT_STATUS_CODE alt_qspi_erase_subsector_bank(uint32_t addr); + +static ALT_STATUS_CODE alt_qspi_replace_bank(uint32_t dst, const char * src, size_t size) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // + // subsec_count : The total number of affected subsector(s), + // including partial subsector(s). + // subsec_addr : The aligned address of the next affected subsector, + // including partial subsector(s). + // subsec_partial_head : The number of subsector unaligned data to be + // written out at the start of the flash write + // request. This data ends at the end of the subsector + // or earlier depending on the number of data to be + // written. + // subsec_partial_tail : The number of subsector unaligned data to be + // written out at the end of the flash write request. + // This data starts at the start of the subsector. If + // only a single subsector is written (partial or + // full), this value will be zero. + // + + uint32_t subsec_count = ((dst + size - 1) >> 12) - (dst >> 12) + 1; + uint32_t subsec_addr = dst & ALT_QSPI_SUBSECTOR_ADDR_MSK; + + uint32_t subsec_partial_head = MIN(ALT_QSPI_SUBSECTOR_SIZE - (dst & (ALT_QSPI_SUBSECTOR_SIZE - 1)), size) & (ALT_QSPI_SUBSECTOR_SIZE - 1); + uint32_t subsec_partial_tail = (size - subsec_partial_head) & (ALT_QSPI_SUBSECTOR_SIZE - 1); + + dprintf("DEBUG[QSPI]: replace(): report: dst = 0x%" PRIx32 "; size = 0x%x.\n", + dst, size); + dprintf("DEBUG[QSPI]: replace(): report: subsec_count = 0x%" PRIx32 "; subsec_addr = 0x%" PRIx32 ".\n", + subsec_count, subsec_addr); + dprintf("DEBUG[QSPI]: replace(): report: partial_head = 0x%" PRIx32 "; partial_tail = 0x%" PRIx32 ".\n", + subsec_partial_head, subsec_partial_tail); + + // Write the first subsector, partial case. + + if (subsec_partial_head) + { + // The write request is not aligned to a subsector so we must do the + // Read-Modify-Write cycle to preserve the existing data at the head of + // the subsector not affected by the write. + + char subsec_buf[ALT_QSPI_SUBSECTOR_SIZE]; + + uint32_t subsec_ofst = dst & ~ALT_QSPI_SUBSECTOR_ADDR_MSK; + + // - Read the subsector into buffer + // - Erase that subsector + // - Copy in the user data into buffer + // - Write out buffer to subsector + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_read_bank(subsec_buf, subsec_addr, subsec_ofst); + } + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_erase_subsector_bank(subsec_addr); + } + if (status == ALT_E_SUCCESS) + { + memcpy(subsec_buf + subsec_ofst, src, subsec_partial_head); + status = alt_qspi_indirect_subsector_aligned_write_helper(subsec_buf, subsec_addr); + } + + // Do some bookkeeping on the user buffer information + src += subsec_partial_head; + size -= subsec_partial_head; + + // Do some bookkeeping on the subsector tracking + subsec_count--; + subsec_addr += ALT_QSPI_SUBSECTOR_SIZE; + + dprintf("DEBUG[QSPI]: replace(): partial head: subsec_ofst = 0x%" PRIx32 "; size left = 0x%x; status = %" PRIi32 ".\n", + subsec_ofst, size, status); + } + + // If there is a partial tail, then take 1 off the subsec_count. This way + // the following loop will write out all the complete subsectors. The tail + // will be written out afterwards. + + if (subsec_partial_tail) + { + subsec_count--; + } + + // Write the aligned subsectors following any partial subsectors. + + for (uint32_t i = 0; i < subsec_count; ++i) + { + // - Erase subsector + // - Write out buffer to subsector + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_erase_subsector_bank(subsec_addr); + } + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_indirect_subsector_aligned_write_helper(src, subsec_addr); + } + + src += ALT_QSPI_SUBSECTOR_SIZE; + size -= ALT_QSPI_SUBSECTOR_SIZE; + + // Don't modify subsec_count as it's being used by the loop. + subsec_addr += ALT_QSPI_SUBSECTOR_SIZE; + + dprintf("DEBUG[QSPI]: replace(): subsec aligned: size left = 0x%x, status = %" PRIi32 ".\n", + size, status); + } + + // Write the last subsector, partial case. + + if (subsec_partial_tail) + { + // The write request is not aligned to a subsector so we must do the + // Read-Modify-Write cycle to preserve the existing data at the end of + // the subsector not affected by the write. + + char subsec_buf[ALT_QSPI_SUBSECTOR_SIZE]; + + // - Read the subsector into buffer + // - Erase that subsector + // - Copy in the user data into buffer + // - Write out buffer to subsector + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_read_bank(subsec_buf + subsec_partial_tail, + subsec_addr + subsec_partial_tail, + ALT_QSPI_SUBSECTOR_SIZE - subsec_partial_tail); + } + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_erase_subsector_bank(subsec_addr); + } + if (status == ALT_E_SUCCESS) + { + memcpy(subsec_buf, src, subsec_partial_tail); + status = alt_qspi_indirect_subsector_aligned_write_helper(subsec_buf, subsec_addr); + } + + src += subsec_partial_tail; + size -= subsec_partial_tail; + + dprintf("DEBUG[QSPI]: replace(): partial tail: size left = 0x%x, status = %" PRIi32 ".\n", + size, status); + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_replace(uint32_t dst, const void * src, size_t size) +{ + if (dst >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (dst + size - 1 >= qspi_device_size) + { + return ALT_E_ERROR; + } + + if (size == 0) + { + return ALT_E_SUCCESS; + } + + if ((uintptr_t)src & 0x3) + { + return ALT_E_ERROR; + } + + if (dst & 0x3) + { + return ALT_E_ERROR; + } + + if (size & 0x3) + { + return ALT_E_ERROR; + } + + ///// + + // Verify that there is not already a read in progress. + if (ALT_QSPI_INDRD_RD_STAT_GET(alt_read_word(ALT_QSPI_INDRD_ADDR))) + { + return ALT_E_ERROR; + } + + // Verify that there is not already a write in progress. + if (ALT_QSPI_INDWR_RDSTAT_GET(alt_read_word(ALT_QSPI_INDWR_ADDR))) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + uint32_t bank_count = ((dst + size - 1) >> 24) - (dst >> 24) + 1; + uint32_t bank_addr = dst & ALT_QSPI_BANK_ADDR_MSK; + uint32_t bank_ofst = dst & (ALT_QSPI_BANK_SIZE - 1); + + const char * data = (const char *)src; + + uint32_t copy_length = MIN(size, ALT_QSPI_BANK_SIZE - bank_ofst); + + dprintf("DEBUG[QSPI]: replace(): bulk: flash_addr = 0x%" PRIx32 "; mem_addr = %p.\n", dst, data); + dprintf("DEBUG[QSPI]: replace(): bulk: bank_count = 0x%" PRIx32 ", bank_ofst = 0x%" PRIx32 ".\n", bank_count, bank_ofst); + + for (uint32_t i = 0; i < bank_count; ++i) + { + dprintf("DEBUG[QSPI]: replace(): bank 0x%" PRIx32 "; copy_length = 0x%" PRIx32 ".\n", bank_addr >> 24, copy_length); + + status = alt_qspi_device_bank_select(bank_addr >> 24); + if (status != ALT_E_SUCCESS) + { + break; + } + + status = alt_qspi_replace_bank(bank_ofst, data, copy_length); + if (status != ALT_E_SUCCESS) + { + break; + } + + bank_addr += ALT_QSPI_BANK_SIZE; + data += copy_length; + size -= copy_length; + + copy_length = MIN(size, ALT_QSPI_BANK_SIZE); + } + + return status; +} + +///// + +ALT_QSPI_BAUD_DIV_t alt_qspi_baud_rate_div_get(void) +{ + uint32_t baud_rate_div = ALT_QSPI_CFG_BAUDDIV_GET(alt_read_word(ALT_QSPI_CFG_ADDR)); + return (ALT_QSPI_BAUD_DIV_t) baud_rate_div; +} + +ALT_STATUS_CODE alt_qspi_baud_rate_div_set(const ALT_QSPI_BAUD_DIV_t baud_rate_div) +{ + if (0xf < (uint32_t)baud_rate_div) + { + // Invalid baud rate divisor value. + return ALT_E_BAD_ARG; + } + + // Set the Master Mode Baud Rate Divisor Field of the QSPI Configuration Register. + alt_replbits_word(ALT_QSPI_CFG_ADDR, + ALT_QSPI_CFG_BAUDDIV_SET_MSK, + ALT_QSPI_CFG_BAUDDIV_SET(baud_rate_div)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_chip_select_config_get(uint32_t* cs, + ALT_QSPI_CS_MODE_t* cs_mode) +{ + uint32_t cfg = alt_read_word(ALT_QSPI_CFG_ADDR); + + *cs = ALT_QSPI_CFG_PERCSLINES_GET(cfg); + *cs_mode = (ALT_QSPI_CS_MODE_t) ALT_QSPI_CFG_PERSELDEC_GET(cfg); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_chip_select_config_set(const uint32_t cs, + const ALT_QSPI_CS_MODE_t cs_mode) +{ + // chip select cs: + // four bit value, bit 0 = cs0, bit 1 = cs1, bit 2 = cs2, bit 3 = cs3 + // since cs is low true, the value of each bit should be zero if enable the cs. + // + // also allows multiple cs line enabled together. + + if (cs > ((1 << ALT_QSPI_CFG_PERCSLINES_WIDTH) - 1)) + { + // [cs] not within possible 4 bit chip select line value range. + return ALT_E_ARG_RANGE; + } + + if ((cs_mode != ALT_QSPI_CS_MODE_SINGLE_SELECT) && (cs_mode != ALT_QSPI_CS_MODE_DECODE)) + { + return ALT_E_INV_OPTION; + } + + // Update the Peripheral Chip Select Lines and Peripheral Select Decode + // Fields of the QSPI Configuration Register value with the chip select + // options. + uint32_t cfg = alt_read_word(ALT_QSPI_CFG_ADDR); + cfg &= ALT_QSPI_CFG_PERCSLINES_CLR_MSK & ALT_QSPI_CFG_PERSELDEC_CLR_MSK; + cfg |= ALT_QSPI_CFG_PERCSLINES_SET(cs) | ALT_QSPI_CFG_PERSELDEC_SET(cs_mode); + alt_write_word(ALT_QSPI_CFG_ADDR, cfg); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_mode_bit_disable(void) +{ + // Clear the Mode Bit Enable Field of the Device Read Instruction Register + // to disable mode bits from being sent after the address bytes. + alt_clrbits_word(ALT_QSPI_DEVRD_ADDR, ALT_QSPI_DEVRD_ENMODBITS_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_mode_bit_enable(void) +{ + // Set the Mode Bit Enable Field of the Device Read Instruction Register + // to enable mode bits to be sent after the address bytes. + alt_setbits_word(ALT_QSPI_DEVRD_ADDR, ALT_QSPI_DEVRD_ENMODBITS_SET_MSK); + + return ALT_E_SUCCESS; +} + +uint32_t alt_qspi_mode_bit_config_get(void) +{ + // Return the 8 bit value from the Mode Field of the Mode Bit Configuration + // Register. + return ALT_QSPI_MODBIT_MOD_GET(alt_read_word(ALT_QSPI_MODBIT_ADDR)); +} + +ALT_STATUS_CODE alt_qspi_mode_bit_config_set(const uint32_t mode_bits) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + if (mode_bits > ((1 << ALT_QSPI_MODBIT_MOD_WIDTH) - 1)) + { + // 'mode_bits' not within possible 8 bit mode value range. + return ALT_E_ARG_RANGE; + } + + // Set the 8 bit value in the Mode Field of the Mode Bit Configuration + // Register. + alt_replbits_word(ALT_QSPI_MODBIT_ADDR, + ALT_QSPI_MODBIT_MOD_SET_MSK, + ALT_QSPI_MODBIT_MOD_SET(mode_bits)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_size_config_get(ALT_QSPI_DEV_SIZE_CONFIG_t * cfg) +{ + // Although not required, it is recommended that the write protect feature + // be enabled prior to enabling the QSPI controller. This will block any AHB + // writes from taking effect. This also means the write protection registers + // (Lower Write Protection, Upper Write Protection, and Write Protection) + // should be setup and the number of bytes per device block in the device + // size configuration register should be setup prior to enabling the QSPI + // controller. + + // Read Device Size Register and get the Number of Bytes per Block, Number + // of Bytes per Device, and Number of Address Bytes Fields. + + uint32_t devsz = alt_read_word(ALT_QSPI_DEVSZ_ADDR); + + cfg->block_size = ALT_QSPI_DEVSZ_BYTESPERSUBSECTOR_GET(devsz); + cfg->page_size = ALT_QSPI_DEVSZ_BYTESPERDEVICEPAGE_GET(devsz); + cfg->addr_size = ALT_QSPI_DEVSZ_NUMADDRBYTES_GET(devsz); + + // Read Lower Write Protection, Upper Write Protection, and Write Protection + // Registers. + + cfg->lower_wrprot_block = ALT_QSPI_LOWWRPROT_SUBSECTOR_GET(alt_read_word(ALT_QSPI_LOWWRPROT_ADDR)); + cfg->upper_wrprot_block = ALT_QSPI_UPPWRPROT_SUBSECTOR_GET(alt_read_word(ALT_QSPI_UPPWRPROT_ADDR)); + cfg->wrprot_enable = ALT_QSPI_WRPROT_EN_GET(alt_read_word(ALT_QSPI_WRPROT_ADDR)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_size_config_set(const ALT_QSPI_DEV_SIZE_CONFIG_t * cfg) +{ + if (cfg->block_size > ((1 << ALT_QSPI_DEVSZ_BYTESPERSUBSECTOR_WIDTH) - 1)) + { + return ALT_E_ARG_RANGE; + } + + if (cfg->page_size > ((1 << ALT_QSPI_DEVSZ_BYTESPERDEVICEPAGE_WIDTH) - 1)) + { + return ALT_E_ARG_RANGE; + } + + if (cfg->addr_size > ((1 << ALT_QSPI_DEVSZ_NUMADDRBYTES_WIDTH) - 1)) + { + return ALT_E_ARG_RANGE; + } + + if (cfg->lower_wrprot_block > cfg->upper_wrprot_block) + { + // Null write protection regions are not allowed. + return ALT_E_ARG_RANGE; + } + + ///// + + uint32_t value = ALT_QSPI_DEVSZ_BYTESPERSUBSECTOR_SET(cfg->block_size) | + ALT_QSPI_DEVSZ_BYTESPERDEVICEPAGE_SET(cfg->page_size) | + ALT_QSPI_DEVSZ_NUMADDRBYTES_SET(cfg->addr_size); + + alt_write_word(ALT_QSPI_DEVSZ_ADDR, value); + + if (cfg->wrprot_enable) + { + alt_write_word(ALT_QSPI_LOWWRPROT_ADDR, cfg->lower_wrprot_block); + alt_write_word(ALT_QSPI_UPPWRPROT_ADDR, cfg->upper_wrprot_block); + } + + // Read Upper Write Protection Register - uppwrprot. + // Set the Write Protection Enable Bit Field of the Write Protection + // Register accordingly. + if (cfg->wrprot_enable) + { + alt_setbits_word(ALT_QSPI_WRPROT_ADDR, ALT_QSPI_WRPROT_EN_SET(1)); + } + else + { + alt_clrbits_word(ALT_QSPI_WRPROT_ADDR, ALT_QSPI_WRPROT_EN_SET(1)); + } + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_read_config_get(ALT_QSPI_DEV_INST_CONFIG_t * cfg) +{ + // Read the Device Read Instruction Register - devrd. + uint32_t devrd = alt_read_word(ALT_QSPI_DEVRD_ADDR); + + cfg->op_code = ALT_QSPI_DEVRD_RDOPCODE_GET(devrd); + cfg->inst_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVRD_INSTWIDTH_GET(devrd); + cfg->addr_xfer_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVRD_ADDRWIDTH_GET(devrd); + cfg->data_xfer_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVRD_DATAWIDTH_GET(devrd); + cfg->dummy_cycles = ALT_QSPI_DEVRD_DUMMYRDCLKS_GET(devrd); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_read_config_set(const ALT_QSPI_DEV_INST_CONFIG_t * cfg) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Validate input + + if (cfg->op_code > ((1 << ALT_QSPI_DEVRD_RDOPCODE_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + switch (cfg->inst_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (cfg->addr_xfer_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (cfg->data_xfer_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + if (cfg->dummy_cycles > ((1 << ALT_QSPI_DEVRD_DUMMYRDCLKS_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + ///// + + // Read the Device Read Instruction Register - devrd. + uint32_t devrd = alt_read_word(ALT_QSPI_DEVRD_ADDR); + + devrd &= ALT_QSPI_DEVRD_RDOPCODE_CLR_MSK & + ALT_QSPI_DEVRD_INSTWIDTH_CLR_MSK & + ALT_QSPI_DEVRD_ADDRWIDTH_CLR_MSK & + ALT_QSPI_DEVRD_DATAWIDTH_CLR_MSK & + ALT_QSPI_DEVRD_DUMMYRDCLKS_CLR_MSK; + + devrd |= ALT_QSPI_DEVRD_RDOPCODE_SET(cfg->op_code) | + ALT_QSPI_DEVRD_INSTWIDTH_SET(cfg->inst_type) | + ALT_QSPI_DEVRD_ADDRWIDTH_SET(cfg->addr_xfer_type) | + ALT_QSPI_DEVRD_DATAWIDTH_SET(cfg->data_xfer_type) | + ALT_QSPI_DEVRD_DUMMYRDCLKS_SET(cfg->dummy_cycles); + + alt_write_word(ALT_QSPI_DEVRD_ADDR, devrd); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_write_config_get(ALT_QSPI_DEV_INST_CONFIG_t * cfg) +{ + // Device Write Instruction Register - devwr. + uint32_t devwr = alt_read_word(ALT_QSPI_DEVWR_ADDR); + + cfg->op_code = ALT_QSPI_DEVWR_WROPCODE_GET(devwr); + // The Instruction Type field in the Device READ Instruction Register only appears + // once and applies to both READ and WRITE opertions. it is not included in the + // Device WRITE Instruction Register. + cfg->inst_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVRD_INSTWIDTH_GET(alt_read_word(ALT_QSPI_DEVRD_ADDR)); + cfg->addr_xfer_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVWR_ADDRWIDTH_GET(devwr); + cfg->data_xfer_type = (ALT_QSPI_MODE_t) ALT_QSPI_DEVWR_DATAWIDTH_GET(devwr); + cfg->dummy_cycles = ALT_QSPI_DEVWR_DUMMYWRCLKS_GET(devwr); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_device_write_config_set(const ALT_QSPI_DEV_INST_CONFIG_t * cfg) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Validate input + + if (cfg->op_code > ((1 << ALT_QSPI_DEVWR_WROPCODE_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + switch (cfg->inst_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (cfg->addr_xfer_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (cfg->data_xfer_type) + { + case ALT_QSPI_MODE_SINGLE: + case ALT_QSPI_MODE_DUAL: + case ALT_QSPI_MODE_QUAD: + break; + default: + return ALT_E_BAD_ARG; + } + + if (cfg->dummy_cycles > ((1 << ALT_QSPI_DEVWR_DUMMYWRCLKS_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + ///// + + // Read the Device Write Instruction Register - devwr. + uint32_t devwr = alt_read_word(ALT_QSPI_DEVWR_ADDR); + + devwr &= ALT_QSPI_DEVWR_WROPCODE_CLR_MSK & + ALT_QSPI_DEVWR_ADDRWIDTH_CLR_MSK & + ALT_QSPI_DEVWR_DATAWIDTH_CLR_MSK & + ALT_QSPI_DEVWR_DUMMYWRCLKS_CLR_MSK; + + devwr |= ALT_QSPI_DEVWR_WROPCODE_SET(cfg->op_code) | + ALT_QSPI_DEVWR_ADDRWIDTH_SET(cfg->addr_xfer_type) | + ALT_QSPI_DEVWR_DATAWIDTH_SET(cfg->data_xfer_type) | + ALT_QSPI_DEVWR_DUMMYWRCLKS_SET(cfg->dummy_cycles); + + alt_write_word(ALT_QSPI_DEVWR_ADDR, devwr); + + // The Instruction Type field in the Device READ Instruction Register only appears + // once and applies to both READ and WRITE operations - it is not included in the + // Device WRITE Instruction Register. Therefore, modify the Instruction Type + // Field in the Device Read Register. + alt_replbits_word(ALT_QSPI_DEVRD_ADDR, + ALT_QSPI_DEVRD_INSTWIDTH_SET_MSK, + ALT_QSPI_DEVRD_INSTWIDTH_SET((uint32_t) cfg->inst_type)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_timing_config_get(ALT_QSPI_TIMING_CONFIG_t * cfg) +{ + // QSPI Configuration Register - cfg + uint32_t cfgreg = alt_read_word(ALT_QSPI_CFG_ADDR); + cfg->clk_phase = (ALT_QSPI_CLK_PHASE_t) ALT_QSPI_CFG_SELCLKPHASE_GET(cfgreg); + cfg->clk_pol = (ALT_QSPI_CLK_POLARITY_t) ALT_QSPI_CFG_SELCLKPOL_GET(cfgreg); + + // QSPI Device Delay Register + uint32_t delayreg = alt_read_word(ALT_QSPI_DELAY_ADDR); + cfg->cs_sot = ALT_QSPI_DELAY_INIT_GET(delayreg); + cfg->cs_eot = ALT_QSPI_DELAY_AFTER_GET(delayreg); + cfg->cs_dads = ALT_QSPI_DELAY_BTWN_GET(delayreg); + cfg->cs_da = ALT_QSPI_DELAY_NSS_GET(delayreg); + + // Read Data Capture Register + cfg->rd_datacap = ALT_QSPI_RDDATACAP_DELAY_GET(alt_read_word(ALT_QSPI_RDDATACAP_ADDR)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_timing_config_set(const ALT_QSPI_TIMING_CONFIG_t * cfg) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Validate parameter(s) + + switch (cfg->clk_phase) + { + case ALT_QSPI_CLK_PHASE_ACTIVE: + case ALT_QSPI_CLK_PHASE_INACTIVE: + break; + default: + return ALT_E_BAD_ARG; + } + + switch (cfg->clk_pol) + { + case ALT_QSPI_CLK_POLARITY_LOW: + case ALT_QSPI_CLK_POLARITY_HIGH: + break; + default: + return ALT_E_BAD_ARG; + } + + if (cfg->cs_da > ((1 << ALT_QSPI_DELAY_NSS_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + if (cfg->cs_dads > ((1 << ALT_QSPI_DELAY_BTWN_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + if (cfg->cs_eot > ((1 << ALT_QSPI_DELAY_AFTER_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + if (cfg->cs_sot > ((1 << ALT_QSPI_DELAY_INIT_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + if (cfg->rd_datacap > ((1 << ALT_QSPI_RDDATACAP_DELAY_WIDTH) - 1)) + { + return ALT_E_BAD_ARG; + } + + ///// + + // QSPI Configuration Register - cfg + uint32_t cfgreg = alt_read_word(ALT_QSPI_CFG_ADDR); + cfgreg &= ALT_QSPI_CFG_SELCLKPHASE_CLR_MSK & + ALT_QSPI_CFG_SELCLKPOL_CLR_MSK; + cfgreg |= ALT_QSPI_CFG_SELCLKPHASE_SET(cfg->clk_phase) | + ALT_QSPI_CFG_SELCLKPOL_SET(cfg->clk_pol); + alt_write_word(ALT_QSPI_CFG_ADDR, cfgreg); + + // QSPI Device Delay Register + uint32_t delayreg = ALT_QSPI_DELAY_INIT_SET(cfg->cs_sot) | + ALT_QSPI_DELAY_AFTER_SET(cfg->cs_eot) | + ALT_QSPI_DELAY_BTWN_SET(cfg->cs_dads) | + ALT_QSPI_DELAY_NSS_SET(cfg->cs_da); + alt_write_word(ALT_QSPI_DELAY_ADDR, delayreg); + + // Read Data Capture Register + + alt_write_word(ALT_QSPI_RDDATACAP_ADDR, + ALT_QSPI_RDDATACAP_BYP_SET(1) | + ALT_QSPI_RDDATACAP_DELAY_SET(cfg->rd_datacap)); + + return ALT_E_SUCCESS; +} + +///// + +ALT_STATUS_CODE alt_qspi_direct_disable(void) +{ + // Clear (set to 0) the Enable Direct Access Controller Field of the QSPI + // Configuration Register to disable the Direct Access Controller. + alt_clrbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENDIRACC_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_direct_enable(void) +{ + // Set (set to 1) the Enable Direct Access Controller Field of the QSPI + // Configuration Register to enable the Direct Access Controller. + alt_setbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENDIRACC_SET_MSK); + + return ALT_E_SUCCESS; +} + +uint32_t alt_qspi_ahb_remap_address_get(void) +{ + // Read and return the value of the Remap Address Register. + return ALT_QSPI_REMAPADDR_VALUE_GET(alt_read_word(ALT_QSPI_REMAPADDR_ADDR)); +} + +ALT_STATUS_CODE alt_qspi_ahb_remap_address_set(const uint32_t ahb_remap_addr) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + // Read and return the value of the Remap Address Register. + alt_setbits_word(ALT_QSPI_REMAPADDR_ADDR, ALT_QSPI_REMAPADDR_VALUE_SET(ahb_remap_addr)); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_ahb_address_remap_disable(void) +{ + // Clear (set to 0) the Enable AHB Address Remapping Field of the QSPI + // Configuration Register to disable AHB address remapping. + alt_clrbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENAHBREMAP_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_ahb_address_remap_enable(void) +{ + // Set (set to 1) the Enable AHB Address Remapping Field of the QSPI + // Configuration Register to enable AHB address remapping. + alt_setbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENAHBREMAP_SET_MSK); + + return ALT_E_SUCCESS; +} + +///// + +static ALT_STATUS_CODE alt_qspi_indirect_read_start_bank(uint32_t flash_addr, + size_t num_bytes) +{ + alt_write_word(ALT_QSPI_INDRDSTADDR_ADDR, flash_addr); + alt_write_word(ALT_QSPI_INDRDCNT_ADDR, num_bytes); + alt_write_word(ALT_QSPI_INDRD_ADDR, ALT_QSPI_INDRD_START_SET_MSK | + ALT_QSPI_INDRD_IND_OPS_DONE_STAT_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_indirect_read_start(const uint32_t flash_addr, + const size_t num_bytes) +{ + // flash_addr and num_bytes restriction is to prevent possible unaligned + // exceptions. + + if (flash_addr & 0x3) + { + return ALT_E_ERROR; + } + + if (num_bytes & 0x3) + { + return ALT_E_ERROR; + } + + if (num_bytes == 0) + { + // Do not report this as a success. If a indirect read was not + // previously completed, it may be cleared already, at which point + // alt_qspi_indirect_read_is_complete() will never report true. + return ALT_E_ERROR; + } + + if (flash_addr > qspi_device_size) + { + return ALT_E_ERROR; + } + + if (flash_addr + num_bytes > qspi_device_size) + { + return ALT_E_ERROR; + } + + // Verify request does not cross bank boundary. + // This limitation is due to the 3-byte addressing limitation. + if ((flash_addr & ALT_QSPI_BANK_ADDR_MSK) != ((flash_addr + num_bytes - 1) & ALT_QSPI_BANK_ADDR_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that there is not already a read in progress. + if (ALT_QSPI_INDRD_RD_STAT_GET(alt_read_word(ALT_QSPI_INDRD_ADDR))) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status; + status = alt_qspi_device_bank_select(flash_addr >> 24); + if (status != ALT_E_SUCCESS) + { + return status; + } + + ///// + + return alt_qspi_indirect_read_start_bank(flash_addr, + num_bytes); + +} + +ALT_STATUS_CODE alt_qspi_indirect_read_finish(void) +{ + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_indirect_read_cancel(void) +{ + // An indirect operation may be cancelled at any time by setting Indirect + // Transfer Control Register bit [1]. + alt_write_word(ALT_QSPI_INDRD_ADDR, ALT_QSPI_INDRD_CANCEL_SET_MSK); + + return ALT_E_SUCCESS; +} + +uint32_t alt_qspi_indirect_read_fill_level(void) +{ + // Return the SRAM Fill Level (Indirect Read Partition) Field of the SRAM + // Fill Register to get the SRAM Fill Level for the Indirect Read Partition + // in units of SRAM Words (4 bytes). + return ALT_QSPI_SRAMFILL_INDRDPART_GET(alt_read_word(ALT_QSPI_SRAMFILL_ADDR)); +} + +uint32_t alt_qspi_indirect_read_watermark_get(void) +{ + // Return the Watermark value in the Indirect Read Transfer Watermark Register. + return alt_read_word(ALT_QSPI_INDRDWATER_ADDR); +} + +ALT_STATUS_CODE alt_qspi_indirect_read_watermark_set(const uint32_t watermark) +{ + // Verify that there is not already a read in progress. + if (ALT_QSPI_INDRD_RD_STAT_GET(alt_read_word(ALT_QSPI_INDRD_ADDR))) + { + return ALT_E_ERROR; + } + + // Set the Watermark value in the Indirect Read Transfer Watermark Register. + alt_write_word(ALT_QSPI_INDRDWATER_ADDR, watermark); + + return ALT_E_SUCCESS; +} + +bool alt_qspi_indirect_read_is_complete(void) +{ + // The value of the Indirect Completion Status Field of the Indirect Read + // Transfer Control Register is set by hardware when an indirect read + // operation has completed. + return (alt_read_word(ALT_QSPI_INDRD_ADDR) & ALT_QSPI_INDRD_IND_OPS_DONE_STAT_SET_MSK) != 0; +} + +static ALT_STATUS_CODE alt_qspi_indirect_write_start_bank(uint32_t flash_addr, + size_t num_bytes) +{ + alt_write_word(ALT_QSPI_INDWRSTADDR_ADDR, flash_addr); + alt_write_word(ALT_QSPI_INDWRCNT_ADDR, num_bytes); + alt_write_word(ALT_QSPI_INDWR_ADDR, ALT_QSPI_INDWR_START_SET_MSK | + ALT_QSPI_INDWR_INDDONE_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_indirect_write_start(const uint32_t flash_addr, + const size_t num_bytes) +{ + // flash_addr and num_bytes restriction is to prevent possible unaligned + // exceptions. + + if (flash_addr & 0x3) + { + return ALT_E_ERROR; + } + + if (num_bytes & 0x3) + { + return ALT_E_ERROR; + } + + if (num_bytes == 0) + { + // Do not report this as a success. If a indirect write was not + // previously completed, it may be cleared already, at which point + // alt_qspi_indirect_write_is_complete() will never report true. + return ALT_E_ERROR; + } + + if (num_bytes > 256) + { + // The Micron part can only write up to 256 bytes at a time. + return ALT_E_ERROR; + } + + if (flash_addr > qspi_device_size) + { + return ALT_E_ERROR; + } + + if (flash_addr + num_bytes > qspi_device_size) + { + return ALT_E_ERROR; + } + +/* + // Verify request does not cross bank boundary. + // This limitation is due to the 3-byte addressing limitation. + if ((flash_addr & ALT_QSPI_BANK_ADDR_MSK) != ((flash_addr + num_bytes - 1) & ALT_QSPI_BANK_ADDR_MSK)) + { + return ALT_E_ERROR; + } +*/ + // Verify request does not cross page boundary. + // This limitation is in place for the Micron part used. + if ((flash_addr & ALT_QSPI_PAGE_ADDR_MSK) != ((flash_addr + num_bytes - 1) & ALT_QSPI_PAGE_ADDR_MSK)) + { + return ALT_E_ERROR; + } + + // Verify that there is not already a write in progress. + if (ALT_QSPI_INDWR_RDSTAT_GET(alt_read_word(ALT_QSPI_INDWR_ADDR))) + { + return ALT_E_ERROR; + } + + ///// + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + status = alt_qspi_device_bank_select(flash_addr >> 24); + if (status != ALT_E_SUCCESS) + { + return status; + } + + ///// + + return alt_qspi_indirect_write_start_bank(flash_addr, + num_bytes); +} + +ALT_STATUS_CODE alt_qspi_indirect_write_finish(void) +{ +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + return alt_qspi_N25Q_flag_wait_for_program(ALT_QSPI_TIMEOUT_INFINITE); +#else + return ALT_E_SUCCESS; +#endif +} + +ALT_STATUS_CODE alt_qspi_indirect_write_cancel(void) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_flag_wait_for_program(ALT_QSPI_TIMEOUT_INFINITE); + } +#endif + + if (status == ALT_E_SUCCESS) + { + // An indirect operation may be cancelled at any time by setting Indirect + // Transfer Control Register bit [1]. + alt_write_word(ALT_QSPI_INDWR_ADDR, ALT_QSPI_INDWR_CANCEL_SET_MSK); + } + + return status; +} + +uint32_t alt_qspi_indirect_write_fill_level(void) +{ + // Return the SRAM Fill Level (Indirect Write Partition) Field of the SRAM + // Fill Register to get the SRAM Fill Level for the Indirect Write Partition + // in units of SRAM Words (4 bytes). + return ALT_QSPI_SRAMFILL_INDWRPART_GET(alt_read_word(ALT_QSPI_SRAMFILL_ADDR)); +} + +uint32_t alt_qspi_indirect_write_watermark_get(void) +{ + // Return the Watermark value in the Indirect Write Transfer Watermark Register. + return alt_read_word(ALT_QSPI_INDWRWATER_ADDR); +} + +ALT_STATUS_CODE alt_qspi_indirect_write_watermark_set(const uint32_t watermark) +{ + // Verify that there is not already a write in progress. + if (ALT_QSPI_INDWR_RDSTAT_GET(alt_read_word(ALT_QSPI_INDWR_ADDR))) + { + return ALT_E_ERROR; + } + + // Set the Watermark value in the Indirect Write Transfer Watermark Register. + alt_write_word(ALT_QSPI_INDWRWATER_ADDR, watermark); + + return ALT_E_SUCCESS; +} + +bool alt_qspi_indirect_write_is_complete(void) +{ + // The value of the Indirect Completion Status Field of the Indirect Write + // Transfer Control Register is set by hardware when an indirect write + // operation has completed. + return (alt_read_word(ALT_QSPI_INDWR_ADDR) & ALT_QSPI_INDWR_INDDONE_SET_MSK) != 0; +} + +///// + +uint32_t alt_qspi_sram_partition_get(void) +{ + // The number of locations allocated to indirect read is equal to the value + // of the SRAM partition register. See the documentation for this function + // regarding the + 1 in the IP documentation. This way the get() and set() + // will be symmetrical. + + return ALT_QSPI_SRAMPART_ADDR_GET(alt_read_word(ALT_QSPI_SRAMPART_ADDR)); +} + +ALT_STATUS_CODE alt_qspi_sram_partition_set(const uint32_t read_part_size) +{ + if (read_part_size > ((1 << ALT_QSPI_SRAMPART_ADDR_WIDTH) - 1)) + { + return ALT_E_ARG_RANGE; + } + + alt_replbits_word(ALT_QSPI_SRAMPART_ADDR, + ALT_QSPI_SRAMPART_ADDR_SET_MSK, + ALT_QSPI_SRAMPART_ADDR_SET(read_part_size)); + + return ALT_E_SUCCESS; +} + +///// + + +static ALT_STATUS_CODE alt_qspi_erase_subsector_bank(uint32_t addr) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wren(); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_addr_cmd(ALT_QSPI_STIG_OPCODE_SUBSEC_ERASE, 0, addr, 10000); + } + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_flag_wait_for_erase(ALT_QSPI_TIMEOUT_INFINITE); + } +#endif + + return status; +} + +ALT_STATUS_CODE alt_qspi_erase_subsector(const uint32_t addr) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_bank_select(addr >> 24); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_erase_subsector_bank(addr); + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_erase_sector(const uint32_t addr) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_bank_select(addr >> 24); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wren(); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_addr_cmd(ALT_QSPI_STIG_OPCODE_SEC_ERASE, 0, addr, ALT_QSPI_TIMEOUT_INFINITE); + } + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_flag_wait_for_erase(ALT_QSPI_TIMEOUT_INFINITE); + } +#endif + + return status; +} + +ALT_STATUS_CODE alt_qspi_erase_chip(void) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + if (qspi_device_size >= (2 * ALT_QSPI_N25Q_DIE_SIZE)) + { + // NOTE: This path is specifically for 512 Mib and 1 Gib Micron N25Q + // chips only. + + dprintf("DEBUG[QSPI]: erase[chip]: FYI, wait time is ~800s for 128 MiB.\n"); + + uint32_t die_count = qspi_device_size / ALT_QSPI_N25Q_DIE_SIZE; + + for (int i = 0; i < die_count; ++i) + { + if (status != ALT_E_SUCCESS) + { + break; + } + + dprintf("DEBUG[QSPI]: Erase chip: die = %d, total = %" PRIu32 ".\n", i, die_count); + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_bank_select(i * (ALT_QSPI_N25Q_DIE_SIZE / ALT_QSPI_BANK_SIZE)); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wren(); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_addr_cmd(ALT_QSPI_STIG_OPCODE_DIE_ERASE, 0, + i * ALT_QSPI_N25Q_DIE_SIZE, + ALT_QSPI_TIMEOUT_INFINITE); + } + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_flag_wait_for_erase(ALT_QSPI_TIMEOUT_INFINITE); + } +#endif + } + } + else + { + // NOTE: Untested path. + + dprintf("DEBUG[QSPI]: Bulk erase.\n"); + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_bank_select(0); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wren(); + } + + if (status == ALT_E_SUCCESS) + { + // If BULK_ERASE is like other ERASE, it needs the address command. + status = alt_qspi_stig_addr_cmd(ALT_QSPI_STIG_OPCODE_BULK_ERASE, 0, + 0, + ALT_QSPI_TIMEOUT_INFINITE); + } + +#if ALT_QSPI_PROVISION_MICRON_N25Q_SUPPORT + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_N25Q_flag_wait_for_erase(ALT_QSPI_TIMEOUT_INFINITE); + } +#endif + } + + return status; +} + +///// + +ALT_STATUS_CODE alt_qspi_dma_disable(void) +{ + // Clear (set to 0) the Enable DMA Peripheral Interface Field of the QSPI + // Configuration Register to disable the DMA peripheral interface. + alt_clrbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENDMA_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_dma_enable(void) +{ + // Set (set to 1) the Enable DMA Peripheral Interface Field of the QSPI + // Configuration Register to enable the DMA peripheral interface. + alt_setbits_word(ALT_QSPI_CFG_ADDR, ALT_QSPI_CFG_ENDMA_SET_MSK); + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_dma_config_get(uint32_t * single_type_sz, + uint32_t * burst_type_sz) +{ + // Get the current value of the DMA Peripheral Register - dmaper + uint32_t dmaper = alt_read_word(ALT_QSPI_DMAPER_ADDR); + + // For both values, a programmed value of 0 represents a single byte. The + // actual number of bytes used is 2 ** (value in this register field). + *single_type_sz = 1 << ALT_QSPI_DMAPER_NUMSGLREQBYTES_GET(dmaper); + *burst_type_sz = 1 << ALT_QSPI_DMAPER_NUMBURSTREQBYTES_GET(dmaper); + + return ALT_E_SUCCESS; +} + +// +// Returns true if [n] is a power of 2 value otherwise returns false. +// +static bool is_pow_2(uint32_t n) +{ + return ((n > 0) && ((n & (n - 1)) == 0)); +} + +// +// Return the log base 2 value of a number that is known to be a power of 2. +// +static uint32_t log2u(uint32_t value) +{ + uint32_t exp = 0; + while ((exp < 32) && (value != (1 << exp))) + { + ++exp; + } + return exp; +} + +ALT_STATUS_CODE alt_qspi_dma_config_set(const uint32_t single_type_sz, + const uint32_t burst_type_sz) +{ + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + if (single_type_sz < 4) + { + return ALT_E_ERROR; + } + + if (burst_type_sz < 4) + { + return ALT_E_ERROR; + } + + if (burst_type_sz < single_type_sz) + { + return ALT_E_ERROR; + } + + const uint32_t single_type_sz_max = 1 << ((1 << ALT_QSPI_DMAPER_NUMSGLREQBYTES_WIDTH) - 1); + const uint32_t burst_type_sz_max = 1 << ((1 << ALT_QSPI_DMAPER_NUMBURSTREQBYTES_WIDTH) - 1); + + // Both parameter values must be a power of 2 between 1 and 32728. + if ( (single_type_sz > single_type_sz_max) || !is_pow_2(single_type_sz) + || (burst_type_sz > burst_type_sz_max) || !is_pow_2(burst_type_sz) + ) + { + return ALT_E_ARG_RANGE; + } + + // Get the current value of the DMA Peripheral Register - dmaper + uint32_t dmaper = alt_read_word(ALT_QSPI_DMAPER_ADDR); + dmaper &= ALT_QSPI_DMAPER_NUMBURSTREQBYTES_CLR_MSK & + ALT_QSPI_DMAPER_NUMSGLREQBYTES_CLR_MSK; + dmaper |= ALT_QSPI_DMAPER_NUMBURSTREQBYTES_SET(log2u(burst_type_sz)) | + ALT_QSPI_DMAPER_NUMSGLREQBYTES_SET(log2u(single_type_sz)); + alt_write_word(ALT_QSPI_DMAPER_ADDR, dmaper); + + return ALT_E_SUCCESS; +} + +///// + +// +// Private STIG and device commands +// + +static ALT_STATUS_CODE alt_qspi_stig_cmd_helper(uint32_t reg_value, uint32_t timeout) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + bool infinite = (timeout == ALT_QSPI_TIMEOUT_INFINITE); + + alt_write_word(ALT_QSPI_FLSHCMD_ADDR, reg_value); + alt_write_word(ALT_QSPI_FLSHCMD_ADDR, reg_value | ALT_QSPI_FLSHCMD_EXECCMD_E_EXECUTE); + + do + { + reg_value = alt_read_word(ALT_QSPI_FLSHCMD_ADDR); + if (!(reg_value & ALT_QSPI_FLSHCMD_CMDEXECSTAT_SET_MSK)) + { + break; + } + + } while (timeout-- || infinite); + + if (timeout == (uint32_t)-1 && !infinite) + { + status = ALT_E_TMO; + } + + return status; +} + +ALT_STATUS_CODE alt_qspi_stig_cmd(uint32_t opcode, uint32_t dummy, uint32_t timeout) +{ + if (dummy > ((1 << ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_WIDTH) - 1)) + { + return ALT_E_ERROR; + } + + uint32_t reg = ALT_QSPI_FLSHCMD_CMDOPCODE_SET(opcode) | + ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_SET(dummy); + + return alt_qspi_stig_cmd_helper(reg, timeout); +} + +ALT_STATUS_CODE alt_qspi_stig_rd_cmd(uint8_t opcode, + uint32_t dummy, + uint32_t num_bytes, + uint32_t * output, + uint32_t timeout) +{ + if (dummy > ((1 << ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_WIDTH) - 1)) + { + return ALT_E_ERROR; + } + + // STIG read can only return up to 8 bytes. + if ((num_bytes > 8) || (num_bytes == 0)) + { + return ALT_E_BAD_ARG; + } + + uint32_t reg_value = + ALT_QSPI_FLSHCMD_CMDOPCODE_SET(opcode) | + ALT_QSPI_FLSHCMD_ENRDDATA_SET(ALT_QSPI_FLSHCMD_ENRDDATA_E_EN) | + ALT_QSPI_FLSHCMD_NUMRDDATABYTES_SET(num_bytes - 1) | + ALT_QSPI_FLSHCMD_ENCMDADDR_SET(ALT_QSPI_FLSHCMD_ENCMDADDR_E_DISD) | + ALT_QSPI_FLSHCMD_ENMODBIT_SET(ALT_QSPI_FLSHCMD_ENMODBIT_E_DISD) | + ALT_QSPI_FLSHCMD_NUMADDRBYTES_SET(0) | + ALT_QSPI_FLSHCMD_ENWRDATA_SET(ALT_QSPI_FLSHCMD_ENWRDATA_E_NOACTION) | + ALT_QSPI_FLSHCMD_NUMWRDATABYTES_SET(0) | + ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_SET(dummy); + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + status = alt_qspi_stig_cmd_helper(reg_value, timeout); + if (status != ALT_E_SUCCESS) + { + return status; + } + + output[0] = alt_read_word(ALT_QSPI_FLSHCMDRDDATALO_ADDR); + + if (num_bytes > 4) + { + output[1] = alt_read_word(ALT_QSPI_FLSHCMDRDDATAUP_ADDR); + } + + return ALT_E_SUCCESS; +} + +ALT_STATUS_CODE alt_qspi_stig_wr_cmd(uint8_t opcode, + uint32_t dummy, + uint32_t num_bytes, + const uint32_t * input, + uint32_t timeout) +{ + if (dummy > ((1 << ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_WIDTH) - 1)) + { + return ALT_E_ERROR; + } + + // STIG can only write up to 8 bytes. + if ((num_bytes > 8) || (num_bytes == 0)) + { + return ALT_E_BAD_ARG; + } + + uint32_t reg_value = + ALT_QSPI_FLSHCMD_CMDOPCODE_SET(opcode) | + ALT_QSPI_FLSHCMD_ENRDDATA_SET(ALT_QSPI_FLSHCMD_ENRDDATA_E_NOACTION) | + ALT_QSPI_FLSHCMD_NUMRDDATABYTES_SET(0) | + ALT_QSPI_FLSHCMD_ENCMDADDR_SET(ALT_QSPI_FLSHCMD_ENCMDADDR_E_DISD) | + ALT_QSPI_FLSHCMD_ENMODBIT_SET(ALT_QSPI_FLSHCMD_ENMODBIT_E_DISD) | + ALT_QSPI_FLSHCMD_NUMADDRBYTES_SET(0) | + ALT_QSPI_FLSHCMD_ENWRDATA_SET(ALT_QSPI_FLSHCMD_ENWRDATA_E_WRDATABYTES) | + ALT_QSPI_FLSHCMD_NUMWRDATABYTES_SET(num_bytes - 1) | + ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_SET(dummy); + + alt_write_word(ALT_QSPI_FLSHCMDWRDATALO_ADDR, input[0]); + + if (num_bytes > 4) + { + alt_write_word(ALT_QSPI_FLSHCMDWRDATAUP_ADDR, input[1]); + } + + return alt_qspi_stig_cmd_helper(reg_value, timeout); +} + +ALT_STATUS_CODE alt_qspi_stig_addr_cmd(uint8_t opcode, + uint32_t dummy, + uint32_t address, + uint32_t timeout) +{ + if (dummy > ((1 << ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_WIDTH) - 1)) + { + return ALT_E_ERROR; + } + + uint32_t reg = ALT_QSPI_FLSHCMD_CMDOPCODE_SET(opcode) | + ALT_QSPI_FLSHCMD_NUMDUMMYBYTES_SET(dummy); + + reg |= ALT_QSPI_FLSHCMD_ENCMDADDR_SET(ALT_QSPI_FLSHCMD_ENCMDADDR_E_END); + reg |= ALT_QSPI_FLSHCMD_NUMADDRBYTES_SET(ALT_QSPI_FLSHCMD_NUMADDRBYTES_E_ADDRBYTE3); + + alt_write_word(ALT_QSPI_FLSHCMDADDR_ADDR, address); + + return alt_qspi_stig_cmd_helper(reg, timeout); +} + +///// + +ALT_STATUS_CODE alt_qspi_device_wren(void) +{ + // Write enable through STIG (not required, auto send by controller during write) + return alt_qspi_stig_cmd(ALT_QSPI_STIG_OPCODE_WREN, 0, 10000); +} + +ALT_STATUS_CODE alt_qspi_device_wrdis(void) +{ + // Write disable through STIG (not required, auto send by controller during write) + return alt_qspi_stig_cmd(ALT_QSPI_STIG_OPCODE_WRDIS, 0, 10000); +} + +ALT_STATUS_CODE alt_qspi_device_rdid(uint32_t * rdid) +{ + // Read flash device ID through STIG + return alt_qspi_stig_rd_cmd(ALT_QSPI_STIG_OPCODE_RDID, 0, 4, rdid, 10000); +} + +ALT_STATUS_CODE alt_qspi_discovery_parameter(uint32_t * param) +{ + // Read flash discovery parameters through STIG + + return alt_qspi_stig_rd_cmd(ALT_QSPI_STIG_OPCODE_DISCVR_PARAM, 8, 8, param, 10000); +} + +ALT_STATUS_CODE alt_qspi_device_bank_select(uint32_t bank) +{ + ALT_STATUS_CODE status = ALT_E_SUCCESS; + dprintf("DEBUG[QSPI]: bank_select(): switching to bank 0x%" PRIu32 ".\n", bank); + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wren(); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_stig_wr_cmd(ALT_QSPI_STIG_OPCODE_WR_EXT_REG, 0, 1, &bank, 10000); + } + + if (status == ALT_E_SUCCESS) + { + status = alt_qspi_device_wrdis(); + } + + return status; +} + +///// + +static bool alt_qspi_is_enabled(void) +{ + uint32_t cfg = alt_read_word(ALT_QSPI_CFG_ADDR); + + if (cfg & ALT_QSPI_CFG_EN_SET_MSK) + { + return true; + } + else + { + return false; + } +} + +ALT_STATUS_CODE alt_qspi_ecc_start(void * block, size_t size) +{ + if (size < (ALT_QSPI_PAGE_SIZE * 8)) + { + return ALT_E_ERROR; + } + + if (alt_qspi_is_enabled() == false) + { + return ALT_E_ERROR; + } + + if (alt_qspi_is_idle() == false) + { + return ALT_E_ERROR; + } + + ALT_STATUS_CODE status = ALT_E_SUCCESS; + + // 1. Configure SRAM Partition Register to 126 words for read, 2 words for write. + // 2. Enable ECC on QSPI RAM + // 3. Trigger an indirect read transfer that will fill up 126 words in FIFO by + // monitoring read FIFO fill level; Do not read out data through AHB. + // 4. Start AHB read and start indirect write operation to write back to the same + // device location, this will fill up and initilaize the write partition RAM. + // 5. To clear spurious interrupts, reset the QSPI controller. + + // Save the previous partition size + + uint32_t sram_orig = alt_qspi_sram_partition_get(); + dprintf("DEBUG[QSPI][ECC]: Save original SRAM as %" PRIu32 ".\n", sram_orig); + + // Step 1 + + uint32_t sram_fill = (1 << ALT_QSPI_SRAMPART_ADDR_WIDTH) - 2; + alt_qspi_sram_partition_set(sram_fill); + dprintf("DEBUG[QSPI][ECC]: Set new SRAM as %" PRIu32 ".\n", sram_fill); + + // Step 2 + + dprintf("DEBUG[QSPI][ECC]: Enable ECC in SysMgr.\n"); + alt_write_word(ALT_SYSMGR_ECC_QSPI_ADDR, ALT_SYSMGR_ECC_QSPI_EN_SET_MSK); + + // Step 3 + + // Issue a read ~ 2x larger than the read partition. We will read out 1 page, + // which will be used as the buffer to write back to QSPI. This way no data + // actually changes thus no erase will be needed. + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Start indirect read PAGE * 8.\n"); + status = alt_qspi_indirect_read_start(0x0, ALT_QSPI_PAGE_SIZE * 8); + } + + // Read out 1 page for the write data + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Reading out 1 page ...\n"); + + uint32_t read_size = 0; + char * buffer = block; + while (read_size < ALT_QSPI_PAGE_SIZE) + { + uint32_t level = alt_qspi_indirect_read_fill_level(); + level = MIN(level, (ALT_QSPI_PAGE_SIZE - read_size) / sizeof(uint32_t)); + + uint32_t * data = (uint32_t *)(&buffer[read_size]); + for (uint32_t i = 0; i < level; ++i) + { + *data = alt_read_word(ALT_QSPIDATA_ADDR); + ++data; + } + + read_size += level * sizeof(uint32_t); + } + + if (read_size != ALT_QSPI_PAGE_SIZE) + { + status = ALT_E_ERROR; + } + } + + // Wait for read FIFO to report it is up to the specified fill level. + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Waiting for read fill level ...\n"); + + uint32_t timeout = 10000; + + while (alt_qspi_indirect_read_fill_level() < sram_fill) + { + if (--timeout == 0) + { + dprintf("DEBUG[QSPI][ECC]: Waiting for read fill timeout !!!\n"); + status = ALT_E_TMO; + break; + } + } + } + + // Step 4 + + // Issue a write of 1 page of the same data from 0x0. + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Start indirect write PAGE.\n"); + status = alt_qspi_indirect_write_start(0x0, ALT_QSPI_PAGE_SIZE); + } + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Writing in 1 page ...\n"); + + uint32_t write_size = 0; + char * buffer = block; + + while (write_size < ALT_QSPI_PAGE_SIZE) + { + uint32_t space = 2 - alt_qspi_indirect_write_fill_level(); + if (space == 0) + { + dprintf("DEBUG[QSPI][ECC]: Write FIFO filled at write_size = %" PRIu32 ".\n", write_size); + // Space = 0; which means all 2 positions in the write FIFO is filled, + // meaning it has been initialized with respect to ECC. + break; + } + + space = MIN(space, (ALT_QSPI_PAGE_SIZE - write_size) / sizeof(uint32_t)); + + uint32_t * data = (uint32_t *)(&buffer[write_size]); + for (uint32_t i = 0; i < space; ++i) + { + alt_write_word(ALT_QSPIDATA_ADDR, *data); + ++data; + } + + write_size += space * sizeof(uint32_t); + } + + if (write_size != ALT_QSPI_PAGE_SIZE) + { + dprintf("DEBUG[QSPI][ECC]: Cancel indirect write.\n"); + status = alt_qspi_indirect_write_cancel(); + } + } + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Finish indirect write.\n"); + status = alt_qspi_indirect_write_finish(); + } + + // Cancel the indirect read as it has initialized the read FIFO partition. + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Cancel indirect read.\n"); + status = alt_qspi_indirect_read_cancel(); + } + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Finish indirect read.\n"); + status = alt_qspi_indirect_read_finish(); + } + + // Step 5 + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Clear any pending spurious QSPI ECC interrupts.\n"); + + alt_write_word(ALT_SYSMGR_ECC_QSPI_ADDR, + ALT_SYSMGR_ECC_QSPI_EN_SET_MSK + | ALT_SYSMGR_ECC_QSPI_SERR_SET_MSK + | ALT_SYSMGR_ECC_QSPI_DERR_SET_MSK); + } + + ///// + + // Restore original partition + + if (status == ALT_E_SUCCESS) + { + dprintf("DEBUG[QSPI][ECC]: Restore original SRAM as %" PRIu32 ".\n", sram_orig); + status = alt_qspi_sram_partition_set(sram_orig); + } + + return status; +} |