mirror of
https://github.com/google/pebble.git
synced 2025-07-04 22:00:38 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
93
src/fw/drivers/stm32f2/clocksource.c
Normal file
93
src/fw/drivers/stm32f2/clocksource.c
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "drivers/clocksource.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/delay.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
//! How long to wait for the LSE to start. Empirically about 4 seconds.
|
||||
//! STM32 datasheet says typical max is 2.
|
||||
static const int LSE_READY_TIMEOUT_MS = 5000;
|
||||
|
||||
void clocksource_lse_configure(void) {
|
||||
// Only start the LSE oscillator if it is not already running. The oscillator
|
||||
// will normally be running even during standby mode to keep the RTC ticking;
|
||||
// it is only disabled when the microcontroller completely loses power.
|
||||
if (clocksource_is_lse_started()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "LSE oscillator already running");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Starting LSE oscillator");
|
||||
RCC_LSEConfig(BOARD_LSE_MODE);
|
||||
for (int i = 0; i < LSE_READY_TIMEOUT_MS; ++i) {
|
||||
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "LSE oscillator started after %d ms", i);
|
||||
break;
|
||||
}
|
||||
|
||||
delay_us(1000);
|
||||
watchdog_feed();
|
||||
}
|
||||
if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "LSE oscillator did not start");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool clocksource_is_lse_started(void) {
|
||||
return (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET);
|
||||
}
|
||||
|
||||
void clocksource_MCO1_enable(bool on) {
|
||||
static int8_t s_refcount = 0;
|
||||
|
||||
portENTER_CRITICAL();
|
||||
|
||||
PBL_ASSERTN(BOARD_CONFIG_MCO1.output_enabled);
|
||||
if (on) {
|
||||
gpio_af_init(
|
||||
&BOARD_CONFIG_MCO1.af_cfg, GPIO_OType_PP, GPIO_Speed_2MHz, GPIO_PuPd_NOPULL);
|
||||
// LSE is 32kHz, we want 32kHz for our external clock and is used by:
|
||||
// - The cc2564 bluetooth module
|
||||
// - Snowy / Spalding display VCOM
|
||||
RCC_MCO1Config(RCC_MCO1Source_LSE, RCC_MCO1Div_1);
|
||||
++s_refcount;
|
||||
} else {
|
||||
PBL_ASSERTN(s_refcount > 0);
|
||||
--s_refcount;
|
||||
if (s_refcount == 0) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling MCO1");
|
||||
gpio_analog_init(&BOARD_CONFIG_MCO1.an_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL();
|
||||
}
|
479
src/fw/drivers/stm32f2/dma.c
Normal file
479
src/fw/drivers/stm32f2/dma.c
Normal file
|
@ -0,0 +1,479 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/dma.h"
|
||||
|
||||
#include "drivers/mpu.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/stm32f2/dma_definitions.h"
|
||||
#include "mcu/cache.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define CMSIS_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
|
||||
#define CHSEL_OFFSET __builtin_ctz(DMA_SxCR_CHSEL)
|
||||
|
||||
// all interrupt flags for streams 0-3
|
||||
#define ALL_INTERRUPT_FLAGS_L(s) (DMA_LIFCR_CTCIF##s | DMA_LIFCR_CHTIF##s | DMA_LIFCR_CTEIF##s | \
|
||||
DMA_LIFCR_CDMEIF##s | DMA_LIFCR_CFEIF##s)
|
||||
// all interrupt flags for streams 4-7
|
||||
#define ALL_INTERRUPT_FLAGS_H(s) (DMA_HIFCR_CTCIF##s | DMA_HIFCR_CHTIF##s | DMA_HIFCR_CTEIF##s | \
|
||||
DMA_HIFCR_CDMEIF##s | DMA_HIFCR_CFEIF##s)
|
||||
|
||||
|
||||
// Stream Interrupt flag helper functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_clear_all_interrupt_flags(DMARequest *this) {
|
||||
switch ((uintptr_t)this->stream->periph) {
|
||||
case (uintptr_t)DMA1_Stream0:
|
||||
case (uintptr_t)DMA2_Stream0:
|
||||
this->stream->controller->periph->LIFCR = ALL_INTERRUPT_FLAGS_L(0);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream1:
|
||||
case (uintptr_t)DMA2_Stream1:
|
||||
this->stream->controller->periph->LIFCR = ALL_INTERRUPT_FLAGS_L(1);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream2:
|
||||
case (uintptr_t)DMA2_Stream2:
|
||||
this->stream->controller->periph->LIFCR = ALL_INTERRUPT_FLAGS_L(2);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream3:
|
||||
case (uintptr_t)DMA2_Stream3:
|
||||
this->stream->controller->periph->LIFCR = ALL_INTERRUPT_FLAGS_L(3);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream4:
|
||||
case (uintptr_t)DMA2_Stream4:
|
||||
this->stream->controller->periph->HIFCR = ALL_INTERRUPT_FLAGS_H(4);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream5:
|
||||
case (uintptr_t)DMA2_Stream5:
|
||||
this->stream->controller->periph->HIFCR = ALL_INTERRUPT_FLAGS_H(5);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream6:
|
||||
case (uintptr_t)DMA2_Stream6:
|
||||
this->stream->controller->periph->HIFCR = ALL_INTERRUPT_FLAGS_H(6);
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream7:
|
||||
case (uintptr_t)DMA2_Stream7:
|
||||
this->stream->controller->periph->HIFCR = ALL_INTERRUPT_FLAGS_H(7);
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_get_and_clear_interrupt_flags(DMARequest *this, bool *has_tc, bool *has_ht) {
|
||||
switch ((uintptr_t)this->stream->periph) {
|
||||
case (uintptr_t)DMA1_Stream0:
|
||||
case (uintptr_t)DMA2_Stream0:
|
||||
*has_tc = this->stream->controller->periph->LISR & DMA_LISR_TCIF0;
|
||||
*has_ht = this->stream->controller->periph->LISR & DMA_LISR_HTIF0;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream1:
|
||||
case (uintptr_t)DMA2_Stream1:
|
||||
*has_tc = this->stream->controller->periph->LISR & DMA_LISR_TCIF1;
|
||||
*has_ht = this->stream->controller->periph->LISR & DMA_LISR_HTIF1;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream2:
|
||||
case (uintptr_t)DMA2_Stream2:
|
||||
*has_tc = this->stream->controller->periph->LISR & DMA_LISR_TCIF2;
|
||||
*has_ht = this->stream->controller->periph->LISR & DMA_LISR_HTIF2;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream3:
|
||||
case (uintptr_t)DMA2_Stream3:
|
||||
*has_tc = this->stream->controller->periph->LISR & DMA_LISR_TCIF3;
|
||||
*has_ht = this->stream->controller->periph->LISR & DMA_LISR_HTIF3;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream4:
|
||||
case (uintptr_t)DMA2_Stream4:
|
||||
*has_tc = this->stream->controller->periph->HISR & DMA_HISR_TCIF4;
|
||||
*has_ht = this->stream->controller->periph->HISR & DMA_HISR_HTIF4;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream5:
|
||||
case (uintptr_t)DMA2_Stream5:
|
||||
*has_tc = this->stream->controller->periph->HISR & DMA_HISR_TCIF5;
|
||||
*has_ht = this->stream->controller->periph->HISR & DMA_HISR_HTIF5;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream6:
|
||||
case (uintptr_t)DMA2_Stream6:
|
||||
*has_tc = this->stream->controller->periph->HISR & DMA_HISR_TCIF6;
|
||||
*has_ht = this->stream->controller->periph->HISR & DMA_HISR_HTIF6;
|
||||
break;
|
||||
case (uintptr_t)DMA1_Stream7:
|
||||
case (uintptr_t)DMA2_Stream7:
|
||||
*has_tc = this->stream->controller->periph->HISR & DMA_HISR_TCIF7;
|
||||
*has_ht = this->stream->controller->periph->HISR & DMA_HISR_HTIF7;
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
prv_clear_all_interrupt_flags(this);
|
||||
}
|
||||
|
||||
|
||||
// Controller clock control
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_use_controller(DMAController *this) {
|
||||
const int old_refcount = atomic_fetch_add(&this->state->refcount, 1);
|
||||
if (old_refcount == 0) {
|
||||
periph_config_enable(this->periph, this->rcc_bit);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_release_controller(DMAController *this) {
|
||||
const int refcount = atomic_fetch_sub(&this->state->refcount, 1) - 1;
|
||||
PBL_ASSERT(refcount >= 0, "Attempted to release a DMA controller that is not in use!");
|
||||
if (refcount == 0) {
|
||||
periph_config_disable(this->periph, this->rcc_bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialization
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static uint32_t prv_get_data_size_bytes(DMARequest *this) {
|
||||
switch (this->data_size) {
|
||||
case DMARequestDataSize_Byte:
|
||||
return 1;
|
||||
case DMARequestDataSize_HalfWord:
|
||||
return 2;
|
||||
case DMARequestDataSize_Word:
|
||||
return 4;
|
||||
default:
|
||||
WTF;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_set_constant_config(DMARequest *this) {
|
||||
uint32_t cr_value = 0;
|
||||
uint32_t fcr_value = 0;
|
||||
|
||||
// set the channel
|
||||
PBL_ASSERTN((this->channel & (DMA_SxCR_CHSEL >> CHSEL_OFFSET)) == this->channel);
|
||||
cr_value |= this->channel << CHSEL_OFFSET;
|
||||
|
||||
// set the priority
|
||||
PBL_ASSERTN((this->priority & DMA_SxCR_PL) == this->priority);
|
||||
cr_value |= this->priority;
|
||||
|
||||
// set the data size
|
||||
PBL_ASSERTN((this->data_size & (DMA_SxCR_MSIZE | DMA_SxCR_PSIZE)) == this->data_size);
|
||||
cr_value |= this->data_size;
|
||||
|
||||
// set the direction
|
||||
PBL_ASSERTN((this->type & (DMA_SxCR_DIR_0 | DMA_SxCR_DIR_1)) == this->type);
|
||||
cr_value |= this->type;
|
||||
|
||||
// set the incrementing modes, FIFO, and burst sizes
|
||||
switch (this->type) {
|
||||
case DMARequestType_MemoryToMemory:
|
||||
// memory and peripheral burst of 8 (found to be fastest based on testing on Snowy / Robert)
|
||||
cr_value |= DMA_SxCR_MBURST_1 | DMA_SxCR_PBURST_1;
|
||||
// memory and peripheral incrementing enabled
|
||||
cr_value |= DMA_SxCR_MINC | DMA_SxCR_PINC;
|
||||
// enable the FIFO with a threshold of 1/2 full
|
||||
fcr_value |= DMA_SxFCR_DMDIS;
|
||||
fcr_value |= DMA_SxFCR_FTH_0;
|
||||
break;
|
||||
case DMARequestType_MemoryToPeripheral:
|
||||
case DMARequestType_PeripheralToMemory:
|
||||
// just enable incrementing of memory address (no FIFO, single burst)
|
||||
cr_value |= DMA_SxCR_MINC;
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
prv_use_controller(this->stream->controller);
|
||||
// make sure the stream is disabled before trying to configure
|
||||
PBL_ASSERTN((this->stream->periph->CR & DMA_SxCR_EN) == 0);
|
||||
this->stream->periph->CR = cr_value;
|
||||
this->stream->periph->FCR = fcr_value;
|
||||
prv_release_controller(this->stream->controller);
|
||||
|
||||
// Configure and enable the IRQ if necessary (DMA interrupts enabled later)
|
||||
if (this->irq_priority != IRQ_PRIORITY_INVALID) {
|
||||
NVIC_SetPriority(this->stream->irq_channel, this->irq_priority);
|
||||
NVIC_EnableIRQ(this->stream->irq_channel);
|
||||
}
|
||||
}
|
||||
|
||||
void dma_request_init(DMARequest *this) {
|
||||
if (this->state->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we only support 1 transfer per stream so assert that the stream isn't already initialized
|
||||
PBL_ASSERTN(!this->stream->state->initialized);
|
||||
// sanity check that the stream and controller are valid
|
||||
switch ((uintptr_t)this->stream->periph) {
|
||||
case (uintptr_t)DMA1_Stream0:
|
||||
case (uintptr_t)DMA1_Stream1:
|
||||
case (uintptr_t)DMA1_Stream2:
|
||||
case (uintptr_t)DMA1_Stream3:
|
||||
case (uintptr_t)DMA1_Stream4:
|
||||
case (uintptr_t)DMA1_Stream5:
|
||||
case (uintptr_t)DMA1_Stream6:
|
||||
case (uintptr_t)DMA1_Stream7:
|
||||
PBL_ASSERTN(this->stream->controller->periph == DMA1);
|
||||
break;
|
||||
case (uintptr_t)DMA2_Stream0:
|
||||
case (uintptr_t)DMA2_Stream1:
|
||||
case (uintptr_t)DMA2_Stream2:
|
||||
case (uintptr_t)DMA2_Stream3:
|
||||
case (uintptr_t)DMA2_Stream4:
|
||||
case (uintptr_t)DMA2_Stream5:
|
||||
case (uintptr_t)DMA2_Stream6:
|
||||
case (uintptr_t)DMA2_Stream7:
|
||||
PBL_ASSERTN(this->stream->controller->periph == DMA2);
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
this->stream->state->initialized = true;
|
||||
|
||||
prv_set_constant_config(this);
|
||||
this->state->initialized = true;
|
||||
}
|
||||
|
||||
|
||||
// Transfer APIs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_validate_memory(DMARequest *this, void *dst, const void *src, uint32_t length) {
|
||||
if (mpu_memory_is_cachable(src)) {
|
||||
// Flush the source buffer from cache so that SRAM has the correct data.
|
||||
uintptr_t aligned_src = (uintptr_t)src;
|
||||
size_t aligned_length = length;
|
||||
dcache_align(&aligned_src, &aligned_length);
|
||||
dcache_flush((const void *)aligned_src, aligned_length);
|
||||
}
|
||||
|
||||
const uint32_t alignment_mask = prv_get_data_size_bytes(this) - 1;
|
||||
if (mpu_memory_is_cachable(dst)) {
|
||||
// If a cache line within the dst gets evicted while we do the transfer, it'll corrupt SRAM, so
|
||||
// just invalidate it now.
|
||||
dcache_invalidate(dst, length);
|
||||
// since the dst address is cachable, it needs to be aligned to a cache line and
|
||||
// the length must be an even multiple of cache lines
|
||||
const uint32_t dst_alignment_mask = dcache_alignment_mask_minimum(alignment_mask);
|
||||
PBL_ASSERTN(((length & dst_alignment_mask) == 0) &&
|
||||
(((uintptr_t)dst & dst_alignment_mask) == 0) &&
|
||||
(((uintptr_t)src & alignment_mask) == 0));
|
||||
} else {
|
||||
PBL_ASSERTN(((length & alignment_mask) == 0) &&
|
||||
(((uintptr_t)dst & alignment_mask) == 0) &&
|
||||
(((uintptr_t)src & alignment_mask) == 0));
|
||||
}
|
||||
|
||||
#if PLATFORM_ROBERT
|
||||
// There is an erratum in the STM32F7xx MCUs in which causes DMA transfers
|
||||
// that read from the DTCM to read corrupted data if the MCU enters sleep mode
|
||||
// during the transfer. Note that writes to DTCM will not be corrupted.
|
||||
extern const char __DTCM_RAM_size__[];
|
||||
PBL_ASSERT((uintptr_t)src >= (RAMDTCM_BASE + (uintptr_t)__DTCM_RAM_size__) ||
|
||||
((uintptr_t)src + length) <= RAMDTCM_BASE,
|
||||
"DMA transfer will be corrupted if MCU enters sleep mode");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_request_start(DMARequest *this, void *dst, const void *src, uint32_t length,
|
||||
DMARequestTransferType transfer_type) {
|
||||
this->state->transfer_dst = dst;
|
||||
this->state->transfer_length = length;
|
||||
prv_validate_memory(this, dst, src, length);
|
||||
prv_use_controller(this->stream->controller);
|
||||
|
||||
// set the data length in terms of units (we assert that it's an even number in
|
||||
// prv_validate_memory above)
|
||||
this->stream->periph->NDTR = length / prv_get_data_size_bytes(this);
|
||||
|
||||
// set the addresses
|
||||
switch (this->type) {
|
||||
case DMARequestType_MemoryToMemory:
|
||||
case DMARequestType_PeripheralToMemory:
|
||||
this->stream->periph->PAR = (uint32_t)src;
|
||||
this->stream->periph->M0AR = (uint32_t)dst;
|
||||
break;
|
||||
case DMARequestType_MemoryToPeripheral:
|
||||
this->stream->periph->PAR = (uint32_t)dst;
|
||||
this->stream->periph->M0AR = (uint32_t)src;
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
// set the mode and enable the appropriate interrupts
|
||||
switch (transfer_type) {
|
||||
case DMARequestTransferType_Direct:
|
||||
this->stream->periph->CR &= ~DMA_SxCR_CIRC;
|
||||
this->stream->periph->CR |= DMA_SxCR_TCIE;
|
||||
break;
|
||||
case DMARequestTransferType_Circular:
|
||||
this->stream->periph->CR |= DMA_SxCR_CIRC | DMA_SxCR_HTIE | DMA_SxCR_TCIE;
|
||||
break;
|
||||
case DMARequestTransferType_None:
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
// "As a general recommendation, it is advised to clear all flags in the DMA_LIFCR and
|
||||
// DMA_HIFCR registers before starting a new transfer." -- STM32 AN4031 (DM00046011.pdf)
|
||||
// "Before setting EN bit to '1' to start a new transfer, the event flags corresponding to the
|
||||
// stream in DMA_LISR or DMA_HISR register must be" -- Page 213, STM RM0402
|
||||
prv_clear_all_interrupt_flags(this);
|
||||
|
||||
// Start the DMA transfer
|
||||
this->stream->periph->CR |= DMA_SxCR_EN;
|
||||
}
|
||||
|
||||
void dma_request_start_direct(DMARequest *this, void *dst, const void *src, uint32_t length,
|
||||
DMADirectRequestHandler handler, void *context) {
|
||||
PBL_ASSERTN(this->state->initialized);
|
||||
|
||||
PBL_ASSERTN(this->state->transfer_type == DMARequestTransferType_None);
|
||||
this->state->transfer_type = DMARequestTransferType_Direct;
|
||||
|
||||
this->state->direct_transfer_handler = handler;
|
||||
this->state->context = context;
|
||||
PBL_ASSERTN(!this->stream->state->current_request);
|
||||
this->stream->state->current_request = this;
|
||||
|
||||
prv_request_start(this, dst, src, length, DMARequestTransferType_Direct);
|
||||
}
|
||||
|
||||
void dma_request_start_circular(DMARequest *this, void *dst, const void *src, uint32_t length,
|
||||
DMACircularRequestHandler handler, void *context) {
|
||||
PBL_ASSERTN(this->state->initialized);
|
||||
|
||||
PBL_ASSERTN(this->state->transfer_type == DMARequestTransferType_None);
|
||||
this->state->transfer_type = DMARequestTransferType_Circular;
|
||||
|
||||
this->state->circular_transfer_handler = handler;
|
||||
this->state->context = context;
|
||||
PBL_ASSERTN(!this->stream->state->current_request);
|
||||
this->stream->state->current_request = this;
|
||||
|
||||
// TODO: We don't currently support DMA'ing into a cachable region of memory (i.e. SRAM) for
|
||||
// circular transfers. The reason is that it gets complicated because the consumer might be
|
||||
// reading from the buffer at any time (as UART does), as opposed to direct transfers where the
|
||||
// consumer is always reading only after the transfer has completed.
|
||||
PBL_ASSERTN(!mpu_memory_is_cachable(dst));
|
||||
prv_request_start(this, dst, src, length, DMARequestTransferType_Circular);
|
||||
}
|
||||
|
||||
void dma_request_stop(DMARequest *this) {
|
||||
if (this->state->transfer_type == DMARequestTransferType_None) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->stream->periph->CR &= ~(DMA_SxCR_HTIE | DMA_SxCR_TCIE);
|
||||
|
||||
// disable the stream
|
||||
this->stream->periph->CR &= ~DMA_SxCR_EN;
|
||||
while ((this->stream->periph->CR & DMA_SxCR_EN) != 0) {}
|
||||
prv_release_controller(this->stream->controller);
|
||||
|
||||
// clean up our state
|
||||
this->state->transfer_dst = NULL;
|
||||
this->state->transfer_length = 0;
|
||||
this->state->direct_transfer_handler = NULL;
|
||||
this->state->circular_transfer_handler = NULL;
|
||||
this->state->context = NULL;
|
||||
this->state->transfer_type = DMARequestTransferType_None;
|
||||
this->stream->state->current_request = NULL;
|
||||
}
|
||||
|
||||
|
||||
uint32_t dma_request_get_current_data_counter(DMARequest *this) {
|
||||
return this->stream->periph->NDTR;
|
||||
}
|
||||
|
||||
bool dma_request_in_progress(DMARequest *this) {
|
||||
return this->state->transfer_type != DMARequestTransferType_None;
|
||||
}
|
||||
|
||||
void dma_request_set_memory_increment_disabled(DMARequest *this, bool disabled) {
|
||||
prv_use_controller(this->stream->controller);
|
||||
if (disabled) {
|
||||
this->stream->periph->CR &= ~DMA_SxCR_MINC;
|
||||
} else {
|
||||
this->stream->periph->CR |= DMA_SxCR_MINC;
|
||||
}
|
||||
prv_release_controller(this->stream->controller);
|
||||
}
|
||||
|
||||
|
||||
// ISR
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void dma_stream_irq_handler(DMAStream *stream) {
|
||||
bool should_context_switch = false;
|
||||
|
||||
DMARequest *this = stream->state->current_request;
|
||||
PBL_ASSERTN(this);
|
||||
PBL_ASSERTN(this->stream == stream);
|
||||
|
||||
bool has_tc;
|
||||
bool has_ht;
|
||||
prv_get_and_clear_interrupt_flags(this, &has_tc, &has_ht);
|
||||
if (!has_tc && !has_ht) {
|
||||
// we shouldn't be here
|
||||
portEND_SWITCHING_ISR(should_context_switch);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->state->transfer_type) {
|
||||
case DMARequestTransferType_Direct:
|
||||
if (has_tc) {
|
||||
if (mpu_memory_is_cachable(this->state->transfer_dst)) {
|
||||
dcache_invalidate(this->state->transfer_dst, this->state->transfer_length);
|
||||
}
|
||||
|
||||
// Automatically stop the transfer before calling the handler so that the handler can start
|
||||
// another transfer immediately. We need to preserve the handler and context first.
|
||||
DMADirectRequestHandler handler = this->state->direct_transfer_handler;
|
||||
void *context = this->state->context;
|
||||
dma_request_stop(this);
|
||||
|
||||
if (handler && handler(this, context)) {
|
||||
should_context_switch = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DMARequestTransferType_Circular:
|
||||
if (this->state->circular_transfer_handler) {
|
||||
if (this->state->circular_transfer_handler(this, this->state->context, has_tc)) {
|
||||
should_context_switch = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DMARequestTransferType_None:
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
portEND_SWITCHING_ISR(should_context_switch);
|
||||
}
|
100
src/fw/drivers/stm32f2/dma_definitions.h
Normal file
100
src/fw/drivers/stm32f2/dma_definitions.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dma.h"
|
||||
#include "kernel/util/stop.h"
|
||||
|
||||
typedef struct DMAControllerState {
|
||||
int refcount;
|
||||
} DMAControllerState;
|
||||
|
||||
typedef const struct DMAController {
|
||||
DMAControllerState *state;
|
||||
DMA_TypeDef *periph;
|
||||
uint32_t rcc_bit;
|
||||
} DMAController;
|
||||
|
||||
typedef struct DMAStreamState {
|
||||
bool initialized;
|
||||
//! The current in-progress request (or NULL if not in progress)
|
||||
DMARequest *current_request;
|
||||
} DMAStreamState;
|
||||
|
||||
typedef const struct DMAStream {
|
||||
DMAStreamState *state;
|
||||
DMAController *controller;
|
||||
DMA_Stream_TypeDef *periph;
|
||||
uint8_t irq_channel;
|
||||
} DMAStream;
|
||||
|
||||
typedef enum DMARequestPriority {
|
||||
DMARequestPriority_Low = 0,
|
||||
DMARequestPriority_Medium = DMA_SxCR_PL_0,
|
||||
DMARequestPriority_High = DMA_SxCR_PL_1,
|
||||
DMARequestPriority_VeryHigh = DMA_SxCR_PL_0 | DMA_SxCR_PL_1,
|
||||
} DMARequestPriority;
|
||||
|
||||
typedef enum DMARequestType {
|
||||
//! Transfers from one memory buffer to another (essentially a memcpy)
|
||||
DMARequestType_MemoryToMemory = DMA_SxCR_DIR_1,
|
||||
//! Transfers from a peripheral's data register to a memory buffer
|
||||
DMARequestType_PeripheralToMemory = 0,
|
||||
//! Transfers from memory buffer to a peripheral's data buffer
|
||||
DMARequestType_MemoryToPeripheral = DMA_SxCR_DIR_0,
|
||||
} DMARequestType;
|
||||
|
||||
typedef enum DMARequestDataSize {
|
||||
DMARequestDataSize_Byte = 0,
|
||||
DMARequestDataSize_HalfWord = DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0,
|
||||
DMARequestDataSize_Word = DMA_SxCR_MSIZE_1 | DMA_SxCR_PSIZE_1,
|
||||
} DMARequestDataSize;
|
||||
|
||||
typedef enum DMARequestTransferType {
|
||||
DMARequestTransferType_None = 0,
|
||||
DMARequestTransferType_Direct,
|
||||
DMARequestTransferType_Circular,
|
||||
} DMARequestTransferType;
|
||||
|
||||
typedef struct DMARequestState {
|
||||
bool initialized;
|
||||
//! The type of request currently in-progress
|
||||
DMARequestTransferType transfer_type;
|
||||
//! The destination and length of the current transfer
|
||||
void *transfer_dst;
|
||||
uint32_t transfer_length;
|
||||
//! The handler for transfer events
|
||||
union {
|
||||
DMADirectRequestHandler direct_transfer_handler;
|
||||
DMACircularRequestHandler circular_transfer_handler;
|
||||
};
|
||||
void *context;
|
||||
} DMARequestState;
|
||||
|
||||
typedef const struct DMARequest {
|
||||
DMARequestState *state;
|
||||
DMAStream *stream;
|
||||
uint32_t channel;
|
||||
uint32_t irq_priority;
|
||||
DMARequestPriority priority;
|
||||
DMARequestType type;
|
||||
DMARequestDataSize data_size;
|
||||
} DMARequest;
|
||||
|
||||
|
||||
void dma_stream_irq_handler(DMAStream *this);
|
256
src/fw/drivers/stm32f2/exti.c
Normal file
256
src/fw/drivers/stm32f2/exti.c
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/exti.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "kernel/events.h"
|
||||
#include "mcu/interrupts.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
//! Tracks whether we've disabled interrupts as part of locking out other people from our EXTI
|
||||
//! registers.
|
||||
static bool s_exti_locked = false;
|
||||
|
||||
//! Have we already configured the EXTI9_5_IRQn interrupt?
|
||||
static bool s_9_5_nvic_configured = false;
|
||||
//! Have we already configured the EXTI15_10_IRQn interrupt?
|
||||
static bool s_15_10_nvic_configured = false;
|
||||
|
||||
static ExtiHandlerCallback s_exti_handlers[16];
|
||||
|
||||
//! Convert a exti number (value 0 to 22) to one of the EXTI_LineX defines
|
||||
static uint32_t prv_exti_line_to_bit(int exti_line) {
|
||||
return 0x1 << exti_line;
|
||||
}
|
||||
|
||||
static void prv_lock(void) {
|
||||
if (mcu_state_are_interrupts_enabled()) {
|
||||
__disable_irq();
|
||||
s_exti_locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_unlock(void) {
|
||||
if (s_exti_locked) {
|
||||
__enable_irq();
|
||||
s_exti_locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
static IRQn_Type prv_get_irq_enum(int exti_line) {
|
||||
if (exti_line <= 4) {
|
||||
return EXTI0_IRQn + exti_line;
|
||||
}
|
||||
if (exti_line <= 9) {
|
||||
return EXTI9_5_IRQn;
|
||||
}
|
||||
if (exti_line <= 15) {
|
||||
return EXTI15_10_IRQn;
|
||||
}
|
||||
if (exti_line == ExtiLineOther_RTCAlarm) {
|
||||
return RTC_Alarm_IRQn;
|
||||
}
|
||||
if (exti_line == ExtiLineOther_RTCWakeup) {
|
||||
return RTC_WKUP_IRQn;
|
||||
}
|
||||
WTF;
|
||||
}
|
||||
|
||||
static void prv_configure_nvic_channel(IRQn_Type irqn) {
|
||||
NVIC_SetPriority(irqn, EXTI_PRIORITY);
|
||||
NVIC_EnableIRQ(irqn);
|
||||
}
|
||||
|
||||
static void prv_check_nvic_channel(IRQn_Type irqn) {
|
||||
// Make sure we haven't already set up the channel in question.
|
||||
if (irqn == EXTI9_5_IRQn) {
|
||||
if (s_9_5_nvic_configured) {
|
||||
return; // Already configured
|
||||
}
|
||||
s_9_5_nvic_configured = true;
|
||||
} else if (irqn == EXTI15_10_IRQn) {
|
||||
if (s_15_10_nvic_configured) {
|
||||
return; // Already configured
|
||||
}
|
||||
s_15_10_nvic_configured = true;
|
||||
}
|
||||
|
||||
prv_configure_nvic_channel(irqn);
|
||||
}
|
||||
|
||||
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb) {
|
||||
periph_config_acquire_lock();
|
||||
periph_config_enable(SYSCFG, RCC_APB2Periph_SYSCFG);
|
||||
|
||||
// Select which GPIO to monitor
|
||||
SYSCFG->EXTICR[cfg.exti_line >> 0x02] &=
|
||||
~(((uint32_t)0x0F) << (0x04 * (cfg.exti_line & (uint8_t)0x03)));
|
||||
SYSCFG->EXTICR[cfg.exti_line >> 0x02] |=
|
||||
(((uint32_t)cfg.exti_port_source) << (0x04 * (cfg.exti_line & (uint8_t)0x03)));
|
||||
|
||||
periph_config_disable(SYSCFG, RCC_APB2Periph_SYSCFG);
|
||||
periph_config_release_lock();
|
||||
|
||||
s_exti_handlers[cfg.exti_line] = cb;
|
||||
|
||||
// Do the rest of the configuration
|
||||
exti_configure_other(cfg.exti_line, trigger);
|
||||
}
|
||||
|
||||
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger) {
|
||||
// Clear IT Pending bit
|
||||
EXTI->PR = prv_exti_line_to_bit(exti_line);
|
||||
|
||||
prv_lock();
|
||||
|
||||
switch (trigger) {
|
||||
case ExtiTrigger_Rising:
|
||||
EXTI->RTSR |= prv_exti_line_to_bit(exti_line);
|
||||
EXTI->FTSR &= ~prv_exti_line_to_bit(exti_line);
|
||||
break;
|
||||
case ExtiTrigger_Falling:
|
||||
EXTI->RTSR &= ~prv_exti_line_to_bit(exti_line);
|
||||
EXTI->FTSR |= prv_exti_line_to_bit(exti_line);
|
||||
break;
|
||||
case ExtiTrigger_RisingFalling:
|
||||
EXTI->RTSR |= prv_exti_line_to_bit(exti_line);
|
||||
EXTI->FTSR |= prv_exti_line_to_bit(exti_line);
|
||||
break;
|
||||
}
|
||||
|
||||
prv_unlock();
|
||||
|
||||
periph_config_acquire_lock();
|
||||
prv_check_nvic_channel(prv_get_irq_enum(exti_line));
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
void exti_enable_other(ExtiLineOther exti_line) {
|
||||
prv_lock();
|
||||
|
||||
EXTI->IMR |= prv_exti_line_to_bit(exti_line);
|
||||
|
||||
prv_unlock();
|
||||
}
|
||||
|
||||
void exti_disable_other(ExtiLineOther exti_line) {
|
||||
prv_lock();
|
||||
|
||||
uint32_t exti_bit = prv_exti_line_to_bit(exti_line);
|
||||
|
||||
EXTI->IMR &= ~exti_bit;
|
||||
EXTI->PR = exti_bit;
|
||||
|
||||
// No need to disable the NVIC ISR. If all the EXTIs that feed a given shared ISR are disabled
|
||||
// the ISR won't fire.
|
||||
|
||||
prv_unlock();
|
||||
}
|
||||
|
||||
void exti_set_pending(ExtiConfig cfg) {
|
||||
IRQn_Type irq;
|
||||
switch (cfg.exti_line) {
|
||||
case 0: irq = EXTI0_IRQn; break;
|
||||
case 1: irq = EXTI1_IRQn; break;
|
||||
case 2: irq = EXTI2_IRQn; break;
|
||||
case 3: irq = EXTI3_IRQn; break;
|
||||
case 4: irq = EXTI4_IRQn; break;
|
||||
case 5 ... 9: irq = EXTI9_5_IRQn; break;
|
||||
case 10 ... 15: irq = EXTI15_10_IRQn; break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
NVIC_SetPendingIRQ(irq);
|
||||
}
|
||||
|
||||
void exti_clear_pending_other(ExtiLineOther exti_line) {
|
||||
uint32_t exti_bit = prv_exti_line_to_bit(exti_line);
|
||||
EXTI->PR = exti_bit;
|
||||
}
|
||||
|
||||
// Helper functions for handling ISRs
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_handle_exti(int exti_line) {
|
||||
// Clear IT Pending bit
|
||||
EXTI->PR = prv_exti_line_to_bit(exti_line);
|
||||
|
||||
const ExtiHandlerCallback cb = s_exti_handlers[exti_line];
|
||||
if (cb) {
|
||||
bool should_context_switch = false;
|
||||
cb(&should_context_switch);
|
||||
portEND_SWITCHING_ISR(should_context_switch);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_check_handle_exti(int exti_line) {
|
||||
if (EXTI->PR & prv_exti_line_to_bit(exti_line)) {
|
||||
prv_handle_exti(exti_line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Actual ISR functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EXTI0_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI0_IRQn);
|
||||
prv_handle_exti(0);
|
||||
}
|
||||
|
||||
void EXTI1_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI1_IRQn);
|
||||
prv_handle_exti(1);
|
||||
}
|
||||
|
||||
void EXTI2_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI2_IRQn);
|
||||
prv_handle_exti(2);
|
||||
}
|
||||
|
||||
void EXTI3_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI3_IRQn);
|
||||
prv_handle_exti(3);
|
||||
}
|
||||
|
||||
void EXTI4_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI4_IRQn);
|
||||
prv_handle_exti(4);
|
||||
}
|
||||
|
||||
void EXTI9_5_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI9_5_IRQn);
|
||||
for (int i = 5; i <= 9; ++i) {
|
||||
prv_check_handle_exti(i);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI15_10_IRQHandler(void) {
|
||||
NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
||||
for (int i = 10; i <= 15; ++i) {
|
||||
prv_check_handle_exti(i);
|
||||
}
|
||||
}
|
||||
|
169
src/fw/drivers/stm32f2/gpio.c
Normal file
169
src/fw/drivers/stm32f2/gpio.c
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_GPIO (9)
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "debug/power_tracking.h"
|
||||
|
||||
static const PowerSystem s_gpio_to_power_system_map[MAX_GPIO] = {
|
||||
[0] = PowerSystemMcuGpioA,
|
||||
[1] = PowerSystemMcuGpioB,
|
||||
[2] = PowerSystemMcuGpioC,
|
||||
[3] = PowerSystemMcuGpioD,
|
||||
[7] = PowerSystemMcuGpioH,
|
||||
};
|
||||
|
||||
static void prv_init_common(const InputConfig *input_config, GPIO_InitTypeDef *gpio_init) {
|
||||
gpio_use(input_config->gpio);
|
||||
GPIO_Init(input_config->gpio, gpio_init);
|
||||
gpio_release(input_config->gpio);
|
||||
}
|
||||
|
||||
static uint8_t s_gpio_clock_count[MAX_GPIO];
|
||||
|
||||
void gpio_use(GPIO_TypeDef* GPIOx) {
|
||||
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
|
||||
portENTER_CRITICAL();
|
||||
if((idx < MAX_GPIO) && !(s_gpio_clock_count[idx]++)) {
|
||||
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
|
||||
power_tracking_start(s_gpio_to_power_system_map[idx]);
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void gpio_release(GPIO_TypeDef* GPIOx) {
|
||||
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
|
||||
portENTER_CRITICAL();
|
||||
if((idx < MAX_GPIO) && s_gpio_clock_count[idx] && !(--s_gpio_clock_count[idx])) {
|
||||
CLEAR_BIT(RCC->AHB1ENR, (0x1 << idx));
|
||||
power_tracking_stop(s_gpio_to_power_system_map[idx]);
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void gpio_output_init(const OutputConfig *pin_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = pin_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_OUT,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
gpio_use(pin_config->gpio);
|
||||
GPIO_Init(pin_config->gpio, &init);
|
||||
gpio_release(pin_config->gpio);
|
||||
}
|
||||
|
||||
void gpio_output_set(const OutputConfig *pin_config, bool asserted) {
|
||||
if (!pin_config->active_high) {
|
||||
asserted = !asserted;
|
||||
}
|
||||
gpio_use(pin_config->gpio);
|
||||
GPIO_WriteBit(pin_config->gpio, pin_config->gpio_pin,
|
||||
asserted? Bit_SET : Bit_RESET);
|
||||
gpio_release(pin_config->gpio);
|
||||
}
|
||||
|
||||
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = pupd
|
||||
};
|
||||
|
||||
gpio_use(af_config->gpio);
|
||||
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source,
|
||||
af_config->gpio_af);
|
||||
GPIO_Init(af_config->gpio, &init);
|
||||
gpio_release(af_config->gpio);
|
||||
}
|
||||
|
||||
void gpio_af_configure_low_power(const AfConfig *af_config) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AN,
|
||||
.GPIO_Speed = GPIO_Speed_2MHz,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
gpio_use(af_config->gpio);
|
||||
GPIO_Init(af_config->gpio, &init);
|
||||
gpio_release(af_config->gpio);
|
||||
}
|
||||
|
||||
void gpio_af_configure_fixed_output(const AfConfig *af_config, bool asserted) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_OUT,
|
||||
.GPIO_Speed = GPIO_Speed_2MHz,
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
gpio_use(af_config->gpio);
|
||||
GPIO_Init(af_config->gpio, &init);
|
||||
GPIO_WriteBit(af_config->gpio, af_config->gpio_pin,
|
||||
asserted? Bit_SET : Bit_RESET);
|
||||
gpio_release(af_config->gpio);
|
||||
}
|
||||
|
||||
void gpio_input_init(const InputConfig *input_config) {
|
||||
if (input_config->gpio == NULL) {
|
||||
// TODO: PBL-15063
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_input_init_pull_up_down(input_config, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
|
||||
void gpio_input_init_pull_up_down(const InputConfig *input_config, GPIOPuPd_TypeDef pupd) {
|
||||
GPIO_InitTypeDef gpio_init = {
|
||||
.GPIO_Mode = GPIO_Mode_IN,
|
||||
.GPIO_PuPd = pupd,
|
||||
.GPIO_Pin = input_config->gpio_pin
|
||||
};
|
||||
|
||||
prv_init_common(input_config, &gpio_init);
|
||||
}
|
||||
|
||||
bool gpio_input_read(const InputConfig *input_config) {
|
||||
gpio_use(input_config->gpio);
|
||||
uint8_t bit = GPIO_ReadInputDataBit(input_config->gpio, input_config->gpio_pin);
|
||||
gpio_release(input_config->gpio);
|
||||
|
||||
return bit != 0;
|
||||
}
|
||||
|
||||
void gpio_analog_init(const InputConfig *input_config) {
|
||||
GPIO_InitTypeDef gpio_init = {
|
||||
.GPIO_Pin = input_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AN,
|
||||
.GPIO_Speed = GPIO_Speed_2MHz,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
prv_init_common(input_config, &gpio_init);
|
||||
}
|
19
src/fw/drivers/stm32f2/gpio_defaults.c
Normal file
19
src/fw/drivers/stm32f2/gpio_defaults.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 gpio_init_all(void) {
|
||||
// TODO - PBL-15063
|
||||
}
|
359
src/fw/drivers/stm32f2/i2c_hal.c
Normal file
359
src/fw/drivers/stm32f2/i2c_hal.c
Normal file
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/i2c_hal.h"
|
||||
#include "drivers/i2c_definitions.h"
|
||||
#include "drivers/stm32f2/i2c_hal_definitions.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#define I2C_IRQ_PRIORITY (0xc)
|
||||
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
|
||||
#define I2C_READ_WRITE_BIT (0x01)
|
||||
|
||||
|
||||
static uint32_t s_guard_events[] = {
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_RECEIVED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
};
|
||||
|
||||
void i2c_hal_init(I2CBus *bus) {
|
||||
NVIC_SetPriority(bus->hal->ev_irq_channel, I2C_IRQ_PRIORITY);
|
||||
NVIC_SetPriority(bus->hal->er_irq_channel, I2C_IRQ_PRIORITY);
|
||||
NVIC_EnableIRQ(bus->hal->ev_irq_channel);
|
||||
NVIC_EnableIRQ(bus->hal->er_irq_channel);
|
||||
I2C_DeInit(bus->hal->i2c);
|
||||
}
|
||||
|
||||
static uint32_t prv_get_apb1_frequency(void) {
|
||||
RCC_ClocksTypeDef rcc_clocks;
|
||||
RCC_GetClocksFreq(&rcc_clocks);
|
||||
return rcc_clocks.PCLK1_Frequency;
|
||||
}
|
||||
|
||||
static const int DUTY_CYCLE_DIVIDERS[] = {
|
||||
[I2CDutyCycle_16_9] = 25,
|
||||
[I2CDutyCycle_2] = 3
|
||||
};
|
||||
|
||||
static uint32_t prv_prescalar_to_frequency(I2CDutyCycle duty_cycle, uint32_t prescalar) {
|
||||
const uint32_t pclk1 = prv_get_apb1_frequency();
|
||||
return pclk1 / (prescalar * DUTY_CYCLE_DIVIDERS[duty_cycle]);
|
||||
}
|
||||
|
||||
//! @return A prescalar that will result in a frequency that's close to but not greater than
|
||||
//! desired_maximum_frequency.
|
||||
static uint32_t prv_frequency_to_prescalar(I2CDutyCycle duty_cycle,
|
||||
uint32_t desired_maximum_frequency) {
|
||||
const uint32_t pclk1 = prv_get_apb1_frequency();
|
||||
|
||||
uint32_t prescalar =
|
||||
pclk1 / (desired_maximum_frequency * DUTY_CYCLE_DIVIDERS[duty_cycle]);
|
||||
|
||||
// Check to see what frequency our calculated prescalar is actually going to give us. If the
|
||||
// numbers don't divide evenly, that means we'll have a prescalar that's too low, and will end
|
||||
// up giving us a speed that's faster than we wanted. Add one to the prescalar in this case
|
||||
// to make sure we stay within spec.
|
||||
const uint32_t remainder =
|
||||
pclk1 % (desired_maximum_frequency * DUTY_CYCLE_DIVIDERS[duty_cycle]);
|
||||
if (remainder != 0) {
|
||||
prescalar += 1;
|
||||
}
|
||||
|
||||
return prescalar;
|
||||
}
|
||||
|
||||
void i2c_hal_enable(I2CBus *bus) {
|
||||
periph_config_enable(bus->hal->i2c, bus->hal->clock_ctrl);
|
||||
|
||||
I2C_InitTypeDef I2C_init_struct;
|
||||
I2C_StructInit(&I2C_init_struct);
|
||||
|
||||
if (bus->hal->clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { // Fast mode
|
||||
switch (bus->hal->duty_cycle) {
|
||||
case I2CDutyCycle_16_9:
|
||||
I2C_init_struct.I2C_DutyCycle = I2C_DutyCycle_16_9;
|
||||
break;
|
||||
case I2CDutyCycle_2:
|
||||
I2C_init_struct.I2C_DutyCycle = I2C_DutyCycle_2;
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the prescalar we're going to end up using to get as close as possible to
|
||||
// bus->hal->clock_speed without going over.
|
||||
const uint32_t prescalar = prv_frequency_to_prescalar(bus->hal->duty_cycle,
|
||||
bus->hal->clock_speed);
|
||||
|
||||
// Convert it back to a frequency since the I2C_Init function wants a frequency, not a raw
|
||||
// prescalar value.
|
||||
const uint32_t frequency = prv_prescalar_to_frequency(bus->hal->duty_cycle,
|
||||
prescalar);
|
||||
I2C_init_struct.I2C_ClockSpeed = frequency;
|
||||
|
||||
I2C_init_struct.I2C_Ack = I2C_Ack_Enable;
|
||||
|
||||
I2C_Init(bus->hal->i2c, &I2C_init_struct);
|
||||
I2C_Cmd(bus->hal->i2c, ENABLE);
|
||||
}
|
||||
|
||||
void i2c_hal_disable(I2CBus *bus) {
|
||||
periph_config_disable(bus->hal->i2c, bus->hal->clock_ctrl);
|
||||
I2C_DeInit(bus->hal->i2c);
|
||||
}
|
||||
|
||||
bool i2c_hal_is_busy(I2CBus *bus) {
|
||||
return ((bus->hal->i2c->SR2 & I2C_SR2_BUSY) != 0);
|
||||
}
|
||||
|
||||
void prv_disable_all_interrupts(I2CBus *bus) {
|
||||
bus->hal->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
}
|
||||
|
||||
void i2c_hal_abort_transfer(I2CBus *bus) {
|
||||
// Disable all interrupts on the bus
|
||||
prv_disable_all_interrupts(bus);
|
||||
// Generate a stop condition
|
||||
bus->hal->i2c->CR1 |= I2C_CR1_STOP;
|
||||
}
|
||||
|
||||
void i2c_hal_init_transfer(I2CBus *bus) {
|
||||
// Enable Acks
|
||||
bus->hal->i2c->CR1 |= I2C_CR1_ACK;
|
||||
bus->state->transfer.state = I2CTransferState_WriteAddressTx;
|
||||
}
|
||||
|
||||
void i2c_hal_start_transfer(I2CBus *bus) {
|
||||
// Generate start event
|
||||
bus->hal->i2c->CR1 |= I2C_CR1_START;
|
||||
// Enable event and error interrupts
|
||||
bus->hal->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
|
||||
}
|
||||
|
||||
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
|
||||
|
||||
//! End a transfer and disable further interrupts
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE prv_end_transfer_irq(I2CBus *bus, bool result) {
|
||||
prv_disable_all_interrupts(bus);
|
||||
|
||||
// Generate stop condition
|
||||
bus->hal->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->state->transfer.state = I2CTransferState_Complete;
|
||||
|
||||
I2CTransferEvent event = result ? I2CTransferEvent_TransferComplete : I2CTransferEvent_Error;
|
||||
return i2c_handle_transfer_event(bus, event);
|
||||
}
|
||||
|
||||
//! Pause a transfer, disabling interrupts during the pause
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE prv_pause_transfer_irq(I2CBus *bus) {
|
||||
prv_disable_all_interrupts(bus);
|
||||
return i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
|
||||
}
|
||||
|
||||
//! Handle an IRQ event on the specified \a bus
|
||||
static portBASE_TYPE prv_event_irq_handler(I2CBus *bus) {
|
||||
I2C_TypeDef *i2c = bus->hal->i2c;
|
||||
I2CTransfer *transfer = &bus->state->transfer;
|
||||
|
||||
if (transfer->state == I2CTransferState_Complete) {
|
||||
// Disable interrupts if spurious interrupt received
|
||||
i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Check that the expected event occurred
|
||||
if (I2C_CheckEvent(i2c, s_guard_events[transfer->state]) == ERROR) {
|
||||
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
|
||||
// discernible event associated with it occur after repeat start events are generated
|
||||
return pdFALSE;
|
||||
}
|
||||
portBASE_TYPE should_context_switch = pdFALSE;
|
||||
|
||||
switch (transfer->state) {
|
||||
case I2CTransferState_WriteAddressTx:
|
||||
if (transfer->type == I2CTransferType_SendRegisterAddress) {
|
||||
// Write the I2C bus address to select it in write mode.
|
||||
i2c->DR = transfer->device_address & ~I2C_READ_WRITE_BIT;
|
||||
transfer->state = I2CTransferState_WriteRegAddress;
|
||||
} else {
|
||||
if (transfer->direction == I2CTransferDirection_Read) {
|
||||
// Write the I2C bus address to select it in read mode.
|
||||
i2c->DR = transfer->device_address | I2C_READ_WRITE_BIT;
|
||||
transfer->state = I2CTransferState_WaitForData;
|
||||
} else {
|
||||
// Write the I2C bus address to select it in write mode.
|
||||
i2c->DR = transfer->device_address & ~I2C_READ_WRITE_BIT;
|
||||
transfer->state = I2CTransferState_WriteData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case I2CTransferState_WriteRegAddress:
|
||||
// Write the register address
|
||||
i2c->DR = transfer->register_address;
|
||||
|
||||
if (transfer->direction == I2CTransferDirection_Read) {
|
||||
transfer->state = I2CTransferState_RepeatStart;
|
||||
} else {
|
||||
// Enable TXE interrupt for writing
|
||||
i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
transfer->state = I2CTransferState_WriteData;
|
||||
}
|
||||
break;
|
||||
|
||||
case I2CTransferState_RepeatStart:
|
||||
// Generate a repeat start
|
||||
i2c->CR1 |= I2C_CR1_START;
|
||||
transfer->state = I2CTransferState_WriteAddressRx;
|
||||
break;
|
||||
|
||||
case I2CTransferState_WriteAddressRx:
|
||||
// Write the I2C bus address again, but this time in read mode.
|
||||
i2c->DR = transfer->device_address | I2C_READ_WRITE_BIT;
|
||||
if (transfer->size == 1) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
transfer->state = I2CTransferState_WaitForData;
|
||||
break;
|
||||
|
||||
case I2CTransferState_WaitForData:
|
||||
// This state just ensures that the transition to receive mode event happened
|
||||
|
||||
// Enable RXNE interrupt for writing
|
||||
i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
transfer->state = I2CTransferState_ReadData;
|
||||
break;
|
||||
|
||||
case I2CTransferState_ReadData:
|
||||
transfer->data[transfer->idx] = i2c->DR;
|
||||
transfer->idx++;
|
||||
|
||||
if (transfer->idx + 1 == transfer->size) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
else if (transfer->idx == transfer->size) {
|
||||
// End transfer after all bytes have been received
|
||||
i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
should_context_switch = prv_end_transfer_irq(bus, true);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case I2CTransferState_WriteData:
|
||||
i2c->DR = transfer->data[transfer->idx];
|
||||
transfer->idx++;
|
||||
if (transfer->idx == transfer->size) {
|
||||
i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
transfer->state = I2CTransferState_EndWrite;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case I2CTransferState_EndWrite:
|
||||
// End transfer after all bytes have been sent
|
||||
should_context_switch = prv_end_transfer_irq(bus, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Should never reach here (state machine logic broken)
|
||||
WTF;
|
||||
break;
|
||||
}
|
||||
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
//! Handle error interrupt on the specified \a bus
|
||||
static portBASE_TYPE prv_error_irq_handler(I2CBus *bus) {
|
||||
I2C_TypeDef *i2c = bus->hal->i2c;
|
||||
I2CTransfer *transfer = &bus->state->transfer;
|
||||
|
||||
if (transfer->state == I2CTransferState_Complete) {
|
||||
// Disable interrupts if spurious interrupt received
|
||||
i2c->CR2 &= ~I2C_CR2_ITERREN;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Data overrun and bus errors can only really be handled by terminating the transfer and
|
||||
// trying to recover the bus to an idle state. Each error will be logged. In each case a stop
|
||||
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
|
||||
// a soft reset of the bus will be performed (handled in wait I2C_do_transfer).
|
||||
|
||||
if ((i2c->SR1 & I2C_SR1_OVR) != 0) {
|
||||
// Data overrun
|
||||
i2c->SR1 &= ~I2C_SR1_OVR;
|
||||
|
||||
I2C_DEBUG("Data overrun during I2C transaction; Bus: %s", bus->name);
|
||||
}
|
||||
if ((i2c->SR1 & I2C_SR1_BERR) != 0) {
|
||||
i2c->SR1 &= ~I2C_SR1_BERR;
|
||||
|
||||
// Bus error: invalid start or stop condition detected
|
||||
I2C_DEBUG("Bus error detected during I2C transaction; Bus: %s", bus->name);
|
||||
}
|
||||
if ((i2c->SR1 & I2C_SR1_AF) != 0) {
|
||||
i2c->SR1 &= ~I2C_SR1_AF;
|
||||
|
||||
// NACK received.
|
||||
//
|
||||
// The MFI chip will cause NACK errors during read operations after writing a start bit (first
|
||||
// start or repeat start indicating that it is busy. The transfer must be paused and the start
|
||||
// condition sent again after a delay and the state machine set back a step.
|
||||
//
|
||||
// If the NACK is received after any other action log an error and abort the transfer
|
||||
|
||||
if (transfer->state == I2CTransferState_WaitForData) {
|
||||
transfer->state = I2CTransferState_WriteAddressRx;
|
||||
return prv_pause_transfer_irq(bus);
|
||||
}
|
||||
else if (transfer->state == I2CTransferState_WriteRegAddress) {
|
||||
transfer->state = I2CTransferState_WriteAddressTx;
|
||||
return prv_pause_transfer_irq(bus);
|
||||
}
|
||||
else {
|
||||
I2C_DEBUG("NACK received during I2C transfer; Bus: %s", bus->name);
|
||||
}
|
||||
}
|
||||
|
||||
return prv_end_transfer_irq(bus, false);
|
||||
}
|
||||
|
||||
void i2c_hal_event_irq_handler(I2CBus *bus) {
|
||||
portEND_SWITCHING_ISR(prv_event_irq_handler(bus));
|
||||
}
|
||||
|
||||
void i2c_hal_error_irq_handler(I2CBus *bus) {
|
||||
portEND_SWITCHING_ISR(prv_error_irq_handler(bus));
|
||||
}
|
37
src/fw/drivers/stm32f2/i2c_hal_definitions.h
Normal file
37
src/fw/drivers/stm32f2/i2c_hal_definitions.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum I2CDutyCycle {
|
||||
I2CDutyCycle_16_9,
|
||||
I2CDutyCycle_2
|
||||
} I2CDutyCycle;
|
||||
|
||||
typedef struct I2CBusHal {
|
||||
I2C_TypeDef *const i2c;
|
||||
uint32_t clock_ctrl; ///< Peripheral clock control flag
|
||||
uint32_t clock_speed; ///< Bus clock speed
|
||||
I2CDutyCycle duty_cycle; ///< Bus clock duty cycle in fast mode
|
||||
IRQn_Type ev_irq_channel; ///< I2C Event interrupt (One of X_IRQn). For example, I2C1_EV_IRQn.
|
||||
IRQn_Type er_irq_channel; ///< I2C Error interrupt (One of X_IRQn). For example, I2C1_ER_IRQn.
|
||||
} I2CBusHal;
|
||||
|
||||
void i2c_hal_event_irq_handler(I2CBus *device);
|
||||
void i2c_hal_error_irq_handler(I2CBus *device);
|
38
src/fw/drivers/stm32f2/mcu.c
Normal file
38
src/fw/drivers/stm32f2/mcu.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/mcu.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#if MICRO_FAMILY_STM32F7
|
||||
const uint32_t *STM32_UNIQUE_DEVICE_ID_ADDR = (uint32_t*) 0x1ff0f420;
|
||||
#else
|
||||
const uint32_t *STM32_UNIQUE_DEVICE_ID_ADDR = (uint32_t*) 0x1fff7a10;
|
||||
#endif
|
||||
|
||||
const uint32_t* mcu_get_serial(void) {
|
||||
return STM32_UNIQUE_DEVICE_ID_ADDR;
|
||||
}
|
||||
|
||||
uint32_t mcu_cycles_to_milliseconds(uint64_t cpu_ticks) {
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
return ((cpu_ticks * 1000) / clocks.HCLK_Frequency);
|
||||
}
|
87
src/fw/drivers/stm32f2/otp.c
Normal file
87
src/fw/drivers/stm32f2/otp.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/otp.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F7)
|
||||
// See page 83 of STM Reference Manual RM0410:
|
||||
# define OTP_SLOTS_BASE_ADDR (0x1FF0F000)
|
||||
# define OTP_LOCKS_BASE_ADDR (0x1FF0F400)
|
||||
# define OTP_SLOT_SIZE_BYTES (64)
|
||||
#else
|
||||
// See page 53 of STM Reference Manual RM0033:
|
||||
# define OTP_SLOTS_BASE_ADDR (0x1FFF7800)
|
||||
# define OTP_LOCKS_BASE_ADDR (0x1FFF7A00)
|
||||
# define OTP_SLOT_SIZE_BYTES (32)
|
||||
#endif
|
||||
|
||||
char * otp_get_slot(const uint8_t index) {
|
||||
PBL_ASSERTN(index < NUM_OTP_SLOTS);
|
||||
return (char * const) (OTP_SLOTS_BASE_ADDR + (OTP_SLOT_SIZE_BYTES * index));
|
||||
}
|
||||
|
||||
uint8_t * otp_get_lock(const uint8_t index) {
|
||||
PBL_ASSERTN(index < NUM_OTP_SLOTS);
|
||||
return (uint8_t * const) (OTP_LOCKS_BASE_ADDR + index);
|
||||
}
|
||||
|
||||
bool otp_is_locked(const uint8_t index) {
|
||||
return (*otp_get_lock(index) == 0);
|
||||
}
|
||||
|
||||
OtpWriteResult otp_write_slot(const uint8_t index, const char *value) {
|
||||
if (otp_is_locked(index)) {
|
||||
return OtpWriteFailAlreadyWritten;
|
||||
}
|
||||
char * const field = otp_get_slot(index);
|
||||
uint8_t * const lock = otp_get_lock(index);
|
||||
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
bool failed = false; // Because it's OTP we need to keep trying
|
||||
// and report failure afterwards for the whole operation
|
||||
for(size_t i = 0; i < strlen(value) + 1; i++) {
|
||||
if (FLASH_ProgramByte((uint32_t)&field[i], value[i]) != FLASH_COMPLETE) {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
// Lock the OTP sector
|
||||
if (FLASH_ProgramByte((uint32_t)lock, 0) != FLASH_COMPLETE) {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
FLASH_Lock();
|
||||
|
||||
if(failed) {
|
||||
return OtpWriteFailCorrupt;
|
||||
} else {
|
||||
return OtpWriteSuccess;
|
||||
}
|
||||
}
|
118
src/fw/drivers/stm32f2/periph_config.c
Normal file
118
src/fw/drivers/stm32f2/periph_config.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/periph_config.h"
|
||||
#include "os/mutex.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#define PERIPH_CONFIG_DEBUG 0
|
||||
|
||||
#if PERIPH_CONFIG_DEBUG
|
||||
#define PERIPH_CONFIG_LOG(fmt, args...) \
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## args)
|
||||
#else
|
||||
#define PERIPH_CONFIG_LOG(fmt, args...)
|
||||
#endif
|
||||
|
||||
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
|
||||
|
||||
static PebbleMutex * s_periph_config_mutex;
|
||||
|
||||
#if PERIPH_CONFIG_DEBUG
|
||||
static const char *prv_string_for_cmd(ClockCmd cmd) {
|
||||
if (cmd == RCC_APB1PeriphClockCmd) {
|
||||
return "APB1";
|
||||
} else if (cmd == RCC_APB2PeriphClockCmd) {
|
||||
return "APB2";
|
||||
} else if (cmd == RCC_AHB1PeriphClockCmd) {
|
||||
return "AHB1";
|
||||
} else if (cmd == RCC_AHB2PeriphClockCmd) {
|
||||
return "AHB2";
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// F(S)MC is the only AHB3 peripheral
|
||||
#ifdef FMC_R_BASE
|
||||
#define AHB3_BASE FMC_R_BASE
|
||||
#else
|
||||
#define AHB3_BASE FSMC_R_BASE
|
||||
#endif
|
||||
|
||||
_Static_assert(APB1PERIPH_BASE < APB2PERIPH_BASE, "Clock mapping assumptions don't hold");
|
||||
_Static_assert(APB2PERIPH_BASE < AHB1PERIPH_BASE, "Clock mapping assumptions don't hold");
|
||||
_Static_assert(AHB1PERIPH_BASE < AHB2PERIPH_BASE, "Clock mapping assumptions don't hold");
|
||||
_Static_assert(AHB2PERIPH_BASE < AHB3_BASE, "Clock mapping assumptions don't hold");
|
||||
|
||||
// Note: this works only with peripheral (<...>Typedef_t *) defines, not with RCC defines
|
||||
static ClockCmd prv_get_clock_cmd(uintptr_t periph_addr) {
|
||||
PBL_ASSERTN(periph_addr >= APB1PERIPH_BASE);
|
||||
if (periph_addr < APB2PERIPH_BASE) {
|
||||
return RCC_APB1PeriphClockCmd;
|
||||
} else if (periph_addr < AHB1PERIPH_BASE) {
|
||||
return RCC_APB2PeriphClockCmd;
|
||||
} else if (periph_addr < AHB2PERIPH_BASE) {
|
||||
return RCC_AHB1PeriphClockCmd;
|
||||
} else if (periph_addr < AHB3_BASE) {
|
||||
return RCC_AHB2PeriphClockCmd;
|
||||
} else {
|
||||
return RCC_AHB3PeriphClockCmd;
|
||||
}
|
||||
}
|
||||
|
||||
void periph_config_init(void) {
|
||||
s_periph_config_mutex = mutex_create();
|
||||
}
|
||||
|
||||
void periph_config_acquire_lock(void) {
|
||||
mutex_lock(s_periph_config_mutex);
|
||||
}
|
||||
|
||||
void periph_config_release_lock(void) {
|
||||
mutex_unlock(s_periph_config_mutex);
|
||||
}
|
||||
|
||||
void periph_config_enable(void *periph, uint32_t rcc_bit) {
|
||||
ClockCmd clock_cmd = prv_get_clock_cmd((uintptr_t)periph);
|
||||
#if PERIPH_CONFIG_DEBUG
|
||||
if (prv_string_for_cmd(clock_cmd))
|
||||
PERIPH_CONFIG_LOG("Enabling clock %s", prv_string_for_cmd(clock_cmd));
|
||||
#endif
|
||||
portENTER_CRITICAL();
|
||||
clock_cmd(rcc_bit, ENABLE);
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void periph_config_disable(void *periph, uint32_t rcc_bit) {
|
||||
ClockCmd clock_cmd = prv_get_clock_cmd((uintptr_t)periph);
|
||||
#if PERIPH_CONFIG_DEBUG
|
||||
if (prv_string_for_cmd(clock_cmd))
|
||||
PERIPH_CONFIG_LOG("Disabling clock %s", prv_string_for_cmd(clock_cmd));
|
||||
#endif
|
||||
portENTER_CRITICAL();
|
||||
clock_cmd(rcc_bit, DISABLE);
|
||||
portEXIT_CRITICAL();
|
||||
}
|
75
src/fw/drivers/stm32f2/pwm.c
Normal file
75
src/fw/drivers/stm32f2/pwm.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pwm.h"
|
||||
#include "drivers/timer.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
void pwm_init(const PwmConfig *pwm, uint32_t resolution, uint32_t frequency) {
|
||||
periph_config_enable(pwm->timer.peripheral, pwm->timer.config_clock);
|
||||
|
||||
// Initialize PWM Timer
|
||||
TIM_TimeBaseInitTypeDef tim_config;
|
||||
TIM_TimeBaseStructInit(&tim_config);
|
||||
tim_config.TIM_Period = resolution - 1;
|
||||
tim_config.TIM_Prescaler = timer_find_prescaler(&pwm->timer, frequency);
|
||||
tim_config.TIM_CounterMode = TIM_CounterMode_Up;
|
||||
tim_config.TIM_ClockDivision = 0;
|
||||
TIM_TimeBaseInit(pwm->timer.peripheral, &tim_config);
|
||||
|
||||
// PWM Mode configuration
|
||||
TIM_OCInitTypeDef tim_oc_init;
|
||||
TIM_OCStructInit(&tim_oc_init);
|
||||
tim_oc_init.TIM_OCMode = TIM_OCMode_PWM1;
|
||||
tim_oc_init.TIM_OutputState = TIM_OutputState_Enable;
|
||||
tim_oc_init.TIM_Pulse = 0;
|
||||
tim_oc_init.TIM_OCPolarity = TIM_OCPolarity_High;
|
||||
pwm->timer.init(pwm->timer.peripheral, &tim_oc_init);
|
||||
|
||||
pwm->timer.preload(pwm->timer.peripheral, TIM_OCPreload_Enable);
|
||||
TIM_ARRPreloadConfig(pwm->timer.peripheral, ENABLE);
|
||||
|
||||
periph_config_disable(pwm->timer.peripheral, pwm->timer.config_clock);
|
||||
}
|
||||
|
||||
void pwm_set_duty_cycle(const PwmConfig *pwm, uint32_t duty_cycle) {
|
||||
TIM_OCInitTypeDef tim_oc_init;
|
||||
TIM_OCStructInit(&tim_oc_init);
|
||||
tim_oc_init.TIM_OCMode = TIM_OCMode_PWM1;
|
||||
tim_oc_init.TIM_OutputState = TIM_OutputState_Enable;
|
||||
tim_oc_init.TIM_Pulse = duty_cycle;
|
||||
tim_oc_init.TIM_OCPolarity = TIM_OCPolarity_High;
|
||||
pwm->timer.init(pwm->timer.peripheral, &tim_oc_init);
|
||||
}
|
||||
|
||||
void pwm_enable(const PwmConfig *pwm, bool enable) {
|
||||
if (enable) {
|
||||
gpio_af_init(&pwm->afcfg, GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_DOWN);
|
||||
periph_config_enable(pwm->timer.peripheral, pwm->timer.config_clock);
|
||||
} else {
|
||||
periph_config_disable(pwm->timer.peripheral, pwm->timer.config_clock);
|
||||
gpio_output_init(&pwm->output, GPIO_OType_PP, GPIO_Speed_100MHz);
|
||||
gpio_output_set(&pwm->output, false /* force low */);
|
||||
}
|
||||
|
||||
const FunctionalState state = (enable) ? ENABLE : DISABLE;
|
||||
TIM_Cmd(pwm->timer.peripheral, state);
|
||||
}
|
36
src/fw/drivers/stm32f2/pwr.c
Normal file
36
src/fw/drivers/stm32f2/pwr.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/pwr.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
void pwr_enable_wakeup(bool enable) {
|
||||
PWR_WakeUpPinCmd(enable ? ENABLE : DISABLE);
|
||||
}
|
||||
|
||||
void pwr_flash_power_down_stop_mode(bool power_down) {
|
||||
PWR_FlashPowerDownCmd(power_down ? ENABLE : DISABLE);
|
||||
}
|
||||
|
||||
void pwr_access_backup_domain(bool enable_access) {
|
||||
periph_config_enable(PWR, RCC_APB1Periph_PWR);
|
||||
PWR_BackupAccessCmd(enable_access ? ENABLE : DISABLE);
|
||||
periph_config_disable(PWR, RCC_APB1Periph_PWR);
|
||||
}
|
97
src/fw/drivers/stm32f2/rng.c
Normal file
97
src/fw/drivers/stm32f2/rng.c
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/rng.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
|
||||
bool rng_rand(uint32_t *rand_out) {
|
||||
#ifdef TARGET_QEMU
|
||||
return false;
|
||||
#endif
|
||||
PBL_ASSERTN(rand_out);
|
||||
|
||||
bool success = false;
|
||||
// maximum number of seed errors we allow before giving up:
|
||||
uint8_t attempts_left = 3;
|
||||
uint8_t non_equal_count = 0;
|
||||
uint32_t previous_value = 0;
|
||||
|
||||
periph_config_acquire_lock();
|
||||
periph_config_enable(RNG, RCC_AHB2Periph_RNG);
|
||||
RNG->CR |= RNG_CR_RNGEN;
|
||||
|
||||
while (true) {
|
||||
// Poll the status register's ready bit:
|
||||
while (attempts_left) {
|
||||
const uint32_t status = RNG->SR;
|
||||
// Check clock flags, they would indicate programmer error.
|
||||
PBL_ASSERTN((status & (RNG_SR_CECS | RNG_SR_CEIS)) == 0);
|
||||
|
||||
// First check the seed error bits:
|
||||
// We're checking both the interrupt flag and status flag, it's not very clear from the docs
|
||||
// what the right thing to do is.
|
||||
if (status & (RNG_SR_SECS | RNG_SR_SEIS)) {
|
||||
// When there is a seed error, ST recommends clearing SEI,
|
||||
// then disabling / re-enabling the peripheral:
|
||||
RNG->SR &= ~RNG_SR_SEIS;
|
||||
RNG->CR &= ~RNG_CR_RNGEN;
|
||||
RNG->CR |= RNG_CR_RNGEN;
|
||||
|
||||
non_equal_count = 0;
|
||||
previous_value = 0;
|
||||
--attempts_left;
|
||||
continue;
|
||||
}
|
||||
if (status & RNG_SR_DRDY) {
|
||||
break; // The next random number is ready
|
||||
}
|
||||
}
|
||||
|
||||
if (!attempts_left) {
|
||||
break;
|
||||
}
|
||||
|
||||
// As per Cory's and the ST reference manual's suggestion: "As required by the FIPS PUB
|
||||
// (Federal Information Processing Standard Publication) 140-2, the first random number
|
||||
// generated after setting the RNGEN bit should not be used, but saved for comparison with the
|
||||
// next generated random number. Each subsequent generated random number has to be compared with
|
||||
// the previously generated number. The test fails if any two compared numbers are equal
|
||||
// (continuous random number generator test)."
|
||||
*rand_out = RNG->DR;
|
||||
if (*rand_out != previous_value) {
|
||||
++non_equal_count;
|
||||
if (non_equal_count >= 2) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
previous_value = *rand_out;
|
||||
}
|
||||
|
||||
RNG->CR &= ~RNG_CR_RNGEN;
|
||||
periph_config_disable(RNG, RCC_AHB2Periph_RNG);
|
||||
periph_config_release_lock();
|
||||
return success;
|
||||
}
|
343
src/fw/drivers/stm32f2/rtc.c
Normal file
343
src/fw/drivers/stm32f2/rtc.c
Normal file
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/rtc.h"
|
||||
#include "drivers/rtc_private.h"
|
||||
#include "drivers/stm32f2/rtc_calibration.h"
|
||||
|
||||
#include "console/dbgserial.h"
|
||||
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/watchdog.h"
|
||||
|
||||
#include "kernel/util/stop.h"
|
||||
#include "mcu/interrupts.h"
|
||||
|
||||
#include "services/common/regular_timer.h"
|
||||
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/reset.h"
|
||||
|
||||
#include "util/time/time.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef uint32_t RtcIntervalTicks;
|
||||
|
||||
static const unsigned int LSE_FREQUENCY_HZ = 32768;
|
||||
#define SECONDS_IN_A_DAY (60 * 60 * 24)
|
||||
#define TICKS_IN_AN_INTERVAL SECONDS_IN_A_DAY
|
||||
|
||||
//! This variable is a UNIX timestamp of what the current wall clock time was at tick s_time_tick_base.
|
||||
static time_t s_time_base = 0;
|
||||
//! This variable is the tick where the wall clock time was equal to s_time_base. If you subtract this variable
|
||||
//! from the current tick count, you'll get the number of ticks that have elapsed since s_time_base, which will
|
||||
//! allow you to calculate the current wall clock time. Note that this value may be negative on startup, see
|
||||
//! restore_rtc_time_state
|
||||
static int64_t s_time_tick_base = 0;
|
||||
|
||||
//! The value of the RTC registers last time we checked them.
|
||||
static RtcIntervalTicks s_last_ticks = 0;
|
||||
//! This value is added to the current value of the RTC ticks to get the number
|
||||
//! of ticks since system start. Incremented whenever we detect a rollover.
|
||||
static RtcTicks s_coarse_ticks = 1;
|
||||
|
||||
//! The time that we last set the alarm at. See rtc_alarm_set and rtc_alarm_get_elapsed_ticks.
|
||||
static RtcTicks s_alarm_set_time = 0;
|
||||
|
||||
static bool s_tick_alarm_initialized = false;
|
||||
|
||||
static void save_rtc_time_state(RtcIntervalTicks current_rtc_ticks);
|
||||
|
||||
void rtc_calibrate_frequency(uint32_t frequency) {
|
||||
RTCCalibConfig config = rtc_calibration_get_config(frequency, LSE_FREQUENCY_HZ * 1000);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Calibrating RTC by %s%"PRIu32" units",
|
||||
(config.sign == RTC_CalibSign_Positive) ? "+" : "-", config.units);
|
||||
|
||||
// This is a no-op if RTC_CALIBRATION_TESTING is undefined.
|
||||
rtc_calibration_init_timer();
|
||||
|
||||
RTC_CoarseCalibConfig(config.sign, config.units);
|
||||
RTC_CoarseCalibCmd(ENABLE);
|
||||
}
|
||||
|
||||
//! Our RTC tick counter actually overflows once every 86 seconds. If we don't call rtc_get_ticks() every 86 seconds,
|
||||
//! the counter may roll over multiple times, causing our clock to appear to have gaps. This repeating callback allows
|
||||
//! us to make sure this doesn't happen.
|
||||
static void rtc_resync_timer_callback() {
|
||||
rtc_get_ticks();
|
||||
}
|
||||
|
||||
static uint8_t BcdToByte(uint8_t Value) {
|
||||
const uint8_t tmp = ((uint8_t)(Value & (uint8_t)0xF0) >> (uint8_t)0x4) * 10;
|
||||
return (tmp + (Value & (uint8_t)0x0F));
|
||||
}
|
||||
|
||||
static RtcIntervalTicks get_rtc_interval_ticks(void) {
|
||||
uint32_t time_register = RTC->TR;
|
||||
|
||||
const uint8_t hours = BcdToByte((time_register & (RTC_TR_HT | RTC_TR_HU)) >> 16);
|
||||
const uint8_t minutes = BcdToByte((time_register & (RTC_TR_MNT | RTC_TR_MNU)) >> 8);
|
||||
const uint8_t seconds = BcdToByte(time_register & (RTC_TR_ST | RTC_TR_SU));
|
||||
|
||||
return (((hours * 60) + minutes) * 60) + seconds;
|
||||
}
|
||||
|
||||
static RtcIntervalTicks elapsed_ticks(RtcIntervalTicks before, RtcIntervalTicks after) {
|
||||
int32_t result = after - before;
|
||||
if (result < 0) {
|
||||
result = (TICKS_IN_AN_INTERVAL - before) + after;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void restore_rtc_time_state(void) {
|
||||
// Recover the previously set time from the RTC backup registers.
|
||||
RtcIntervalTicks last_save_time_ticks = RTC_ReadBackupRegister(CURRENT_INTERVAL_TICKS_REGISTER);
|
||||
time_t last_save_time = RTC_ReadBackupRegister(CURRENT_TIME_REGISTER);
|
||||
|
||||
RtcIntervalTicks current_ticks = get_rtc_interval_ticks();
|
||||
const int32_t ticks_since_last_save = elapsed_ticks(last_save_time_ticks, current_ticks);
|
||||
s_time_base = last_save_time + (ticks_since_last_save / RTC_TICKS_HZ);
|
||||
|
||||
s_time_tick_base = -(((int64_t)current_ticks) % RTC_TICKS_HZ);
|
||||
|
||||
#ifdef VERBOSE_LOGGING
|
||||
char buffer[TIME_STRING_BUFFER_SIZE];
|
||||
PBL_LOG_VERBOSE("Restore RTC: saved: %"PRIu32" diff: %"PRIu32, last_save_time_ticks, ticks_since_last_save);
|
||||
PBL_LOG_VERBOSE("Restore RTC: saved_time: %s raw: %lu", time_t_to_string(buffer, last_save_time), last_save_time);
|
||||
PBL_LOG_VERBOSE("Restore RTC: current time: %s", time_t_to_string(buffer, s_time_base));
|
||||
PBL_LOG_VERBOSE("Restore RTC: s_time_tick_base: %"PRId64, s_time_tick_base);
|
||||
#endif
|
||||
}
|
||||
|
||||
static time_t ticks_to_time(RtcTicks ticks) {
|
||||
return s_time_base + ((ticks - s_time_tick_base) / RTC_TICKS_HZ);
|
||||
}
|
||||
|
||||
static RtcIntervalTicks get_last_save_time_ticks(void) {
|
||||
return RTC_ReadBackupRegister(CURRENT_INTERVAL_TICKS_REGISTER);
|
||||
}
|
||||
|
||||
static void save_rtc_time_state_exact(RtcIntervalTicks current_rtc_ticks, time_t time) {
|
||||
RTC_WriteBackupRegister(CURRENT_TIME_REGISTER, time);
|
||||
RTC_WriteBackupRegister(CURRENT_INTERVAL_TICKS_REGISTER, current_rtc_ticks);
|
||||
|
||||
// Dbgserial instead of PBL_LOG to avoid infinite recursion due to PBL_LOG wanting to know
|
||||
// the current ticks.
|
||||
//char buffer[128];
|
||||
//dbgserial_putstr_fmt(buffer, 128, "Saving RTC state: ticks: %"PRIu32" time: %s raw: %lu", current_rtc_ticks, time_t_to_string(time), time);
|
||||
//itoa(time, buffer, sizeof(buffer));
|
||||
//dbgserial_putstr(buffer);
|
||||
//dbgserial_putstr("Done");
|
||||
}
|
||||
|
||||
static void save_rtc_time_state(RtcIntervalTicks current_rtc_ticks) {
|
||||
// Floor it to the latest second
|
||||
const RtcIntervalTicks current_rtc_ticks_at_second = (current_rtc_ticks / RTC_TICKS_HZ) * RTC_TICKS_HZ;
|
||||
|
||||
save_rtc_time_state_exact(current_rtc_ticks_at_second, ticks_to_time(s_coarse_ticks + current_rtc_ticks));
|
||||
}
|
||||
|
||||
static void initialize_fast_mode_state(void) {
|
||||
RtcIntervalTicks before_ticks = get_rtc_interval_ticks();
|
||||
|
||||
// Set the RTC to value 0 so we start from scratch nicely
|
||||
RTC_TimeTypeDef rtc_time;
|
||||
RTC_TimeStructInit(&rtc_time);
|
||||
RTC_SetTime(RTC_Format_BIN, &rtc_time);
|
||||
|
||||
// Reset the last ticks counter so we don't rollover prematurely.
|
||||
// This value will be set to non-zero if anyone asked for the tick count
|
||||
// before this point.
|
||||
s_last_ticks = 0;
|
||||
|
||||
// Refresh the saved time so it's more current.
|
||||
save_rtc_time_state_exact(TICKS_IN_AN_INTERVAL - (RTC_TICKS_HZ - (before_ticks % RTC_TICKS_HZ)), ticks_to_time(s_coarse_ticks));
|
||||
//save_rtc_time_state(0);
|
||||
}
|
||||
|
||||
void rtc_init(void) {
|
||||
periph_config_acquire_lock();
|
||||
rtc_enable_backup_regs();
|
||||
periph_config_release_lock();
|
||||
|
||||
restore_rtc_time_state();
|
||||
initialize_fast_mode_state();
|
||||
|
||||
#ifdef PBL_LOG_ENABLED
|
||||
char buffer[TIME_STRING_BUFFER_SIZE];
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Current time is <%s>", rtc_get_time_string(buffer));
|
||||
#endif
|
||||
}
|
||||
|
||||
void rtc_init_timers(void) {
|
||||
static RegularTimerInfo rtc_sync_timer = { .list_node = { 0, 0 }, .cb = rtc_resync_timer_callback};
|
||||
regular_timer_add_minutes_callback(&rtc_sync_timer);
|
||||
}
|
||||
|
||||
//! How frequently we save the time state to the backup registers in ticks.
|
||||
#define SAVE_TIME_FREQUENCY (30 * RTC_TICKS_HZ)
|
||||
|
||||
static void check_and_handle_rollover(RtcIntervalTicks rtc_ticks) {
|
||||
bool save_needed = false;
|
||||
|
||||
const RtcIntervalTicks last_ticks = s_last_ticks;
|
||||
s_last_ticks = rtc_ticks;
|
||||
|
||||
if (rtc_ticks < last_ticks) {
|
||||
// We've wrapped. Add on the number of seconds in a day to the base number.
|
||||
s_coarse_ticks += TICKS_IN_AN_INTERVAL;
|
||||
|
||||
save_needed = true;
|
||||
} else if (elapsed_ticks(get_last_save_time_ticks(), rtc_ticks) > SAVE_TIME_FREQUENCY) {
|
||||
// If we didn't do this, we would have an edge case where if the watch reset
|
||||
// immediately before rollover and then rolled over before we booted again,
|
||||
// we wouldn't be able to detect the rollover and we'd think the saved state
|
||||
// is very fresh, when really it's over an interval old. By saving multiple
|
||||
// times an interval this is still possible to happen, but it's much less likely.
|
||||
// We would need to be shutdown for (SECONDS_IN_A_DAY - SAVE_TIME_FREQUENCY) ticks
|
||||
// for this to happen.
|
||||
save_needed = true;
|
||||
}
|
||||
|
||||
|
||||
if (save_needed) {
|
||||
save_rtc_time_state(rtc_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
static RtcTicks get_ticks(void) {
|
||||
|
||||
// Prevent this from being interrupted
|
||||
bool ints_enabled = mcu_state_are_interrupts_enabled();
|
||||
if (ints_enabled) {
|
||||
__disable_irq();
|
||||
}
|
||||
|
||||
RtcTicks rtc_interval_ticks = get_rtc_interval_ticks();
|
||||
check_and_handle_rollover(rtc_interval_ticks);
|
||||
|
||||
if (ints_enabled) {
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
return s_coarse_ticks + rtc_interval_ticks;
|
||||
}
|
||||
|
||||
void rtc_set_time(time_t time) {
|
||||
#ifdef PBL_LOG_ENABLED
|
||||
char buffer[TIME_STRING_BUFFER_SIZE];
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Setting time to %lu <%s>", time, time_t_to_string(buffer, time));
|
||||
#endif
|
||||
|
||||
s_time_base = time;
|
||||
s_time_tick_base = get_ticks();
|
||||
|
||||
save_rtc_time_state(s_time_tick_base - s_coarse_ticks);
|
||||
}
|
||||
|
||||
time_t rtc_get_time(void) {
|
||||
return ticks_to_time(get_ticks());
|
||||
}
|
||||
|
||||
void rtc_get_time_ms(time_t* out_seconds, uint16_t* out_ms) {
|
||||
RtcTicks ticks = get_ticks();
|
||||
|
||||
RtcTicks ticks_since_time_base = (ticks - s_time_tick_base);
|
||||
*out_seconds = s_time_base + (ticks_since_time_base / RTC_TICKS_HZ);
|
||||
|
||||
RtcTicks ticks_this_second = ticks_since_time_base % RTC_TICKS_HZ;
|
||||
*out_ms = (ticks_this_second * 1000) / RTC_TICKS_HZ;
|
||||
}
|
||||
|
||||
RtcTicks rtc_get_ticks(void) {
|
||||
return get_ticks();
|
||||
}
|
||||
|
||||
|
||||
void rtc_alarm_init(void) {
|
||||
RTC_ITConfig(RTC_IT_ALRA, DISABLE);
|
||||
RTC_AlarmCmd(RTC_Alarm_A, DISABLE);
|
||||
|
||||
RTC_ClearITPendingBit(RTC_IT_ALRA);
|
||||
|
||||
exti_configure_other(ExtiLineOther_RTCAlarm, ExtiTrigger_Rising);
|
||||
exti_enable_other(ExtiLineOther_RTCAlarm);
|
||||
|
||||
s_tick_alarm_initialized = true;
|
||||
}
|
||||
|
||||
void rtc_alarm_set(RtcTicks num_ticks) {
|
||||
PBL_ASSERTN(s_tick_alarm_initialized);
|
||||
|
||||
RTC_ITConfig(RTC_IT_ALRA, DISABLE);
|
||||
RTC_AlarmCmd(RTC_Alarm_A, DISABLE);
|
||||
|
||||
RTC_AlarmTypeDef alarm_config;
|
||||
RTC_AlarmStructInit(&alarm_config);
|
||||
alarm_config.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay;
|
||||
|
||||
s_alarm_set_time = rtc_get_ticks();
|
||||
|
||||
RtcTicks alarm_expiry_time = s_alarm_set_time + num_ticks;
|
||||
|
||||
uint32_t days, hours, minutes, seconds;
|
||||
time_util_split_seconds_into_parts(alarm_expiry_time, &days, &hours, &minutes, &seconds);
|
||||
|
||||
(void) days; // Don't care about days.
|
||||
alarm_config.RTC_AlarmTime.RTC_Hours = hours;
|
||||
alarm_config.RTC_AlarmTime.RTC_Minutes = minutes;
|
||||
alarm_config.RTC_AlarmTime.RTC_Seconds = seconds;
|
||||
|
||||
RTC_SetAlarm(RTC_Format_BIN, RTC_Alarm_A, &alarm_config);
|
||||
|
||||
RTC_ITConfig(RTC_IT_ALRA, ENABLE);
|
||||
RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
|
||||
|
||||
RTC_ClearFlag(RTC_FLAG_ALRAF);
|
||||
EXTI_ClearITPendingBit(EXTI_Line17);
|
||||
RTC_ClearITPendingBit(RTC_IT_ALRA);
|
||||
}
|
||||
|
||||
RtcTicks rtc_alarm_get_elapsed_ticks(void) {
|
||||
return rtc_get_ticks() - s_alarm_set_time;
|
||||
}
|
||||
|
||||
bool rtc_alarm_is_initialized(void) {
|
||||
return s_tick_alarm_initialized;
|
||||
}
|
||||
|
||||
|
||||
//! Handler for the RTC alarm interrupt. We don't actually have to do anything in this handler,
|
||||
//! just the interrupt firing is enough to bring us out of stop mode.
|
||||
void RTC_Alarm_IRQHandler(void) {
|
||||
if (RTC_GetITStatus(RTC_IT_ALRA) != RESET) {
|
||||
RTC_AlarmCmd(RTC_Alarm_A, DISABLE);
|
||||
|
||||
RTC_ClearITPendingBit(RTC_IT_ALRA);
|
||||
EXTI_ClearITPendingBit(EXTI_Line17);
|
||||
}
|
||||
}
|
147
src/fw/drivers/stm32f2/rtc_calibration.c
Normal file
147
src/fw/drivers/stm32f2/rtc_calibration.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "rtc_calibration.h"
|
||||
|
||||
#include "system/logging.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
|
||||
RTCCalibConfig rtc_calibration_get_config(uint32_t frequency, uint32_t target) {
|
||||
if (frequency == 0) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "RTC frequency invalid - Skipping calibration");
|
||||
return (RTCCalibConfig) {
|
||||
.sign = RTC_CalibSign_Positive,
|
||||
.units = 0
|
||||
};
|
||||
}
|
||||
|
||||
// Difference in frequency in mHz (ex. 224 = .224 Hz off from target frequency)
|
||||
const int32_t rtc_freq_diff = target - frequency;
|
||||
|
||||
// RTC_CoarseCalibConfig uses units of +4.069ppm or -2.035ppm.
|
||||
// Formula:
|
||||
// ppm = 1e6(target - frequency)/(target)
|
||||
// positive units = ppm / 4.069
|
||||
// negative units = ppm / -2.035
|
||||
uint32_t rtc_calib_sign, rtc_calib_units;
|
||||
const uint64_t numerator = 1000000000 * (uint64_t)ABS(rtc_freq_diff);
|
||||
uint64_t divisor;
|
||||
|
||||
if (rtc_freq_diff >= 0) {
|
||||
divisor = 4069;
|
||||
rtc_calib_sign = RTC_CalibSign_Positive;
|
||||
} else {
|
||||
divisor = 2035;
|
||||
rtc_calib_sign = RTC_CalibSign_Negative;
|
||||
}
|
||||
|
||||
rtc_calib_units = ROUND(numerator, divisor * target);
|
||||
|
||||
return (RTCCalibConfig) {
|
||||
.sign = rtc_calib_sign,
|
||||
// Coarse calibration has a range of -63ppm to 126ppm.
|
||||
.units = MIN(rtc_calib_units, 31)
|
||||
};
|
||||
}
|
||||
|
||||
// For RTC calibration testing
|
||||
#ifdef RTC_CALIBRATION_TESTING
|
||||
|
||||
#include "drivers/rtc.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
void rtc_calibration_init_timer(void) {
|
||||
const uint32_t timer_clock_hz = 32000;
|
||||
|
||||
// The timer is on ABP1 which is clocked by PCLK1
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
uint32_t timer_clock = clocks.PCLK1_Frequency; // Hz
|
||||
|
||||
uint32_t prescale = RCC->CFGR & RCC_CFGR_PPRE1;
|
||||
if (prescale != RCC_CFGR_PPRE1_DIV1) {
|
||||
// per the stm32 'clock tree' diagram, if the prescaler for APBx is not 1, then
|
||||
// the timer clock is at double the APBx frequency
|
||||
timer_clock *= 2;
|
||||
}
|
||||
|
||||
// Clock frequency to run the timer at
|
||||
uint32_t prescaler = timer_clock / timer_clock_hz;
|
||||
uint32_t period = timer_clock_hz;
|
||||
|
||||
// period & prescaler values are 16 bits, check for configuration errors
|
||||
PBL_ASSERTN(period <= UINT16_MAX && prescaler <= UINT16_MAX);
|
||||
|
||||
periph_config_enable(TIM7, RCC_APB1Periph_TIM7);
|
||||
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
/* Enable the TIM7 gloabal Interrupt */
|
||||
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
|
||||
NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0b;
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
// Set up a timer that runs at 1Hz and activates once every second
|
||||
TIM_TimeBaseInitTypeDef tim_config;
|
||||
TIM_TimeBaseStructInit(&tim_config);
|
||||
tim_config.TIM_Period = period;
|
||||
// The timer is on ABP1 which is clocked by PCLK1
|
||||
tim_config.TIM_Prescaler = prescaler;
|
||||
// tim_config.TIM_ClockDivision = TIM_CKD_DIV4;
|
||||
tim_config.TIM_CounterMode = TIM_CounterMode_Up;
|
||||
TIM_TimeBaseInit(TIM7, &tim_config);
|
||||
|
||||
TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);
|
||||
TIM_Cmd(TIM7, ENABLE);
|
||||
}
|
||||
|
||||
static void prv_delta_ticks(void) {
|
||||
static uint64_t last_tick = 0;
|
||||
|
||||
uint64_t rtc_ticks = rtc_get_ticks();
|
||||
PBL_LOG(LOG_LEVEL_INFO, "RTC tick delta: %d", rtc_ticks - last_tick);
|
||||
|
||||
last_tick = rtc_ticks;
|
||||
}
|
||||
|
||||
void TIM7_IRQHandler(void) {
|
||||
static uint8_t count = 0;
|
||||
|
||||
// Workaround M3 bug that causes interrupt to fire twice:
|
||||
// https://my.st.com/public/Faq/Lists/faqlst/DispForm.aspx?ID=143
|
||||
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
|
||||
|
||||
if (count == 0) {
|
||||
prv_delta_ticks();
|
||||
}
|
||||
|
||||
// Log delta ticks every ~60 seconds
|
||||
count++;
|
||||
count %= 60;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void rtc_calibration_init_timer(void) {}
|
||||
|
||||
#endif
|
30
src/fw/drivers/stm32f2/rtc_calibration.h
Normal file
30
src/fw/drivers/stm32f2/rtc_calibration.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct RTCCalibConfig {
|
||||
uint32_t sign;
|
||||
uint32_t units;
|
||||
} RTCCalibConfig;
|
||||
|
||||
//! Calculate the appropriate coarse calibration config given the measured and target frequencies
|
||||
//! (in mHz)
|
||||
RTCCalibConfig rtc_calibration_get_config(uint32_t frequency, uint32_t target);
|
||||
|
||||
void rtc_calibration_init_timer(void);
|
696
src/fw/drivers/stm32f2/spi.c
Normal file
696
src/fw/drivers/stm32f2/spi.c
Normal file
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "spi_definitions.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "drivers/spi_dma.h"
|
||||
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
#include "util/units.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
//! Deduced by looking at the prescalers in stm32f2xx_spi.h
|
||||
#define SPI_FREQ_LOG_TO_PRESCALER(LG) (((LG) - 1) * 0x8)
|
||||
//! Bits in CR1 we intend to keep when updating it
|
||||
#define CR1_CLEAR_MASK ((uint16_t)0x3040)
|
||||
|
||||
//! SPI / I2S DMA definitions
|
||||
typedef enum SpiI2sDma {
|
||||
SpiI2sDma_ReqTx = 0x0002,
|
||||
SpiI2sDma_ReqRx = 0x0001
|
||||
} SpiI2sDma;
|
||||
|
||||
//! SPI Master/Slave
|
||||
typedef enum SpiMode {
|
||||
SpiMode_Master = 0x0104,
|
||||
SpiMode_Slave = 0x0000
|
||||
} SpiMode;
|
||||
|
||||
//! SPI Data Size
|
||||
typedef enum SpiDataSize {
|
||||
SpiDataSize_16b = 0x0800,
|
||||
SpiDataSize_8b = 0x0000
|
||||
} SpiDataSize;
|
||||
|
||||
//! SPI Slave Select
|
||||
typedef enum SpiSlaveSelect {
|
||||
SpiSlaveSelect_Soft = 0x0200,
|
||||
SpiSlaveSelect_Hard = 0x0000
|
||||
} SpiSlaveSelect;
|
||||
|
||||
typedef enum {
|
||||
SpiDisable = 0,
|
||||
SpiEnable
|
||||
} SpiFunctionalState;
|
||||
|
||||
//
|
||||
// Private SPI bus functions. No higher level code should
|
||||
// get access to SPIBus functions or data directly
|
||||
//
|
||||
|
||||
static bool prv_spi_get_flag_status(const SPIBus *bus, SpiI2sFlag flag) {
|
||||
/* Check the status of the specified SPI flag */
|
||||
return (bus->spi->SR & (uint16_t)flag) != 0;
|
||||
}
|
||||
|
||||
static bool prv_spi_transmit_is_idle(const SPIBus *bus) {
|
||||
return prv_spi_get_flag_status(bus, SpiI2sFlag_TXE);
|
||||
}
|
||||
|
||||
static bool prv_spi_receive_is_ready(const SPIBus *bus) {
|
||||
return prv_spi_get_flag_status(bus, SpiI2sFlag_RXNE);
|
||||
}
|
||||
|
||||
void prv_spi_send_data(const SPIBus *bus, uint16_t Data) {
|
||||
#if MICRO_FAMILY_STM32F7
|
||||
// STM32F7 needs to access as 8 bits in order to actually do 8 bits.
|
||||
// This _does_ work on F4, but QEMU doesn't agree, so let's just do it safely.
|
||||
*(volatile uint8_t*)&bus->spi->DR = Data;
|
||||
#else
|
||||
bus->spi->DR = Data;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t prv_spi_receive_data(const SPIBus *bus) {
|
||||
#if MICRO_FAMILY_STM32F7
|
||||
// STM32F7 needs to access as 8 bits in order to actually do 8 bits.
|
||||
// This _does_ work on F4, but QEMU doesn't agree, so let's just do it safely.
|
||||
return *(volatile uint8_t*)&bus->spi->DR;
|
||||
#else
|
||||
return bus->spi->DR;
|
||||
#endif
|
||||
}
|
||||
|
||||
void prv_spi_enable_peripheral_clock(const SPIBus *bus) {
|
||||
periph_config_enable(bus->spi, bus->state->spi_clock_periph);
|
||||
}
|
||||
|
||||
void prv_spi_disable_peripheral_clock(const SPIBus *bus) {
|
||||
periph_config_disable(bus->spi, bus->state->spi_clock_periph);
|
||||
}
|
||||
|
||||
static void prv_spi_clear_flags(const SPIBus *bus) {
|
||||
prv_spi_receive_data(bus);
|
||||
prv_spi_get_flag_status(bus, (SpiI2sFlag)0);
|
||||
}
|
||||
|
||||
static void prv_spi_dma_cmd(const SPIBus *bus, SpiI2sDma dma_bits, bool enable) {
|
||||
if (enable) {
|
||||
bus->spi->CR2 |= (uint16_t)dma_bits;
|
||||
} else {
|
||||
bus->spi->CR2 &= (uint16_t)~dma_bits;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_spi_cmd(const SPIBus *bus, SpiFunctionalState state) {
|
||||
if (state != SpiDisable) {
|
||||
/* Enable the selected SPI peripheral */
|
||||
bus->spi->CR1 |= SPI_CR1_SPE;
|
||||
} else {
|
||||
/* Disable the selected SPI peripheral */
|
||||
bus->spi->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
|
||||
}
|
||||
}
|
||||
|
||||
void prv_spi_pick_peripheral(const SPIBus *bus) {
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
if (bus->spi == SPI1) {
|
||||
bus->state->spi_clock_periph = RCC_APB2Periph_SPI1;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_2;
|
||||
} else if (bus->spi == SPI2) {
|
||||
bus->state->spi_clock_periph = RCC_APB1Periph_SPI2;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK1_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_1;
|
||||
} else if (bus->spi == SPI3) {
|
||||
bus->state->spi_clock_periph = RCC_APB1Periph_SPI3;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK1_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_1;
|
||||
#ifdef SPI4
|
||||
} else if (bus->spi == SPI4) {
|
||||
bus->state->spi_clock_periph = RCC_APB2Periph_SPI4;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_2;
|
||||
#endif
|
||||
#ifdef SPI5
|
||||
} else if (bus->spi == SPI5) {
|
||||
bus->state->spi_clock_periph = RCC_APB2Periph_SPI5;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_2;
|
||||
#endif
|
||||
#ifdef SPI6
|
||||
} else if (bus->spi == SPI6) {
|
||||
bus->state->spi_clock_periph = RCC_APB2Periph_SPI6;
|
||||
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
|
||||
bus->state->spi_apb = SpiAPB_2;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t prv_spi_find_prescaler(const SPIBus *bus) {
|
||||
int lg;
|
||||
if (bus->state->spi_clock_speed_hz > (bus->state->spi_clock_periph_speed / 2)) {
|
||||
lg = 1; // Underclock to the highest possible frequency
|
||||
} else {
|
||||
uint32_t divisor = bus->state->spi_clock_periph_speed / bus->state->spi_clock_speed_hz;
|
||||
lg = ceil_log_two(divisor);
|
||||
}
|
||||
|
||||
// Prescalers only exists for values in [2 - 256] range
|
||||
PBL_ASSERTN(lg > 0);
|
||||
PBL_ASSERTN(lg < 9);
|
||||
|
||||
// return prescaler
|
||||
return (SPI_FREQ_LOG_TO_PRESCALER(lg));
|
||||
}
|
||||
|
||||
void prv_spi_transmit_flush_blocking(const SPIBus *bus) {
|
||||
while (!prv_spi_transmit_is_idle(bus)) continue;
|
||||
}
|
||||
|
||||
void prv_spi_receive_wait_ready_blocking(const SPIBus *bus) {
|
||||
while (!prv_spi_receive_is_ready(bus)) continue;
|
||||
}
|
||||
|
||||
static void prv_configure_spi_sclk(const AfConfig *clk_pin, uint16_t spi_sclk_speed) {
|
||||
gpio_af_init(clk_pin, GPIO_OType_PP, spi_sclk_speed, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
|
||||
static void prv_spi_bus_deinit(const SPIBus *bus, bool is_bidirectional) {
|
||||
// The pins are no longer in use so reconfigure as analog inputs to save some power
|
||||
|
||||
// SCLK
|
||||
InputConfig sclk = { .gpio = bus->spi_sclk.gpio, .gpio_pin = bus->spi_sclk.gpio_pin };
|
||||
gpio_analog_init(&sclk);
|
||||
|
||||
// MOSI
|
||||
InputConfig mosi = { .gpio = bus->spi_mosi.gpio, .gpio_pin = bus->spi_mosi.gpio_pin };
|
||||
gpio_analog_init(&mosi);
|
||||
|
||||
// MISO
|
||||
if (is_bidirectional) {
|
||||
InputConfig miso = { .gpio = bus->spi_miso.gpio, .gpio_pin = bus->spi_miso.gpio_pin };
|
||||
gpio_analog_init(&miso);
|
||||
}
|
||||
|
||||
bus->state->initialized = false;
|
||||
}
|
||||
|
||||
void prv_spi_bus_init(const SPIBus *bus, bool is_bidirectional) {
|
||||
if (bus->state->initialized) {
|
||||
return;
|
||||
}
|
||||
// copy the speed over to the transient state since the slave port can change it
|
||||
bus->state->spi_clock_speed_hz = bus->spi_clock_speed_hz;
|
||||
prv_spi_pick_peripheral(bus);
|
||||
bus->state->initialized = true;
|
||||
// SCLK
|
||||
prv_configure_spi_sclk(&bus->spi_sclk, bus->spi_sclk_speed);
|
||||
// MOSI
|
||||
gpio_af_init(&bus->spi_mosi, GPIO_OType_PP, bus->spi_sclk_speed, GPIO_PuPd_NOPULL);
|
||||
// MISO
|
||||
if (is_bidirectional) {
|
||||
gpio_af_init(&bus->spi_miso, GPIO_OType_PP, bus->spi_sclk_speed, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_spi_slave_init(const SPISlavePort *slave) {
|
||||
prv_spi_enable_peripheral_clock(slave->spi_bus);
|
||||
SPIBus *bus = slave->spi_bus;
|
||||
// Grab existing configuration
|
||||
uint16_t tmpreg = bus->spi->CR1;
|
||||
// Clear BIDIMode, BIDIOE, RxONLY, SSM, SSI, LSBFirst, BR, MSTR, CPOL and CPHA bits
|
||||
tmpreg &= CR1_CLEAR_MASK;
|
||||
// get the baudrate prescaler
|
||||
uint32_t prescaler = prv_spi_find_prescaler(bus);
|
||||
// Master mode, 8 bit Data Size and Soft Slave select are hardcoded
|
||||
// Direction, CPOL, CPHA, baudrate prescaler and first-bit come from the device config
|
||||
tmpreg |= (uint16_t)((uint32_t)slave->spi_direction | SpiMode_Master |
|
||||
SpiDataSize_8b | slave->spi_cpol |
|
||||
slave->spi_cpha | SpiSlaveSelect_Soft |
|
||||
prescaler | slave->spi_first_bit);
|
||||
// Write result back to CR1
|
||||
bus->spi->CR1 = tmpreg;
|
||||
|
||||
#if MICRO_FAMILY_STM32F7
|
||||
// On STM32F7 we need to set FRXTH in order to do 8-bit transfers.
|
||||
// If we don't, the MCU always tries to read 16-bits even though we
|
||||
// specified that the data is 8-bits.
|
||||
// Why clear isn't 8-bit is beyond me, but ok.
|
||||
bus->spi->CR2 |= SPI_CR2_FRXTH;
|
||||
#endif
|
||||
|
||||
// Activate the SPI mode (Reset I2SMOD bit in I2SCFGR register)
|
||||
bus->spi->I2SCFGR &= (uint16_t)~((uint16_t)SPI_I2SCFGR_I2SMOD);
|
||||
|
||||
prv_spi_disable_peripheral_clock(slave->spi_bus);
|
||||
}
|
||||
|
||||
static void prv_spi_slave_deinit(const SPISlavePort *slave) {
|
||||
spi_ll_slave_acquire(slave);
|
||||
SPIBus *bus = slave->spi_bus;
|
||||
if (bus->state->spi_apb == SpiAPB_1) {
|
||||
// Enable SPIx reset state
|
||||
RCC_APB1PeriphResetCmd(bus->state->spi_clock_periph, ENABLE);
|
||||
// Release SPIx from reset state
|
||||
RCC_APB1PeriphResetCmd(bus->state->spi_clock_periph, DISABLE);
|
||||
} else if (bus->state->spi_apb == SpiAPB_2) {
|
||||
// Enable SPIx reset state
|
||||
RCC_APB2PeriphResetCmd(bus->state->spi_clock_periph, ENABLE);
|
||||
// Release SPIx from reset state
|
||||
RCC_APB2PeriphResetCmd(bus->state->spi_clock_periph, DISABLE);
|
||||
}
|
||||
spi_ll_slave_release(slave);
|
||||
}
|
||||
|
||||
//
|
||||
//! High level slave port interface
|
||||
//! This part of the API can be used for fairly straightforward SPI interactions
|
||||
//! The assertion and deassertion of the SCS line is automatic
|
||||
//
|
||||
|
||||
static bool prv_is_bidrectional(const SPISlavePort *slave) {
|
||||
bool is_bidirectional = (slave->spi_direction == SpiDirection_2LinesFullDuplex) ||
|
||||
(slave->spi_direction == SpiDirection_2LinesRxOnly);
|
||||
|
||||
return (is_bidirectional);
|
||||
}
|
||||
|
||||
void spi_slave_port_deinit(const SPISlavePort *slave) {
|
||||
// don't deinitialize twice
|
||||
if (!slave->slave_state->initialized) {
|
||||
return;
|
||||
}
|
||||
prv_spi_slave_deinit(slave);
|
||||
prv_spi_bus_deinit(slave->spi_bus, prv_is_bidrectional(slave));
|
||||
slave->slave_state->initialized = false;
|
||||
}
|
||||
|
||||
void spi_slave_port_init(const SPISlavePort *slave) {
|
||||
// don't initialize twice
|
||||
if (slave->slave_state->initialized) {
|
||||
return;
|
||||
}
|
||||
slave->slave_state->initialized = true;
|
||||
slave->slave_state->acquired = false;
|
||||
slave->slave_state->scs_selected = false;
|
||||
prv_spi_bus_init(slave->spi_bus, prv_is_bidrectional(slave));
|
||||
|
||||
// SCS
|
||||
gpio_output_init(&slave->spi_scs, GPIO_OType_PP, slave->spi_bus->spi_sclk_speed);
|
||||
gpio_output_set(&slave->spi_scs, false); // SCS not asserted (high)
|
||||
|
||||
// Set up an SPI
|
||||
prv_spi_slave_deinit(slave);
|
||||
|
||||
prv_spi_slave_init(slave);
|
||||
|
||||
// Set up DMA
|
||||
if (slave->rx_dma) {
|
||||
dma_request_init(slave->rx_dma);
|
||||
}
|
||||
if (slave->tx_dma) {
|
||||
dma_request_init(slave->tx_dma);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_spi_acquire_helper(const SPISlavePort *slave) {
|
||||
spi_ll_slave_acquire(slave);
|
||||
spi_ll_slave_scs_assert(slave);
|
||||
}
|
||||
|
||||
static void prv_spi_release_helper(const SPISlavePort *slave) {
|
||||
spi_ll_slave_scs_deassert(slave);
|
||||
spi_ll_slave_release(slave);
|
||||
}
|
||||
|
||||
uint8_t spi_slave_read_write(const SPISlavePort *slave, uint8_t out) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
uint8_t ret = spi_ll_slave_read_write(slave, out);
|
||||
prv_spi_release_helper(slave);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void spi_slave_write(const SPISlavePort *slave, uint8_t out) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
spi_ll_slave_write(slave, out);
|
||||
prv_spi_release_helper(slave);
|
||||
}
|
||||
|
||||
void spi_slave_burst_read(const SPISlavePort *slave, void *in, size_t len) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
spi_ll_slave_burst_read(slave, in, len);
|
||||
prv_spi_release_helper(slave);
|
||||
}
|
||||
|
||||
void spi_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
spi_ll_slave_burst_write(slave, out, len);
|
||||
prv_spi_release_helper(slave);
|
||||
}
|
||||
|
||||
void spi_slave_burst_read_write(const SPISlavePort *slave, const void *out, void *in, size_t len) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
spi_ll_slave_burst_read_write(slave, out, in, len);
|
||||
prv_spi_release_helper(slave);
|
||||
}
|
||||
|
||||
void spi_slave_burst_read_write_scatter(const SPISlavePort *slave,
|
||||
const SPIScatterGather *sc_info,
|
||||
size_t num_sg) {
|
||||
prv_spi_acquire_helper(slave);
|
||||
spi_ll_slave_burst_read_write_scatter(slave, sc_info, num_sg);
|
||||
prv_spi_release_helper(slave);
|
||||
}
|
||||
|
||||
void spi_slave_set_frequency(const SPISlavePort *slave, uint32_t frequency_hz) {
|
||||
slave->spi_bus->state->spi_clock_speed_hz = frequency_hz;
|
||||
prv_spi_slave_init(slave);
|
||||
}
|
||||
|
||||
void spi_slave_wait_until_idle_blocking(const SPISlavePort *slave) {
|
||||
while (prv_spi_get_flag_status(slave->spi_bus, SpiI2sFlag_BSY)) continue;
|
||||
}
|
||||
|
||||
uint32_t spi_get_dma_base_address(const SPISlavePort *slave) {
|
||||
return (uint32_t)&(slave->spi_bus->spi->DR);
|
||||
}
|
||||
//
|
||||
//! Low level slave port interface
|
||||
//! This part of the API can be used for slightly more complex SPI operations
|
||||
//! (such as piecemeal reads or writes). Assertion and deassertion of SCS
|
||||
//! is up to the caller. Asserts in the code will help to ensure that the
|
||||
//! API is used correctly.
|
||||
//
|
||||
|
||||
void spi_ll_slave_acquire(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired == false);
|
||||
prv_spi_enable_peripheral_clock(slave->spi_bus);
|
||||
prv_spi_clear_flags(slave->spi_bus);
|
||||
slave->slave_state->acquired = true;
|
||||
spi_ll_slave_spi_enable(slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_release(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
spi_slave_wait_until_idle_blocking(slave);
|
||||
prv_spi_clear_flags(slave->spi_bus);
|
||||
spi_ll_slave_spi_disable(slave);
|
||||
slave->slave_state->acquired = false;
|
||||
prv_spi_disable_peripheral_clock(slave->spi_bus);
|
||||
}
|
||||
|
||||
void spi_ll_slave_spi_enable(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
prv_spi_cmd(slave->spi_bus, SpiEnable);
|
||||
}
|
||||
|
||||
void spi_ll_slave_spi_disable(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
prv_spi_cmd(slave->spi_bus, SpiDisable);
|
||||
}
|
||||
|
||||
void spi_ll_slave_scs_assert(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->scs_selected == false);
|
||||
slave->slave_state->scs_selected = true;
|
||||
gpio_output_set(&slave->spi_scs, true); // SCS asserted (low)
|
||||
}
|
||||
|
||||
void spi_ll_slave_scs_deassert(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->scs_selected);
|
||||
slave->slave_state->scs_selected = false;
|
||||
gpio_output_set(&slave->spi_scs, false); // SCS not asserted (high)
|
||||
}
|
||||
|
||||
uint8_t spi_ll_slave_read_write(const SPISlavePort *slave, uint8_t out) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->scs_selected);
|
||||
prv_spi_transmit_flush_blocking(slave->spi_bus);
|
||||
prv_spi_send_data(slave->spi_bus, out);
|
||||
prv_spi_receive_wait_ready_blocking(slave->spi_bus);
|
||||
return prv_spi_receive_data(slave->spi_bus);
|
||||
}
|
||||
|
||||
void spi_ll_slave_write(const SPISlavePort *slave, uint8_t out) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->scs_selected);
|
||||
prv_spi_transmit_flush_blocking(slave->spi_bus);
|
||||
prv_spi_send_data(slave->spi_bus, out);
|
||||
}
|
||||
|
||||
void spi_ll_slave_burst_read(const SPISlavePort *slave, void *in, size_t len) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->scs_selected);
|
||||
uint8_t *cptr = in;
|
||||
while (len--) {
|
||||
*(cptr++) = spi_ll_slave_read_write(slave, 0); // useless write-data
|
||||
}
|
||||
}
|
||||
|
||||
void spi_ll_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
const uint8_t *cptr = out;
|
||||
while (len--) {
|
||||
prv_spi_send_data(slave->spi_bus, *(cptr++));
|
||||
prv_spi_transmit_flush_blocking(slave->spi_bus);
|
||||
}
|
||||
}
|
||||
|
||||
void spi_ll_slave_burst_read_write(const SPISlavePort *slave,
|
||||
const void *out,
|
||||
void *in,
|
||||
size_t len) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
const uint8_t *outp = out;
|
||||
uint8_t *inp = in;
|
||||
for (size_t n = 0; n < len; ++n) {
|
||||
uint8_t byte_out = outp ? *(outp++) : 0;
|
||||
uint8_t byte_in = spi_ll_slave_read_write(slave, byte_out);
|
||||
if (inp) {
|
||||
*(inp++) = byte_in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spi_ll_slave_burst_read_write_scatter(const SPISlavePort *slave,
|
||||
const SPIScatterGather *sc_info,
|
||||
size_t num_sg) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
for (size_t elem = 0; elem < num_sg; ++elem) {
|
||||
const SPIScatterGather *sg = &sc_info[elem];
|
||||
spi_ll_slave_burst_read_write(slave, sg->sg_out, sg->sg_in, sg->sg_len);
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_dma_irq_handler(DMARequest *request, void *context) {
|
||||
const SPISlavePort *slave = context;
|
||||
PBL_ASSERTN(slave);
|
||||
bool is_done = false;
|
||||
switch (slave->slave_state->dma_state) {
|
||||
case SPISlavePortDMAState_Read:
|
||||
case SPISlavePortDMAState_Write:
|
||||
case SPISlavePortDMAState_ReadWriteOneInterrupt:
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_Idle;
|
||||
is_done = true;
|
||||
break;
|
||||
case SPISlavePortDMAState_ReadWrite:
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWriteOneInterrupt;
|
||||
break;
|
||||
case SPISlavePortDMAState_Idle:
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
SPIDMACompleteHandler handler = slave->slave_state->dma_complete_handler;
|
||||
if (is_done && handler) {
|
||||
return handler(slave, slave->slave_state->dma_complete_context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void spi_ll_slave_read_dma_start(const SPISlavePort *slave, void *in, size_t len,
|
||||
SPIDMACompleteHandler handler, void *context) {
|
||||
PBL_ASSERTN(slave->rx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_Read;
|
||||
slave->slave_state->dma_complete_handler = handler;
|
||||
slave->slave_state->dma_complete_context = context;
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, true);
|
||||
dma_request_start_direct(slave->rx_dma, in, (void *)&slave->spi_bus->spi->DR, len,
|
||||
prv_dma_irq_handler, (void *)slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_read_dma_stop(const SPISlavePort *slave) {
|
||||
if (slave->slave_state->dma_state != SPISlavePortDMAState_Read) {
|
||||
return;
|
||||
}
|
||||
dma_request_stop(slave->rx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, false);
|
||||
slave->slave_state->dma_complete_handler = NULL;
|
||||
slave->slave_state->dma_complete_context = NULL;
|
||||
}
|
||||
|
||||
void spi_ll_slave_write_dma_start(const SPISlavePort *slave, const void *out, size_t len,
|
||||
SPIDMACompleteHandler handler, void *context) {
|
||||
PBL_ASSERTN(slave->tx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_Write;
|
||||
slave->slave_state->dma_complete_handler = handler;
|
||||
slave->slave_state->dma_complete_context = context;
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, true);
|
||||
dma_request_start_direct(slave->tx_dma, (void *)&slave->spi_bus->spi->DR, out, len,
|
||||
prv_dma_irq_handler, (void *)slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_write_dma_stop(const SPISlavePort *slave) {
|
||||
if (slave->slave_state->dma_state != SPISlavePortDMAState_Write) {
|
||||
return;
|
||||
}
|
||||
dma_request_stop(slave->tx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, false);
|
||||
slave->slave_state->dma_complete_handler = NULL;
|
||||
slave->slave_state->dma_complete_context = NULL;
|
||||
}
|
||||
|
||||
void spi_ll_slave_read_write_dma_start(const SPISlavePort *slave, const void *out, void *in,
|
||||
size_t len, SPIDMACompleteHandler handler, void *context) {
|
||||
PBL_ASSERTN(slave->rx_dma && slave->tx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
|
||||
slave->slave_state->dma_complete_handler = handler;
|
||||
slave->slave_state->dma_complete_context = context;
|
||||
|
||||
if (out) {
|
||||
dma_request_set_memory_increment_disabled(slave->tx_dma, false);
|
||||
} else {
|
||||
dma_request_set_memory_increment_disabled(slave->tx_dma, true);
|
||||
static const uint8_t s_zero = 0;
|
||||
out = &s_zero;
|
||||
}
|
||||
|
||||
if (in) {
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWrite;
|
||||
// start the read DMA
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, true);
|
||||
dma_request_start_direct(slave->rx_dma, in, (void *)&slave->spi_bus->spi->DR, len,
|
||||
prv_dma_irq_handler, (void *)slave);
|
||||
} else {
|
||||
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWriteOneInterrupt;
|
||||
}
|
||||
// start the write DMA
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, true);
|
||||
dma_request_start_direct(slave->tx_dma, (void *)&slave->spi_bus->spi->DR, out, len,
|
||||
prv_dma_irq_handler, (void *)slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_read_write_dma_stop(const SPISlavePort *slave) {
|
||||
if ((slave->slave_state->dma_state != SPISlavePortDMAState_ReadWrite) &&
|
||||
(slave->slave_state->dma_state != SPISlavePortDMAState_ReadWriteOneInterrupt)) {
|
||||
return;
|
||||
}
|
||||
PBL_ASSERTN(slave->tx_dma && slave->rx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
dma_request_stop(slave->rx_dma);
|
||||
dma_request_stop(slave->tx_dma);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, false);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, false);
|
||||
slave->slave_state->dma_complete_handler = NULL;
|
||||
slave->slave_state->dma_complete_context = NULL;
|
||||
}
|
||||
|
||||
bool spi_ll_slave_dma_in_progress(const SPISlavePort *slave) {
|
||||
PBL_ASSERTN(slave->tx_dma || slave->rx_dma);
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
PBL_ASSERTN(slave->slave_state->acquired);
|
||||
return (slave->rx_dma && dma_request_in_progress(slave->rx_dma)) ||
|
||||
(slave->tx_dma && dma_request_in_progress(slave->tx_dma));
|
||||
}
|
||||
|
||||
void spi_ll_slave_set_tx_dma(const SPISlavePort *slave, bool enable) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
spi_ll_slave_acquire(slave);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, enable);
|
||||
spi_ll_slave_release(slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_set_rx_dma(const SPISlavePort *slave, bool enable) {
|
||||
PBL_ASSERTN(slave->slave_state->initialized);
|
||||
spi_ll_slave_acquire(slave);
|
||||
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, enable);
|
||||
spi_ll_slave_release(slave);
|
||||
}
|
||||
|
||||
void spi_ll_slave_drive_clock(const SPISlavePort *slave, bool enable) {
|
||||
const AfConfig *spi_sclk = &slave->spi_bus->spi_sclk;
|
||||
|
||||
if (enable) {
|
||||
OutputConfig clk_as_gpio = {
|
||||
.gpio = spi_sclk->gpio,
|
||||
.gpio_pin = spi_sclk->gpio_pin,
|
||||
.active_high = true,
|
||||
};
|
||||
gpio_output_init(&clk_as_gpio, GPIO_OType_PP, GPIO_Speed_50MHz);
|
||||
gpio_output_set(&clk_as_gpio, false);
|
||||
} else {
|
||||
prv_configure_spi_sclk(spi_sclk, slave->spi_bus->spi_sclk_speed);
|
||||
}
|
||||
}
|
||||
|
||||
void spi_ll_slave_clear_errors(const SPISlavePort *slave) {
|
||||
// First, empty the RX FIFO by reading the data. If in TX-only mode, it's possible that
|
||||
// received data (0x00s) will be left in the RX FIFO.
|
||||
|
||||
// NOTE: Obviously, do not call this function with transfer in progress.
|
||||
while (slave->spi_bus->spi->SR & SPI_SR_RXNE) {
|
||||
(void)slave->spi_bus->spi->DR;
|
||||
}
|
||||
|
||||
// If the FIFO overflowed, the OVR error will be flagged. Clear the error.
|
||||
(void)slave->spi_bus->spi->DR;
|
||||
(void)slave->spi_bus->spi->SR;
|
||||
}
|
132
src/fw/drivers/stm32f2/spi_definitions.h
Normal file
132
src/fw/drivers/stm32f2/spi_definitions.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/spi.h"
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
//! Generic(ish) definitions of how we wish a particular SPI to be configured
|
||||
//! (Initially based on ST configuration and registers)
|
||||
//! board.h and board_xxxx.h will use these definitions to configure each SPI
|
||||
//! spi.c will use these definitions to program the device
|
||||
|
||||
// REVISIT: We may like to split the definition and control of the SCS
|
||||
// signal out of the main spi driver and into a separate driver so
|
||||
// that if we ever share an SPI and use multiple SCS bits to select
|
||||
// the destination we can control them individually. As it stands now
|
||||
// we have exactly one.
|
||||
|
||||
//! SPI transmission modes (unidirectional/bidirectional etc)
|
||||
typedef enum SpiDirection {
|
||||
SpiDirection_2LinesFullDuplex = 0x0000,
|
||||
SpiDirection_2LinesRxOnly = 0x0400,
|
||||
SpiDirection_1LineRx = 0x8000,
|
||||
SpiDirection_1LineTx = 0xC000
|
||||
} SpiDirection;
|
||||
|
||||
//! SPI Clock Polarity
|
||||
typedef enum SpiCPol {
|
||||
SpiCPol_Low = 0x0,
|
||||
SpiCPol_High = 0x2
|
||||
} SpiCPol;
|
||||
|
||||
//! SPI Clock Phase
|
||||
typedef enum SpiCPha {
|
||||
SpiCPha_1Edge = 0x0,
|
||||
SpiCPha_2Edge = 0x1
|
||||
} SpiCPha;
|
||||
|
||||
//! SPI MSB / LSB First Bit Transmission
|
||||
typedef enum SpiFirstBit {
|
||||
SpiFirstBit_MSB = 0x0000,
|
||||
SpiFirstBit_LSB = 0x0080
|
||||
} SpiFirstBit;
|
||||
|
||||
//! SPI / I2S Flags
|
||||
typedef enum SpiI2sFlag {
|
||||
SpiI2sFlag_RXNE = 0x0001,
|
||||
SpiI2sFlag_TXE = 0x0002,
|
||||
I2sFlag_CHSIDE = 0x0004,
|
||||
I2sFlag_UDR = 0x0008,
|
||||
SpiFlag_CRCERR = 0x0010,
|
||||
SpiFlag_MODF = 0x0020,
|
||||
SpiI2sFlag_OVR = 0x0040,
|
||||
SpiI2sFlag_BSY = 0x0080,
|
||||
SpiI2sFlag_TIFRFE = 0x0100
|
||||
} SpiI2sFlag;
|
||||
|
||||
typedef enum SpiAPB {
|
||||
SpiAPB_1,
|
||||
SpiAPB_2
|
||||
} SpiAPB;
|
||||
|
||||
typedef struct SPIBusState {
|
||||
uint32_t spi_clock_speed_hz; // can be changed by slave port
|
||||
uint32_t spi_clock_periph; // mapped to SPI peripheral
|
||||
uint32_t spi_clock_periph_speed;
|
||||
SpiAPB spi_apb;
|
||||
bool initialized;
|
||||
} SPIBusState;
|
||||
|
||||
//! An SPI Bus specifies an SPI Instance and the I/O pins
|
||||
//! used for the CLK, MOSI and MISO pins
|
||||
//! The communication specific parameters (direction and
|
||||
//! phase etc) and the pin to use for slave select are set per
|
||||
//! SPISlavePort
|
||||
//! REVISIT: There is currently no arbitration between possible
|
||||
//! slave ports on the same bus - since for now all of our
|
||||
//! SPI devices are point-to-point
|
||||
typedef const struct SPIBus {
|
||||
SPIBusState *state;
|
||||
SPI_TypeDef *const spi;
|
||||
AfConfig spi_sclk;
|
||||
AfConfig spi_miso;
|
||||
AfConfig spi_mosi;
|
||||
uint16_t spi_sclk_speed;
|
||||
// uint32_t spi_clock_ctrl;
|
||||
uint32_t spi_clock_speed_hz;
|
||||
} SPIBus;
|
||||
|
||||
typedef enum SPISlavePortDMAState {
|
||||
SPISlavePortDMAState_Idle,
|
||||
SPISlavePortDMAState_Read,
|
||||
SPISlavePortDMAState_Write,
|
||||
SPISlavePortDMAState_ReadWrite,
|
||||
SPISlavePortDMAState_ReadWriteOneInterrupt,
|
||||
} SPISlavePortDMAState;
|
||||
|
||||
typedef struct SPISlavePortState {
|
||||
bool initialized;
|
||||
bool acquired;
|
||||
bool scs_selected;
|
||||
SPIDMACompleteHandler dma_complete_handler;
|
||||
void *dma_complete_context;
|
||||
SPISlavePortDMAState dma_state;
|
||||
} SPISlavePortState;
|
||||
|
||||
typedef const struct SPISlavePort {
|
||||
SPISlavePortState *slave_state;
|
||||
SPIBus *spi_bus;
|
||||
OutputConfig spi_scs;
|
||||
SpiDirection spi_direction;
|
||||
SpiCPol spi_cpol;
|
||||
SpiCPha spi_cpha;
|
||||
SpiFirstBit spi_first_bit;
|
||||
DMARequest *rx_dma;
|
||||
DMARequest *tx_dma;
|
||||
} SPISlavePort;
|
64
src/fw/drivers/stm32f2/spi_legacy.c
Normal file
64
src/fw/drivers/stm32f2/spi_legacy.c
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// REVISIT:
|
||||
// This is a legacy implementation of the prescaler calculation
|
||||
// code used by the roll-your-own SPI implementations.
|
||||
// Once the new driver is used for all SPI interaction, this
|
||||
// function can go away.
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
#include "board/board.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
// Deduced by looking at the prescalers in stm32f2xx_spi.h
|
||||
#define SPI_FREQ_LOG_TO_PRESCALER(LG) (((LG) - 1) * 0x8)
|
||||
|
||||
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock) {
|
||||
// Get the clocks
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
|
||||
uint32_t clock = 0;
|
||||
// Find which peripheral clock we belong to
|
||||
if (periph_clock == SpiPeriphClockAPB1) {
|
||||
clock = clocks.PCLK1_Frequency;
|
||||
} else if (periph_clock == SpiPeriphClockAPB2) {
|
||||
clock = clocks.PCLK2_Frequency;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
int lg;
|
||||
if (bus_frequency > (clock / 2)) {
|
||||
lg = 1; // Underclock to the highest possible frequency
|
||||
} else {
|
||||
uint32_t divisor = clock / bus_frequency;
|
||||
lg = ceil_log_two(divisor);
|
||||
}
|
||||
|
||||
// Prescalers only exists for values in [2 - 256] range
|
||||
PBL_ASSERTN(lg > 0);
|
||||
PBL_ASSERTN(lg < 9);
|
||||
|
||||
// return prescaler
|
||||
return (SPI_FREQ_LOG_TO_PRESCALER(lg));
|
||||
}
|
53
src/fw/drivers/stm32f2/system_flash.c
Normal file
53
src/fw/drivers/stm32f2/system_flash.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/system_flash.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "system/logging.h"
|
||||
|
||||
void system_flash_erase(uint16_t sector) {
|
||||
PBL_LOG_VERBOSE("system_flash_erase");
|
||||
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
|
||||
if (FLASH_EraseSector(sector, VoltageRange_1) != FLASH_COMPLETE) {
|
||||
PBL_LOG(LOG_LEVEL_ALWAYS, "failed to erase sector %u", sector);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void system_flash_write_byte(uint32_t address, uint8_t data) {
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
|
||||
if (FLASH_ProgramByte(address, data) != FLASH_COMPLETE) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "failed to write address %p", (void*) address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t system_flash_read(uint32_t address) {
|
||||
uint32_t data = *(volatile uint32_t*) address;
|
||||
return data;
|
||||
}
|
68
src/fw/drivers/stm32f2/timer.c
Normal file
68
src/fw/drivers/stm32f2/timer.c
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/timer.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
static uint32_t prv_adjust_frequency(TIM_TypeDef *stm32_timer) {
|
||||
#ifdef MICRO_FAMILY_STM32F4
|
||||
PBL_ASSERTN((RCC->DCKCFGR & RCC_DCKCFGR_TIMPRE) != RCC_DCKCFGR_TIMPRE);
|
||||
#endif
|
||||
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
|
||||
uint32_t ppre_mask;
|
||||
uint32_t clock_freq;
|
||||
uint32_t ppre_div1_mask;
|
||||
if ((uintptr_t)stm32_timer < AHB2PERIPH_BASE) {
|
||||
clock_freq = clocks.PCLK1_Frequency;
|
||||
ppre_mask = RCC_CFGR_PPRE1;
|
||||
ppre_div1_mask = RCC_CFGR_PPRE1_DIV1;
|
||||
} else { // AHB2
|
||||
clock_freq = clocks.PCLK2_Frequency;
|
||||
ppre_mask = RCC_CFGR_PPRE1;
|
||||
ppre_div1_mask = RCC_CFGR_PPRE1_DIV1;
|
||||
}
|
||||
|
||||
// From STM32F2xx Reference manual, section 5.2 (Clocks):
|
||||
// The timer clock frequencies are automatically set by hardware.
|
||||
// There are two cases:
|
||||
// 1. If the APB prescaler is 1, the timer clock frequencies are set to the
|
||||
// same frequency as that of the APB domain to which the timers are
|
||||
// connected.
|
||||
// 2. Otherwise, they are set to twice (×2) the frequency of the APB domain
|
||||
// to which the timers are connected.
|
||||
if ((RCC->CFGR & ppre_mask) == ppre_div1_mask) {
|
||||
return clock_freq;
|
||||
} else {
|
||||
return clock_freq * 2;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t timer_find_prescaler(const TimerConfig *timer, uint32_t frequency) {
|
||||
uint32_t timer_clock = prv_adjust_frequency(timer->peripheral);
|
||||
PBL_ASSERT(timer_clock >= frequency, "Timer clock frequency too low (LR %p)",
|
||||
__builtin_return_address(0));
|
||||
return (timer_clock / frequency) - 1;
|
||||
}
|
||||
|
345
src/fw/drivers/stm32f2/uart.c
Normal file
345
src/fw/drivers/stm32f2/uart.c
Normal file
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "uart_definitions.h"
|
||||
#include "drivers/uart.h"
|
||||
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
#include <mcu/interrupts.h>
|
||||
|
||||
// The STM32F2 standard peripheral library uses a precision of 100 which is plenty, so we'll do the
|
||||
// same.
|
||||
#define DIV_PRECISION (100)
|
||||
|
||||
|
||||
// Initialization / Configuration APIs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef enum UARTCR1Flags {
|
||||
UARTCR1Flags_Duplex = USART_CR1_TE | USART_CR1_RE,
|
||||
UARTCR1Flags_TE = USART_CR1_TE,
|
||||
UARTCR1Flags_RE = USART_CR1_RE,
|
||||
} UARTCR1Flags;
|
||||
|
||||
static void prv_init(UARTDevice *dev, bool is_open_drain, UARTCR1Flags cr1_extra_flags) {
|
||||
// Enable peripheral clock
|
||||
periph_config_enable(dev->periph, dev->rcc_apb_periph);
|
||||
|
||||
// configure GPIO
|
||||
const GPIOOType_TypeDef otype = is_open_drain ? GPIO_OType_OD : GPIO_OType_PP;
|
||||
if (dev->tx_gpio.gpio) {
|
||||
gpio_af_init(&dev->tx_gpio, otype, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
if (dev->rx_gpio.gpio) {
|
||||
// half-duplex should only define a TX pin
|
||||
PBL_ASSERTN(!dev->half_duplex);
|
||||
gpio_af_init(&dev->rx_gpio, otype, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
|
||||
// configure the UART peripheral control registers
|
||||
// - 8-bit word length
|
||||
// - no parity
|
||||
// - RX / TX enabled
|
||||
// - 1 stop bit
|
||||
// - no flow control
|
||||
dev->periph->CR1 = cr1_extra_flags;
|
||||
dev->periph->CR2 = 0;
|
||||
dev->periph->CR3 = (dev->half_duplex ? USART_CR3_HDSEL : 0);
|
||||
|
||||
// QEMU doesn't want you to read the DR while the UART is not enabled, but it
|
||||
// should be fine to clear errors this way
|
||||
#if !TARGET_QEMU
|
||||
// Clear any stale errors that may be in the registers. This can be accomplished
|
||||
// by reading the status register followed by the data register
|
||||
(void)dev->periph->SR;
|
||||
(void)dev->periph->DR;
|
||||
#endif
|
||||
|
||||
dev->periph->CR1 |= USART_CR1_UE;
|
||||
|
||||
dev->state->initialized = true;
|
||||
|
||||
// initialize the DMA request
|
||||
if (dev->rx_dma) {
|
||||
dma_request_init(dev->rx_dma);
|
||||
}
|
||||
}
|
||||
|
||||
void uart_init(UARTDevice *dev) {
|
||||
prv_init(dev, false /* !is_open_drain */, UARTCR1Flags_Duplex);
|
||||
}
|
||||
|
||||
void uart_init_open_drain(UARTDevice *dev) {
|
||||
prv_init(dev, true /* is_open_drain */, UARTCR1Flags_Duplex);
|
||||
}
|
||||
|
||||
void uart_init_tx_only(UARTDevice *dev) {
|
||||
prv_init(dev, false /* !is_open_drain */, UARTCR1Flags_TE);
|
||||
}
|
||||
|
||||
void uart_init_rx_only(UARTDevice *dev) {
|
||||
prv_init(dev, false /* !is_open_drain */, UARTCR1Flags_RE);
|
||||
}
|
||||
|
||||
void uart_deinit(UARTDevice *dev) {
|
||||
dev->periph->CR1 &= ~USART_CR1_UE;
|
||||
periph_config_disable(dev->periph, dev->rcc_apb_periph);
|
||||
// Change the pins to be digital inputs rather than AF pins. We can't change to analog inputs
|
||||
// because those aren't 5V tolerant which these pins may need to be.
|
||||
if (dev->tx_gpio.gpio) {
|
||||
const InputConfig input_config = {
|
||||
.gpio = dev->tx_gpio.gpio,
|
||||
.gpio_pin = dev->tx_gpio.gpio_pin
|
||||
};
|
||||
gpio_input_init(&input_config);
|
||||
}
|
||||
if (dev->rx_gpio.gpio) {
|
||||
const InputConfig input_config = {
|
||||
.gpio = dev->rx_gpio.gpio,
|
||||
.gpio_pin = dev->rx_gpio.gpio_pin
|
||||
};
|
||||
gpio_input_init(&input_config);
|
||||
}
|
||||
}
|
||||
|
||||
void uart_set_baud_rate(UARTDevice *dev, uint32_t baud_rate) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
|
||||
RCC_ClocksTypeDef RCC_ClocksStatus;
|
||||
RCC_GetClocksFreq(&RCC_ClocksStatus);
|
||||
uint64_t scaled_apbclock = DIV_PRECISION;
|
||||
if ((dev->periph == USART1) || (dev->periph == USART6)) {
|
||||
scaled_apbclock *= RCC_ClocksStatus.PCLK2_Frequency;
|
||||
} else {
|
||||
scaled_apbclock *= RCC_ClocksStatus.PCLK1_Frequency;
|
||||
}
|
||||
|
||||
// We need to calculate the divider to get from the clock frequency down to the sampling
|
||||
// frequency (samples * baud_rate) and store it in USART_BBR as a fixed-point number with a
|
||||
// franctional component equal to the number of samples per symbol. In other words, if OVER8=0,
|
||||
// the fractional component will be 4 bits, and if OVER8=1, it will be 3 bits.
|
||||
// The formula works out to: DIV = f_clk / (samples * BAUD)
|
||||
|
||||
const bool over8 = dev->periph->CR1 & USART_CR1_OVER8;
|
||||
const uint32_t samples = over8 ? 8 : 16;
|
||||
|
||||
// calculate the divider multiplied by DIV_PRECISION
|
||||
const uint32_t div_temp = scaled_apbclock / (samples * baud_rate);
|
||||
|
||||
// calculate the mantissa component of BRR
|
||||
const uint32_t mantissa = div_temp / DIV_PRECISION;
|
||||
// isolate the fraction component by subtracting the mantissa component
|
||||
uint32_t fraction = div_temp - mantissa * DIV_PRECISION;
|
||||
// convert the fractional component to be in terms of the number of samples (with rounding)
|
||||
fraction = (fraction * samples + (DIV_PRECISION / 2)) / DIV_PRECISION;
|
||||
|
||||
if (over8) {
|
||||
// 3 bits of fraction
|
||||
dev->periph->BRR = (mantissa << 3) | (fraction & 0x7);
|
||||
} else {
|
||||
// 4 bits of fraction
|
||||
dev->periph->BRR = (mantissa << 4) | (fraction & 0xF);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read / Write APIs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void uart_write_byte(UARTDevice *dev, uint8_t data) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
|
||||
// wait for us to be ready to send
|
||||
while (!uart_is_tx_ready(dev)) continue;
|
||||
|
||||
dev->periph->DR = data;
|
||||
}
|
||||
|
||||
uint8_t uart_read_byte(UARTDevice *dev) {
|
||||
// read the data regardless since it will clear interrupt flags
|
||||
return dev->periph->DR;
|
||||
}
|
||||
|
||||
UARTRXErrorFlags uart_has_errored_out(UARTDevice *dev) {
|
||||
uint16_t errors = dev->periph->SR;
|
||||
UARTRXErrorFlags flags = {
|
||||
.parity_error = (errors & USART_FLAG_PE) != 0,
|
||||
.overrun_error = (errors & USART_FLAG_ORE) != 0,
|
||||
.framing_error = (errors & USART_FLAG_FE) != 0,
|
||||
.noise_detected = (errors & USART_FLAG_NE) != 0,
|
||||
};
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool uart_is_rx_ready(UARTDevice *dev) {
|
||||
return dev->periph->SR & USART_SR_RXNE;
|
||||
}
|
||||
|
||||
bool uart_has_rx_overrun(UARTDevice *dev) {
|
||||
return dev->periph->SR & USART_SR_ORE;
|
||||
}
|
||||
|
||||
bool uart_has_rx_framing_error(UARTDevice *dev) {
|
||||
return dev->periph->SR & USART_SR_FE;
|
||||
}
|
||||
|
||||
bool uart_is_tx_ready(UARTDevice *dev) {
|
||||
return dev->periph->SR & USART_SR_TXE;
|
||||
}
|
||||
|
||||
bool uart_is_tx_complete(UARTDevice *dev) {
|
||||
return dev->periph->SR & USART_SR_TC;
|
||||
}
|
||||
|
||||
void uart_wait_for_tx_complete(UARTDevice *dev) {
|
||||
while (!uart_is_tx_complete(dev)) continue;
|
||||
}
|
||||
|
||||
|
||||
// Interrupts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_set_interrupt_enabled(UARTDevice *dev, bool enabled) {
|
||||
if (enabled) {
|
||||
PBL_ASSERTN(dev->state->tx_irq_handler || dev->state->rx_irq_handler);
|
||||
// enable the interrupt
|
||||
NVIC_SetPriority(dev->irq_channel, dev->irq_priority);
|
||||
NVIC_EnableIRQ(dev->irq_channel);
|
||||
} else {
|
||||
// disable the interrupt
|
||||
NVIC_DisableIRQ(dev->irq_channel);
|
||||
}
|
||||
}
|
||||
|
||||
void uart_set_rx_interrupt_handler(UARTDevice *dev, UARTRXInterruptHandler irq_handler) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
dev->state->rx_irq_handler = irq_handler;
|
||||
}
|
||||
|
||||
void uart_set_tx_interrupt_handler(UARTDevice *dev, UARTTXInterruptHandler irq_handler) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
dev->state->tx_irq_handler = irq_handler;
|
||||
}
|
||||
|
||||
void uart_set_rx_interrupt_enabled(UARTDevice *dev, bool enabled) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
if (enabled) {
|
||||
dev->state->rx_int_enabled = true;
|
||||
dev->periph->CR1 |= USART_CR1_RXNEIE;
|
||||
prv_set_interrupt_enabled(dev, true);
|
||||
} else {
|
||||
// disable interrupt if TX is also disabled
|
||||
prv_set_interrupt_enabled(dev, dev->state->tx_int_enabled);
|
||||
dev->periph->CR1 &= ~USART_CR1_RXNEIE;
|
||||
dev->state->rx_int_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void uart_set_tx_interrupt_enabled(UARTDevice *dev, bool enabled) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
if (enabled) {
|
||||
dev->state->tx_int_enabled = true;
|
||||
dev->periph->CR1 |= USART_CR1_TXEIE;
|
||||
prv_set_interrupt_enabled(dev, true);
|
||||
} else {
|
||||
// disable interrupt if RX is also disabled
|
||||
prv_set_interrupt_enabled(dev, dev->state->rx_int_enabled);
|
||||
dev->periph->CR1 &= ~USART_CR1_TXEIE;
|
||||
dev->state->tx_int_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void uart_irq_handler(UARTDevice *dev) {
|
||||
PBL_ASSERTN(dev->state->initialized);
|
||||
bool should_context_switch = false;
|
||||
if (dev->state->rx_irq_handler && dev->state->rx_int_enabled) {
|
||||
const UARTRXErrorFlags err_flags = {
|
||||
.overrun_error = uart_has_rx_overrun(dev),
|
||||
.framing_error = uart_has_rx_framing_error(dev),
|
||||
};
|
||||
if (dev->state->rx_dma_buffer) {
|
||||
// process bytes from the DMA buffer
|
||||
const uint32_t dma_length = dev->state->rx_dma_length;
|
||||
const uint32_t next_idx = dma_length - dma_request_get_current_data_counter(dev->rx_dma);
|
||||
// make sure we didn't underflow the index
|
||||
PBL_ASSERTN(next_idx < dma_length);
|
||||
while (dev->state->rx_dma_index != next_idx) {
|
||||
const uint8_t data = dev->state->rx_dma_buffer[dev->state->rx_dma_index];
|
||||
if (dev->state->rx_irq_handler(dev, data, &err_flags)) {
|
||||
should_context_switch = true;
|
||||
}
|
||||
if (++dev->state->rx_dma_index == dma_length) {
|
||||
dev->state->rx_dma_index = 0;
|
||||
}
|
||||
}
|
||||
// explicitly clear error flags since we're not reading from the data register
|
||||
uart_clear_all_interrupt_flags(dev);
|
||||
} else {
|
||||
const bool has_byte = uart_is_rx_ready(dev);
|
||||
// read the data register regardless to clear the error flags
|
||||
const uint8_t data = uart_read_byte(dev);
|
||||
if (has_byte) {
|
||||
if (dev->state->rx_irq_handler(dev, data, &err_flags)) {
|
||||
should_context_switch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dev->state->tx_irq_handler && dev->state->tx_int_enabled && uart_is_tx_ready(dev)) {
|
||||
if (dev->state->tx_irq_handler(dev)) {
|
||||
should_context_switch = true;
|
||||
}
|
||||
}
|
||||
portEND_SWITCHING_ISR(should_context_switch);
|
||||
}
|
||||
|
||||
void uart_clear_all_interrupt_flags(UARTDevice *dev) {
|
||||
dev->periph->SR &= ~(USART_SR_TXE | USART_SR_RXNE | USART_SR_ORE);
|
||||
}
|
||||
|
||||
|
||||
// DMA
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void uart_start_rx_dma(UARTDevice *dev, void *buffer, uint32_t length) {
|
||||
dev->periph->CR3 |= USART_CR3_DMAR;
|
||||
dma_request_start_circular(dev->rx_dma, buffer, (void *)&dev->periph->DR, length, NULL, NULL);
|
||||
dev->state->rx_dma_index = 0;
|
||||
dev->state->rx_dma_length = length;
|
||||
dev->state->rx_dma_buffer = buffer;
|
||||
}
|
||||
|
||||
void uart_stop_rx_dma(UARTDevice *dev) {
|
||||
dev->state->rx_dma_buffer = NULL;
|
||||
dev->state->rx_dma_length = 0;
|
||||
dma_request_stop(dev->rx_dma);
|
||||
dev->periph->CR3 &= ~USART_CR3_DMAR;
|
||||
}
|
||||
|
||||
void uart_clear_rx_dma_buffer(UARTDevice *dev) {
|
||||
dev->state->rx_dma_index = dev->state->rx_dma_length -
|
||||
dma_request_get_current_data_counter(dev->rx_dma);
|
||||
}
|
51
src/fw/drivers/stm32f2/uart_definitions.h
Normal file
51
src/fw/drivers/stm32f2/uart_definitions.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/uart.h"
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef struct UARTState {
|
||||
bool initialized;
|
||||
UARTRXInterruptHandler rx_irq_handler;
|
||||
UARTTXInterruptHandler tx_irq_handler;
|
||||
bool rx_int_enabled;
|
||||
bool tx_int_enabled;
|
||||
uint8_t *rx_dma_buffer;
|
||||
uint32_t rx_dma_length;
|
||||
uint32_t rx_dma_index;
|
||||
} UARTDeviceState;
|
||||
|
||||
typedef const struct UARTDevice {
|
||||
UARTDeviceState *state;
|
||||
bool half_duplex;
|
||||
AfConfig tx_gpio;
|
||||
AfConfig rx_gpio;
|
||||
USART_TypeDef *periph;
|
||||
uint32_t rcc_apb_periph;
|
||||
uint8_t irq_channel;
|
||||
uint8_t irq_priority;
|
||||
DMARequest *rx_dma;
|
||||
} UARTDevice;
|
||||
|
||||
// thinly wrapped by the IRQ handler in board_*.c
|
||||
void uart_irq_handler(UARTDevice *dev);
|
143
src/fw/drivers/stm32f2/voltage_monitor.c
Normal file
143
src/fw/drivers/stm32f2/voltage_monitor.c
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/voltage_monitor.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "os/mutex.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
// All boards use ADC1 solely for Vref, so we should never be using it for anything else.
|
||||
#define VREF_ADC ADC1
|
||||
#define VREF_ADC_CLOCK RCC_APB2Periph_ADC1
|
||||
|
||||
static PebbleMutex *s_adc_mutex;
|
||||
|
||||
void voltage_monitor_init(void) {
|
||||
s_adc_mutex = mutex_create();
|
||||
}
|
||||
|
||||
void voltage_monitor_device_init(VoltageMonitorDevice *device) {
|
||||
gpio_analog_init(&device->input);
|
||||
}
|
||||
|
||||
// It takes ~12µs to get our ADC readings. From time to time, we're busy
|
||||
// processing elsewhere for upwards of 25µs and end up getting overrun issues.
|
||||
// In the case that overrun occurs, clean the flag and return false so that we
|
||||
// know to restart the sample group.
|
||||
static bool prv_wait_for_conversion(ADC_TypeDef *ADCx) {
|
||||
while (ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC) == RESET) {
|
||||
if (ADC_GetFlagStatus(ADCx, ADC_FLAG_OVR) == SET) {
|
||||
ADC_ClearFlag(ADCx, ADC_FLAG_OVR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void voltage_monitor_read(VoltageMonitorDevice *device, VoltageReading *reading_out) {
|
||||
mutex_lock(s_adc_mutex);
|
||||
|
||||
bool same_adc = (device->adc == VREF_ADC);
|
||||
|
||||
// Enable ADC's APB interface clock
|
||||
periph_config_enable(VREF_ADC, VREF_ADC_CLOCK);
|
||||
if (!same_adc) {
|
||||
periph_config_enable(device->adc, device->clock_ctrl);
|
||||
}
|
||||
ADC_TempSensorVrefintCmd(ENABLE);
|
||||
|
||||
// Common configuration (applicable for all ADCs)
|
||||
ADC_CommonInitTypeDef ADC_CommonInitStruct;
|
||||
ADC_CommonStructInit(&ADC_CommonInitStruct);
|
||||
// Single ADC mode
|
||||
ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;
|
||||
// ADCCLK = PCLK2/2
|
||||
ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4;
|
||||
// Available only for multi ADC mode
|
||||
ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
|
||||
// Delay between 2 sampling phases
|
||||
ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
|
||||
ADC_CommonInit(&ADC_CommonInitStruct);
|
||||
|
||||
ADC_InitTypeDef ADC_InitStruct;
|
||||
ADC_StructInit(&ADC_InitStruct);
|
||||
ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
|
||||
ADC_InitStruct.ADC_ScanConvMode = same_adc ? ENABLE : DISABLE;
|
||||
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
|
||||
ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
|
||||
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
|
||||
ADC_InitStruct.ADC_NbrOfConversion = same_adc ? 2 : 1;
|
||||
|
||||
ADC_Init(VREF_ADC, &ADC_InitStruct);
|
||||
if (!same_adc) {
|
||||
ADC_Init(device->adc, &ADC_InitStruct);
|
||||
}
|
||||
// Regular channel configuration
|
||||
ADC_RegularChannelConfig(VREF_ADC, ADC_Channel_Vrefint, 1, ADC_SampleTime_144Cycles);
|
||||
ADC_RegularChannelConfig(device->adc, device->adc_channel, same_adc ? 2 : 1,
|
||||
ADC_SampleTime_144Cycles);
|
||||
if (same_adc) {
|
||||
// ScanConvMode enabled, so need to request EOC on each channel conversion
|
||||
ADC_EOCOnEachRegularChannelCmd(VREF_ADC, ENABLE);
|
||||
}
|
||||
|
||||
ADC_Cmd(VREF_ADC, ENABLE);
|
||||
if (!same_adc) {
|
||||
ADC_Cmd(device->adc, ENABLE);
|
||||
}
|
||||
delay_us(10); // Tstab (ADC stabilization) needs 3us and temp sensor Tstart is 10us
|
||||
|
||||
*reading_out = (VoltageReading) {};
|
||||
|
||||
int i = 0;
|
||||
while (i < NUM_CONVERSIONS) {
|
||||
ADC_SoftwareStartConv(VREF_ADC);
|
||||
if (!prv_wait_for_conversion(VREF_ADC)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t vref = ADC_GetConversionValue(VREF_ADC);
|
||||
|
||||
ADC_SoftwareStartConv(device->adc);
|
||||
if (!prv_wait_for_conversion(device->adc)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t vmon = ADC_GetConversionValue(device->adc);
|
||||
|
||||
// Only save values and increment counter if both reads were successful
|
||||
reading_out->vref_total += vref;
|
||||
reading_out->vmon_total += vmon;
|
||||
++i;
|
||||
}
|
||||
|
||||
ADC_Cmd(VREF_ADC, DISABLE);
|
||||
if (!same_adc) {
|
||||
ADC_Cmd(device->adc, DISABLE);
|
||||
}
|
||||
ADC_TempSensorVrefintCmd(DISABLE);
|
||||
periph_config_disable(VREF_ADC, VREF_ADC_CLOCK);
|
||||
if (!same_adc) {
|
||||
periph_config_disable(device->adc, device->clock_ctrl);
|
||||
}
|
||||
|
||||
mutex_unlock(s_adc_mutex);
|
||||
}
|
68
src/fw/drivers/stm32f2/watchdog.c
Normal file
68
src/fw/drivers/stm32f2/watchdog.c
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/watchdog.h"
|
||||
|
||||
#include "util/bitset.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void watchdog_init(void) {
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
|
||||
|
||||
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
|
||||
IWDG_SetReload(0xfff);
|
||||
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
|
||||
|
||||
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
|
||||
}
|
||||
|
||||
void watchdog_start(void) {
|
||||
IWDG_Enable();
|
||||
watchdog_feed();
|
||||
}
|
||||
|
||||
// This behaves differently from the bootloader and the firmware.
|
||||
void watchdog_feed(void) {
|
||||
IWDG_ReloadCounter();
|
||||
}
|
||||
|
||||
bool watchdog_check_reset_flag(void) {
|
||||
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
|
||||
}
|
||||
|
||||
McuRebootReason watchdog_clear_reset_flag(void) {
|
||||
McuRebootReason mcu_reboot_reason = {
|
||||
.brown_out_reset = (RCC_GetFlagStatus(RCC_FLAG_BORRST) != RESET),
|
||||
.pin_reset = (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET),
|
||||
.power_on_reset = (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET),
|
||||
.software_reset = (RCC_GetFlagStatus(RCC_FLAG_SFTRST) != RESET),
|
||||
.independent_watchdog_reset = (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET),
|
||||
.window_watchdog_reset = (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET),
|
||||
.low_power_manager_reset = (RCC_GetFlagStatus(RCC_FLAG_LPWRRST) != RESET)
|
||||
};
|
||||
|
||||
RCC_ClearFlag();
|
||||
|
||||
return mcu_reboot_reason;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue