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,479 @@
/*
* 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.h"
#include "i2c_definitions.h"
#include "i2c_hal.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/delay.h"
#include "util/size.h"
#include <inttypes.h>
#include "stm32f7xx.h"
#define I2C_ERROR_TIMEOUT_MS (1000)
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
// MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1000ms timeout.
// The longest operation of the MFi chip is "start signature generation", which seems to take
// 223-224 NACKs, but sometimes for unknown reasons it can take much longer.
#define I2C_NACK_COUNT_MAX (1000)
typedef enum {
Read,
Write
} TransferDirection;
typedef enum {
SendRegisterAddress, // Send a register address, followed by a repeat start for reads
NoRegisterAddress // Do not send a register address
} TransferType;
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
static bool prv_semaphore_take(I2CBusState *bus) {
return true;
}
static bool prv_semaphore_wait(I2CBusState *bus) {
bus->busy = true;
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while ((timeout_attempts-- > 0) && (bus->busy)) {};
bus->busy = false;
return (timeout_attempts != 0);
}
static void prv_semaphore_give(I2CBusState *bus) {
bus->busy = false;
}
static void prv_semaphore_give_from_isr(I2CBusState *bus) {
bus->busy = false;
return;
}
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
static void prv_rail_ctl(I2CBus *bus, bool enable) {
bus->rail_ctl_fn(bus, enable);
if (enable) {
// wait for the bus supply to stabilize and the peripherals to start up.
// the MFI chip requires its reset pin to be stable for at least 10ms from startup.
delay_ms(20);
}
}
//! Power down I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_down(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
prv_rail_ctl(bus, false);
// Drain through pull-ups
OutputConfig out_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, false);
OutputConfig out_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_sda, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_sda, false);
}
//! Configure bus pins for use by I2C peripheral
//! Lock bus and peripheral config access before configuring pins
static void prv_bus_pins_cfg_i2c(I2CBus *bus) {
gpio_af_init(&bus->scl_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
gpio_af_init(&bus->sda_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
}
static void prv_bus_pins_cfg_input(I2CBus *bus) {
InputConfig in_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
};
gpio_input_init(&in_scl);
InputConfig in_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
}
//! Power up I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_up(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
static const uint32_t MIN_STOP_TIME_MS = 10;
delay_ms(MIN_STOP_TIME_MS);
prv_bus_pins_cfg_input(bus);
prv_rail_ctl(bus, true);
}
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
//! Always lock the bus and peripheral config access before enabling it
static void prv_bus_enable(I2CBus *bus) {
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
if (bus->state->user_count == 0) {
prv_bus_rail_power_up(bus);
}
prv_bus_pins_cfg_i2c(bus);
i2c_hal_enable(bus);
}
//! De-initialize and gate the clock to the peripheral
//! Power down rail if the bus supports that and no devices are using it
//! Always lock the bus and peripheral config access before disabling it
static void prv_bus_disable(I2CBus *bus) {
i2c_hal_disable(bus);
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin
// configuration during a bus reset)
if (bus->state->user_count == 0) {
prv_bus_rail_power_down(bus);
} else {
prv_bus_pins_cfg_input(bus);
}
}
//! Perform a soft reset of the bus
//! Always lock the bus before reset
static void prv_bus_reset(I2CBus *bus) {
prv_bus_disable(bus);
prv_bus_enable(bus);
}
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
void i2c_init(I2CBus *bus) {
PBL_ASSERTN(bus);
*bus->state = (I2CBusState) {};
i2c_hal_init(bus);
if (bus->rail_gpio.gpio) {
gpio_output_init(&bus->rail_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
}
prv_bus_rail_power_down(bus);
}
void i2c_use(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
prv_bus_enable(slave->bus);
}
slave->bus->state->user_count++;
}
void i2c_release(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %s", slave->bus->name);
return;
}
slave->bus->state->user_count--;
if (slave->bus->state->user_count == 0) {
prv_bus_disable(slave->bus);
}
}
void i2c_reset(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %s when still in use by "
"another bus", slave->bus->name);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %s", slave->bus->name);
// decrement user count for reset so that if this user is the only user, the
// bus will be powered down during the reset
slave->bus->state->user_count--;
// Reset and reconfigure bus and pins
prv_bus_reset(slave->bus);
// Restore user count
slave->bus->state->user_count++;
}
bool i2c_bitbang_recovery(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
static const int MAX_TOGGLE_COUNT = 10;
static const int TOGGLE_DELAY = 10;
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted bitbang recovery on disabled bus %s", slave->bus->name);
return false;
}
InputConfig in_sda = {
.gpio = slave->bus->sda_gpio.gpio,
.gpio_pin = slave->bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
OutputConfig out_scl = {
.gpio = slave->bus->scl_gpio.gpio,
.gpio_pin = slave->bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, true);
bool recovered = false;
for (int i = 0; i < MAX_TOGGLE_COUNT; ++i) {
gpio_output_set(&out_scl, false);
delay_ms(TOGGLE_DELAY);
gpio_output_set(&out_scl, true);
delay_ms(TOGGLE_DELAY);
if (gpio_input_read(&in_sda)) {
recovered = true;
break;
}
}
if (recovered) {
PBL_LOG(LOG_LEVEL_DEBUG, "I2C Bus %s recovered", slave->bus->name);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Bus %s still hung after bitbang reset", slave->bus->name);
}
prv_bus_pins_cfg_i2c(slave->bus);
prv_bus_reset(slave->bus);
return recovered;
}
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
//! Wait a short amount of time for busy bit to clear
static bool prv_wait_for_not_busy(I2CBus *bus) {
static const int WAIT_DELAY = 10; // milliseconds
if (i2c_hal_is_busy(bus)) {
delay_ms(WAIT_DELAY);
if (i2c_hal_is_busy(bus)) {
PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for bus %s to become non-busy", bus->name);
return false;
}
}
return true;
}
//! Set up and start a transfer to a bus, wait for it to finish and clean up after the transfer
//! has completed
static bool prv_do_transfer(I2CBus *bus, TransferDirection direction, uint16_t device_address,
uint8_t register_address, uint32_t size, uint8_t *data,
TransferType type) {
if (bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %s", bus->name);
return false;
}
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
// before exiting) reset the bus and wait for it to become not-busy
// Exit if bus remains busy. User module should reset the I2C module at this point
if (i2c_hal_is_busy(bus)) {
prv_bus_reset(bus);
if (!prv_wait_for_not_busy(bus)) {
// Bus did not recover after reset
PBL_LOG(LOG_LEVEL_ERROR, "I2C bus did not recover after reset (%s)", bus->name);
return false;
}
}
// Take binary semaphore so that next take will block
PBL_ASSERT(prv_semaphore_take(bus->state), "Could not acquire semaphore token");
// Set up transfer
bus->state->transfer = (I2CTransfer) {
.device_address = device_address,
.register_address = register_address,
.direction = direction,
.type = type,
.size = size,
.idx = 0,
.data = data,
};
i2c_hal_init_transfer(bus);
bus->state->transfer_nack_count = 0;
bool result = false;
bool complete = false;
do {
i2c_hal_start_transfer(bus);
// Wait on semaphore until it is released by interrupt or a timeout occurs
if (prv_semaphore_wait(bus->state)) {
if ((bus->state->transfer_event == I2CTransferEvent_TransferComplete) ||
(bus->state->transfer_event == I2CTransferEvent_Error)) {
// Track the max transfer duration so we can keep tabs on the MFi chip's nacking behavior
if (bus->state->transfer_event == I2CTransferEvent_Error) {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %s", bus->name);
}
complete = true;
result = (bus->state->transfer_event == I2CTransferEvent_TransferComplete);
} else if (bus->state->transfer_nack_count < I2C_NACK_COUNT_MAX) {
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it
// is busy
// Retry start condition after a short delay.
// A NACK count is incremented for each NACK received, so that legitimate NACK
// errors cause the transfer to be aborted (after the NACK count max has been reached).
bus->state->transfer_nack_count++;
// Wait 1-2ms:
delay_ms(2);
} else {
// Too many NACKs received, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %s", bus->name);
break;
}
} else {
// Timeout, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %s", bus->name);
break;
}
} while (!complete);
// Return semaphore token so another transfer can be started
prv_semaphore_give(bus->state);
// Wait for bus to to clear the busy flag before a new transfer starts
// Theoretically a transfer could complete successfully, but the busy flag never clears,
// which would cause the next transfer to fail
if (!prv_wait_for_not_busy(bus)) {
// Reset I2C bus if busy flag does not clear
prv_bus_reset(bus);
}
return result;
}
bool i2c_read_register(I2CSlavePort *slave, uint8_t register_address, uint8_t *result) {
return i2c_read_register_block(slave, register_address, 1, result);
}
bool i2c_read_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Read, slave->address, register_address_start, read_size,
result_buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_read_block(I2CSlavePort *slave, uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
bool result = prv_do_transfer(slave->bus, Read, slave->address, 0, read_size, result_buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_register(I2CSlavePort *slave, uint8_t register_address, uint8_t value) {
return i2c_write_register_block(slave, register_address, 1, &value);
}
bool i2c_write_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, register_address_start,
write_size, (uint8_t*)buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_block(I2CSlavePort *slave, uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, 0, write_size, (uint8_t*)buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block write failed on bus %s", slave->bus->name);
}
return result;
}
/*----------------------HAL INTERFACE--------------------------------*/
void i2c_handle_transfer_event(I2CBus *bus, I2CTransferEvent event) {
bus->state->transfer_event = event;
prv_semaphore_give_from_isr(bus->state);
}

View file

@ -0,0 +1,95 @@
/*
* 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 <stdbool.h>
#include <stdint.h>
typedef enum I2CTransferEvent {
I2CTransferEvent_Timeout,
I2CTransferEvent_TransferComplete,
I2CTransferEvent_NackReceived,
I2CTransferEvent_Error,
} I2CTransferEvent;
typedef enum {
I2CTransferDirection_Read,
I2CTransferDirection_Write
} I2CTransferDirection;
typedef enum {
// Send a register address, followed by a repeat start for reads
I2CTransferType_SendRegisterAddress,
// Do not send a register address; used for block writes/reads
I2CTransferType_NoRegisterAddress
} I2CTransferType;
typedef enum I2CTransferState {
I2CTransferState_WriteAddressTx,
I2CTransferState_WriteRegAddress,
I2CTransferState_RepeatStart,
I2CTransferState_WriteAddressRx,
I2CTransferState_WaitForData,
I2CTransferState_ReadData,
I2CTransferState_WriteData,
I2CTransferState_EndWrite,
I2CTransferState_Complete,
} I2CTransferState;
typedef struct I2CTransfer {
I2CTransferState state;
uint16_t device_address;
I2CTransferDirection direction;
I2CTransferType type;
uint8_t register_address;
uint32_t size;
uint32_t idx;
uint8_t *data;
} I2CTransfer;
typedef struct I2CBusState {
I2CTransfer transfer;
I2CTransferEvent transfer_event;
int transfer_nack_count;
int user_count;
volatile bool busy;
} I2CBusState;
struct I2CBus {
I2CBusState *const state;
const struct I2CBusHal *const hal;
AfConfig scl_gpio; ///< Alternate Function configuration for SCL pin
AfConfig sda_gpio; ///< Alternate Function configuration for SDA pin
OutputConfig rail_gpio; ///< Control pin for rail
void (* const rail_ctl_fn)(I2CBus *device, bool enabled); ///< Control function for this rail.
const char *name; //! Device ID for logging purposes
};
struct I2CSlavePort {
const I2CBus *bus;
uint16_t address;
};
//! Initialize the I2C driver.
void i2c_init(I2CBus *bus);
//! Transfer event handler implemented in i2c.c and called by HAL implementation
void i2c_handle_transfer_event(I2CBus *device, I2CTransferEvent event);
#define I2C_DEBUG(fmt, args...) \
PBL_LOG_COLOR_D(LOG_DOMAIN_I2C, LOG_LEVEL_DEBUG, LOG_COLOR_LIGHT_MAGENTA, fmt, ## args)

View file

@ -0,0 +1,336 @@
/*
* 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_hal.h"
#include "i2c_definitions.h"
#include "i2c_hal_definitions.h"
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/attributes.h"
#include "stm32f7xx.h"
#define I2C_IRQ_PRIORITY (0xc)
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
#define I2C_FAST_MODE_CLOCK_SPEED_MAX (400000)
#define I2C_FAST_MODE_PLUS_CLOCK_SPEED_MAX (1000000)
#define TIMINGR_MASK_PRESC (0x0F)
#define TIMINGR_MASK_SCLH (0xFF)
#define TIMINGR_MASK_SCLL (0xFF)
#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)
typedef union PACKED TIMINGR {
struct {
int32_t SCLL:8;
int32_t SCLH:8;
int32_t SDADEL:4;
int32_t SCLDEL:4;
int32_t reserved:4;
int32_t PRESC:4;
};
int32_t reg;
} TIMINGR;
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);
}
static void prv_i2c_init(I2C_TypeDef *i2c, TIMINGR timingr) {
// 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
i2c->CR1 &= ~I2C_CR1_PE;
i2c->CR1 &= ~CR1_CLEAR_MASK;
// Set the timing register
i2c->TIMINGR = timingr.reg;
// I2C only used as a master; disable slave address acknowledgement
i2c->OAR1 = 0;
i2c->OAR2 = 0;
// Enable i2c Peripheral; clear any configured interrupt bits; use analog filter
i2c->CR1 |= I2C_CR1_PE;
// Clear CR2, making it ready for the next transaction
i2c->CR2 &= ~CR2_CLEAR_MASK;
}
void i2c_hal_enable(I2CBus *bus) {
// We don't need to support Fast Mode Plus yet, so make sure the desired clock speed is less than
// the maximum Fast Mode clock speed.
// When Fast Mode support is added the duty-cycle settings will probably have to be re-thought.
PBL_ASSERT(bus->hal->clock_speed <= I2C_FAST_MODE_CLOCK_SPEED_MAX,
"Fast Mode Plus not yet supported");
uint32_t duty_cycle_low = 1;
uint32_t duty_cycle_high = 1;
if (bus->hal->clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { // Fast mode
if (bus->hal->duty_cycle == I2CDutyCycle_16_9) {
duty_cycle_low = 16;
duty_cycle_high = 9;
} else if (bus->hal->duty_cycle == I2CDutyCycle_2) {
duty_cycle_low = 2;
duty_cycle_high = 1;
} else {
WTF; // It might be possible to encode a duty cycle differently from the legacy I2C, if it's
// ever necessary. Currently it's not, so just maintain the previous implementation
}
}
RCC_ClocksTypeDef rcc_clocks;
RCC_GetClocksFreq(&rcc_clocks);
uint32_t prescaler = rcc_clocks.PCLK1_Frequency /
(bus->hal->clock_speed * (duty_cycle_low + duty_cycle_high));
if ((rcc_clocks.PCLK1_Frequency %
(bus->hal->clock_speed * (duty_cycle_low + duty_cycle_high))) == 0) {
// Prescaler is PRESC + 1. This subtracts one so that exact dividers are correct, but if there
// is an integer remainder, the prescaler will ensure that the clock frequency is within spec.
prescaler -= 1;
}
// Make sure all the values fit in their corresponding fields
PBL_ASSERTN((duty_cycle_low <= TIMINGR_MASK_SCLL) &&
(duty_cycle_high <= TIMINGR_MASK_SCLH) &&
(prescaler <= TIMINGR_MASK_PRESC));
periph_config_enable(bus->hal->i2c, bus->hal->clock_ctrl);
// We currently don't need to worry about the other TIMINGR fields (they come out to 0), but might
// need to revisit this if we ever need FM+ speeds.
TIMINGR timingr = {
.PRESC = prescaler,
.SCLH = duty_cycle_high - 1, // Duty cycle high is SCLH + 1
.SCLL = duty_cycle_low - 1, // Duty cycle low is SCLL + 1
};
prv_i2c_init(bus->hal->i2c, timingr);
}
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 void 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;
i2c_handle_transfer_event(bus, event);
}
//! Handle an IRQ event on the specified \a bus
static void 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;
i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
return;
}
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) {
prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
return;
}
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) {
prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
return;
}
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;
}
}
static void 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;
}
prv_end_transfer_irq(bus, I2CTransferEvent_Error);
}
void i2c_hal_event_irq_handler(I2CBus *bus) {
prv_event_irq_handler(bus);
}
void i2c_hal_error_irq_handler(I2CBus *bus) {
prv_error_irq_handler(bus);
}

View file

@ -0,0 +1,35 @@
/*
* 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 "board/board.h"
#include <stdbool.h>
void i2c_hal_init(I2CBus *bus);
void i2c_hal_enable(I2CBus *bus);
void i2c_hal_disable(I2CBus *bus);
bool i2c_hal_is_busy(I2CBus *bus);
void i2c_hal_abort_transfer(I2CBus *bus);
void i2c_hal_init_transfer(I2CBus *bus);
void i2c_hal_start_transfer(I2CBus *bus);

View file

@ -0,0 +1,37 @@
/*
* 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 <stdbool.h>
#include <stdint.h>
typedef enum I2CDutyCycle {
I2CDutyCycle_16_9,
I2CDutyCycle_2
} I2CDutyCycle;
typedef struct I2CBusHal {
I2C_TypeDef *const i2c;
uint32_t clock_ctrl; ///< Peripheral clock control flag
uint32_t clock_speed; ///< Bus clock speed
I2CDutyCycle duty_cycle; ///< Bus clock duty cycle in fast mode
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);