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