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,278 @@
/*
* 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/stm32f7/i2c_hal_definitions.h"
#include "drivers/stm32f7/i2c_timingr.h"
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "FreeRTOS.h"
#define STM32F7_COMPATIBLE
#include <mcu.h>
#define I2C_IRQ_PRIORITY (0xc)
#define CR1_CLEAR_MASK (0x00CFE0FF)
#define CR2_CLEAR_MASK (0x07FF7FFF)
#define CR2_NBYTES_OFFSET (16)
#define CR2_TRANSFER_SETUP_MASK (I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | \
I2C_CR2_AUTOEND | I2C_CR2_RD_WRN | I2C_CR2_START | \
I2C_CR2_STOP)
static void prv_i2c_deinit(I2CBus *bus) {
// Reset the clock to the peripheral
RCC_APB1PeriphResetCmd(bus->hal->clock_ctrl, ENABLE);
RCC_APB1PeriphResetCmd(bus->hal->clock_ctrl, DISABLE);
}
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);
prv_i2c_deinit(bus);
}
void i2c_hal_enable(I2CBus *bus) {
const I2CBusHal *hal = bus->hal;
periph_config_enable(hal->i2c, hal->clock_ctrl);
// Soft reset of the state machine and status bits by disabling the peripheral.
// Note: PE must be low for 3 APB cycles after this is done for the reset to be successful
hal->i2c->CR1 &= ~I2C_CR1_PE;
hal->i2c->CR1 &= ~CR1_CLEAR_MASK;
// Set the timing register
RCC_ClocksTypeDef rcc_clocks;
RCC_GetClocksFreq(&rcc_clocks);
const uint32_t timingr = i2c_timingr_calculate(
rcc_clocks.PCLK1_Frequency, hal->bus_mode, hal->clock_speed,
hal->rise_time_ns, hal->fall_time_ns);
PBL_ASSERT(timingr != I2C_TIMINGR_INVALID_VALUE, "Could not calculate TIMINGR values!");
hal->i2c->TIMINGR = timingr;
// I2C only used as a master; disable slave address acknowledgement
hal->i2c->OAR1 = 0;
hal->i2c->OAR2 = 0;
// Enable i2c Peripheral; clear any configured interrupt bits; use analog filter
hal->i2c->CR1 |= I2C_CR1_PE;
// Clear CR2, making it ready for the next transaction
hal->i2c->CR2 &= ~CR2_CLEAR_MASK;
}
void i2c_hal_disable(I2CBus *bus) {
periph_config_disable(bus->hal->i2c, bus->hal->clock_ctrl);
prv_i2c_deinit(bus);
}
bool i2c_hal_is_busy(I2CBus *bus) {
return ((bus->hal->i2c->ISR & I2C_ISR_BUSY) != 0);
}
static void prv_disable_all_interrupts(I2CBus *bus) {
bus->hal->i2c->CR1 &= ~(I2C_CR1_TXIE |
I2C_CR1_RXIE |
I2C_CR1_TCIE |
I2C_CR1_NACKIE |
I2C_CR1_ERRIE);
}
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->CR2 |= I2C_CR2_STOP;
}
void i2c_hal_init_transfer(I2CBus *bus) {
I2CTransfer *transfer = &bus->state->transfer;
if (transfer->type == I2CTransferType_SendRegisterAddress) {
transfer->state = I2CTransferState_WriteRegAddress;
} else {
if (transfer->direction == I2CTransferDirection_Read) {
transfer->state = I2CTransferState_ReadData;
} else {
transfer->state = I2CTransferState_WriteData;
}
}
}
static void prv_enable_interrupts(I2CBus *bus) {
bus->hal->i2c->CR1 |= I2C_CR1_ERRIE | // Enable error interrupt
I2C_CR1_NACKIE | // Enable NACK interrupt
I2C_CR1_TCIE | // Enable transfer complete interrupt
I2C_CR1_TXIE; // Enable transmit interrupt
if (bus->state->transfer.direction == I2CTransferDirection_Read) {
bus->hal->i2c->CR1 |= I2C_CR1_RXIE; // Enable receive interrupt
}
}
static void prv_resume_transfer(I2CBus *bus, bool generate_start) {
const I2CTransfer *transfer = &bus->state->transfer;
uint32_t cr2_value = transfer->device_address & I2C_CR2_SADD;
if ((transfer->direction == I2CTransferDirection_Read) &&
(transfer->state != I2CTransferState_WriteRegAddress)) {
cr2_value |= I2C_CR2_RD_WRN;
}
const uint32_t remaining = bus->state->transfer.size - bus->state->transfer.idx;
if (remaining > UINT8_MAX) {
cr2_value |= I2C_CR2_RELOAD;
cr2_value |= I2C_CR2_NBYTES;
} else {
cr2_value |= (remaining << CR2_NBYTES_OFFSET) & I2C_CR2_NBYTES;
}
if (generate_start) {
cr2_value |= I2C_CR2_START;
}
bus->hal->i2c->CR2 = cr2_value;
}
void i2c_hal_start_transfer(I2CBus *bus) {
prv_enable_interrupts(bus);
if (bus->state->transfer.state == I2CTransferState_WriteRegAddress) {
// For writes, we'll reload with the payload once we send the address. Otherwise, we'd need to
// send a repeated start, which we don't want to do.
const bool reload = bus->state->transfer.direction == I2CTransferDirection_Write;
bus->hal->i2c->CR2 = (bus->state->transfer.device_address & I2C_CR2_SADD) |
(1 << CR2_NBYTES_OFFSET) |
(reload ? I2C_CR2_RELOAD : 0) |
I2C_CR2_START;
} else {
prv_resume_transfer(bus, true /* generate_start */);
}
}
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
static portBASE_TYPE prv_end_transfer_irq(I2CBus *bus, I2CTransferEvent event) {
prv_disable_all_interrupts(bus);
// Generate stop condition
bus->hal->i2c->CR2 |= I2C_CR2_STOP;
bus->state->transfer.state = I2CTransferState_Complete;
return i2c_handle_transfer_event(bus, event);
}
//! 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;
switch (transfer->state) {
case I2CTransferState_WriteRegAddress:
if ((i2c->ISR & I2C_ISR_TXIS) != 0) {
i2c->TXDR = transfer->register_address;
}
if ((transfer->direction == I2CTransferDirection_Read) && (i2c->ISR & I2C_ISR_TC)) {
// done writing the register address for a read request - start a read request
transfer->state = I2CTransferState_ReadData;
prv_resume_transfer(bus, true /* generate_start */);
} else if ((transfer->direction == I2CTransferDirection_Write) && (i2c->ISR & I2C_ISR_TCR)) {
// done writing the register address for a write request - "reload" the write payload
transfer->state = I2CTransferState_WriteData;
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_NACKF) != 0) {
i2c->ICR |= I2C_ICR_NACKCF;
return i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
}
break;
case I2CTransferState_ReadData:
if ((i2c->ISR & I2C_ISR_RXNE) != 0) {
transfer->data[transfer->idx++] = i2c->RXDR;
}
if ((i2c->ISR & I2C_ISR_TCR) != 0) {
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_TC) != 0) {
return prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
}
break;
case I2CTransferState_WriteData:
if ((i2c->ISR & I2C_ISR_TXIS) != 0) {
i2c->TXDR = transfer->data[transfer->idx++];
}
if ((i2c->ISR & I2C_ISR_NACKF) != 0) {
i2c->ICR |= I2C_ICR_NACKCF;
return i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
}
if ((i2c->ISR & I2C_ISR_TCR) != 0) {
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_TC) != 0) {
return prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
}
break;
case I2CTransferState_Complete:
if (i2c->ISR & I2C_ISR_TXE) {
// We seem to get a spurious interrupt after the last byte is sent
// There is no bit to specifically disable this interrupt and the interrupt may have already
// been pended when we would disable it, so just handle it silently.
break;
}
// Fallthrough
// These extra states were defined for the F4 implementation but are not necessary for the F7,
// because the interrupt scheme is a lot nicer.
case I2CTransferState_RepeatStart:
case I2CTransferState_EndWrite:
case I2CTransferState_WaitForData:
case I2CTransferState_WriteAddressRx:
case I2CTransferState_WriteAddressTx:
default:
WTF;
}
return pdFALSE;
}
static portBASE_TYPE prv_error_irq_handler(I2CBus *bus) {
I2C_TypeDef *i2c = bus->hal->i2c;
if ((i2c->ISR & I2C_ISR_BERR) != 0) {
i2c->ICR |= I2C_ICR_BERRCF;
}
if ((i2c->ISR & I2C_ISR_OVR) != 0) {
i2c->ICR |= I2C_ICR_OVRCF;
}
if ((i2c->ISR & I2C_ISR_ARLO) != 0) {
i2c->ICR |= I2C_ICR_ARLOCF;
}
return prv_end_transfer_irq(bus, I2CTransferEvent_Error);
}
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,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.
*/
#pragma once
#include "drivers/stm32f7/i2c_timingr.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct I2CBusHal {
I2C_TypeDef *const i2c;
uint32_t clock_ctrl; ///< Peripheral clock control flag
I2CBusMode bus_mode;
uint32_t clock_speed; ///< Bus clock speed
uint32_t rise_time_ns; ///< SCL/SDA rise time in nanoseconds
uint32_t fall_time_ns; ///< SCL/SDA fall time in nanoseconds
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,141 @@
/*
* 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 "i2c_timingr.h"
#include "util/attributes.h"
#include "util/math.h"
#include "util/units.h"
static const struct {
uint32_t clock_speed_max;
uint32_t min_scl_low_ps;
uint32_t min_scl_high_ps;
uint32_t min_sda_setup_ps;
} s_timing_data[] = {
[I2CBusMode_Standard] = {
.clock_speed_max = 100000,
.min_scl_low_ps = 4700000,
.min_scl_high_ps = 4000000,
.min_sda_setup_ps = 250000,
},
[I2CBusMode_FastMode] = {
.clock_speed_max = 400000,
.min_scl_low_ps = 1300000,
.min_scl_high_ps = 600000,
.min_sda_setup_ps = 100000,
},
};
// Per the STM32F7 reference manual, the I2C peripheral adds 2-3 cycles to sync SCL with I2CCLK. In
// practice, 3 has been observed.
#define I2C_SYNC_CYCLES (3)
#define TIMINGR_MAX_SCLL (0x100) // 8 bits storing (SCLL - 1)
#define TIMINGR_MAX_SCLH (0x100) // 8 bits storing (SCLH - 1)
#define TIMINGR_MAX_SCLDEL (0x10) // 4 bits storing (SCLDEL - 1)
#define TIMINGR_MAX_PRESC (0x10) // 4 bits storing (PRESC - 1)
typedef union PACKED TIMINGR {
struct {
uint32_t SCLL:8;
uint32_t SCLH:8;
uint32_t SDADEL:4;
uint32_t SCLDEL:4;
uint32_t reserved:4;
uint32_t PRESC:4;
};
uint32_t reg;
} TIMINGR;
_Static_assert(sizeof(TIMINGR) == sizeof(uint32_t), "Invalid TIMINGR size");
uint32_t i2c_timingr_calculate(uint32_t i2c_clk_frequency,
I2CBusMode bus_mode,
uint32_t target_bus_frequency,
uint32_t rise_time_ns,
uint32_t fall_time_ns) {
if (bus_mode != I2CBusMode_Standard && bus_mode != I2CBusMode_FastMode) {
// This is FM+ (or higher) and is not currently supported.
return I2C_TIMINGR_INVALID_VALUE;
}
if (target_bus_frequency > s_timing_data[bus_mode].clock_speed_max) {
return I2C_TIMINGR_INVALID_VALUE;
}
uint32_t min_scl_low_ps = s_timing_data[bus_mode].min_scl_low_ps;
uint32_t min_scl_high_ps = s_timing_data[bus_mode].min_scl_high_ps;
for (uint32_t prescaler = 1; prescaler <= TIMINGR_MAX_PRESC; ++prescaler) {
const uint32_t base_frequency = i2c_clk_frequency / prescaler;
const uint64_t base_period_ps = PS_PER_S / base_frequency;
// Calculate what the total SCL period should be in terms of base clock cycles and then
// recalculate the target frequency based on that value. The result will be the highest
// target frequency we can obtain without going over.
uint32_t total_scl_cycles = base_frequency / target_bus_frequency;
// Calculate the number of overhead cycles. This includes the rise time, fall time, and sync
// cycles for both SCLL and SCLH.
const uint32_t overhead_i2cclk_cycles =
DIVIDE_CEIL((rise_time_ns + fall_time_ns) * PS_PER_NS,
PS_PER_S / i2c_clk_frequency) +
I2C_SYNC_CYCLES * 2;
const uint32_t overhead_cycles = DIVIDE_CEIL(overhead_i2cclk_cycles, prescaler);
// Figure out how many base clock cycles the minimum SCL periods correspond to.
uint32_t scl_low = DIVIDE_CEIL(min_scl_low_ps, base_period_ps);
uint32_t scl_high = DIVIDE_CEIL(min_scl_high_ps, base_period_ps);
// Calculate the number of extra cycles we have.
const int32_t extra_cycles = total_scl_cycles - scl_low - scl_high - overhead_cycles;
if (extra_cycles < 0) {
// The base frequency is too slow to satisfy the target frequency, and continuing will only
// get slower, so give up.
return I2C_TIMINGR_INVALID_VALUE;
}
// Split up the extra cycles evenly between the low and high periods. If necessary, give the
// extra one to the high period arbitrarily.
scl_low += extra_cycles / 2;
scl_high += extra_cycles - (extra_cycles / 2);
// Calculate the SDA setup time delay, which is confusingly referred to as SCLDEL.
uint32_t scl_delay = DIVIDE_CEIL(
(rise_time_ns * PS_PER_NS) + s_timing_data[bus_mode].min_sda_setup_ps,
base_period_ps);
// Check if the computed values are valid. If they aren't valid, we'll increase the prescaler
// try again with a higher prescaler.
// NOTE: We could end up in a situation where it is not valid, but could be if we split up the
// extra cycles differently. We're not going to worry about this because it doesn't currently
// happen in practice, and if it does, the next prescaler value will give us a valid (although
// slightly sub-optimal) result.
if ((scl_low <= TIMINGR_MAX_SCLL) && (scl_high <= TIMINGR_MAX_SCLH) &&
(scl_delay <= TIMINGR_MAX_SCLDEL)) {
// 1 less than the SCLL / SCLH / SCLDEL / PRESC values should be stored in the register.
const TIMINGR result = (TIMINGR) {
.SCLL = scl_low - 1,
.SCLH = scl_high - 1,
.SCLDEL = scl_delay - 1,
.PRESC = prescaler - 1,
};
return result.reg;
}
}
// We tried every possible prescaler and couldn't find valid TIMINGR values.
return I2C_TIMINGR_INVALID_VALUE;
}

View file

@ -0,0 +1,34 @@
/*
* 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>
// This is always invalid because it inclues a value being set in the reserved field.
#define I2C_TIMINGR_INVALID_VALUE (0xffffffff)
typedef enum I2CBusMode {
I2CBusMode_Standard, ///< I2C Standard Mode (up to 100 kHz)
I2CBusMode_FastMode, ///< I2C Fast Mode (up to 400 kHz)
I2CBusMode_FastModePlus, ///< I2C Fast Mode Plus (up to 1 MHz)
} I2CBusMode;
uint32_t i2c_timingr_calculate(uint32_t i2c_clk_frequency,
I2CBusMode bus_mode,
uint32_t target_bus_frequency,
uint32_t rise_time_ns,
uint32_t fall_time_ns);

View file

@ -0,0 +1,108 @@
/*
* 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 STM32F7_COMPATIBLE
#include <mcu.h>
#define IS_LPTIM(periph) IS_LPTIM_ALL_PERIPH((LPTIM_TypeDef *)(periph))
void pwm_init(const PwmConfig *pwm, uint32_t resolution, uint32_t frequency) {
periph_config_enable(pwm->timer.peripheral, pwm->timer.config_clock);
if (IS_LPTIM(pwm->timer.peripheral)) {
// Initialize low power timer
LPTIM_InitTypeDef config = {
.LPTIM_ClockSource = LPTIM_ClockSource_APBClock_LPosc,
.LPTIM_Prescaler = LPTIM_Prescaler_DIV128,
.LPTIM_Waveform = LPTIM_Waveform_PWM_OnePulse,
// low polarity means it'll be high for the specified duty cycle
.LPTIM_OutputPolarity = LPTIM_OutputPolarity_Low
};
LPTIM_Init(pwm->timer.lp_peripheral, &config);
LPTIM_SelectSoftwareStart(pwm->timer.lp_peripheral);
// The timer must be enabled before setting the auto-reload value.
LPTIM_Cmd(pwm->timer.lp_peripheral, ENABLE);
LPTIM_SetAutoreloadValue(pwm->timer.lp_peripheral, resolution);
// Wait for the Auto-Reload value to be applied before disabling the timer.
#if !TARGET_QEMU
while (LPTIM_GetFlagStatus(pwm->timer.lp_peripheral,
LPTIM_FLAG_ARROK) == RESET) continue;
#endif
LPTIM_Cmd(pwm->timer.lp_peripheral, DISABLE);
} else {
// Initialize regular 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) {
if (IS_LPTIM(pwm->timer.peripheral)) {
LPTIM_SetCompareValue(pwm->timer.lp_peripheral, duty_cycle);
LPTIM_SelectOperatingMode(pwm->timer.lp_peripheral, LPTIM_Mode_Continuous);
} else {
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);
// The ".active_high" attribute determines the idle state of the PWM output
gpio_output_set(&pwm->output, false /* force low */);
}
const FunctionalState state = (enable) ? ENABLE : DISABLE;
if (IS_LPTIM(pwm->timer.peripheral)) {
LPTIM_Cmd(pwm->timer.lp_peripheral, state);
} else {
TIM_Cmd(pwm->timer.peripheral, state);
}
}

View file

@ -0,0 +1,48 @@
/*
* 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 STM32F7_COMPATIBLE
#include <mcu.h>
void pwr_enable_wakeup(bool enable) {
if (enable) {
__atomic_or_fetch(&PWR->CSR2, PWR_CSR2_EWUP1, __ATOMIC_RELAXED);
} else {
__atomic_and_fetch(&PWR->CSR2, ~PWR_CSR2_EWUP1, __ATOMIC_RELAXED);
}
}
void pwr_flash_power_down_stop_mode(bool power_down) {
if (power_down) {
__atomic_or_fetch(&PWR->CR1, PWR_CR1_FPDS, __ATOMIC_RELAXED);
} else {
__atomic_and_fetch(&PWR->CR1, ~PWR_CR1_FPDS, __ATOMIC_RELAXED);
}
}
void pwr_access_backup_domain(bool enable_access) {
periph_config_enable(PWR, RCC_APB1Periph_PWR);
if (enable_access) {
__atomic_or_fetch(&PWR->CR1, PWR_CR1_DBP, __ATOMIC_RELAXED);
} else {
__atomic_and_fetch(&PWR->CR1, ~PWR_CR1_DBP, __ATOMIC_RELAXED);
}
periph_config_disable(PWR, RCC_APB1Periph_PWR);
}

View file

@ -0,0 +1,331 @@
/*
* 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 STM32F7_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_clear_all_errors(UARTDevice *dev) {
dev->periph->ICR |= (USART_ICR_ORECF | USART_ICR_PECF | USART_ICR_NCF | USART_ICR_FECF);
}
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);
}
// clear any lingering errors
prv_clear_all_errors(dev);
// configure the UART peripheral control registers
// - 8-bit word length
// - no parity
// - RX / TX enabled
// - 1 stop bit
// - no flow control
dev->periph->CR1 &= ~USART_CR1_UE;
dev->periph->CR2 = (dev->do_swap_rx_tx ? USART_CR2_SWAP : 0);
dev->periph->CR3 = (dev->half_duplex ? USART_CR3_HDSEL : 0);
dev->periph->CR1 = cr1_extra_flags | 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;
}
if (dev->periph->CR1 & USART_CR1_OVER8) {
scaled_apbclock <<= 1;
}
// calculate the baud rate value
const uint32_t div = (scaled_apbclock / baud_rate);
const uint32_t brr = (div & 0xFFF0) | ((div & 0xF) >> 1);
// we can only change the baud rate when the UART is disabled
dev->periph->CR1 &= ~USART_CR1_UE;
dev->periph->BRR = brr / DIV_PRECISION;
dev->periph->CR1 |= USART_CR1_UE;
}
// 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->TDR = data;
}
uint8_t uart_read_byte(UARTDevice *dev) {
// explicitly clear the error flags to match up with F4 behavior
prv_clear_all_errors(dev);
// read the data regardless since it will clear interrupt flags
return dev->periph->RDR;
}
UARTRXErrorFlags uart_has_errored_out(UARTDevice *dev) {
uint16_t errors = dev->periph->ISR;
UARTRXErrorFlags flags = {
.parity_error = (errors & USART_ISR_PE) != 0,
.overrun_error = (errors & USART_ISR_ORE) != 0,
.framing_error = (errors & USART_ISR_FE) != 0,
.noise_detected = (errors & USART_ISR_NE) != 0,
};
return flags;
}
bool uart_is_rx_ready(UARTDevice *dev) {
return dev->periph->ISR & USART_ISR_RXNE;
}
bool uart_has_rx_overrun(UARTDevice *dev) {
return dev->periph->ISR & USART_ISR_ORE;
}
bool uart_has_rx_framing_error(UARTDevice *dev) {
return dev->periph->ISR & USART_ISR_FE;
}
bool uart_is_tx_ready(UARTDevice *dev) {
return dev->periph->ISR & USART_ISR_TXE;
}
bool uart_is_tx_complete(UARTDevice *dev) {
return dev->periph->ISR & USART_ISR_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->RQR |= USART_RQR_RXFRQ;
dev->periph->ICR |= USART_ICR_ORECF;
}
// 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->RDR, 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,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.
*/
#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;
// Conveniently, the F7 supports swapping RX/TX if they get laid out incorrectly :)
bool do_swap_rx_tx;
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);