Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View 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();
}

View 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);
}

View 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);

View 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);
}
}

View 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);
}

View 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
}

View 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));
}

View 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);

View 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);
}

View 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;
}
}

View 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();
}

View 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);
}

View 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);
}

View 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;
}

View 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);
}
}

View 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

View 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);

View 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;
}

View 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;

View 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));
}

View 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;
}

View 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;
}

View 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);
}

View 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);

View 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);
}

View 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;
}