mirror of
https://github.com/google/pebble.git
synced 2025-05-24 12:14:53 +00:00
278 lines
9.4 KiB
C
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));
|
|
}
|