mirror of
https://github.com/google/pebble.git
synced 2025-05-24 12:14:53 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
278
src/fw/drivers/stm32f7/i2c_hal.c
Normal file
278
src/fw/drivers/stm32f7/i2c_hal.c
Normal 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));
|
||||
}
|
36
src/fw/drivers/stm32f7/i2c_hal_definitions.h
Normal file
36
src/fw/drivers/stm32f7/i2c_hal_definitions.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
141
src/fw/drivers/stm32f7/i2c_timingr.c
Normal file
141
src/fw/drivers/stm32f7/i2c_timingr.c
Normal 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;
|
||||
}
|
34
src/fw/drivers/stm32f7/i2c_timingr.h
Normal file
34
src/fw/drivers/stm32f7/i2c_timingr.h
Normal 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);
|
108
src/fw/drivers/stm32f7/pwm.c
Normal file
108
src/fw/drivers/stm32f7/pwm.c
Normal 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);
|
||||
}
|
||||
}
|
48
src/fw/drivers/stm32f7/pwr.c
Normal file
48
src/fw/drivers/stm32f7/pwr.c
Normal 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);
|
||||
}
|
331
src/fw/drivers/stm32f7/uart.c
Normal file
331
src/fw/drivers/stm32f7/uart.c
Normal 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);
|
||||
}
|
53
src/fw/drivers/stm32f7/uart_definitions.h
Normal file
53
src/fw/drivers/stm32f7/uart_definitions.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
Loading…
Add table
Add a link
Reference in a new issue