circuitpython/ports/stm/common-hal/busio/I2C.c
Scott Shawcroft e9f8ed41dc Switch to IDF 5.2 I2C API
Make busio.I2C use finalizers for reset instead of bulk reset. This
makes it easier to track and free the memory that the IDF allocates
internally for its "handle".
2024-09-19 14:47:45 -04:00

371 lines
11 KiB
C

// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft
// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#include <stdbool.h>
#include "shared-bindings/busio/I2C.h"
#include "py/mperrno.h"
#include "py/runtime.h"
#include "shared-bindings/microcontroller/Pin.h"
// I2C timing specs for the H7 and F7
// Configured for maximum possible clock settings for the family
#if (CPY_STM32F7)
#ifndef CPY_I2CFAST_TIMINGR
#define CPY_I2CFAST_TIMINGR 0x6000030D
#endif
#ifndef CPY_I2CSTANDARD_TIMINGR
#define CPY_I2CSTANDARD_TIMINGR 0x20404768
#endif
#elif (CPY_STM32H7)
#ifndef CPY_I2CFAST_TIMINGR
#define CPY_I2CFAST_TIMINGR 0x00B03FDB
#endif
#ifndef CPY_I2CSTANDARD_TIMINGR
#define CPY_I2CSTANDARD_TIMINGR 0x307075B1
#endif
#elif (CPY_STM32L4)
#ifndef CPY_I2CFAST_TIMINGR
#define CPY_I2CFAST_TIMINGR 0x00B03FDB
#endif
#ifndef CPY_I2CSTANDARD_TIMINGR
#define CPY_I2CSTANDARD_TIMINGR 0x307075B1
#endif
#endif
// Arrays use 0 based numbering: I2C1 is stored at index 0
#define MAX_I2C 4
STATIC bool reserved_i2c[MAX_I2C];
#define ALL_CLOCKS 0xFF
static void i2c_clock_enable(uint8_t mask);
static void i2c_clock_disable(uint8_t mask);
static void i2c_assign_irq(busio_i2c_obj_t *self, I2C_TypeDef *I2Cx);
void common_hal_busio_i2c_construct(busio_i2c_obj_t *self,
const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) {
// Match pins to I2C objects
I2C_TypeDef *I2Cx;
uint8_t sda_len = MP_ARRAY_SIZE(mcu_i2c_sda_list);
uint8_t scl_len = MP_ARRAY_SIZE(mcu_i2c_scl_list);
bool i2c_taken = false;
for (uint i = 0; i < sda_len; i++) {
if (mcu_i2c_sda_list[i].pin == sda) {
for (uint j = 0; j < scl_len; j++) {
if ((mcu_i2c_scl_list[j].pin == scl)
&& (mcu_i2c_scl_list[j].periph_index == mcu_i2c_sda_list[i].periph_index)) {
// Keep looking if the I2C is taken, could be another SCL that works
if (reserved_i2c[mcu_i2c_scl_list[j].periph_index - 1]) {
i2c_taken = true;
continue;
}
self->scl = &mcu_i2c_scl_list[j];
self->sda = &mcu_i2c_sda_list[i];
break;
}
}
if (self->scl != NULL) {
// Multi-level break to pick lowest peripheral
break;
}
}
}
// Handle typedef selection, errors
if (self->sda != NULL && self->scl != NULL) {
I2Cx = mcu_i2c_banks[self->sda->periph_index - 1];
} else {
if (i2c_taken) {
mp_raise_ValueError(MP_ERROR_TEXT("Hardware in use, try alternative pins"));
} else {
raise_ValueError_invalid_pins();
}
}
// Start GPIO for each pin
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = pin_mask(sda->number);
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = self->sda->altfn_index;
HAL_GPIO_Init(pin_port(sda->port), &GPIO_InitStruct);
GPIO_InitStruct.Pin = pin_mask(scl->number);
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = self->scl->altfn_index;
HAL_GPIO_Init(pin_port(scl->port), &GPIO_InitStruct);
// Note: due to I2C soft reboot issue, do not relocate clock init.
i2c_clock_enable(1 << (self->sda->periph_index - 1));
reserved_i2c[self->sda->periph_index - 1] = true;
// Create root pointer and assign IRQ
MP_STATE_PORT(cpy_i2c_obj_all)[self->sda->periph_index - 1] = self;
i2c_assign_irq(self, I2Cx);
// Handle the HAL handle differences
#if (CPY_STM32H7 || CPY_STM32F7 || CPY_STM32L4)
if (frequency == 400000) {
self->handle.Init.Timing = CPY_I2CFAST_TIMINGR;
} else if (frequency == 100000) {
self->handle.Init.Timing = CPY_I2CSTANDARD_TIMINGR;
} else {
mp_arg_error_invalid(MP_QSTR_frequency);
}
#else
self->handle.Init.ClockSpeed = frequency;
self->handle.Init.DutyCycle = I2C_DUTYCYCLE_2;
#endif
self->handle.Instance = I2Cx;
self->handle.Init.OwnAddress1 = 0;
self->handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
self->handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
self->handle.Init.OwnAddress2 = 0;
self->handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
self->handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
self->handle.State = HAL_I2C_STATE_RESET;
if (HAL_I2C_Init(&(self->handle)) != HAL_OK) {
mp_raise_RuntimeError(MP_ERROR_TEXT("I2C init error"));
}
common_hal_mcu_pin_claim(sda);
common_hal_mcu_pin_claim(scl);
self->frame_in_prog = false;
// start the receive interrupt chain
HAL_NVIC_DisableIRQ(self->irq); // prevent handle lock contention
HAL_NVIC_SetPriority(self->irq, 1, 0);
HAL_NVIC_EnableIRQ(self->irq);
}
void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) {
never_reset_pin_number(self->scl->pin->port, self->scl->pin->number);
never_reset_pin_number(self->sda->pin->port, self->sda->pin->number);
}
bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) {
return self->sda == NULL;
}
void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) {
if (common_hal_busio_i2c_deinited(self)) {
return;
}
i2c_clock_disable(1 << (self->sda->periph_index - 1));
reserved_i2c[self->sda->periph_index - 1] = false;
reset_pin_number(self->sda->pin->port, self->sda->pin->number);
reset_pin_number(self->scl->pin->port, self->scl->pin->number);
self->sda = NULL;
self->scl = NULL;
common_hal_busio_i2c_mark_deinit(self);
}
void common_hal_busio_i2c_mark_deinit(busio_i2c_obj_t *self) {
self->sda = NULL;
}
bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) {
return HAL_I2C_IsDeviceReady(&(self->handle), (uint16_t)(addr << 1), 2, 2) == HAL_OK;
}
bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) {
if (common_hal_busio_i2c_deinited(self)) {
return false;
}
bool grabbed_lock = false;
// Critical section code that may be required at some point.
// uint32_t store_primask = __get_PRIMASK();
// __disable_irq();
// __DMB();
if (!self->has_lock) {
grabbed_lock = true;
self->has_lock = true;
}
// __DMB();
// __set_PRIMASK(store_primask);
return grabbed_lock;
}
bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) {
return self->has_lock;
}
void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) {
self->has_lock = false;
}
static uint8_t _common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr,
const uint8_t *data, size_t len, bool transmit_stop_bit) {
HAL_StatusTypeDef result;
if (!transmit_stop_bit) {
uint32_t xfer_opt;
if (!self->frame_in_prog) {
xfer_opt = I2C_FIRST_FRAME;
} else {
// handle rare possibility of multiple restart writes in a row
xfer_opt = I2C_NEXT_FRAME;
}
result = HAL_I2C_Master_Seq_Transmit_IT(&(self->handle),
(uint16_t)(addr << 1), (uint8_t *)data,
(uint16_t)len, xfer_opt);
while (HAL_I2C_GetState(&(self->handle)) != HAL_I2C_STATE_READY) {
RUN_BACKGROUND_TASKS;
}
self->frame_in_prog = true;
} else {
result = HAL_I2C_Master_Transmit(&(self->handle), (uint16_t)(addr << 1),
(uint8_t *)data, (uint16_t)len, 500);
}
return result == HAL_OK ? 0 : MP_EIO;
}
uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr,
const uint8_t *data, size_t len) {
return _common_hal_busio_i2c_write(self, addr, data, len, true);
}
uint8_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr,
uint8_t *data, size_t len) {
if (!self->frame_in_prog) {
return HAL_I2C_Master_Receive(&(self->handle), (uint16_t)(addr << 1), data, (uint16_t)len, 500)
== HAL_OK ? 0 : MP_EIO;
} else {
HAL_StatusTypeDef result = HAL_I2C_Master_Seq_Receive_IT(&(self->handle),
(uint16_t)(addr << 1), (uint8_t *)data,
(uint16_t)len, I2C_LAST_FRAME);
while (HAL_I2C_GetState(&(self->handle)) != HAL_I2C_STATE_READY) {
RUN_BACKGROUND_TASKS;
}
self->frame_in_prog = false;
return result;
}
}
uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr,
uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) {
uint8_t result = _common_hal_busio_i2c_write(self, addr, out_data, out_len, false);
if (result != 0) {
return result;
}
return common_hal_busio_i2c_read(self, addr, in_data, in_len);
}
static void i2c_clock_enable(uint8_t mask) {
// Note: hard reset required due to soft reboot issue.
#ifdef I2C1
if (mask & (1 << 0)) {
__HAL_RCC_I2C1_CLK_ENABLE();
__HAL_RCC_I2C1_FORCE_RESET();
__HAL_RCC_I2C1_RELEASE_RESET();
}
#endif
#ifdef I2C2
if (mask & (1 << 1)) {
__HAL_RCC_I2C2_CLK_ENABLE();
__HAL_RCC_I2C2_FORCE_RESET();
__HAL_RCC_I2C2_RELEASE_RESET();
}
#endif
#ifdef I2C3
if (mask & (1 << 2)) {
__HAL_RCC_I2C3_CLK_ENABLE();
__HAL_RCC_I2C3_FORCE_RESET();
__HAL_RCC_I2C3_RELEASE_RESET();
}
#endif
#ifdef I2C4
if (mask & (1 << 3)) {
__HAL_RCC_I2C4_CLK_ENABLE();
__HAL_RCC_I2C4_FORCE_RESET();
__HAL_RCC_I2C4_RELEASE_RESET();
}
#endif
}
static void i2c_clock_disable(uint8_t mask) {
#ifdef I2C1
if (mask & (1 << 0)) {
__HAL_RCC_I2C1_CLK_DISABLE();
}
#endif
#ifdef I2C2
if (mask & (1 << 1)) {
__HAL_RCC_I2C2_CLK_DISABLE();
}
#endif
#ifdef I2C3
if (mask & (1 << 2)) {
__HAL_RCC_I2C3_CLK_DISABLE();
}
#endif
#ifdef I2C4
if (mask & (1 << 3)) {
__HAL_RCC_I2C4_CLK_DISABLE();
}
#endif
}
static void i2c_assign_irq(busio_i2c_obj_t *self, I2C_TypeDef *I2Cx) {
#ifdef I2C1
if (I2Cx == I2C1) {
self->irq = I2C1_EV_IRQn;
}
#endif
#ifdef I2C2
if (I2Cx == I2C2) {
self->irq = I2C2_EV_IRQn;
}
#endif
#ifdef I2C3
if (I2Cx == I2C3) {
self->irq = I2C3_EV_IRQn;
}
#endif
#ifdef I2C4
if (I2Cx == I2C4) {
self->irq = I2C4_EV_IRQn;
}
#endif
}
static void call_hal_irq(int i2c_num) {
// Create casted context pointer
busio_i2c_obj_t *context = (busio_i2c_obj_t *)MP_STATE_PORT(cpy_i2c_obj_all)[i2c_num - 1];
if (context != NULL) {
HAL_NVIC_ClearPendingIRQ(context->irq);
HAL_I2C_EV_IRQHandler(&context->handle);
}
}
void I2C1_EV_IRQHandler(void) {
call_hal_irq(1);
}
void I2C2_EV_IRQHandler(void) {
call_hal_irq(2);
}
void I2C3_EV_IRQHandler(void) {
call_hal_irq(3);
}
void I2C4_EV_IRQHandler(void) {
call_hal_irq(4);
}
MP_REGISTER_ROOT_POINTER(void *cpy_i2c_obj_all[MAX_I2C]);