pebble/src/fw/drivers/stm32f7/i2c_hal.c
2025-01-27 11:38:16 -08:00

278 lines
9.4 KiB
C

/*
* 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));
}