diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 6caaa3d4ad..6d6624c2a1 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -66,6 +66,7 @@ if(CMAKE_CROSSCOMPILING) asf4-drivers/hal/src/hal_delay.c asf4-drivers/hal/src/hal_timer.c asf4-drivers/hal/src/hal_usb_device.c + asf4-drivers/hal/src/hal_spi_m_dma.c asf4-drivers/hal/src/hal_rand_sync.c asf4-drivers/hal/src/hal_flash.c asf4-drivers/hal/src/hal_pac.c @@ -75,6 +76,7 @@ if(CMAKE_CROSSCOMPILING) asf4-drivers/hal/src/hal_usart_async.c asf4-drivers/hal/utils/src/utils_ringbuffer.c asf4-drivers/hpl/gclk/hpl_gclk.c + asf4-drivers/hpl/dmac/hpl_dmac.c asf4-drivers/hpl/oscctrl/hpl_oscctrl.c asf4-drivers/hpl/mclk/hpl_mclk.c asf4-drivers/hpl/osc32kctrl/hpl_osc32kctrl.c diff --git a/external/asf4-drivers/Config/hpl_dmac_config.h b/external/asf4-drivers/Config/hpl_dmac_config.h index 90499fc27f..076626acd3 100644 --- a/external/asf4-drivers/Config/hpl_dmac_config.h +++ b/external/asf4-drivers/Config/hpl_dmac_config.h @@ -8,7 +8,7 @@ // Indicates whether dmac is enabled or not // dmac_enable #ifndef CONF_DMAC_ENABLE -#define CONF_DMAC_ENABLE 0 +#define CONF_DMAC_ENABLE 1 #endif // Priority Level 0 @@ -105,7 +105,7 @@ // Channel 0 settings // dmac_channel_0_settings #ifndef CONF_DMAC_CHANNEL_0_SETTINGS -#define CONF_DMAC_CHANNEL_0_SETTINGS 0 +#define CONF_DMAC_CHANNEL_0_SETTINGS 1 #endif // Channel Run in Standby @@ -122,7 +122,7 @@ // Defines the trigger action used for a transfer // dmac_trigact_0 #ifndef CONF_DMAC_TRIGACT_0 -#define CONF_DMAC_TRIGACT_0 0 +#define CONF_DMAC_TRIGACT_0 2 #endif // Trigger source @@ -214,7 +214,7 @@ // Defines the peripheral trigger which is source of the transfer // dmac_trifsrc_0 #ifndef CONF_DMAC_TRIGSRC_0 -#define CONF_DMAC_TRIGSRC_0 0 +#define CONF_DMAC_TRIGSRC_0 11 #endif // Channel Arbitration Level @@ -277,14 +277,14 @@ // Defines whether source or destination addresses are using the step size settings // dmac_stepsel_0 #ifndef CONF_DMAC_STEPSEL_0 -#define CONF_DMAC_STEPSEL_0 0 +#define CONF_DMAC_STEPSEL_0 1 #endif // Source Address Increment // Indicates whether the source address incrementation is enabled or not // dmac_srcinc_0 #ifndef CONF_DMAC_SRCINC_0 -#define CONF_DMAC_SRCINC_0 0 +#define CONF_DMAC_SRCINC_0 1 #endif // Destination Address Increment diff --git a/external/asf4-drivers/Config/hpl_sercom_config.h b/external/asf4-drivers/Config/hpl_sercom_config.h index 06cf5e641a..7f74bcb010 100644 --- a/external/asf4-drivers/Config/hpl_sercom_config.h +++ b/external/asf4-drivers/Config/hpl_sercom_config.h @@ -235,6 +235,185 @@ #define CONF_SERCOM_0_USART_RECEIVE_PULSE_LENGTH 0 #endif #endif +// +// Enable configuration of module +#ifndef CONF_SERCOM_3_SPI_ENABLE +#define CONF_SERCOM_3_SPI_ENABLE 1 +#endif + +// SPI DMA TX Channel <0-32> +// This defines DMA channel to be used +// spi_master_dma_tx_channel +#ifndef CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL 0 +#endif + +// SPI RX Channel Enable +// spi_master_rx_channel +#ifndef CONF_SERCOM_3_SPI_RX_CHANNEL +#define CONF_SERCOM_3_SPI_RX_CHANNEL 0 +#endif + +// DMA Channel <0-32> +// This defines DMA channel to be used +// spi_master_dma_rx_channel +#ifndef CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL 1 +#endif + +// + +// Set module in SPI Master mode +#ifndef CONF_SERCOM_3_SPI_MODE +#define CONF_SERCOM_3_SPI_MODE 0x03 +#endif + +// Basic Configuration + +// Receive buffer enable +// Enable receive buffer to receive data from slave (RXEN) +// spi_master_rx_enable +#ifndef CONF_SERCOM_3_SPI_RXEN +#define CONF_SERCOM_3_SPI_RXEN 0x0 +#endif + +// Character Size +// Bit size for all characters sent over the SPI bus (CHSIZE) +// <0x0=>8 bits +// <0x1=>9 bits +// spi_master_character_size +#ifndef CONF_SERCOM_3_SPI_CHSIZE +#define CONF_SERCOM_3_SPI_CHSIZE 0x0 +#endif +// Baud rate <1-18000000> +// The SPI data transfer rate +// spi_master_baud_rate +#ifndef CONF_SERCOM_3_SPI_BAUD +#define CONF_SERCOM_3_SPI_BAUD 3000000 +#endif + +// + +// Advanced Configuration +// spi_master_advanced +#ifndef CONF_SERCOM_3_SPI_ADVANCED +#define CONF_SERCOM_3_SPI_ADVANCED 1 +#endif + +// Dummy byte <0x00-0x1ff> +// spi_master_dummybyte +// Dummy byte used when reading data from the slave without sending any data +#ifndef CONF_SERCOM_3_SPI_DUMMYBYTE +#define CONF_SERCOM_3_SPI_DUMMYBYTE 0x1ff +#endif + +// Data Order +// <0=>MSB first +// <1=>LSB first +// I least significant or most significant bit is shifted out first (DORD) +// spi_master_arch_dord +#ifndef CONF_SERCOM_3_SPI_DORD +#define CONF_SERCOM_3_SPI_DORD 0x0 +#endif + +// Clock Polarity +// <0=>SCK is low when idle +// <1=>SCK is high when idle +// Determines if the leading edge is rising or falling with a corresponding opposite edge at the trailing edge. (CPOL) +// spi_master_arch_cpol +#ifndef CONF_SERCOM_3_SPI_CPOL +#define CONF_SERCOM_3_SPI_CPOL 0x0 +#endif + +// Clock Phase +// <0x0=>Sample input on leading edge +// <0x1=>Sample input on trailing edge +// Determines if input data is sampled on leading or trailing SCK edge. (CPHA) +// spi_master_arch_cpha +#ifndef CONF_SERCOM_3_SPI_CPHA +#define CONF_SERCOM_3_SPI_CPHA 0x0 +#endif + +// Immediate Buffer Overflow Notification +// Controls when OVF is asserted (IBON) +// <0x0=>In data stream +// <0x1=>On buffer overflow +// spi_master_arch_ibon +#ifndef CONF_SERCOM_3_SPI_IBON +#define CONF_SERCOM_3_SPI_IBON 0x0 +#endif + +// Run in stand-by +// Module stays active in stand-by sleep mode. (RUNSTDBY) +// spi_master_arch_runstdby +#ifndef CONF_SERCOM_3_SPI_RUNSTDBY +#define CONF_SERCOM_3_SPI_RUNSTDBY 0x0 +#endif + +// Debug Stop Mode +// Behavior of the baud-rate generator when CPU is halted by external debugger. (DBGSTOP) +// <0=>Keep running +// <1=>Halt +// spi_master_arch_dbgstop +#ifndef CONF_SERCOM_3_SPI_DBGSTOP +#define CONF_SERCOM_3_SPI_DBGSTOP 0 +#endif + +// + +// Address mode disabled in master mode +#ifndef CONF_SERCOM_3_SPI_AMODE_EN +#define CONF_SERCOM_3_SPI_AMODE_EN 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_AMODE +#define CONF_SERCOM_3_SPI_AMODE 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_ADDR +#define CONF_SERCOM_3_SPI_ADDR 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_ADDRMASK +#define CONF_SERCOM_3_SPI_ADDRMASK 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_SSDE +#define CONF_SERCOM_3_SPI_SSDE 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_MSSEN +#define CONF_SERCOM_3_SPI_MSSEN 0x0 +#endif + +#ifndef CONF_SERCOM_3_SPI_PLOADEN +#define CONF_SERCOM_3_SPI_PLOADEN 0 +#endif + +// Receive Data Pinout +// <0x0=>PAD[0] +// <0x1=>PAD[1] +// <0x2=>PAD[2] +// <0x3=>PAD[3] +// spi_master_rxpo +#ifndef CONF_SERCOM_3_SPI_RXPO +#define CONF_SERCOM_3_SPI_RXPO 2 +#endif + +// Transmit Data Pinout +// <0x0=>PAD[0,1]_DO_SCK +// <0x1=>PAD[2,3]_DO_SCK +// <0x2=>PAD[3,1]_DO_SCK +// <0x3=>PAD[0,3]_DO_SCK +// spi_master_txpo +#ifndef CONF_SERCOM_3_SPI_TXPO +#define CONF_SERCOM_3_SPI_TXPO 0 +#endif + +// Calculate baud register value from requested baudrate value +#ifndef CONF_SERCOM_3_SPI_BAUD_RATE +#define CONF_SERCOM_3_SPI_BAUD_RATE ((float)CONF_GCLK_SERCOM3_CORE_FREQUENCY / (float)(2 * CONF_SERCOM_3_SPI_BAUD)) - 1 +#endif #include diff --git a/external/asf4-drivers/hal/include/hal_spi_m_dma.h b/external/asf4-drivers/hal/include/hal_spi_m_dma.h new file mode 100644 index 0000000000..24e7b11e5a --- /dev/null +++ b/external/asf4-drivers/hal/include/hal_spi_m_dma.h @@ -0,0 +1,257 @@ +/** + * \file + * + * \brief SPI DMA related functionality declaration. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#ifndef _HAL_SPI_M_DMA_H_INCLUDED +#define _HAL_SPI_M_DMA_H_INCLUDED + +#include +#include + +/** + * \addtogroup doc_driver_hal_spi_master_dma + * + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration of spi_descriptor. */ +struct spi_m_dma_descriptor; + +/** The callback types */ +enum spi_m_dma_cb_type { + /** Callback type for DMA transfer buffer done */ + SPI_M_DMA_CB_TX_DONE, + /** Callback type for DMA receive buffer done */ + SPI_M_DMA_CB_RX_DONE, + /** Callback type for DMA errors */ + SPI_M_DMA_CB_ERROR, + SPI_M_DMA_CB_N +}; + +/** + * \brief SPI Master DMA callback type + */ +typedef void (*spi_m_dma_cb_t)(struct _dma_resource *resource); + +/** \brief SPI HAL driver struct for DMA access + */ +struct spi_m_dma_descriptor { + struct _spi_m_dma_hpl_interface *func; + /** Pointer to SPI device instance */ + struct _spi_m_dma_dev dev; + /** I/O read/write */ + struct io_descriptor io; + /** DMA resource */ + struct _dma_resource *resource; +}; + +/** \brief Set the SPI HAL instance function pointer for HPL APIs. + * + * Set SPI HAL instance function pointer for HPL APIs. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] func Pointer to the HPL api structure. + * + */ +void spi_m_dma_set_func_ptr(struct spi_m_dma_descriptor *spi, void *const func); + +/** \brief Initialize the SPI HAL instance and hardware for DMA mode + * + * Initialize SPI HAL with dma mode. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] hw Pointer to the hardware base. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_INVALID_DATA Error, initialized. + */ +int32_t spi_m_dma_init(struct spi_m_dma_descriptor *spi, void *const hw); + +/** \brief Deinitialize the SPI HAL instance + * + * Abort transfer, disable and reset SPI, de-init software. + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_deinit(struct spi_m_dma_descriptor *spi); + +/** \brief Enable SPI + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_enable(struct spi_m_dma_descriptor *spi); + +/** \brief Disable SPI + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_disable(struct spi_m_dma_descriptor *spi); + +/** \brief Set SPI baudrate + * + * Works if SPI is initialized as master. + * In the function a sanity check is used to confirm it's called in the correct mode. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] baud_val The target baudrate value + * (See "baudrate calculation" for calculating the value). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy. + * + * note: This api should be used to write the baudrate register with the given baud_val + * paramter, the user has to calculate the baud_val for required baud rate and pass it as + * argument(baud_val) to this api + */ +int32_t spi_m_dma_set_baudrate(struct spi_m_dma_descriptor *spi, const uint32_t baud_val); + +/** \brief Set SPI mode + * + * Set SPI transfer mode (\ref spi_transfer_mode), + * which controls clock polarity and clock phase: + * - Mode 0: leading edge is rising edge, data sample on leading edge. + * - Mode 1: leading edge is rising edge, data sample on trailing edge. + * - Mode 2: leading edge is falling edge, data sample on leading edge. + * - Mode 3: leading edge is falling edge, data sample on trailing edge. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] mode The mode (\ref spi_transfer_mode). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + */ +int32_t spi_m_dma_set_mode(struct spi_m_dma_descriptor *spi, const enum spi_transfer_mode mode); + +/** \brief Set the SPI transfer character size in number of bits + * + * The character size (\ref spi_char_size) influence the way the data is + * sent/received. + * For char size <= 8-bit, data is stored byte by byte. + * For char size between 9-bit ~ 16-bit, data is stored in 2-byte length. + * Note that the default and recommended char size is 8-bit since it's + * supported by all system. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] char_size The char size (\ref spi_char_size). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + * \retval ERR_INVALID_ARG The char size is not supported. + */ +int32_t spi_m_dma_set_char_size(struct spi_m_dma_descriptor *spi, const enum spi_char_size char_size); + +/** \brief Set SPI transfer data order + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] dord The data order: send LSB/MSB first. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + * \retval ERR_INVALID The data order is not supported. + */ +int32_t spi_m_dma_set_data_order(struct spi_m_dma_descriptor *spi, const enum spi_data_order dord); + +/** \brief Perform the SPI data transfer (TX and RX) with the DMA + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] txbuf Pointer to the transfer information. + * \param[out] rxbuf Pointer to the receiver information. + * \param[in] length SPI transfer data length. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy. + */ +int32_t spi_m_dma_transfer(struct spi_m_dma_descriptor *spi, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length); + +/** \brief Register a function as an SPI transfer completion callback + * + * Register a callback function specified by its \c type. + * - SPI_CB_COMPLETE: set the function that will be called on the SPI transfer + * completion including deactivating the CS. + * - SPI_CB_XFER: set the function that will be called on the SPI buffer transfer + * completion. + * Register a NULL function to not use the callback. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] type Callback type (\ref spi_m_dma_cb_type). + * \param[in] func Pointer to callback function. + */ +void spi_m_dma_register_callback(struct spi_m_dma_descriptor *spi, const enum spi_m_dma_cb_type type, + spi_m_dma_cb_t func); + +/** + * \brief Return I/O descriptor for this SPI instance + * + * This function will return an I/O instance for this SPI driver instance + * + * \param[in] spi An SPI master descriptor, which is used to communicate through + * SPI + * \param[in, out] io A pointer to an I/O descriptor pointer type + * + * \retval ERR_NONE + */ +int32_t spi_m_dma_get_io_descriptor(struct spi_m_dma_descriptor *const spi, struct io_descriptor **io); + +/** \brief Retrieve the current driver version + * + * \return Current driver version. + */ +uint32_t spi_m_dma_get_version(void); + +#ifdef __cplusplus +} +#endif +/**@}*/ +#endif /* ifndef _HAL_SPI_M_DMA_H_INCLUDED */ diff --git a/external/asf4-drivers/hal/include/hpl_spi_dma.h b/external/asf4-drivers/hal/include/hpl_spi_dma.h new file mode 100644 index 0000000000..04a3015807 --- /dev/null +++ b/external/asf4-drivers/hal/include/hpl_spi_dma.h @@ -0,0 +1,88 @@ +/** + * \file + * + * \brief Common SPI DMA related functionality declaration. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#ifndef _HPL_SPI_DMA_H_INCLUDED +#define _HPL_SPI_DMA_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** The callback types */ +enum _spi_dma_dev_cb_type { + /** Callback type for DMA transmit. */ + SPI_DEV_CB_DMA_TX, + /** Callback type for DMA receive. */ + SPI_DEV_CB_DMA_RX, + /** Callback type for DMA error. */ + SPI_DEV_CB_DMA_ERROR, + /** Number of callbacks. */ + SPI_DEV_CB_DMA_N +}; + +struct _spi_dma_dev; + +/** + * \brief The prototype for callback on SPI DMA. + */ +typedef void (*_spi_dma_cb_t)(struct _dma_resource *resource); + +/** + * \brief The callbacks offered by SPI driver + */ +struct _spi_dma_dev_callbacks { + _spi_dma_cb_t tx; + _spi_dma_cb_t rx; + _spi_dma_cb_t error; +}; + +/** SPI driver to support DMA HAL */ +struct _spi_dma_dev { + /** Pointer to the hardware base or private data for special device. */ + void *prvt; + /** Pointer to callback functions */ + struct _spi_dma_dev_callbacks callbacks; + /** IRQ instance for SPI device. */ + struct _irq_descriptor irq; + /** DMA resource */ + struct _dma_resource *resource; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef _HPL_SPI_DMA_H_INCLUDED */ diff --git a/external/asf4-drivers/hal/src/hal_spi_m_dma.c b/external/asf4-drivers/hal/src/hal_spi_m_dma.c new file mode 100644 index 0000000000..976d0f886c --- /dev/null +++ b/external/asf4-drivers/hal/src/hal_spi_m_dma.c @@ -0,0 +1,183 @@ +/** + * \file + * + * \brief I/O SPI DMA related functionality implementation. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#include "hal_atomic.h" +#include "hal_spi_m_dma.h" +#include +#include + +/** + * \brief Driver version + */ +#define SPI_DRIVER_VERSION 0x00000001u + +static int32_t _spi_m_dma_io_write(struct io_descriptor *const io, const uint8_t *const buf, const uint16_t length); +static int32_t _spi_m_dma_io_read(struct io_descriptor *const io, uint8_t *const buf, const uint16_t length); + +/** + * \brief Initialize the SPI HAL instance function pointer for HPL APIs. + */ +void spi_m_dma_set_func_ptr(struct spi_m_dma_descriptor *spi, void *const func) +{ + ASSERT(spi); + spi->func = (struct _spi_m_dma_hpl_interface *)func; +} + +int32_t spi_m_dma_init(struct spi_m_dma_descriptor *spi, void *const hw) +{ + int32_t rc = 0; + ASSERT(spi && hw); + spi->dev.prvt = (void *)hw; + rc = _spi_m_dma_init(&spi->dev, hw); + + if (rc) { + return rc; + } + + spi->io.read = _spi_m_dma_io_read; + spi->io.write = _spi_m_dma_io_write; + + return ERR_NONE; +} + +void spi_m_dma_deinit(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_deinit(&spi->dev); +} + +void spi_m_dma_enable(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_enable(&spi->dev); +} + +void spi_m_dma_disable(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_disable(&spi->dev); +} + +int32_t spi_m_dma_set_baudrate(struct spi_m_dma_descriptor *spi, const uint32_t baud_val) +{ + ASSERT(spi); + return _spi_m_dma_set_baudrate(&spi->dev, baud_val); +} + +int32_t spi_m_dma_set_mode(struct spi_m_dma_descriptor *spi, const enum spi_transfer_mode mode) +{ + ASSERT(spi); + return _spi_m_dma_set_mode(&spi->dev, mode); +} + +int32_t spi_m_dma_set_char_size(struct spi_m_dma_descriptor *spi, const enum spi_char_size char_size) +{ + ASSERT(spi); + return _spi_m_dma_set_char_size(&spi->dev, char_size); +} + +int32_t spi_m_dma_set_data_order(struct spi_m_dma_descriptor *spi, const enum spi_data_order dord) +{ + ASSERT(spi); + return _spi_m_dma_set_data_order(&spi->dev, dord); +} + +/** \brief Do SPI read in background + * + * It never blocks and return quickly, user check status or set callback to + * know when data is ready to process. + * + * \param[in, out] spi Pointer to the HAL SPI instance. + * \param[out] p_buf Pointer to the buffer to store read data. + * \param[in] size Size of the data in number of characters. + * \return ERR_NONE on success, or an error code on failure. + * \retval ERR_NONE Success, transfer started. + * \retval ERR_BUSY Busy. + */ +static int32_t _spi_m_dma_io_read(struct io_descriptor *io, uint8_t *const buf, const uint16_t length) +{ + ASSERT(io); + + struct spi_m_dma_descriptor *spi = CONTAINER_OF(io, struct spi_m_dma_descriptor, io); + return _spi_m_dma_transfer(&spi->dev, NULL, buf, length); +} + +/** \brief Do SPI data write in background + * + * The data read back is discarded. + * + * It never blocks and return quickly, user check status or set callback to + * know when data is sent. + * + * \param[in, out] spi Pointer to the HAL SPI instance. + * \param[in] p_buf Pointer to the buffer to store data to write. + * \param[in] size Size of the data in number of characters. + * + * \return ERR_NONE on success, or an error code on failure. + * \retval ERR_NONE Success, transfer started. + * \retval ERR_BUSY Busy. + */ +static int32_t _spi_m_dma_io_write(struct io_descriptor *io, const uint8_t *const buf, const uint16_t length) +{ + ASSERT(io); + + struct spi_m_dma_descriptor *spi = CONTAINER_OF(io, struct spi_m_dma_descriptor, io); + return _spi_m_dma_transfer(&spi->dev, buf, NULL, length); +} + +int32_t spi_m_dma_transfer(struct spi_m_dma_descriptor *spi, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length) +{ + ASSERT(spi); + return _spi_m_dma_transfer(&spi->dev, txbuf, rxbuf, length); +} + +void spi_m_dma_register_callback(struct spi_m_dma_descriptor *spi, const enum spi_m_dma_cb_type type, + spi_m_dma_cb_t func) +{ + ASSERT(spi); + _spi_m_dma_register_callback(&spi->dev, (enum _spi_dma_dev_cb_type)type, func); +} + +int32_t spi_m_dma_get_io_descriptor(struct spi_m_dma_descriptor *const spi, struct io_descriptor **io) +{ + ASSERT(spi && io); + *io = &spi->io; + + return 0; +} + +uint32_t spi_m_dma_get_version(void) +{ + return SPI_DRIVER_VERSION; +} diff --git a/external/asf4-drivers/hpl/dmac/hpl_dmac.c b/external/asf4-drivers/hpl/dmac/hpl_dmac.c index c7b03b0095..7dddc9d1e3 100644 --- a/external/asf4-drivers/hpl/dmac/hpl_dmac.c +++ b/external/asf4-drivers/hpl/dmac/hpl_dmac.c @@ -37,7 +37,6 @@ #include #include -#if CONF_DMAC_ENABLE /* Section containing first descriptors for all DMAC channels */ COMPILER_ALIGNED(16) DmacDescriptor _descriptor_section[DMAC_CH_NUM]; @@ -259,5 +258,3 @@ void DMAC_4_Handler(void) { _dmac_handler(); } - -#endif /* CONF_DMAC_ENABLE */ diff --git a/external/asf4-drivers/hpl/sercom/hpl_sercom.c b/external/asf4-drivers/hpl/sercom/hpl_sercom.c index 47f3d37849..754a432866 100644 --- a/external/asf4-drivers/hpl/sercom/hpl_sercom.c +++ b/external/asf4-drivers/hpl/sercom/hpl_sercom.c @@ -31,6 +31,7 @@ * \asf_license_stop * */ +#include #include #include #include @@ -3026,3 +3027,428 @@ void _spi_s_async_set_irq_state(struct _spi_async_dev *const device, const enum { _spi_m_async_set_irq_state(device, type, state); } +#ifndef CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL 1 +#endif + +#ifndef CONF_SERCOM_0_SPI_RX_CHANNEL +#define CONF_SERCOM_0_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_1_SPI_RX_CHANNEL +#define CONF_SERCOM_1_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_2_SPI_RX_CHANNEL +#define CONF_SERCOM_2_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_3_SPI_RX_CHANNEL +#define CONF_SERCOM_3_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_4_SPI_RX_CHANNEL +#define CONF_SERCOM_4_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_5_SPI_RX_CHANNEL +#define CONF_SERCOM_5_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_6_SPI_RX_CHANNEL +#define CONF_SERCOM_6_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_7_SPI_RX_CHANNEL +#define CONF_SERCOM_7_SPI_RX_CHANNEL 0 +#endif + +/** \internal Enable SERCOM SPI RX + * + * \param[in] hw Pointer to the hardware register base. + * + * \return Enabling status + */ +static int32_t _spi_sync_rx_enable(void *const hw) +{ + if (hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_CTRLB)) { + return ERR_BUSY; + } + + hri_sercomspi_set_CTRLB_RXEN_bit(hw); + + return ERR_NONE; +} + +/** \internal Disable SERCOM SPI RX + * + * \param[in] hw Pointer to the hardware register base. + * + * \return Disabling status + */ +static int32_t _spi_sync_rx_disable(void *const hw) +{ + if (hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_CTRLB)) { + return ERR_BUSY; + } + hri_sercomspi_clear_CTRLB_RXEN_bit(hw); + + return ERR_NONE; +} + +static int32_t _spi_m_dma_rx_enable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_rx_enable(dev->prvt); +} + +static int32_t _spi_m_dma_rx_disable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_rx_disable(dev->prvt); +} + +/** + * \brief Get the spi source address for DMA + * \param[in] dev Pointer to the SPI device instance + * + * \return The spi source address + */ +static uint32_t _spi_m_get_source_for_dma(void *const hw) +{ + return (uint32_t) & (((Sercom *)hw)->SPI.DATA); +} + +/** + * \brief Get the spi destination address for DMA + * \param[in] dev Pointer to the SPI device instance + * + * \return The spi destination address + */ +static uint32_t _spi_m_get_destination_for_dma(void *const hw) +{ + return (uint32_t) & (((Sercom *)hw)->SPI.DATA); +} + +/** + * \brief Return the SPI TX DMA channel index + * \param[in] hw_addr The hardware register base address + * + * \return SPI TX DMA channel index. + */ +static uint8_t _spi_get_tx_dma_channel(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL; + default: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + } +} + +/** + * \brief Return whether SPI RX DMA channel is enabled or not + * \param[in] hw_addr The hardware register base address + * + * \return one if enabled. + */ +static uint8_t _spi_is_rx_dma_channel_enabled(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_RX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_RX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_RX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_RX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_RX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_RX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_RX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_RX_CHANNEL; + default: + return false; + } +} + +/** + * \brief Return the SPI RX DMA channel index + * \param[in] hw_addr The hardware register base address + * + * \return SPI RX DMA channel index. + */ +static uint8_t _spi_get_rx_dma_channel(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL; + default: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + } +} + +/** + * \brief Callback for RX + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_rx_complete(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.rx) { + dev->callbacks.rx(resource); + } +} + +/** + * \brief Callback for TX + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_tx_complete(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.tx) { + dev->callbacks.tx(resource); + } +} + +/** + * \brief Callback for ERROR + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_error_occured(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.error) { + dev->callbacks.error(resource); + } +} + +int32_t _spi_m_dma_init(struct _spi_m_dma_dev *dev, void *const hw) +{ + const struct sercomspi_regs_cfg *regs = _spi_get_regs((uint32_t)hw); + + ASSERT(dev && hw); + + if (regs == NULL) { + return ERR_INVALID_ARG; + } + + if (!hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_SWRST)) { + uint32_t mode = regs->ctrla & SERCOM_SPI_CTRLA_MODE_Msk; + if (hri_sercomspi_get_CTRLA_reg(hw, SERCOM_SPI_CTRLA_ENABLE)) { + hri_sercomspi_clear_CTRLA_ENABLE_bit(hw); + hri_sercomspi_wait_for_sync(hw, SERCOM_SPI_SYNCBUSY_ENABLE); + } + hri_sercomspi_write_CTRLA_reg(hw, SERCOM_SPI_CTRLA_SWRST | mode); + } + hri_sercomspi_wait_for_sync(hw, SERCOM_SPI_SYNCBUSY_SWRST); + + dev->prvt = hw; + + _spi_load_regs_master(hw, regs); + + /* If enabled, initialize DMA rx channel */ + if (_spi_is_rx_dma_channel_enabled(hw)) { + _dma_get_channel_resource(&dev->resource, _spi_get_rx_dma_channel(hw)); + dev->resource->back = dev; + dev->resource->dma_cb.transfer_done = _spi_dma_rx_complete; + dev->resource->dma_cb.error = _spi_dma_error_occured; + } + /* Initialize DMA tx channel */ + _dma_get_channel_resource(&dev->resource, _spi_get_tx_dma_channel(hw)); + dev->resource->back = dev; + dev->resource->dma_cb.transfer_done = _spi_dma_tx_complete; + dev->resource->dma_cb.error = _spi_dma_error_occured; + + return ERR_NONE; +} + +int32_t _spi_m_dma_deinit(struct _spi_m_dma_dev *dev) +{ + return _spi_deinit(dev->prvt); +} + +int32_t _spi_m_dma_enable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_enable(dev->prvt); +} + +int32_t _spi_m_dma_disable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_disable(dev->prvt); +} + +int32_t _spi_m_dma_set_mode(struct _spi_m_dma_dev *dev, const enum spi_transfer_mode mode) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_mode(dev->prvt, mode); +} + +int32_t _spi_m_dma_set_baudrate(struct _spi_m_dma_dev *dev, const uint32_t baud_val) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_baudrate(dev->prvt, baud_val); +} + +int32_t _spi_m_dma_set_data_order(struct _spi_m_dma_dev *dev, const enum spi_data_order dord) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_data_order(dev->prvt, dord); +} + +int32_t _spi_m_dma_set_char_size(struct _spi_m_dma_dev *dev, const enum spi_char_size char_size) +{ + uint8_t size; + + ASSERT(dev && dev->prvt); + + _spi_set_char_size(dev->prvt, char_size, &size); + + return size; +} + +void _spi_m_dma_register_callback(struct _spi_m_dma_dev *dev, enum _spi_dma_dev_cb_type type, _spi_dma_cb_t func) +{ + switch (type) { + case SPI_DEV_CB_DMA_TX: + dev->callbacks.tx = func; + _dma_set_irq_state(_spi_get_tx_dma_channel(dev->prvt), DMA_TRANSFER_COMPLETE_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_RX: + dev->callbacks.rx = func; + _dma_set_irq_state(_spi_get_rx_dma_channel(dev->prvt), DMA_TRANSFER_COMPLETE_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_ERROR: + dev->callbacks.error = func; + _dma_set_irq_state(_spi_get_rx_dma_channel(dev->prvt), DMA_TRANSFER_ERROR_CB, func != NULL); + _dma_set_irq_state(_spi_get_tx_dma_channel(dev->prvt), DMA_TRANSFER_ERROR_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_N: + break; + } +} + +int32_t _spi_m_dma_transfer(struct _spi_m_dma_dev *dev, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length) +{ + const struct sercomspi_regs_cfg *regs = _spi_get_regs((uint32_t)dev->prvt); + uint8_t rx_ch = _spi_get_rx_dma_channel(dev->prvt); + uint8_t tx_ch = _spi_get_tx_dma_channel(dev->prvt); + + if (rxbuf) { + /* Enable spi rx */ + _spi_m_dma_rx_enable(dev); + _dma_set_source_address(rx_ch, (void *)_spi_m_get_source_for_dma(dev->prvt)); + _dma_set_destination_address(rx_ch, rxbuf); + _dma_set_data_amount(rx_ch, length); + _dma_enable_transaction(rx_ch, false); + } else { + /* Disable spi rx */ + _spi_m_dma_rx_disable(dev); + } + + if (txbuf) { + /* Enable spi tx */ + _dma_set_source_address(tx_ch, txbuf); + _dma_set_destination_address(tx_ch, (void *)_spi_m_get_destination_for_dma(dev->prvt)); + _dma_srcinc_enable(tx_ch, true); + _dma_set_data_amount(tx_ch, length); + } else { + _dma_set_source_address(tx_ch, ®s->dummy_byte); + _dma_set_destination_address(tx_ch, (void *)_spi_m_get_destination_for_dma(dev->prvt)); + _dma_srcinc_enable(tx_ch, false); + _dma_set_data_amount(tx_ch, length); + } + _dma_enable_transaction(tx_ch, false); + + return ERR_NONE; +} diff --git a/external/asf4-drivers/hpl/spi/spi_lite.c b/external/asf4-drivers/hpl/spi/spi_lite.c index 4ce85472c0..8885638a68 100644 --- a/external/asf4-drivers/hpl/spi/spi_lite.c +++ b/external/asf4-drivers/hpl/spi/spi_lite.c @@ -158,127 +158,3 @@ void SPI_MEM_read_block(void *block, size_t size) b++; } } - -/** - * \brief Initialize SPI interface - */ -int8_t SPI_OLED_init() -{ - - if (!hri_sercomspi_is_syncing(SERCOM3, SERCOM_SPI_SYNCBUSY_SWRST)) { - uint32_t mode = SERCOM_SPI_CTRLA_MODE(3); - if (hri_sercomspi_get_CTRLA_reg(SERCOM3, SERCOM_SPI_CTRLA_ENABLE)) { - hri_sercomspi_clear_CTRLA_ENABLE_bit(SERCOM3); - hri_sercomspi_wait_for_sync(SERCOM3, SERCOM_SPI_SYNCBUSY_ENABLE); - } - hri_sercomspi_write_CTRLA_reg(SERCOM3, SERCOM_SPI_CTRLA_SWRST | mode); - } - hri_sercomspi_wait_for_sync(SERCOM3, SERCOM_SPI_SYNCBUSY_SWRST); - - hri_sercomspi_write_CTRLA_reg( - SERCOM3, - 0 << SERCOM_SPI_CTRLA_DORD_Pos /* Data Order: disabled */ - | 0 << SERCOM_SPI_CTRLA_CPOL_Pos /* Clock Polarity: disabled */ - | 0 << SERCOM_SPI_CTRLA_CPHA_Pos /* Clock Phase: disabled */ - | 0 << SERCOM_SPI_CTRLA_FORM_Pos /* Frame Format: 0 */ - | 0 << SERCOM_SPI_CTRLA_IBON_Pos /* Immediate Buffer Overflow Notification: disabled */ - | 0 << SERCOM_SPI_CTRLA_RUNSTDBY_Pos /* Run In Standby: disabled */ - | 3 << SERCOM_SPI_CTRLA_MODE_Pos); /* Operating Mode: 3 */ - - hri_sercomspi_write_CTRLA_DOPO_bf(SERCOM3, SERCOM3_TXPO); - hri_sercomspi_write_CTRLA_DIPO_bf(SERCOM3, SERCOM3_RXPO); - - hri_sercomspi_write_CTRLB_reg(SERCOM3, - 1 << SERCOM_SPI_CTRLB_RXEN_Pos /* Receiver Enable: enabled */ - | 0 << SERCOM_SPI_CTRLB_MSSEN_Pos /* Master Slave Select Enabl: disabled */ - | 0 << SERCOM_SPI_CTRLB_AMODE_Pos /* Address Mode: 0 */ - | 0 << SERCOM_SPI_CTRLB_SSDE_Pos /* Slave Select Low Detect Enable: disabled */ - | 0 << SERCOM_SPI_CTRLB_PLOADEN_Pos /* Slave Data Preload Enable: disabled */ - | 0); /* Character Size: 0 */ - - hri_sercomspi_write_BAUD_reg(SERCOM3, SERCOM3_BAUD_RATE); - - // hri_sercomspi_write_DBGCTRL_reg(SERCOM3,0 << SERCOM_SPI_DBGCTRL_DBGSTOP_Pos); /* Debug Stop Mode: disabled */ - - // hri_sercomspi_write_INTEN_reg(SERCOM3,0 << SERCOM_SPI_INTENSET_ERROR_Pos /* Error Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_SSL_Pos /* Slave Select Low Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_RXC_Pos /* Receive Complete Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_TXC_Pos /* Transmit Complete Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_DRE_Pos); /* Data Register Empty Interrupt Enable: disabled */ - - hri_sercomspi_write_CTRLA_ENABLE_bit(SERCOM3, 1 << SERCOM_SPI_CTRLA_ENABLE_Pos); /* Enable: enabled */ - - return 0; -} - -/** - * \brief Enable SPI module - */ -void SPI_OLED_enable() -{ - hri_sercomspi_set_CTRLA_ENABLE_bit(SERCOM3); -} - -/** - * \brief Disable SPI module - */ -void SPI_OLED_disable() -{ - hri_sercomspi_clear_CTRLA_ENABLE_bit(SERCOM3); -} - -/** - * \brief Exchange_byte in SPI module - */ -uint32_t SPI_OLED_exchange_data(uint32_t data) -{ - /* If settings are not applied (pending), we can not go on */ - if (hri_sercomspi_is_syncing( - SERCOM3, (SERCOM_SPI_SYNCBUSY_SWRST | SERCOM_SPI_SYNCBUSY_ENABLE | SERCOM_SPI_SYNCBUSY_CTRLB))) { - return ERR_BUSY; - } - - hri_sercomspi_write_DATA_reg(SERCOM3, data); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - return hri_sercomspi_read_DATA_reg(SERCOM3); -} - -void SPI_OLED_exchange_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, *b); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - *b = hri_sercomspi_read_DATA_reg(SERCOM3); - b++; - } -} - -void SPI_OLED_write_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, *b); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_TXC)) - ; - b++; - } -} - -void SPI_OLED_read_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, 0); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - *b = hri_sercomspi_read_DATA_reg(SERCOM3); - b++; - } -} diff --git a/external/asf4-drivers/hpl/spi/spi_lite.h b/external/asf4-drivers/hpl/spi/spi_lite.h index 2b77d46c34..151d32d9d6 100644 --- a/external/asf4-drivers/hpl/spi/spi_lite.h +++ b/external/asf4-drivers/hpl/spi/spi_lite.h @@ -89,67 +89,17 @@ uint32_t SPI_MEM_exchange_data(uint32_t data); /** * \brief Exchange block in SPI module */ -void SPI_MEM_exchange_block(void *block, size_t size); +void SPI_MEM_exchange_block(void* block, size_t size); /** * \brief Write block in SPI module */ -void SPI_MEM_write_block(void *block, size_t size); +void SPI_MEM_write_block(void* block, size_t size); /** * \brief Read block in SPI module */ -void SPI_MEM_read_block(void *block, size_t size); - -// Calculate baud register value from requested baudrate value -#ifndef SERCOM3_BAUD_RATE -#define SERCOM3_BAUD_RATE (((float)CONF_GCLK_SERCOM3_CORE_FREQUENCY / (float)(2 * 3000000)) - 1) -#endif - -#ifndef SERCOM3_RXPO -#define SERCOM3_RXPO 2 -#endif - -#ifndef SERCOM3_TXPO -#define SERCOM3_TXPO 0 -#endif - -/** - * \brief Initialize usart interface - * - * \return Initialization status. - */ -int8_t SPI_OLED_init(); - -/** - * \brief Enable SPI module - */ -void SPI_OLED_enable(); - -/** - * \brief Disable SPI module - */ -void SPI_OLED_disable(); - -/** - * \brief Exchange byte in SPI module - */ -uint32_t SPI_OLED_exchange_data(uint32_t data); - -/** - * \brief Exchange block in SPI module - */ -void SPI_OLED_exchange_block(void *block, uint8_t size); - -/** - * \brief Write block in SPI module - */ -void SPI_OLED_write_block(void *block, uint8_t size); - -/** - * \brief Read block in SPI module - */ -void SPI_OLED_read_block(void *block, uint8_t size); +void SPI_MEM_read_block(void* block, size_t size); #ifdef __cplusplus } diff --git a/scripts/jlink-bootloader.gdb b/scripts/jlink-bootloader.gdb index a7629aaf46..514c3e4193 100644 --- a/scripts/jlink-bootloader.gdb +++ b/scripts/jlink-bootloader.gdb @@ -1,12 +1,16 @@ # Connect to jlink gdb server target extended-remote :2331 -# load the firmware into ROM -load +# It seems more reliable to reset the chip before loading the new firmware. It +# is also how they do it in the example in the wiki: +# https://kb.segger.com/J-Link_GDB_Server#Console # Reset the CPU monitor reset +# load the firmware into ROM +load + #break Reset_Handler #break HardFault_Handler #break NMI_Handler diff --git a/scripts/jlink.gdb b/scripts/jlink.gdb index 4a9cf9fd0b..899c13530f 100644 --- a/scripts/jlink.gdb +++ b/scripts/jlink.gdb @@ -1,12 +1,16 @@ # Connect to jlink gdb server target extended-remote :2331 -# load the firmware into ROM -load +# It seems more reliable to reset the chip before loading the new firmware. It +# is also how they do it in the example in the wiki: +# https://kb.segger.com/J-Link_GDB_Server#Console # Reset the CPU monitor reset +# load the firmware into ROM +load + # Set VTOR (Vector Table Offset Register) to where the firmware is located set *(uint32_t*)0xE000ED08=0x10000 # Set stack pointer to initial stack pointer according to exception table. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6344c524e..4fe298c25f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,7 @@ set(DRIVER-SOURCES ${CMAKE_SOURCE_DIR}/src/platform/driver_init.c ${CMAKE_SOURCE_DIR}/src/ui/oled/oled.c ${CMAKE_SOURCE_DIR}/src/ui/oled/oled_writer.c + ${CMAKE_SOURCE_DIR}/src/ui/canvas.c ) set(DRIVER-SOURCES ${DRIVER-SOURCES} PARENT_SCOPE) diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c index 3791fe4e72..3eb532bd0b 100644 --- a/src/bootloader/bootloader.c +++ b/src/bootloader/bootloader.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -328,15 +329,14 @@ static void _render_message(const char* message, int duration) { char print[100]; snprintf(print, sizeof(print), "%s", message); - UG_ClearBuffer(); UG_PutString(0, 0, print, false); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); delay_ms(duration); } void bootloader_render_default_screen(void) { - UG_ClearBuffer(); _load_logo(); #if PLATFORM_BITBOX02PLUS == 1 UG_PutString(0, SCREEN_HEIGHT - 9 * 2 - 5, "See the BitBoxApp", false); @@ -354,7 +354,8 @@ void bootloader_render_default_screen(void) } UG_PutString(0, SCREEN_HEIGHT - 9, "See the BitBoxApp", false); #endif - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); } #if PLATFORM_BITBOX02PLUS @@ -368,7 +369,6 @@ void bootloader_render_ble_confirm_screen(bool confirmed) uint32_t pairing_code_int = (*(uint32_t*)&bootloader_pairing_code_bytes[0]) % 1000000; char code_str[10] = {0}; snprintf(code_str, sizeof(code_str), "%06u", (unsigned)pairing_code_int); - UG_ClearBuffer(); uint16_t check_width = IMAGE_DEFAULT_CHECKMARK_HEIGHT + IMAGE_DEFAULT_CHECKMARK_HEIGHT / 2 - 1; if (confirmed) { UG_PutString(15, 0, "Confirm on app", false); @@ -380,13 +380,13 @@ void bootloader_render_ble_confirm_screen(bool confirmed) UG_FontSelect(&font_monogram_5X9); UG_PutString(45, SCREEN_HEIGHT / 2 - 9, code_str, false); UG_FontSelect(&font_font_a_9X9); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); } #endif static void _render_progress(float progress) { - UG_ClearBuffer(); _load_logo(); if (progress > 0) { char label[5] = {0}; @@ -401,7 +401,8 @@ static void _render_progress(float progress) msg = "INSTALLING"; } UG_PutString(SCREEN_WIDTH / 2 - 3, SCREEN_HEIGHT - 9 * 2, msg, false); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); } static void _render_hash(const char* title, const uint8_t* hash) @@ -433,7 +434,6 @@ static void _render_hash(const char* title, const uint8_t* hash) &hash_hex[48]); for (uint8_t i = 1; i <= seconds; i++) { - UG_ClearBuffer(); UG_PutString(0, 0, title, false); snprintf(timer_buf, sizeof(timer_buf), "%ds", seconds - i); @@ -449,7 +449,8 @@ static void _render_hash(const char* title, const uint8_t* hash) UG_FontSelect(f_regular); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); delay_ms(1000); } bootloader_render_default_screen(); @@ -1013,7 +1014,6 @@ static void _check_init(boot_data_t* data) #ifdef BOOTLOADER_DEVDEVICE static bool _devdevice_enter(secbool_u32 firmware_verified) { - UG_ClearBuffer(); UG_PutString(0, 0, " ", false); UG_PutString(0, SCREEN_HEIGHT / 2 - 11, "DEV DEVICE", false); UG_PutString(0, SCREEN_HEIGHT / 2 + 2, "NOT FOR VALUE", false); @@ -1043,7 +1043,8 @@ static bool _devdevice_enter(secbool_u32 firmware_verified) UG_DrawLine(xpos + 5, ypos, xpos, ypos + 5, C_WHITE); UG_DrawLine(xpos - 2, ypos + 3, xpos, ypos + 5, C_WHITE); } - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); while (true) { do { qtouch_process(); diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c index 52959772b0..4ae9d140d7 100644 --- a/src/bootloader/startup.c +++ b/src/bootloader/startup.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -83,7 +84,7 @@ int main(void) bootloader_init(); platform_init(); __stack_chk_guard = rand_sync_read32(&RAND_0); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); #if defined(BOOTLOADER_DEVDEVICE) || PLATFORM_BITBOX02PLUS == 1 qtouch_init(); #endif @@ -189,7 +190,6 @@ int main(void) if (qtouch_is_scroller_active(top_slider)) { bool ok; - UG_ClearBuffer(); if (qtouch_get_scroller_position(top_slider) < 127) { bootloader_render_default_screen(); ok = false; @@ -218,9 +218,10 @@ int main(void) ringbuffer_put(&uart_write_queue, tmp[i]); } bootloader_pairing_request = false; - UG_SendBuffer(); + canvas_commit(); } } + oled_blit(false); #endif } return 0; diff --git a/src/factorysetup.c b/src/factorysetup.c index 24185c255e..ec10afc443 100644 --- a/src/factorysetup.c +++ b/src/factorysetup.c @@ -577,7 +577,7 @@ int main(void) system_init(); platform_init(); __stack_chk_guard = common_stack_chk_guard(); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); screen_splash(); common_main(); diff --git a/src/firmware.c b/src/firmware.c index 6af86f8747..bdc7d291c6 100644 --- a/src/firmware.c +++ b/src/firmware.c @@ -41,7 +41,7 @@ int main(void) system_init(); platform_init(); __stack_chk_guard = common_stack_chk_guard(); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); screen_splash(); qtouch_init(); common_main(); diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c index 8282b8d5db..d5d32e27fd 100644 --- a/src/firmware_main_loop.c +++ b/src/firmware_main_loop.c @@ -35,6 +35,7 @@ #include "workflow/orientation_screen.h" #include #include +#include #include #if APP_U2F == 1 #include "u2f.h" @@ -47,6 +48,7 @@ void firmware_main_loop(void) { + screen_process_init(); // Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read // out all bytes uint8_t uart_read_buf[USART_0_BUFFER_SIZE] = {0}; @@ -177,8 +179,9 @@ void firmware_main_loop(void) #if APP_U2F == 1 u2f_process(); #endif - screen_process(); + oled_blit(false); + /* And finally, run the high-level event processing. */ rust_workflow_spin(); diff --git a/src/platform/driver_init.c b/src/platform/driver_init.c index 69dbf0eaa2..d60c12c2da 100644 --- a/src/platform/driver_init.c +++ b/src/platform/driver_init.c @@ -23,9 +23,7 @@ #include #include -#define PIN_HIGH 1 -#define PIN_LOW 0 - +struct spi_m_dma_descriptor SPI_0; struct sha_sync_descriptor HASH_ALGORITHM_0; struct timer_descriptor TIMER_0; struct flash_descriptor FLASH_0; @@ -116,10 +114,11 @@ static void _spi_init(void) GCLK, SERCOM3_GCLK_ID_CORE, CONF_GCLK_SERCOM3_CORE_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos)); hri_gclk_write_PCHCTRL_reg( GCLK, SERCOM3_GCLK_ID_SLOW, CONF_GCLK_SERCOM3_SLOW_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos)); + hri_mclk_set_APBBMASK_SERCOM3_bit(MCLK); - SPI_OLED_init(); + spi_m_dma_init(&SPI_0, SERCOM3); + _spi_set_pins(); - SPI_OLED_enable(); } static void _spi_mem_clock_init(void) @@ -434,7 +433,7 @@ void system_close_interfaces(void) i2c_m_sync_deinit(&I2C_0); // OLED interface bus // Display remains on last screen - SPI_OLED_disable(); + spi_m_dma_deinit(&SPI_0); // Flash flash_deinit(&FLASH_0); // USB @@ -451,7 +450,7 @@ void bootloader_close_interfaces(void) } // OLED interface bus // Display remains on last screen - SPI_OLED_disable(); + spi_m_dma_deinit(&SPI_0); // Flash flash_deinit(&FLASH_0); // USB diff --git a/src/platform/driver_init.h b/src/platform/driver_init.h index d656433f7e..ec08d36594 100644 --- a/src/platform/driver_init.h +++ b/src/platform/driver_init.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,9 @@ #include "platform_config.h" +#define PIN_HIGH 1 +#define PIN_LOW 0 + #define SHA256_DIGEST_LENGTH 32 // 64 is our typical packet size and it gets a little bit longer over UART due to framing. Set the // buffer size so we can handle at least one whole frame. Must be a power of 2. @@ -59,6 +63,7 @@ extern struct rand_sync_desc RAND_0; extern PPUKCL_PARAM pvPUKCLParam; extern PUKCL_PARAM PUKCLParam; extern struct usart_async_descriptor USART_0; +extern struct spi_m_dma_descriptor SPI_0; #endif /** diff --git a/src/reset.c b/src/reset.c index f860fd4ea8..a76712c65c 100644 --- a/src/reset.c +++ b/src/reset.c @@ -23,12 +23,14 @@ #include "system.h" #include "uart.h" #include +#include #ifndef TESTING #include "securechip/securechip.h" #include #include #include +#include #include #endif @@ -41,9 +43,10 @@ static void _show_reset_label(bool status) { const char* msg = "Device reset"; component_t* comp = status_create(msg, status, NULL, NULL); - screen_clear(); + canvas_clear(); comp->f->render(comp); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); comp->f->cleanup(comp); delay_ms(3000); } diff --git a/src/rust/bitbox02-rust/src/general/screen.rs b/src/rust/bitbox02-rust/src/general/screen.rs index 1aedafb50c..b9f2f29eee 100644 --- a/src/rust/bitbox02-rust/src/general/screen.rs +++ b/src/rust/bitbox02-rust/src/general/screen.rs @@ -14,13 +14,14 @@ use core::time::Duration; -use bitbox02::{delay, ug_clear_buffer, ug_font_select_9x9, ug_put_string, ug_send_buffer}; +use bitbox02::{canvas_clear, canvas_commit, delay, oled_blit, ug_font_select_9x9, ug_put_string}; pub fn print_debug_internal(duration: Duration, msg: &str) { - ug_clear_buffer(); + canvas_clear(); ug_font_select_9x9(); ug_put_string(0, 0, msg, false); - ug_send_buffer(); + canvas_commit(); + oled_blit(); delay(duration); } diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 98af9f82dd..c0e028c99c 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -53,14 +53,14 @@ const ALLOWLIST_TYPES: &[&str] = &[ ]; const ALLOWLIST_FNS: &[&str] = &[ - "UG_ClearBuffer", "UG_FontSelect", "UG_PutString", - "UG_SendBuffer", "bip32_derive_xpub", "bitbox02_smarteeprom_init", "bitbox_secp256k1_dleq_prove", "bitbox_secp256k1_dleq_verify", + "canvas_clear", + "canvas_commit", "confirm_create", "confirm_transaction_address_create", "confirm_transaction_fee_create", @@ -114,6 +114,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "memory_get_platform", "memory_get_securechip_type", "memory_spi_get_active_ble_firmware_version", + "oled_blit", "spi_mem_protected_area_write", "menu_create", "fake_memory_factoryreset", @@ -199,6 +200,7 @@ const BITBOX02_SOURCES: &[&str] = &[ "src/u2f.c", "src/u2f/u2f_app.c", "src/u2f/u2f_packet.c", + "src/ui/canvas.c", "src/ui/components/button.c", "src/ui/components/confirm_gesture.c", "src/ui/components/confirm_transaction.c", @@ -402,6 +404,7 @@ pub fn main() -> Result<(), &'static str> { "test/hardware-fakes/src/fake_component.c", "test/hardware-fakes/src/fake_diskio.c", "test/hardware-fakes/src/fake_memory.c", + "test/hardware-fakes/src/fake_oled.c", "test/hardware-fakes/src/fake_qtouch.c", "test/hardware-fakes/src/fake_screen.c", "test/hardware-fakes/src/fake_securechip.c", diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h index ada97b3899..8959da3264 100644 --- a/src/rust/bitbox02-sys/wrapper.h +++ b/src/rust/bitbox02-sys/wrapper.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs index 4e93f4732b..71f6a7607c 100644 --- a/src/rust/bitbox02/src/lib.rs +++ b/src/rust/bitbox02/src/lib.rs @@ -62,12 +62,16 @@ pub fn ug_put_string(x: i16, y: i16, input: &str, inverted: bool) { } } -pub fn ug_clear_buffer() { - unsafe { bitbox02_sys::UG_ClearBuffer() } +pub fn canvas_clear() { + unsafe { bitbox02_sys::canvas_clear() } } -pub fn ug_send_buffer() { - unsafe { bitbox02_sys::UG_SendBuffer() } +pub fn canvas_commit() { + unsafe { bitbox02_sys::canvas_commit() } +} + +pub fn oled_blit() { + unsafe { bitbox02_sys::oled_blit(true) } } pub fn ug_font_select_9x9() { diff --git a/src/screen.c b/src/screen.c index f0fbecda92..e7d20816d0 100644 --- a/src/screen.c +++ b/src/screen.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,6 @@ static UG_GUI guioled; // Global GUI structure for OLED screen static bool screen_upside_down = false; static void (*_mirror_fn)(bool); -static void (*_clear_fn)(void); UG_COLOR screen_front_color = C_WHITE; UG_COLOR screen_back_color = C_BLACK; @@ -45,12 +45,15 @@ void screen_print_debug(const char* message, int duration) { char print[100]; snprintf(print, sizeof(print), "%s", message); - screen_clear(); + canvas_clear(); UG_FontSelect(&font_font_a_9X9); UG_PutString(0, 0, print, false); - UG_SendBuffer(); + canvas_commit(); + oled_blit(true); #ifndef TESTING if (duration > 0) delay_ms(duration); +#else + (void)duration; #endif } @@ -79,16 +82,14 @@ void screen_print_debug_hex(const uint8_t* bytes, size_t len, int duration) // Careful, this function is used in both the bootloader and the firmware. void screen_splash(void) { - screen_clear(); - int height = IMAGE_DEFAULT_ARROW_HEIGHT; int x = 0; int y = SCREEN_HEIGHT / 2 - height; image_arrow(x - height + 2, y, height, ARROW_RIGHT); image_arrow(SCREEN_WIDTH - x - 2, y, height, ARROW_LEFT); - UG_SendBuffer(); - screen_clear(); + canvas_commit(); + oled_blit(true); } void screen_rotate(void) @@ -105,18 +106,8 @@ bool screen_is_upside_down(void) return screen_upside_down; } -void screen_init( - void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), - void (*mirror_fn)(bool), - void (*clear_fn)(void)) +void screen_init(void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), void (*mirror_fn)(bool)) { _mirror_fn = mirror_fn; - _clear_fn = clear_fn; UG_Init(&guioled, pixel_fn, &font_font_a_11X10, SCREEN_WIDTH, SCREEN_HEIGHT); } - -void screen_clear(void) -{ - ASSERT(_clear_fn); - _clear_fn(); -} diff --git a/src/screen.h b/src/screen.h index a2f43883ff..4be6271d8e 100644 --- a/src/screen.h +++ b/src/screen.h @@ -34,10 +34,7 @@ extern slider_location_t bottom_slider; #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 -void screen_init( - void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), - void (*mirror_fn)(bool), - void (*clear_fn)(void)); +void screen_init(void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), void (*mirror_fn)(bool)); void screen_print_debug(const char* message, int duration); void screen_sprintf_debug(int duration, const char* fmt, ...) __attribute__((format(printf, 2, 0))); void screen_print_debug_hex(const uint8_t* bytes, size_t len, int duration); diff --git a/src/ui/canvas.c b/src/ui/canvas.c new file mode 100644 index 0000000000..85b0b81fdc --- /dev/null +++ b/src/ui/canvas.c @@ -0,0 +1,77 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +// `canvas_commit` is called from an interrupt in `lock_animation.c` and therefore this must be +// volatile. This is probably not enough to make it completely UB free, but it is something +// ¯\_(ツ)_/¯. Eventually `lock_animation.c` will be refactored so that it doesn't call this +// function. +// TODO: When volatile is removed here, also remove the ignored diagnostics later in this file. +static uint8_t* volatile _canvas_active = NULL; +static uint8_t* volatile _canvas_working = NULL; + +// One working buffer and one active buffer. The buffer must be 4 byte aligned for DMA transfers. +static uint8_t _canvas_0[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; +static uint8_t _canvas_1[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; + +void canvas_init(void) +{ + _canvas_working = _canvas_0; + _canvas_active = _canvas_1; +} + +void canvas_fill(uint8_t color) +{ + uint8_t pixels = color << 7 | color << 6 | color << 5 | color << 4 | color << 3 | color << 2 | + color << 1 | color; + memset(canvas_working(), pixels, CANVAS_SIZE); +} + +void canvas_clear(void) +{ + canvas_fill(0); +} + +void canvas_commit(void) +{ +#ifndef TESTING + CRITICAL_SECTION_ENTER() +#endif + uint8_t* volatile _canvas_tmp = _canvas_working; + _canvas_working = _canvas_active; + _canvas_active = _canvas_tmp; +#ifndef TESTING + CRITICAL_SECTION_LEAVE() +#endif + canvas_clear(); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" + +uint8_t* canvas_working(void) +{ + ASSERT(_canvas_working); + return _canvas_working; +} + +uint8_t* canvas_active(void) +{ + ASSERT(_canvas_active); + return _canvas_active; +} +#pragma GCC diagnostic pop diff --git a/src/ui/canvas.h b/src/ui/canvas.h new file mode 100644 index 0000000000..3a05181848 --- /dev/null +++ b/src/ui/canvas.h @@ -0,0 +1,59 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef CANVAS_H +#define CANVAS_H + +#include + +// 8 pixels per byte in the canvas. +#define CANVAS_SIZE ((SCREEN_WIDTH * SCREEN_HEIGHT) / 8) + +#include + +/* + * Initialize canvas + */ + +void canvas_init(void); + +/* + * Fill the whole working canvas with one color + */ +void canvas_fill(uint8_t color); + +/* + * Clear working canvas (fill with 0) + */ +void canvas_clear(void); + +/* + * Commit the current "working" buffer to become "active" and clear the working buffer. + * + * Invalidates pointer returned from `canvas_working()`. `canvas_working()` must be called again to + * get the current working frame buffer. + */ +void canvas_commit(void); + +/* + * Get a pointer to current working canvas. This is the canvas that can be updated and isn't + * currently being displayed. + */ +uint8_t* canvas_working(void); + +/* + * Get a pointer ot the current active canvas, being sent to the display. (Should only be used by + * the screen driver.) + */ +uint8_t* canvas_active(void); +#endif diff --git a/src/ui/components/confirm_gesture.c b/src/ui/components/confirm_gesture.c index 81fbbd8df3..d87a54d8a1 100644 --- a/src/ui/components/confirm_gesture.c +++ b/src/ui/components/confirm_gesture.c @@ -28,7 +28,7 @@ #include #include -#define SCALE 6 // Divide active_count by scale to slow down motion +#define SCALE 3 // Divide active_count by scale to slow down motion typedef struct { bool active_top; // Marker is 'active', i.e., touched diff --git a/src/ui/components/orientation_arrows.c b/src/ui/components/orientation_arrows.c index 5689f32f4d..8c231e8993 100644 --- a/src/ui/components/orientation_arrows.c +++ b/src/ui/components/orientation_arrows.c @@ -72,7 +72,7 @@ static void _render(component_t* component) int16_t x; int16_t y; int16_t height = IMAGE_DEFAULT_ARROW_HEIGHT; - int16_t position = data->screen_count / SCALE; + int16_t position = data->screen_count * SCALE; if (position < COUNT_CHANGE_DIRECTION) { // Horizontal motion x = position; @@ -85,15 +85,15 @@ static void _render(component_t* component) y = position - COUNT_CHANGE_DIRECTION + SCREEN_HEIGHT / 2; image_arrow(x, y, height, ARROW_DOWN); image_arrow(x, SCREEN_HEIGHT - y - height, height, ARROW_UP); - } else if (position < COUNT_SHOW_TEXT + SCALE * 12) { + } else if (position < COUNT_SHOW_TEXT + 12 / SCALE) { // Zoom in to rotate arrow uint8_t r; r = MIN(IMAGE_ROTATE_H / 2, - MAX(0, (position - COUNT_SHOW_TEXT - (12 - IMAGE_ROTATE_H / 2)) / SCALE)); + MAX(0, (position - COUNT_SHOW_TEXT - (12 - IMAGE_ROTATE_H / 2)) * SCALE)); UG_DrawCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, r, screen_front_color); // Raise text - y = (position - COUNT_SHOW_TEXT) / SCALE; // Slower movement + y = (position - COUNT_SHOW_TEXT) * SCALE; // Slower movement for (int i = 0; i < 2; i++) { component_t* sc = component->sub_components.sub_components[i]; sc->position.top = @@ -107,15 +107,15 @@ static void _render(component_t* component) for (int i = 0; i < 2; i++) { // Calculate bounce position y = ((data->screen_count - period / 4) % period) - (i ? 0 : period / 2); - if (y > bounce * SCALE * 4 || y < 0) { + if (y > bounce * 4 / SCALE || y < 0) { // No bounce y = 0; - } else if (y > bounce * SCALE * 2) { - y = bounce * SCALE * 4 - y; + } else if (y > bounce * 2 / SCALE) { + y = bounce * 4 / SCALE - y; } else if (y > 0) { // y = y; } - y = y / SCALE / 4; + y = y / 4 * SCALE; // Bounce text component_t* sc = component->sub_components.sub_components[i]; sc->position.top = i ? y : SCREEN_HEIGHT - sc->dimension.height - y; diff --git a/src/ui/components/status.c b/src/ui/components/status.c index 962d753f96..93ae94142f 100644 --- a/src/ui/components/status.c +++ b/src/ui/components/status.c @@ -21,7 +21,7 @@ #include #include -#define STATUS_DEFAULT_DELAY 500 // counts +#define STATUS_DEFAULT_DELAY 100 // counts typedef struct { bool status; diff --git a/src/ui/graphics/lock_animation.c b/src/ui/graphics/lock_animation.c index e3457bcff1..ecd53dc425 100644 --- a/src/ui/graphics/lock_animation.c +++ b/src/ui/graphics/lock_animation.c @@ -20,6 +20,8 @@ #include "graphics.h" #include +#include +#include #include #ifndef TESTING @@ -145,7 +147,7 @@ static const uint8_t* _get_frame(int frame_idx) } #endif -#define TIMEOUT_TICK_PERIOD_MS 40 +#define TIMEOUT_TICK_PERIOD_MS 50 #ifndef TESTING static struct timer_task _animation_timer_task = {0}; @@ -165,7 +167,7 @@ static void _animation_timer_cb(const struct timer_task* const timer_task) } /* Draw the frame. */ - screen_clear(); + canvas_clear(); position_t pos = { .left = (SCREEN_WIDTH - LOCK_ANIMATION_FRAME_WIDTH) / 2, .top = (SCREEN_HEIGHT - LOCK_ANIMATION_FRAME_HEIGHT) / 2}; @@ -173,7 +175,10 @@ static void _animation_timer_cb(const struct timer_task* const timer_task) in_buffer_t image = { .data = _get_frame(_animation_current_frame), .len = LOCK_ANIMATION_FRAME_SIZE}; graphics_draw_image(&pos, &dim, &image); - UG_SendBuffer(); + // TODO: When this function is refactored away from being called in interrupt context, update + // `_canvas_active` in `canvas.c` to not be volatile. + canvas_commit(); + oled_blit(true); _animation_current_frame++; } #endif @@ -184,6 +189,9 @@ static void _animation_timer_cb(const struct timer_task* const timer_task) void lock_animation_start(void) { #ifndef TESTING + // decrease RTC priority so that it can be interrupted by SERCOM3 TX interrupt so that screen + // blitting works in the interrupt. All interrupts have default priority of 0 (highest) + NVIC_SetPriority(RTC_IRQn, 1); _animation_timer_task.interval = TIMEOUT_TICK_PERIOD_MS; _animation_timer_task.cb = _animation_timer_cb; _animation_timer_task.mode = TIMER_TASK_REPEAT; @@ -197,6 +205,7 @@ void lock_animation_start(void) void lock_animation_stop(void) { #ifndef TESTING + NVIC_SetPriority(RTC_IRQn, 0); timer_stop(&TIMER_0); timer_remove_task(&TIMER_0, &_animation_timer_task); timer_start(&TIMER_0); diff --git a/src/ui/oled/oled.c b/src/ui/oled/oled.c index 713547e457..0b47e24135 100644 --- a/src/ui/oled/oled.c +++ b/src/ui/oled/oled.c @@ -73,19 +73,17 @@ #include #include #include +#include #include #include #include -static bool _frame_buffer_updated = false; -static uint8_t _frame_buffer[128 * 8]; - static volatile bool _enabled = false; struct bb02_display { - void (*configure)(uint8_t*); + void (*configure)(void); void (*set_pixel)(int16_t x, int16_t y, uint8_t c); - void (*update)(void); + void (*blit)(void); void (*off)(void); void (*mirror)(bool); }; @@ -93,52 +91,72 @@ struct bb02_display { static struct bb02_display bb02_display = { .configure = sh1107_configure, .set_pixel = sh1107_set_pixel, - .update = sh1107_update, + .blit = sh1107_blit, .off = sh1107_off, .mirror = sh1107_mirror, }; +static struct timer_task task_blit_screen; +volatile bool blit_screen = true; + +static void blit_screen_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + blit_screen = true; +} + void oled_init(void) { + canvas_init(); + oled_writer_init(); + + // Limit screen blitting to 62.5hz + task_blit_screen.interval = 16; + task_blit_screen.cb = blit_screen_cb; + task_blit_screen.mode = TIMER_TASK_REPEAT; + timer_add_task(&TIMER_0, &task_blit_screen); + if (memory_get_screen_type() == MEMORY_SCREEN_TYPE_SSD1312) { bb02_display.configure = ssd1312_configure; bb02_display.set_pixel = ssd1312_set_pixel; - bb02_display.update = ssd1312_update; + bb02_display.blit = ssd1312_blit; bb02_display.off = ssd1312_off; bb02_display.mirror = ssd1312_mirror; } if (_enabled) { return; } - // DC-DC OFF - gpio_set_pin_level(PIN_OLED_ON, 0); - delay_us(5); - // Hard reset OLED display controller - gpio_set_pin_level(PIN_OLED_RES, 0); - delay_us(5); - gpio_set_pin_level(PIN_OLED_RES, 1); + // * VDD is powered on at the same time as the MCU. + // * PIN_OLED_RES starts high (see driver_init.c) + // * PIN_OLED_ON starts low (see driver_init.c) + + // Wait at least 20ms from VDD ON + delay_ms(25); + + gpio_set_pin_level(PIN_OLED_RES, PIN_LOW); + // Wait at least 3us + delay_us(6); + gpio_set_pin_level(PIN_OLED_RES, PIN_HIGH); delay_us(5); - oled_clear_buffer(); + // VCC ON + gpio_set_pin_level(PIN_OLED_ON, PIN_HIGH); - bb02_display.configure(_frame_buffer); + bb02_display.configure(); + // Wait at least 100ms from DISPLAY_ON before starting to use delay_ms(100); - // DC-DC ON - gpio_set_pin_level(PIN_OLED_ON, 1); _enabled = true; } -void oled_send_buffer(void) +void oled_blit(bool force) { - bb02_display.update(); -} - -void oled_clear_buffer(void) -{ - memset(_frame_buffer, 0, sizeof(_frame_buffer)); + if (blit_screen || force) { + blit_screen = false; + bb02_display.blit(); + } } void oled_mirror(bool mirror) @@ -149,7 +167,6 @@ void oled_mirror(bool mirror) void oled_set_pixel(int16_t x, int16_t y, uint8_t c) { bb02_display.set_pixel(x, y, c); - _frame_buffer_updated = true; } void oled_off(void) @@ -166,5 +183,5 @@ void oled_off(void) void oled_set_brightness(uint8_t value) { // brightness uses the same command on all displays 0x81. - oled_writer_write_cmd_with_param(0x81, value); + oled_writer_write_cmd_with_param_blocking(0x81, value); } diff --git a/src/ui/oled/oled.h b/src/ui/oled/oled.h index c73faedab8..6c7569d8e5 100644 --- a/src/ui/oled/oled.h +++ b/src/ui/oled/oled.h @@ -71,19 +71,14 @@ void oled_init(void); /** - * Prints the frame buffer to the screen. - */ -void oled_send_buffer(void); - -/** - * Clears the frame buffer. + * Sets displayed frames rotated by 180 degrees. */ -void oled_clear_buffer(void); +void oled_mirror(bool mirror); /** - * Sets displayed frames rotated by 180 degrees. + * Transfer active canvas to the screen */ -void oled_mirror(bool mirror); +void oled_blit(bool force); /** * Turn off oled @@ -91,14 +86,15 @@ void oled_mirror(bool mirror); void oled_off(void); /** - * Set a screen pixel. This fills the frame buffer - * prior to it being sent to the screen by oled_send_buffer(). + * Set a pixel on the "working" canvas arcoding to the screen requirements. The working and active + * canvases are flipped with canvas_commit(); + * @param c Must be 0 for black and 1 for white */ void oled_set_pixel(int16_t x, int16_t y, uint8_t c); /** * Set brightness (0x00..0xff). - * 0x00 does not mean black, just loweset brightness. + * 0x00 does not mean black, just lowest brightness. */ void oled_set_brightness(uint8_t value); diff --git a/src/ui/oled/oled_writer.c b/src/ui/oled/oled_writer.c index e71cfe3cdd..af2994b6f5 100644 --- a/src/ui/oled/oled_writer.c +++ b/src/ui/oled/oled_writer.c @@ -14,6 +14,14 @@ #include "oled_writer.h" #include "driver_init.h" +#include + +// microseconds to wait after buffer has been sent out over SPI +// (works with 3, use 6 to have some margin) +#define WAIT_AFTER_TX_READY 6 + +static struct io_descriptor* _io; +volatile bool _tx_ready = true; enum _interface_t { INTERFACE_COMMAND, @@ -30,27 +38,59 @@ static inline void _write(enum _interface_t interface, const uint8_t* buf, size_ uint8_t cmd = interface == INTERFACE_COMMAND ? 0 : 1; gpio_set_pin_level(PIN_OLED_CMD, cmd); gpio_set_pin_level(PIN_OLED_CS, 0); - // It is safe to cast from const here because "write_block" only reads from buf -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" - SPI_OLED_write_block((void*)buf, buf_len); -#pragma GCC diagnostic pop - gpio_set_pin_level(PIN_OLED_CS, 1); + io_write(_io, buf, buf_len); } +// This function intentionally does not block until buffer is transferred void oled_writer_write_data(const uint8_t* buf, size_t buf_len) { _write(INTERFACE_DATA, buf, buf_len); } -void oled_writer_write_cmd(uint8_t command) +void oled_writer_write_data_blocking(const uint8_t* buf, size_t buf_len) +{ + _tx_ready = false; + oled_writer_write_data(buf, buf_len); + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); +} + +void oled_writer_write_cmd_blocking(uint8_t command) { - const uint8_t buf[] = {command}; + _tx_ready = false; + const uint8_t buf[] __attribute((aligned(4))) = {command}; _write(INTERFACE_COMMAND, buf, sizeof(buf)); + // Buffer is stack allocated, need to wait until done + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); } -void oled_writer_write_cmd_with_param(uint8_t command, uint8_t value) +void oled_writer_write_cmd_with_param_blocking(uint8_t command, uint8_t value) { - const uint8_t buf[] = {command, value}; + _tx_ready = false; + const uint8_t buf[] __attribute((aligned(4))) = {command, value}; _write(INTERFACE_COMMAND, buf, sizeof(buf)); + // Buffer is stack allocated, need to wait until done + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); +} + +static void _dma_tx_complete(struct _dma_resource* resource) +{ + (void)resource; + _tx_ready = true; +} + +void oled_writer_init(void) +{ + spi_m_dma_get_io_descriptor(&SPI_0, &_io); + + spi_m_dma_register_callback(&SPI_0, SPI_M_DMA_CB_TX_DONE, _dma_tx_complete); + spi_m_dma_enable(&SPI_0); } diff --git a/src/ui/oled/oled_writer.h b/src/ui/oled/oled_writer.h index c982583644..34d26a5699 100644 --- a/src/ui/oled/oled_writer.h +++ b/src/ui/oled/oled_writer.h @@ -23,14 +23,24 @@ */ void oled_writer_write_data(const uint8_t* buf, size_t buf_len); +/* + * Write display data to graphics RAM + */ +void oled_writer_write_data_blocking(const uint8_t* buf, size_t buf_len); + /* * Write single byte command */ -void oled_writer_write_cmd(uint8_t command); +void oled_writer_write_cmd_blocking(uint8_t command); /* * Write double byte command */ -void oled_writer_write_cmd_with_param(uint8_t command, uint8_t value); +void oled_writer_write_cmd_with_param_blocking(uint8_t command, uint8_t value); + +/* + * Initialize oled writer + */ +void oled_writer_init(void); #endif diff --git a/src/ui/oled/sh1107.c b/src/ui/oled/sh1107.c index 544e672cfe..46b2bb90ad 100644 --- a/src/ui/oled/sh1107.c +++ b/src/ui/oled/sh1107.c @@ -14,6 +14,7 @@ #include "sh1107.h" #include "oled_writer.h" +#include // Specify the column address of display RAM 0-127 #define SH1107_CMD_SET_LOW_COL(column) (0x00 | ((column) & 0x0F)) @@ -73,71 +74,70 @@ // Double byte command (0x00 to 0x7F) #define SH1107_CMD_SET_DISPLAY_START_LINE 0xDC -static uint8_t* _frame_buffer; - -void sh1107_configure(uint8_t* buf) +void sh1107_configure(void) { - _frame_buffer = buf; - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_OFF); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_CONTRAST_CONTROL, 0xff); - oled_writer_write_cmd(SH1107_CMD_SET_VERTICAL_ADDRESSING_MODE); - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); - oled_writer_write_cmd(SH1107_CMD_SET_NORMAL_DISPLAY); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_MULTIPLEX_RATIO, 0x3f); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_CONTRAST_CONTROL, 0xff); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_VERTICAL_ADDRESSING_MODE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_NORMAL_DISPLAY); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_MULTIPLEX_RATIO, 0x3f); // Shift the columns by 96 when display is in non-mirrored orientation - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_PRE_CHARGE_PERIOD, 0x22); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_VCOMH_DESELECT_LEVEL, 0x35); - oled_writer_write_cmd_with_param(0xad, 0x8a); - oled_writer_write_cmd(SH1107_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - sh1107_update(); - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_ON); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_PRE_CHARGE_PERIOD, 0x22); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_VCOMH_DESELECT_LEVEL, 0x35); + oled_writer_write_cmd_with_param_blocking(0xad, 0x8a); + oled_writer_write_cmd_blocking(SH1107_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); + oled_writer_write_data_blocking(canvas_active(), CANVAS_SIZE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_ON); } /* pixels can be accessed via buf[y*16+x/8] >> x%8 */ void sh1107_set_pixel(int16_t x, int16_t y, uint8_t c) { - uint32_t p; if (x < 0 || x > 127) return; if (y < 0 || y > 63) return; - p = y * 16; - p += x / 8; - if (c) { - _frame_buffer[p] |= 1 << (x % 8); - } else { - _frame_buffer[p] &= ~(1 << (x % 8)); - } + uint_fast8_t shift = x % 8; + uint8_t* p = &canvas_working()[y * 16 + x / 8]; + // Clear pixel + *p &= ~(1 << shift); + // Set pixel + *p |= (c & 0x1) << shift; } /* The SH1107 Segment/Common driver specifies that there are 16 pages per column * In total we should be writing 64*128 pixels. 8 bits per page, 16 pages per column and 64 - * columns */ -void sh1107_update(void) + * columns + * + * Since the display requires us to execute commands between each row we need to use the blocking + * interface. + * */ +void sh1107_blit(void) { for (size_t i = 0; i < 64; i++) { - oled_writer_write_cmd(SH1107_CMD_SET_LOW_COL(i)); - oled_writer_write_cmd(SH1107_CMD_SET_HIGH_COL(i)); - oled_writer_write_data(&_frame_buffer[i * 16], 16); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_LOW_COL(i)); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_HIGH_COL(i)); + oled_writer_write_data_blocking(&canvas_active()[i * 16], 16); } } void sh1107_mirror(bool mirror) { if (mirror) { - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_NORMAL); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_NORMAL); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_DOWN); // Shift the columns by 32 when display is in mirrored orientation - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x20); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x20); } else { - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); } } void sh1107_off(void) { - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_OFF); } diff --git a/src/ui/oled/sh1107.h b/src/ui/oled/sh1107.h index f6f0c42ea7..24edbc4496 100644 --- a/src/ui/oled/sh1107.h +++ b/src/ui/oled/sh1107.h @@ -22,10 +22,10 @@ /* * The sh1107 driver will store this pointer and later use it for "set_pixel" and "update". */ -void sh1107_configure(uint8_t* buf); +void sh1107_configure(void); void sh1107_set_pixel(int16_t x, int16_t y, uint8_t c); -void sh1107_update(void); +void sh1107_blit(void); void sh1107_mirror(bool mirror); void sh1107_off(void); diff --git a/src/ui/oled/ssd1312.c b/src/ui/oled/ssd1312.c index 3d11f3ef7a..429215e18f 100644 --- a/src/ui/oled/ssd1312.c +++ b/src/ui/oled/ssd1312.c @@ -15,6 +15,7 @@ #include "ssd1312.h" #include "oled_writer.h" #include +#include #define SSD1312_CMD_SET_LOW_COL(column) (0x00 | ((column) & 0x0F)) #define SSD1312_CMD_SET_HIGH_COL(column) (0x10 | (((column) >> 4) & 0x07)) @@ -78,70 +79,60 @@ // Double byte command #define SSD1312_CMD_SET_CHARGE_PUMP_SETTING 0x8D -static uint8_t* _frame_buffer; - -void ssd1312_configure(uint8_t* buf) +void ssd1312_configure(void) { - _frame_buffer = buf; - oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_OFF); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_CONTRAST_CONTROL, 0xff); - oled_writer_write_cmd_with_param( - SSD1312_CMD_SET_MEMORY_ADDRESSING_MODE, SSD1312_CMD_SET_PAGE_ADDRESSING_MODE); - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); - oled_writer_write_cmd(SSD1312_CMD_SET_NORMAL_DISPLAY); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_MULTIPLEX_RATIO, 0x3f); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_PRE_CHARGE_PERIOD, 0x22); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_VCOMH_SELECT_LEVEL, 0x35); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_IREF, 0x40); - oled_writer_write_cmd(SSD1312_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - ssd1312_update(); - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_ON); + // Initialize in the same order as the product docs example + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_MULTIPLEX_RATIO, 0x3f); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_IREF, 0x40); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_DISPLAY_OFFSET, 0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_NORMAL_DISPLAY); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_START_LINE(0)); + oled_writer_write_cmd_blocking(SSD1312_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_CONTRAST_CONTROL, 0xff); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_PRE_CHARGE_PERIOD, 0x22); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_SEG_PINS, 0x10); + oled_writer_write_cmd_with_param_blocking( + SSD1312_CMD_SET_MEMORY_ADDRESSING_MODE, SSD1312_CMD_SET_SEG_PAGE_H_MODE); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_VCOMH_SELECT_LEVEL, 0x35); + + oled_writer_write_data_blocking(canvas_active(), CANVAS_SIZE); + + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_ON); } void ssd1312_set_pixel(int16_t x, int16_t y, uint8_t c) { - uint32_t p; if (x < 0 || x > 127) return; if (y < 0 || y > 63) return; - p = (y / 8) * 128; - p += x; - if (c) { - _frame_buffer[p] |= 1 << (y % 8); - } else { - _frame_buffer[p] &= ~(1 << (y % 8)); - } + uint_fast8_t shift = x % 8; + uint_fast8_t num_cols = x / 8; + uint8_t* p = &canvas_working()[num_cols * 64 + y]; + // Clear pixel + *p &= ~(1 << shift); + // Set pixel + *p |= (c & 0x1) << shift; } -void ssd1312_update(void) +void ssd1312_blit(void) { - /* The SSD1312 has one page per 8 rows. One page is 128 bytes. Every byte is 8 rows */ - for (size_t i = 0; i < 64 / 8; i++) { - oled_writer_write_cmd(SSD1312_CMD_SET_PAGE_START_ADDRESS(i)); - // Explicitly set column address to 0 during initialization and screen updates to resolve - // intermittent ~20px horizontal offset and wrapping, which was experienced on one Nova - // device so far. This fixes the symptom, not the underlying issue, as we expect the column - // address to be correct if all bytes arrive at the screen. - oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); - oled_writer_write_data(&_frame_buffer[i * 128], 128); - } + oled_writer_write_data(canvas_active(), CANVAS_SIZE); } void ssd1312_mirror(bool mirror) { if (mirror) { - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_128); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_128); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_UP); } else { - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); } } void ssd1312_off(void) { - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_OFF); } diff --git a/src/ui/oled/ssd1312.h b/src/ui/oled/ssd1312.h index 2a0318ef52..bc1714a798 100644 --- a/src/ui/oled/ssd1312.h +++ b/src/ui/oled/ssd1312.h @@ -21,10 +21,10 @@ /* * The ssd1312 driver will store this pointer and later use it for "set_pixel" and "update". */ -void ssd1312_configure(uint8_t* buf); +void ssd1312_configure(void); void ssd1312_set_pixel(int16_t x, int16_t y, uint8_t c); -void ssd1312_update(void); +void ssd1312_blit(void); void ssd1312_mirror(bool mirror); void ssd1312_off(void); diff --git a/src/ui/screen_process.c b/src/ui/screen_process.c index c927574566..c96cbf0f73 100644 --- a/src/ui/screen_process.c +++ b/src/ui/screen_process.c @@ -15,21 +15,44 @@ #include "screen_process.h" #include "screen_stack.h" #include +#include #include +#include #include #include #include #include -static uint8_t screen_frame_cnt = 0; +volatile bool update_ui = true; + +#if !defined(TESTING) +static struct timer_task task_update_ui; + +static void update_ui_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + update_ui = true; +} +#endif + +void screen_process_init(void) +{ +#if !defined(TESTING) + // Limit screen rendering to 62.5hz + task_update_ui.interval = 16; + task_update_ui.cb = update_ui_cb; + task_update_ui.mode = TIMER_TASK_REPEAT; + + timer_add_task(&TIMER_0, &task_update_ui); +#endif +} void ui_screen_render_component(component_t* component) { - screen_clear(); component->position.left = 0; component->position.top = 0; component->f->render(component); - UG_SendBuffer(); + canvas_commit(); } static component_t* _get_waiting_screen(void) @@ -58,15 +81,6 @@ component_t* screen_process_get_top_component(void) return result; } -static void _screen_draw(component_t* component) -{ - if (screen_frame_cnt == SCREEN_FRAME_RATE) { - screen_frame_cnt = 0; - ui_screen_render_component(component); - } - screen_frame_cnt++; -} - #ifndef TESTING /** * Detects if the screen component being displayed has changed @@ -88,10 +102,14 @@ static bool _screen_has_changed(const component_t* current_component) void screen_process(void) { - screen_saver_process(); - component_t* component = screen_process_get_top_component(); - _screen_draw(component); + + if (update_ui) { + update_ui = false; + screen_saver_process(); + + ui_screen_render_component(component); + } #ifndef TESTING /* diff --git a/src/ui/screen_process.h b/src/ui/screen_process.h index 0f26ed9b52..b6376e1f5d 100644 --- a/src/ui/screen_process.h +++ b/src/ui/screen_process.h @@ -28,17 +28,10 @@ void ui_screen_render_component(component_t* component); component_t* screen_process_get_top_component(void); /** - * Runs the UI once. - * - * This function will update the screen (if needed) - * and process gesture-related events. + * Renders screen to frame buffer */ void screen_process(void); -/** - * Period of screen updates. - * The screen is refreshed every SCREEN_FRAME_RATE event loops cycles. - */ -#define SCREEN_FRAME_RATE 30 +void screen_process_init(void); #endif diff --git a/src/ui/ugui/ugui.c b/src/ui/ugui/ugui.c index a8f3ed140a..5b6e40c827 100644 --- a/src/ui/ugui/ugui.c +++ b/src/ui/ugui/ugui.c @@ -852,15 +852,3 @@ void UG_FontSetVSpace( UG_U16 s ) gui->char_v_space = s; } } - -void UG_SendBuffer(void) { -#ifndef TESTING - oled_send_buffer(); -#endif -} - -void UG_ClearBuffer(void) { -#ifndef TESTING - oled_clear_buffer(); -#endif -} diff --git a/src/ui/ugui/ugui.h b/src/ui/ugui/ugui.h index e64074c58d..24ef847ac2 100644 --- a/src/ui/ugui/ugui.h +++ b/src/ui/ugui/ugui.h @@ -135,8 +135,4 @@ UG_S16 UG_GetYDim( void ); void UG_FontSetHSpace( UG_U16 s ); void UG_FontSetVSpace( UG_U16 s ); -/* ssd1306.h wrapper */ -void UG_SendBuffer(void); -void UG_ClearBuffer(void); - #endif diff --git a/test/hardware-fakes/src/fake_oled.c b/test/hardware-fakes/src/fake_oled.c new file mode 100644 index 0000000000..56d41285e6 --- /dev/null +++ b/test/hardware-fakes/src/fake_oled.c @@ -0,0 +1,15 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +void oled_blit(void) {} diff --git a/test/hardware-fakes/src/fake_screen.c b/test/hardware-fakes/src/fake_screen.c index 60e9f409cc..db368def72 100644 --- a/test/hardware-fakes/src/fake_screen.c +++ b/test/hardware-fakes/src/fake_screen.c @@ -43,5 +43,3 @@ bool screen_is_upside_down(void) { return false; } - -void screen_clear(void) {}