From bd95ec5bdf5a30bff5637d1adeb6a1d2def8a3b7 Mon Sep 17 00:00:00 2001 From: cepetr Date: Tue, 27 Aug 2024 11:13:10 +0200 Subject: [PATCH] feat(core/embed): introduce non-blocking i2c drivers [no changelog] --- .../models/D001/boards/stm32f429i-disc1.h | 8 +- core/embed/models/D002/boards/stm32u5a9j-dk.h | 5 + core/embed/models/T2B1/boards/trezor_r_v10.h | 8 +- core/embed/models/T2T1/boards/trezor_t.h | 8 +- .../models/T3B1/boards/trezor_t3b1_revB.h | 5 + .../models/T3T1/boards/trezor_t3t1_revE.h | 15 + .../embed/models/T3T1/boards/trezor_t3t1_v4.h | 10 + core/embed/trezorhal/i2c_bus.h | 185 ++++ core/embed/trezorhal/stm32f4/i2c.c | 4 +- core/embed/trezorhal/stm32f4/i2c_bus.c | 886 +++++++++++++++++ core/embed/trezorhal/stm32f4/systimer.c | 2 +- core/embed/trezorhal/stm32u5/i2c_bus.c | 887 ++++++++++++++++++ core/site_scons/models/D001/discovery.py | 4 + core/site_scons/models/D002/discovery2.py | 4 + core/site_scons/models/T2B1/trezor_r_v10.py | 5 +- core/site_scons/models/T2T1/trezor_t.py | 5 + .../models/T3B1/trezor_t3b1_revB.py | 4 + .../models/T3T1/trezor_t3t1_revE.py | 4 + core/site_scons/models/T3T1/trezor_t3t1_v4.py | 5 +- 19 files changed, 2046 insertions(+), 8 deletions(-) create mode 100644 core/embed/trezorhal/i2c_bus.h create mode 100644 core/embed/trezorhal/stm32f4/i2c_bus.c create mode 100644 core/embed/trezorhal/stm32u5/i2c_bus.c diff --git a/core/embed/models/D001/boards/stm32f429i-disc1.h b/core/embed/models/D001/boards/stm32f429i-disc1.h index cd37a9712..28198342d 100644 --- a/core/embed/models/D001/boards/stm32f429i-disc1.h +++ b/core/embed/models/D001/boards/stm32f429i-disc1.h @@ -24,7 +24,13 @@ #define I2C_INSTANCE_0_SCL_PORT GPIOA #define I2C_INSTANCE_0_SCL_PIN GPIO_PIN_8 #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOA_CLK_ENABLE -#define I2C_INSTANCE_0_RESET_FLG RCC_APB1RSTR_I2C3RST +#define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR +#define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR_I2C3RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C3_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C3_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C3_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C3_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 0 #define TOUCH_I2C_INSTANCE 0 diff --git a/core/embed/models/D002/boards/stm32u5a9j-dk.h b/core/embed/models/D002/boards/stm32u5a9j-dk.h index baf3e7117..059fac72b 100644 --- a/core/embed/models/D002/boards/stm32u5a9j-dk.h +++ b/core/embed/models/D002/boards/stm32u5a9j-dk.h @@ -30,6 +30,11 @@ #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOH_CLK_ENABLE #define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR2 #define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR2_I2C5RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C5_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C5_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C5_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C5_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 0 #define TOUCH_I2C_INSTANCE 0 diff --git a/core/embed/models/T2B1/boards/trezor_r_v10.h b/core/embed/models/T2B1/boards/trezor_r_v10.h index b48479d52..24cda1e8c 100644 --- a/core/embed/models/T2B1/boards/trezor_r_v10.h +++ b/core/embed/models/T2B1/boards/trezor_r_v10.h @@ -50,7 +50,13 @@ #define I2C_INSTANCE_0_SCL_PORT GPIOB #define I2C_INSTANCE_0_SCL_PIN GPIO_PIN_10 #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE -#define I2C_INSTANCE_0_RESET_FLG RCC_APB1RSTR_I2C2RST +#define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR +#define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR_I2C2RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C2_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C2_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C2_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C2_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 50 // Optiga requires 50us guard time #define OPTIGA_I2C_INSTANCE 0 #define OPTIGA_RST_PORT GPIOD diff --git a/core/embed/models/T2T1/boards/trezor_t.h b/core/embed/models/T2T1/boards/trezor_t.h index 25a8711b7..60d569648 100644 --- a/core/embed/models/T2T1/boards/trezor_t.h +++ b/core/embed/models/T2T1/boards/trezor_t.h @@ -42,7 +42,13 @@ #define I2C_INSTANCE_0_SCL_PORT GPIOB #define I2C_INSTANCE_0_SCL_PIN GPIO_PIN_6 #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE -#define I2C_INSTANCE_0_RESET_FLG RCC_APB1RSTR_I2C1RST +#define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR +#define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR_I2C1RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C1_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C1_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C1_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C1_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 0 #define TOUCH_SENSITIVITY 0x06 #define TOUCH_I2C_INSTANCE 0 diff --git a/core/embed/models/T3B1/boards/trezor_t3b1_revB.h b/core/embed/models/T3B1/boards/trezor_t3b1_revB.h index e91a2952d..7c57e7623 100644 --- a/core/embed/models/T3B1/boards/trezor_t3b1_revB.h +++ b/core/embed/models/T3B1/boards/trezor_t3b1_revB.h @@ -54,6 +54,11 @@ #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOG_CLK_ENABLE #define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR1 #define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR1_I2C1RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C1_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C1_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C1_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C1_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 50 // Optiga requires 50us guard time #define OPTIGA_I2C_INSTANCE 0 #define OPTIGA_RST_PORT GPIOE diff --git a/core/embed/models/T3T1/boards/trezor_t3t1_revE.h b/core/embed/models/T3T1/boards/trezor_t3t1_revE.h index 591ff37b5..425b9afc4 100644 --- a/core/embed/models/T3T1/boards/trezor_t3t1_revE.h +++ b/core/embed/models/T3T1/boards/trezor_t3t1_revE.h @@ -50,6 +50,11 @@ #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE #define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR1 #define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR1_I2C1RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C1_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C1_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C1_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C1_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 0 #define I2C_INSTANCE_1 I2C2 #define I2C_INSTANCE_1_CLK_EN __HAL_RCC_I2C2_CLK_ENABLE @@ -63,6 +68,11 @@ #define I2C_INSTANCE_1_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE #define I2C_INSTANCE_1_RESET_REG &RCC->APB1RSTR1 #define I2C_INSTANCE_1_RESET_BIT RCC_APB1RSTR1_I2C2RST +#define I2C_INSTANCE_1_EV_IRQHandler I2C2_EV_IRQHandler +#define I2C_INSTANCE_1_ER_IRQHandler I2C2_ER_IRQHandler +#define I2C_INSTANCE_1_EV_IRQn I2C2_EV_IRQn +#define I2C_INSTANCE_1_ER_IRQn I2C2_ER_IRQn +#define I2C_INSTANCE_1_GUARD_TIME 0 #define I2C_INSTANCE_2 I2C3 #define I2C_INSTANCE_2_CLK_EN __HAL_RCC_I2C3_CLK_ENABLE @@ -76,6 +86,11 @@ #define I2C_INSTANCE_2_SCL_CLK_EN __HAL_RCC_GPIOC_CLK_ENABLE #define I2C_INSTANCE_2_RESET_REG &RCC->APB3RSTR #define I2C_INSTANCE_2_RESET_BIT RCC_APB3RSTR_I2C3RST +#define I2C_INSTANCE_2_EV_IRQHandler I2C3_EV_IRQHandler +#define I2C_INSTANCE_2_ER_IRQHandler I2C3_ER_IRQHandler +#define I2C_INSTANCE_2_EV_IRQn I2C3_EV_IRQn +#define I2C_INSTANCE_2_ER_IRQn I2C3_ER_IRQn +#define I2C_INSTANCE_2_GUARD_TIME 50 // Optiga requires 50us guard time #define TOUCH_PANEL_LX154A2422CPT23 1 #define TOUCH_SENSITIVITY 0x40 diff --git a/core/embed/models/T3T1/boards/trezor_t3t1_v4.h b/core/embed/models/T3T1/boards/trezor_t3t1_v4.h index bf2c0a00d..1a8741e0a 100644 --- a/core/embed/models/T3T1/boards/trezor_t3t1_v4.h +++ b/core/embed/models/T3T1/boards/trezor_t3t1_v4.h @@ -51,6 +51,11 @@ #define I2C_INSTANCE_0_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE #define I2C_INSTANCE_0_RESET_REG &RCC->APB1RSTR1 #define I2C_INSTANCE_0_RESET_BIT RCC_APB1RSTR1_I2C1RST +#define I2C_INSTANCE_0_EV_IRQHandler I2C1_EV_IRQHandler +#define I2C_INSTANCE_0_ER_IRQHandler I2C1_ER_IRQHandler +#define I2C_INSTANCE_0_EV_IRQn I2C1_EV_IRQn +#define I2C_INSTANCE_0_ER_IRQn I2C1_ER_IRQn +#define I2C_INSTANCE_0_GUARD_TIME 0 #define I2C_INSTANCE_1 I2C2 #define I2C_INSTANCE_1_CLK_EN __HAL_RCC_I2C2_CLK_ENABLE @@ -64,6 +69,11 @@ #define I2C_INSTANCE_1_SCL_CLK_EN __HAL_RCC_GPIOB_CLK_ENABLE #define I2C_INSTANCE_1_RESET_REG &RCC->APB1RSTR1 #define I2C_INSTANCE_1_RESET_BIT RCC_APB1RSTR1_I2C2RST +#define I2C_INSTANCE_1_EV_IRQHandler I2C2_EV_IRQHandler +#define I2C_INSTANCE_1_ER_IRQHandler I2C2_ER_IRQHandler +#define I2C_INSTANCE_1_EV_IRQn I2C2_EV_IRQn +#define I2C_INSTANCE_1_ER_IRQn I2C2_ER_IRQn +#define I2C_INSTANCE_1_GUARD_TIME 50 // Optiga requires 50us guard time #define TOUCH_SENSITIVITY 0x40 #define TOUCH_I2C_INSTANCE 0 diff --git a/core/embed/trezorhal/i2c_bus.h b/core/embed/trezorhal/i2c_bus.h new file mode 100644 index 000000000..42c1b1790 --- /dev/null +++ b/core/embed/trezorhal/i2c_bus.h @@ -0,0 +1,185 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_I2C_BUS_H +#define TREZORHAL_I2C_BUS_H + +#include +#include +#include + +// I2C bus abstraction +typedef struct i2c_bus i2c_bus_t; +// I2C packet (series of I2C operations) +typedef struct i2c_packet i2c_packet_t; +// I2C operation (single transfer) +typedef struct i2c_op i2c_op_t; + +// Completion callback +typedef void (*i2c_callback_t)(void* context, i2c_packet_t* packet); + +// I2C packet status +typedef enum { + I2C_STATUS_OK = 0, // Packet completed successfully + I2C_STATUS_PENDING = 1, // Packet is pending + I2C_STATUS_INVARG = 2, // Invalid packet/op parameters + I2C_STATUS_BUSY = 3, // Bus is busy + I2C_STATUS_TIMEOUT = 4, // Timeout occurred + I2C_STATUS_NACK = 5, // Device did not acknowledge + I2C_STATUS_ERROR = 6, // General error + I2C_STATUS_ABORTED = 7, // Packet was aborted + +} i2c_status_t; + +struct i2c_packet { + // Next packet in the driver queue + i2c_packet_t* next; + // I2C device address (7-bit address) + uint8_t address; + // Extra timeout (in milliseconds) added to the default timeout + // to finish each operation + uint16_t timeout; + // I2C_STATUS_xxx + i2c_status_t status; + // Number of operations + uint8_t op_count; + // Pointer to array of operations + i2c_op_t* ops; + // Completion callback function + i2c_callback_t callback; + // Callback context (user provided data) + void* context; +}; + +// I2C operation flags +#define I2C_FLAG_START 0x0001 // Generate START condition before the operation +#define I2C_FLAG_STOP 0x0002 // Generate STOP after the operation +#define I2C_FLAG_TX 0x0004 // Transmit data +#define I2C_FLAG_RX 0x0008 // Receive data +#define I2C_FLAG_EMBED 0x0010 // Embedded data (no reference) + +// I2C operation flags constraints: +// 1) I2C_FLAG_TX | I2C_FLAG_RX is not allowed +// 2) if I2C_FLAG_EMBED is set, size must be <= 4 + +struct i2c_op { + // I2C_FLAG_xxx + uint16_t flags; + // Number of bytes to transfer + uint16_t size; + // Data to read or write + union { + // Pointer to data (I2C_FLAG_EMBED is not set) + void* ptr; + // Embedded data (I2C_FLAG_EMBED is set) + uint8_t data[4]; + }; +}; + +// Acquires I2C bus reference by index (0..2 according to the model) +// +// Returns NULL if bus is not available or can't be initialized. +// +// If the bus was not acquired before, it will be initialized. +i2c_bus_t* i2c_bus_open(uint8_t bus_index); + +// Closes I2C bus handle +// +// After releasing the last bus reference, the bus will be deinitialized. +void i2c_bus_close(i2c_bus_t* bus); + +// Submits I2C packet to the bus +// +// After submitting the packet, the packet status will be set to +// I2C_STATUS_PENDING until the packet is completed. +// +// The caller must not modify the packet (or data pointed by the packet) +// until the packet is completed (callback is called or status +// is not I2C_STATUS_PENDING). +// +// Returns: +// I2C_STATUS_OK -- packet was successfully submitted +i2c_status_t i2c_bus_submit(i2c_bus_t* bus, i2c_packet_t* packet); + +// Aborts pending or queue packet +// +// Immediately after calling this function, the packet status will be +// set to I2C_STATUS_ABORTED and I2C driver will not access the packet anymore. +// +// If the packet is already completed, it does nothing. +// If the packet is queued it will be removed from the queue. +// If the packet is pending, it will be aborted. +// In any case completion callback will not be called. +void i2c_bus_abort(i2c_bus_t* bus, i2c_packet_t* packet); + +// Returns I2C packet status +// +// If the packet is not completed yet, it returns I2C_STATUS_PENDING. +i2c_status_t i2c_packet_status(i2c_packet_t* packet); + +// Waits until I2C packet is completed and returns its final status +i2c_status_t i2c_packet_wait(i2c_packet_t* packet); + +// Helper function to submit and wait for the packet +static inline i2c_status_t i2c_bus_submit_and_wait(i2c_bus_t* bus, + i2c_packet_t* packet) { + i2c_status_t status = i2c_bus_submit(bus, packet); + if (status == I2C_STATUS_OK) { + status = i2c_packet_wait(packet); + } + return status; +} + +/* +void example() { + + i2c_bus_t* bus = i2c_bus_open(DEVICE_I2C_INSTANCE); + + static uint8_t data_out; + + static i2c_op_t ops[] = { + { + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED, + .size = 1, + .data = {0x01}, + }, + { + .flags = I2C_FLAG_RX, + .size = sizeof(data_out), + .ptr = &data_out, + }, + }; + + static i2c_packet_t pkt = { + .callback = NULL, + .context = NULL, + .address = DEVICE_I2C_ADDRESS, + .op_count = ARRAY_LENGTH(ops), + .ops = ops, + }; + + status = i2c_bus_submit(bus, &pkt); + + status = i2c_packet_wait(&pkt); + + i2c_bus_close(&bus); +} +*/ + +#endif // TREZORHAL_I2C_BUS_H diff --git a/core/embed/trezorhal/stm32f4/i2c.c b/core/embed/trezorhal/stm32f4/i2c.c index 319342b76..a6b124ae0 100644 --- a/core/embed/trezorhal/stm32f4/i2c.c +++ b/core/embed/trezorhal/stm32f4/i2c.c @@ -25,7 +25,7 @@ i2c_instance_t i2c_defs[I2C_COUNT] = { .SclPin = I2C_INSTANCE_0_SCL_PIN, .SdaPin = I2C_INSTANCE_0_SDA_PIN, .PinAF = I2C_INSTANCE_0_PIN_AF, - .Reset = I2C_INSTANCE_0_RESET_FLG, + .Reset = I2C_INSTANCE_0_RESET_BIT, }, #ifdef I2C_INSTANCE_1 { @@ -35,7 +35,7 @@ i2c_instance_t i2c_defs[I2C_COUNT] = { .SclPin = I2C_INSTANCE_1_SCL_PIN, .SdaPin = I2C_INSTANCE_1_SDA_PIN, .PinAF = I2C_INSTANCE_1_PIN_AF, - .Reset = I2C_INSTANCE_1_RESET_FLG, + .Reset = I2C_INSTANCE_1_RESET_BIT, }, #endif diff --git a/core/embed/trezorhal/stm32f4/i2c_bus.c b/core/embed/trezorhal/stm32f4/i2c_bus.c new file mode 100644 index 000000000..e3b5854a5 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/i2c_bus.c @@ -0,0 +1,886 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include STM32_HAL_H +#include TREZOR_BOARD + +#include + +#include "common.h" +#include "i2c_bus.h" +#include "irq.h" +#include "systimer.h" + +// I2C bus hardware definition +typedef struct { + // I2C controller registers + I2C_TypeDef* regs; + // SCL pin GPIO port + GPIO_TypeDef* scl_port; + // SDA pin GPIO port + GPIO_TypeDef* sda_port; + // SCL pin number + uint16_t scl_pin; + // SDA pin number + uint16_t sda_pin; + // Alternate function for SCL and SDA pins + uint8_t pin_af; + // Register for I2C controller reset + volatile uint32_t* reset_reg; + // Reset bit specific for this I2C controller + uint32_t reset_bit; + // I2C event IRQ number + uint32_t ev_irq; + // I2C error IRQ number + uint32_t er_irq; + // Guard time [us] between STOP and START condition. + // If zero, the guard time is not used. + uint16_t guard_time; +} i2c_bus_def_t; + +// I2C bus hardware definitions +static const i2c_bus_def_t g_i2c_bus_def[I2C_COUNT] = { + { + .regs = I2C_INSTANCE_0, + .scl_port = I2C_INSTANCE_0_SCL_PORT, + .sda_port = I2C_INSTANCE_0_SDA_PORT, + .scl_pin = I2C_INSTANCE_0_SCL_PIN, + .sda_pin = I2C_INSTANCE_0_SDA_PIN, + .pin_af = I2C_INSTANCE_0_PIN_AF, + .reset_reg = I2C_INSTANCE_0_RESET_REG, + .reset_bit = I2C_INSTANCE_0_RESET_BIT, + .ev_irq = I2C_INSTANCE_0_EV_IRQn, + .er_irq = I2C_INSTANCE_0_ER_IRQn, + .guard_time = I2C_INSTANCE_0_GUARD_TIME, + }, +#ifdef I2C_INSTANCE_1 + { + .regs = I2C_INSTANCE_1, + .scl_port = I2C_INSTANCE_1_SCL_PORT, + .sda_port = I2C_INSTANCE_1_SDA_PORT, + .scl_pin = I2C_INSTANCE_1_SCL_PIN, + .sda_pin = I2C_INSTANCE_1_SDA_PIN, + .pin_af = I2C_INSTANCE_1_PIN_AF, + .reset_reg = I2C_INSTANCE_1_RESET_REG, + .reset_bit = I2C_INSTANCE_1_RESET_BIT, + .ev_irq = I2C_INSTANCE_1_EV_IRQn, + .er_irq = I2C_INSTANCE_1_ER_IRQn, + .guard_time = I2C_INSTANCE_1_GUARD_TIME, + }, +#endif +#ifdef I2C_INSTANCE_2 + { + .regs = I2C_INSTANCE_2, + .scl_port = I2C_INSTANCE_2_SCL_PORT, + .sda_port = I2C_INSTANCE_2_SDA_PORT, + .scl_pin = I2C_INSTANCE_2_SCL_PIN, + .sda_pin = I2C_INSTANCE_2_SDA_PIN, + .pin_af = I2C_INSTANCE_2_PIN_AF, + .reset_reg = I2C_INSTANCE_2_RESET_REG, + .reset_bit = I2C_INSTANCE_2_RESET_BIT, + .ev_irq = I2C_INSTANCE_2_EV_IRQn, + .er_irq = I2C_INSTANCE_2_ER_IRQn, + .guard_time = I2C_INSTANCE_2_GUARD_TIME, + }, +#endif +}; + +struct i2c_bus { + // Number of references to the bus + // (0 means the bus is not initialized) + uint32_t refcount; + + // Hardware definition + const i2c_bus_def_t* def; + + // Timer for timeout handling + systimer_t* timer; + + // Head of the packet queue + // (this packet is currently being processed) + i2c_packet_t* queue_head; + // Tail of the packet queue + // (this packet is the last in the queue) + i2c_packet_t* queue_tail; + + // Next operation index in the current packet + // == 0 => no operation is being processed + // == queue_head->op_count => no more operations + int next_op; + + // Current operation address byte + uint8_t addr_byte; + // Points to the data buffer of the current operation + uint8_t* buff_ptr; + // Remaining number of bytes of the buffer to transfer + uint16_t buff_size; + // Remaining number of bytes of the current operation + // (if the transfer is split into multiple operations it + // may be different from buff_size) + uint16_t transfer_size; + // For case of split transfer, points to the next operation + // that is part of the current transfer + int transfer_op; + + // Set if the STOP condition is requested after the current operation + // when data transfer is completed. + bool stop_requested; + // Set if pending transaction is being aborted + bool abort_pending; + + // Flag indicating that the completion callback is being executed + bool callback_executed; + + // The last time [us] the STOP condition was issued + uint64_t stop_time; +}; + +// I2C bus driver instances +static i2c_bus_t g_i2c_bus_driver[I2C_COUNT] = {0}; + +// Check if the I2C bus pointer is valid +static inline bool i2c_bus_ptr_valid(i2c_bus_t* bus) { + if (bus >= &g_i2c_bus_driver[0] && bus < &g_i2c_bus_driver[I2C_COUNT]) { + uintptr_t offset = (uintptr_t)bus - (uintptr_t)&g_i2c_bus_driver[0]; + if (offset % sizeof(i2c_bus_t) == 0) { + return bus->refcount > 0; + } + } + return false; +} + +// Using calculation from STM32CubeMX +// PCLKx as source, assumed 160MHz +// Fast mode, freq = 400kHz, Rise time = 250ns, Fall time = 100ns +// Fast mode, freq = 200kHz, Rise time = 250ns, Fall time = 100ns +// SCLH and SCLL are manually modified to achieve more symmetric clock +#define I2C_TIMING_400000_Hz 0x30D22728 +#define I2C_TIMING_200000_Hz 0x30D2595A +#define I2C_TIMING I2C_TIMING_200000_Hz + +// We expect the I2C bus to be running at 100kHz +// and max response time of the device is 1000us +#define I2C_BUS_CHAR_TIMEOUT 110 // us +#define I2C_BUS_OP_TIMEOUT 1000 // us + +#define I2C_BUS_TIMEOUT(n) \ + ((I2C_BUS_CHAR_TIMEOUT * 2 + I2C_BUS_OP_TIMEOUT + 999) / 1000) + +// forward declarations +static void i2c_bus_timer_callback(void* context); +static void i2c_bus_head_continue(i2c_bus_t* bus); + +static void i2c_bus_reset(i2c_bus_t* bus) { + const i2c_bus_def_t* def = bus->def; + + // Reset I2C peripheral + *def->reset_reg |= def->reset_bit; + *def->reset_reg &= ~def->reset_bit; + + I2C_TypeDef* regs = def->regs; + + // Configure I2C peripheral + + uint32_t pclk_hz = HAL_RCC_GetPCLK1Freq(); + uint32_t pclk_mhz = I2C_FREQRANGE(pclk_hz); + uint32_t i2c_speed_hz = 200000; + + regs->CR1 = 0; + regs->TRISE = I2C_RISE_TIME(pclk_mhz, i2c_speed_hz); + regs->CR2 = pclk_mhz; + regs->CCR = I2C_SPEED(pclk_hz, i2c_speed_hz, I2C_DUTYCYCLE_16_9); + regs->FLTR = 0; + regs->OAR1 = 0; + regs->OAR2 = 0; + regs->CR1 |= I2C_CR1_PE; +} + +static bool i2c_bus_init(i2c_bus_t* bus, int bus_index) { + memset(bus, 0, sizeof(i2c_bus_t)); + + const i2c_bus_def_t* def = &g_i2c_bus_def[bus_index]; + + bus->def = def; + + switch (bus_index) { + case 0: + // enable I2C clock + I2C_INSTANCE_0_CLK_EN(); + I2C_INSTANCE_0_SCL_CLK_EN(); + I2C_INSTANCE_0_SDA_CLK_EN(); + break; + +#ifdef I2C_INSTANCE_1 + case 1: + I2C_INSTANCE_1_CLK_EN(); + I2C_INSTANCE_1_SCL_CLK_EN(); + I2C_INSTANCE_1_SDA_CLK_EN(); + break; +#endif + +#ifdef I2C_INSTANCE_2 + case 2: + I2C_INSTANCE_2_CLK_EN(); + I2C_INSTANCE_2_SCL_CLK_EN(); + I2C_INSTANCE_2_SDA_CLK_EN(); + break; +#endif + default: + return false; + } + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // Configure SDA and SCL as open-drain output + // and connect to the I2C peripheral + GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->scl_pin; + HAL_GPIO_Init(def->scl_port, &GPIO_InitStructure); + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->sda_pin; + HAL_GPIO_Init(def->sda_port, &GPIO_InitStructure); + + i2c_bus_reset(bus); + + NVIC_SetPriority(def->ev_irq, IRQ_PRI_NORMAL); + NVIC_SetPriority(def->er_irq, IRQ_PRI_NORMAL); + + NVIC_EnableIRQ(def->ev_irq); + NVIC_EnableIRQ(def->er_irq); + + bus->timer = systimer_create(i2c_bus_timer_callback, bus); + if (bus->timer == NULL) { + return false; + } + + return true; +} + +static void i2c_bus_deinit(i2c_bus_t* bus) { + const i2c_bus_def_t* def = bus->def; + + systimer_delete(bus->timer); + + NVIC_DisableIRQ(def->ev_irq); + NVIC_DisableIRQ(def->er_irq); + + I2C_TypeDef* regs = def->regs; + + // Disable I2C peripheral + regs->CR1 = 0; + + // Reset I2C peripheral + *def->reset_reg |= def->reset_bit; + *def->reset_reg &= ~def->reset_bit; +} + +i2c_bus_t* i2c_bus_open(uint8_t bus_index) { + if (bus_index >= I2C_COUNT) { + return NULL; + } + + i2c_bus_t* bus = &g_i2c_bus_driver[bus_index]; + + if (bus->refcount == 0) { + if (!i2c_bus_init(bus, bus_index)) { + i2c_bus_deinit(bus); + return NULL; + } + } + + ++bus->refcount; + + return bus; +} + +void i2c_bus_close(i2c_bus_t* bus) { + if (i2c_bus_ptr_valid(bus)) { + // Bus reference is invalid or not initialized + return; + } + + if (bus->refcount > 0) { + if (--bus->refcount == 0) { + i2c_bus_deinit(bus); + } + } +} + +i2c_status_t i2c_packet_status(i2c_packet_t* packet) { + uint32_t irq_state = disable_irq(); + i2c_status_t status = packet->status; + enable_irq(irq_state); + return status; +} + +i2c_status_t i2c_packet_wait(i2c_packet_t* packet) { + while (true) { + i2c_status_t status = i2c_packet_status(packet); + + if (status != I2C_STATUS_PENDING) { + return status; + } + + // Enter sleep mode and wait for any interrupt + __WFI(); + } +} + +// Invokes the packet completion callback +static inline void i2c_bus_invoke_callback(i2c_bus_t* bus, i2c_packet_t* packet, + i2c_status_t status) { + packet->status = status; + if (packet->callback) { + bus->callback_executed = true; + packet->callback(packet->context, packet); + bus->callback_executed = false; + } +} + +// Appends the packet to the end of the queue +// Returns true if the queue was empty before +// Expects disabled IRQ or calling from IRQ context +static inline bool i2c_bus_add_packet(i2c_bus_t* bus, i2c_packet_t* packet) { + if (bus->queue_tail == NULL) { + bus->queue_head = packet; + bus->queue_tail = packet; + return true; + } else { + bus->queue_tail->next = packet; + bus->queue_tail = packet; + return false; + } +} + +// Removes the packet from the queue (if present) +// Returns true if the removed we removed head of the queue +// Expects disabled IRQ or calling from IRQ context +static inline bool i2c_bus_remove_packet(i2c_bus_t* bus, i2c_packet_t* packet) { + if (packet == bus->queue_head) { + // Remove head of the queue + bus->queue_head = packet->next; + // If the removed paacket was also the tail, reset the tail + if (bus->queue_tail == packet) { + bus->queue_tail = NULL; + } + packet->next = NULL; + return true; + } + + // Remove from the middle or tail of the queue + i2c_packet_t* p = bus->queue_head; + while (p->next != NULL && p->next != packet) { + p = p->next; + } + + if (p->next == packet) { + // The packet found in the queue, remove it + p->next = packet->next; + // Update the tail if necessary + if (bus->queue_tail == packet) { + bus->queue_tail = p; + } + packet->next = NULL; + } + + return false; +} + +i2c_status_t i2c_bus_submit(i2c_bus_t* bus, i2c_packet_t* packet) { + if (!i2c_bus_ptr_valid(bus) || packet == NULL) { + // Invalid bus or packet + return I2C_STATUS_ERROR; + } + + if (packet->next != NULL) { + // Packet is already queued + return I2C_STATUS_ERROR; + } + + packet->status = I2C_STATUS_PENDING; + + // Insert packet into the queue + uint32_t irq_state = disable_irq(); + if (i2c_bus_add_packet(bus, packet)) { + // The queue was empty, start the operation + if (!bus->callback_executed && !bus->abort_pending) { + i2c_bus_head_continue(bus); + } + } + enable_irq(irq_state); + + return I2C_STATUS_OK; +} + +void i2c_bus_abort(i2c_bus_t* bus, i2c_packet_t* packet) { + if (!i2c_bus_ptr_valid(bus) || packet == NULL) { + // Invalid bus or packet + return; + } + + uint32_t irq_state = disable_irq(); + + if (packet->status == I2C_STATUS_PENDING) { + if (i2c_bus_remove_packet(bus, packet) && bus->next_op > 0) { + // The packet was being processed + + // Reset internal state + bus->next_op = 0; + bus->buff_ptr = NULL; + bus->buff_size = 0; + bus->transfer_size = 0; + bus->transfer_op = 0; + + // Inform interrupt handler about pending abort + bus->abort_pending = true; + bus->stop_requested = true; + + // Abort operation may fail if the bus is busy or noisy + // so we need to set a timeout. + systimer_set(bus->timer, I2C_BUS_TIMEOUT(2)); + } + packet->status = I2C_STATUS_ABORTED; + } + + enable_irq(irq_state); +} + +// Completes the current packet by removing it from the queue +// an invoking the completion callback +// +// Must be called with IRQ disabled or from IRQ context +// Expects the operation is finished +static void i2c_bus_head_complete(i2c_bus_t* bus, i2c_status_t status) { + i2c_packet_t* packet = bus->queue_head; + if (packet != NULL) { + // Remove packet from the queue + i2c_bus_remove_packet(bus, packet); + + // Reset internal state + bus->next_op = 0; + bus->buff_ptr = NULL; + bus->buff_size = 0; + bus->transfer_size = 0; + bus->transfer_op = 0; + bus->abort_pending = false; + + systimer_unset(bus->timer); + + // Invoke the completion callback + i2c_bus_invoke_callback(bus, packet, status); + } +} + +// Starts the next operation in the packet by +// programming the I2C controller +// +// Must be called with IRQ disabled or from IRQ context +// Expects no other operation is being processed +static void i2c_bus_head_continue(i2c_bus_t* bus) { + I2C_TypeDef* regs = bus->def->regs; + + if (bus->stop_requested) { + // Issue STOP condition + regs->CR1 |= I2C_CR1_STOP; + if (bus->def->guard_time > 0) { + bus->stop_time = systick_us(); + } + bus->stop_requested = false; + } + + if (bus->abort_pending) { + systimer_unset(bus->timer); + bus->abort_pending = false; + } + + uint32_t cr1 = regs->CR1; + cr1 &= ~(I2C_CR1_POS | I2C_CR1_ACK | I2C_CR1_STOP | I2C_CR1_START); + + uint32_t cr2 = regs->CR2; + cr2 &= ~(I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); + + if (bus->queue_head != NULL) { + i2c_packet_t* packet = bus->queue_head; + + if (bus->next_op < packet->op_count) { + i2c_op_t* op = &packet->ops[bus->next_op++]; + + // Get data ptr and data length + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + + // Calculate transfer size + bus->transfer_size = bus->buff_size; + bus->transfer_op = bus->next_op; + + // Include following operations in the transfer if: + // 1) We are not processing the last operation + // 2) STOP condition is not requested in the current operation + // 3) START condition is not requested in the next operation + // 4) The next operation has the same direction + + while ((bus->next_op != packet->op_count) && + ((op->flags & I2C_FLAG_STOP) == 0) && + (((op + 1)->flags & I2C_FLAG_START) == 0) && + (((op + 1)->flags & I2C_FLAG_TX) == (op->flags & I2C_FLAG_TX))) { + // Move to the next operation + op = &packet->ops[bus->next_op++]; + + if (op->flags & I2C_FLAG_EMBED) { + bus->transfer_size += MIN(op->size, sizeof(op->data)); + } else { + bus->transfer_size += op->size; + } + } + + // STOP conditition: + // 1) if it is explicitly requested + // 2) if it is the last operation in the packet + bus->stop_requested = ((op->flags & I2C_FLAG_STOP) != 0) || + (bus->next_op == packet->op_count); + + // Calculate address byte + bus->addr_byte = packet->address << 1; + + // ACK, POS, ITBUFEN flags are set based on the operation + if (bus->transfer_size > 0) { + if (op->flags & I2C_FLAG_TX) { + cr2 |= I2C_CR2_ITBUFEN; + } else if (op->flags & I2C_FLAG_RX) { + bus->addr_byte |= 1; // Set RW bit to 1 (READ) + if (bus->transfer_size == 1) { + cr2 |= I2C_CR2_ITBUFEN; + } else if (bus->transfer_size == 2) { + cr1 |= I2C_CR1_POS; + } else if (bus->transfer_size == 3) { + cr1 |= I2C_CR1_ACK; + } else if (bus->transfer_size > 3) { + cr2 |= I2C_CR2_ITBUFEN; + cr1 |= I2C_CR1_ACK; + } + } + } + + // Enable event and error interrupts + cr2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; + + // Generate start condition + // (this also clears all status flags) + cr1 |= I2C_CR1_START; + + // Each operation has its own timeout calculated + // based on the number of bytes to transfer and the bus speed + + // expected operation overhead + systimer_set(bus->timer, + I2C_BUS_TIMEOUT(bus->transfer_size) + packet->timeout); + + // Guard time between operations STOP and START condition + if (bus->def->guard_time > 0) { + // Add 5us as a safety margin since the stop_time was set before the + // STOP condition was issued + uint16_t guard_time = bus->def->guard_time + 5; + while (systick_us() - bus->stop_time < guard_time) + ; + } + } + + // Clear BTF flag + (void)regs->DR; + } + + regs->CR1 = cr1; + regs->CR2 = cr2; +} + +// Timer callback handling I2C bus timeout +static void i2c_bus_timer_callback(void* context) { + i2c_bus_t* bus = (i2c_bus_t*)context; + + if (bus->abort_pending) { + // This may be cause by the bus being busy/noisy. + // Reset I2C Controller + i2c_bus_reset(bus); + // Start the next packet + i2c_bus_head_continue(bus); + } else { + // Timeout during normal operation occurred + i2c_packet_t* packet = bus->queue_head; + if (packet != NULL) { + // Determine the status based on the current bus state + I2C_TypeDef* regs = bus->def->regs; + i2c_status_t status; + + if ((regs->CR1 & I2C_CR1_START) && (regs->SR2 & I2C_SR2_BUSY)) { + // START condition was issued but the bus is still busy + status = I2C_STATUS_BUSY; + } else { + status = I2C_STATUS_TIMEOUT; + } + + // Abort pending packet + i2c_bus_abort(bus, packet); + // Invoke the completion callback + i2c_bus_invoke_callback(bus, packet, status); + } + } +} + +static uint8_t i2c_bus_read_buff(i2c_bus_t* bus) { + if (bus->transfer_size > 0) { + while (bus->buff_size == 0 && bus->transfer_op < bus->next_op) { + i2c_op_t* op = &bus->queue_head->ops[bus->transfer_op++]; + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + } + + --bus->transfer_size; + + if (bus->buff_size > 0) { + --bus->buff_size; + return *bus->buff_ptr++; + } + } + + return 0; +} + +static void i2c_bus_write_buff(i2c_bus_t* bus, uint8_t data) { + if (bus->transfer_size > 0) { + while (bus->buff_size == 0 && bus->transfer_op < bus->next_op) { + i2c_op_t* op = &bus->queue_head->ops[bus->transfer_op++]; + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + } + + --bus->transfer_size; + + if (bus->buff_size > 0) { + *bus->buff_ptr++ = data; + --bus->buff_size; + } + } +} + +// I2C bus event interrupt handler +static void i2c_bus_ev_handler(i2c_bus_t* bus) { + I2C_TypeDef* regs = bus->def->regs; + + uint32_t sr1 = regs->SR1; + + if (sr1 & I2C_SR1_SB) { + // START condition generated + // Send the address byte + regs->DR = bus->addr_byte; + // Operation cannot be aborted at this point. + // We need to wait for ADDR flag. + } else if (sr1 & I2C_SR1_ADDR) { + // Address sent and ACKed by the slave + // By reading SR2 we clear ADDR flag and start the data transfer + regs->SR2; + + if (bus->abort_pending) { + // Only TX operation can be aborted at this point + // For RX operation, we need to wait for the first byte + if ((bus->addr_byte & 1) == 0) { + // Issue STOP condition and start the next packet + i2c_bus_head_continue(bus); + } + } else if (bus->transfer_size == 0) { + // Operation contains only address without any data + if (bus->next_op == bus->queue_head->op_count) { + i2c_bus_head_complete(bus, I2C_STATUS_OK); + } + i2c_bus_head_continue(bus); + } + } else if ((bus->addr_byte & 1) == 0) { + // Data transmit phase + if (bus->abort_pending) { + // Issue STOP condition and start the next packet + i2c_bus_head_continue(bus); + } else if ((sr1 & I2C_SR1_TXE) && (regs->CR2 & I2C_CR2_ITBUFEN)) { + // I2C controller transmit buffer is empty. + // The interrupt flag is cleared by writing the DR register. + if (bus->transfer_size > 0) { + // Send the next byte + regs->DR = i2c_bus_read_buff(bus); + if (bus->transfer_size == 0) { + // All data bytes were transmitted + // Disable RXNE interrupt and wait for BTF + regs->CR2 &= ~I2C_CR2_ITBUFEN; + } + } + } else if (sr1 & I2C_SR1_BTF) { + if (bus->transfer_size == 0) { + // All data bytes were shifted out + if (bus->next_op == bus->queue_head->op_count) { + // Last operation in the packet + i2c_bus_head_complete(bus, I2C_STATUS_OK); + } + i2c_bus_head_continue(bus); + } + } + } else { // Data receive phase + if (bus->abort_pending) { + regs->CR1 &= ~(I2C_CR1_ACK | I2C_CR1_POS); + (void)regs->DR; + // Issue STOP condition and start the next packet + i2c_bus_head_continue(bus); + } else if ((sr1 & I2C_SR1_RXNE) && (regs->CR2 & I2C_CR2_ITBUFEN)) { + uint8_t received_byte = regs->DR; + if (bus->transfer_size > 0) { + // Receive the next byte + i2c_bus_write_buff(bus, received_byte); + if (bus->transfer_size == 3) { + // 3 bytes left to receive + // Disable RXNE interrupt and wait for BTF + regs->CR2 &= ~I2C_CR2_ITBUFEN; + } else if (bus->transfer_size == 0) { + // All data bytes were received + // We get here only in case of 1 byte transfers + if (bus->next_op == bus->queue_head->op_count) { + // Last operation in the packet + i2c_bus_head_complete(bus, I2C_STATUS_OK); + } + i2c_bus_head_continue(bus); + } + } + } else if (sr1 & I2C_SR1_BTF) { + if (bus->transfer_size == 3) { + // 3 bytes left to receive + regs->CR1 &= ~I2C_CR1_ACK; + i2c_bus_write_buff(bus, regs->DR); + } else if (bus->transfer_size == 2) { + // 2 left bytes are already in DR a shift register + if (bus->stop_requested) { + // Issue STOP condition before reading the 2 last bytes + regs->CR1 |= I2C_CR1_STOP; + if (bus->def->guard_time > 0) { + bus->stop_time = systick_us(); + } + bus->stop_requested = false; + } + i2c_bus_write_buff(bus, regs->DR); + i2c_bus_write_buff(bus, regs->DR); + + if (bus->next_op == bus->queue_head->op_count) { + i2c_bus_head_complete(bus, I2C_STATUS_OK); + } + i2c_bus_head_continue(bus); + } + } + } +} + +// I2C bus error interrupt handler +static void i2c_bus_er_handler(i2c_bus_t* bus) { + I2C_TypeDef* regs = bus->def->regs; + + uint32_t sr1 = regs->SR1; + + // Clear error flags + regs->SR1 &= ~(I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR); + + if (sr1 & I2C_SR1_AF) { + // NACK received + if (bus->abort_pending) { + // Start the next packet + i2c_bus_head_continue(bus); + } else if (bus->next_op > 0) { + // Complete packet with error + i2c_bus_head_complete(bus, I2C_STATUS_NACK); + // Issue stop condition and start the next packet + bus->stop_requested = true; + i2c_bus_head_continue(bus); + } else { + // Invalid state + } + } + + if (sr1 & I2C_SR1_ARLO) { + if (bus->abort_pending) { + // Packet aborted or invalid state + // Start the next packet + bus->stop_requested = false; + i2c_bus_head_continue(bus); + } else if (bus->next_op > 0) { + // Arbitration lost, complete packet with error + i2c_bus_head_complete(bus, I2C_STATUS_ERROR); + // Start the next packet + bus->stop_requested = false; + i2c_bus_head_continue(bus); + } + } + + if (sr1 & I2C_SR1_BERR) { + // Bus error + // Ignore and continue with pending operation + } +} + +// Interrupt handlers + +#ifdef I2C_INSTANCE_0 +void I2C_INSTANCE_0_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[0]); +} + +void I2C_INSTANCE_0_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[0]); +} +#endif + +#ifdef I2C_INSTANCE_1 +void I2C_INSTANCE_1_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[1]); +} + +void I2C_INSTANCE_1_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[1]); +} +#endif + +#ifdef I2C_INSTANCE_2 +void I2C_INSTANCE_2_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[2]); +} + +void I2C_INSTANCE_2_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[2]); +} +#endif diff --git a/core/embed/trezorhal/stm32f4/systimer.c b/core/embed/trezorhal/stm32f4/systimer.c index 57fa8b688..8636d3c6c 100644 --- a/core/embed/trezorhal/stm32f4/systimer.c +++ b/core/embed/trezorhal/stm32f4/systimer.c @@ -29,7 +29,7 @@ // // Consider different implementation (i.e. priority queue // using binary heap if MAX_SYSTIMERS exceeds 10 or more) -#define MAX_SYSTIMERS 4 +#define MAX_SYSTIMERS 8 // User timer instance struct systimer { diff --git a/core/embed/trezorhal/stm32u5/i2c_bus.c b/core/embed/trezorhal/stm32u5/i2c_bus.c new file mode 100644 index 000000000..4237cd4b3 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/i2c_bus.c @@ -0,0 +1,887 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include STM32_HAL_H +#include TREZOR_BOARD + +#include + +#include "common.h" +#include "i2c_bus.h" +#include "irq.h" +#include "systimer.h" + +// I2C bus hardware definition +typedef struct { + // I2C controller registers + I2C_TypeDef* regs; + // SCL pin GPIO port + GPIO_TypeDef* scl_port; + // SDA pin GPIO port + GPIO_TypeDef* sda_port; + // SCL pin number + uint16_t scl_pin; + // SDA pin number + uint16_t sda_pin; + // Alternate function for SCL and SDA pins + uint8_t pin_af; + // Register for I2C controller reset + volatile uint32_t* reset_reg; + // Reset bit specific for this I2C controller + uint32_t reset_bit; + // I2C event IRQ number + uint32_t ev_irq; + // I2C error IRQ number + uint32_t er_irq; + // Guard time [us] between STOP and START condition. + // If zero, the guard time is not used. + uint16_t guard_time; +} i2c_bus_def_t; + +// I2C bus hardware definitions +static const i2c_bus_def_t g_i2c_bus_def[I2C_COUNT] = { + { + .regs = I2C_INSTANCE_0, + .scl_port = I2C_INSTANCE_0_SCL_PORT, + .sda_port = I2C_INSTANCE_0_SDA_PORT, + .scl_pin = I2C_INSTANCE_0_SCL_PIN, + .sda_pin = I2C_INSTANCE_0_SDA_PIN, + .pin_af = I2C_INSTANCE_0_PIN_AF, + .reset_reg = I2C_INSTANCE_0_RESET_REG, + .reset_bit = I2C_INSTANCE_0_RESET_BIT, + .ev_irq = I2C_INSTANCE_0_EV_IRQn, + .er_irq = I2C_INSTANCE_0_ER_IRQn, + .guard_time = I2C_INSTANCE_0_GUARD_TIME, + }, +#ifdef I2C_INSTANCE_1 + { + .regs = I2C_INSTANCE_1, + .scl_port = I2C_INSTANCE_1_SCL_PORT, + .sda_port = I2C_INSTANCE_1_SDA_PORT, + .scl_pin = I2C_INSTANCE_1_SCL_PIN, + .sda_pin = I2C_INSTANCE_1_SDA_PIN, + .pin_af = I2C_INSTANCE_1_PIN_AF, + .reset_reg = I2C_INSTANCE_1_RESET_REG, + .reset_bit = I2C_INSTANCE_1_RESET_BIT, + .ev_irq = I2C_INSTANCE_1_EV_IRQn, + .er_irq = I2C_INSTANCE_1_ER_IRQn, + .guard_time = I2C_INSTANCE_1_GUARD_TIME, + }, +#endif +#ifdef I2C_INSTANCE_2 + { + .regs = I2C_INSTANCE_2, + .scl_port = I2C_INSTANCE_2_SCL_PORT, + .sda_port = I2C_INSTANCE_2_SDA_PORT, + .scl_pin = I2C_INSTANCE_2_SCL_PIN, + .sda_pin = I2C_INSTANCE_2_SDA_PIN, + .pin_af = I2C_INSTANCE_2_PIN_AF, + .reset_reg = I2C_INSTANCE_2_RESET_REG, + .reset_bit = I2C_INSTANCE_2_RESET_BIT, + .ev_irq = I2C_INSTANCE_2_EV_IRQn, + .er_irq = I2C_INSTANCE_2_ER_IRQn, + .guard_time = I2C_INSTANCE_2_GUARD_TIME, + }, +#endif +}; + +struct i2c_bus { + // Number of references to the bus + // (0 means the bus is not initialized) + uint32_t refcount; + + // Hardware definition + const i2c_bus_def_t* def; + + // Timer for timeout handling + systimer_t* timer; + + // Head of the packet queue + // (this packet is currently being processed) + i2c_packet_t* queue_head; + // Tail of the packet queue + // (this packet is the last in the queue) + i2c_packet_t* queue_tail; + + // Next operation index in the current packet + // == 0 => no operation is being processed + // == queue_head->op_count => no more operations + int next_op; + + // Points to the data buffer of the current operation + uint8_t* buff_ptr; + // Remaining number of bytes of the buffer to transfer + uint16_t buff_size; + // Remaining number of bytes of the current operation + // (if the transfer is split into multiple operations it + // may be different from buff_size) + uint16_t transfer_size; + // For case of split transfer, points to the next operation + // that is part of the current transfer + int transfer_op; + + // Set if the STOP condition is requested after the current operation + // when data transfer is completed. + bool stop_requested; + // Set if pending transaction is being aborted + bool abort_pending; + // Set if NACK was detected + bool nack; + // Data for clearing TXIS interrupt flag + // during an invalid or abort state + uint8_t dummy_data; + + // Flag indicating that the completion callback is being executed + bool callback_executed; + + // The last time [us] the STOP condition was issued + uint64_t stop_time; +}; + +// I2C bus driver instances +static i2c_bus_t g_i2c_bus_driver[I2C_COUNT] = {0}; + +// Check if the I2C bus pointer is valid +static inline bool i2c_bus_ptr_valid(i2c_bus_t* bus) { + if (bus >= &g_i2c_bus_driver[0] && bus < &g_i2c_bus_driver[I2C_COUNT]) { + uintptr_t offset = (uintptr_t)bus - (uintptr_t)&g_i2c_bus_driver[0]; + if (offset % sizeof(i2c_bus_t) == 0) { + return bus->refcount > 0; + } + } + return false; +} + +// Using calculation from STM32CubeMX +// PCLKx as source, assumed 160MHz +// Fast mode, freq = 400kHz, Rise time = 250ns, Fall time = 100ns +// Fast mode, freq = 200kHz, Rise time = 250ns, Fall time = 100ns +// SCLH and SCLL are manually modified to achieve more symmetric clock +#define I2C_TIMING_400000_Hz 0x30D22728 +#define I2C_TIMING_200000_Hz 0x30D2595A +#define I2C_TIMING I2C_TIMING_200000_Hz + +// We expect the I2C bus to be running at 100kHz +// and max response time of the device is 1000us +#define I2C_BUS_CHAR_TIMEOUT 110 // us +#define I2C_BUS_OP_TIMEOUT 1000 // us + +#define I2C_BUS_TIMEOUT(n) \ + ((I2C_BUS_CHAR_TIMEOUT * 2 + I2C_BUS_OP_TIMEOUT + 999) / 1000) + +// forward declarations +static void i2c_bus_timer_callback(void* context); +static void i2c_bus_head_continue(i2c_bus_t* bus); + +static void i2c_bus_unlock(i2c_bus_t* bus) { + const i2c_bus_def_t* def = bus->def; + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // Set SDA and SCL high + HAL_GPIO_WritePin(def->sda_port, def->sda_pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(def->scl_port, def->scl_pin, GPIO_PIN_SET); + + // Configure SDA and SCL as open-drain output + // and connect to the I2C peripheral + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->scl_pin; + HAL_GPIO_Init(def->scl_port, &GPIO_InitStructure); + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->sda_pin; + HAL_GPIO_Init(def->sda_port, &GPIO_InitStructure); + + uint32_t clock_count = 16; + + while ((HAL_GPIO_ReadPin(def->sda_port, def->sda_pin) == GPIO_PIN_RESET) && + (clock_count-- > 0)) { + // Clock SCL + HAL_GPIO_WritePin(def->scl_port, def->scl_pin, GPIO_PIN_RESET); + systick_delay_us(10); + HAL_GPIO_WritePin(def->scl_port, def->scl_pin, GPIO_PIN_SET); + systick_delay_us(10); + } +} + +static bool i2c_bus_init(i2c_bus_t* bus, int bus_index) { + memset(bus, 0, sizeof(i2c_bus_t)); + + const i2c_bus_def_t* def = &g_i2c_bus_def[bus_index]; + + bus->def = def; + + switch (bus_index) { + case 0: + // enable I2C clock + I2C_INSTANCE_0_CLK_EN(); + I2C_INSTANCE_0_SCL_CLK_EN(); + I2C_INSTANCE_0_SDA_CLK_EN(); + break; + +#ifdef I2C_INSTANCE_1 + case 1: + I2C_INSTANCE_1_CLK_EN(); + I2C_INSTANCE_1_SCL_CLK_EN(); + I2C_INSTANCE_1_SDA_CLK_EN(); + break; +#endif + +#ifdef I2C_INSTANCE_2 + case 2: + I2C_INSTANCE_2_CLK_EN(); + I2C_INSTANCE_2_SCL_CLK_EN(); + I2C_INSTANCE_2_SDA_CLK_EN(); + break; +#endif + default: + return false; + } + + // Unlocks potentialy locked I2C bus by + // generating 9 clock pulses on SCL while SDA is low + i2c_bus_unlock(bus); + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // Configure SDA and SCL as open-drain output + // and connect to the I2C peripheral + GPIO_InitStructure.Mode = GPIO_MODE_AF_OD; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->scl_pin; + HAL_GPIO_Init(def->scl_port, &GPIO_InitStructure); + + GPIO_InitStructure.Alternate = def->pin_af; + GPIO_InitStructure.Pin = def->sda_pin; + HAL_GPIO_Init(def->sda_port, &GPIO_InitStructure); + + // Reset I2C peripheral + *def->reset_reg |= def->reset_bit; + *def->reset_reg &= ~def->reset_bit; + + I2C_TypeDef* regs = def->regs; + + // Configure I2C peripheral + regs->CR1 = 0; + regs->TIMINGR = I2C_TIMING; + regs->CR2 = 0; + regs->OAR1 = 0; + regs->OAR2 = 0; + regs->CR1 |= I2C_CR1_PE; + + // Configure I2C interrupts + regs->CR1 |= I2C_CR1_ERRIE | I2C_CR1_NACKIE | I2C_CR1_STOPIE | I2C_CR1_TCIE | + I2C_CR1_RXIE | I2C_CR1_TXIE; + + NVIC_SetPriority(def->ev_irq, IRQ_PRI_NORMAL); + NVIC_SetPriority(def->er_irq, IRQ_PRI_NORMAL); + + NVIC_EnableIRQ(def->ev_irq); + NVIC_EnableIRQ(def->er_irq); + + bus->timer = systimer_create(i2c_bus_timer_callback, bus); + if (bus->timer == NULL) { + return false; + } + + return true; +} + +static void i2c_bus_deinit(i2c_bus_t* bus) { + const i2c_bus_def_t* def = bus->def; + + systimer_delete(bus->timer); + + NVIC_DisableIRQ(def->ev_irq); + NVIC_DisableIRQ(def->er_irq); + + I2C_TypeDef* regs = def->regs; + + // Disable I2C peripheral + regs->CR1 = 0; + + // Reset I2C peripheral + *def->reset_reg |= def->reset_bit; + *def->reset_reg &= ~def->reset_bit; +} + +i2c_bus_t* i2c_bus_open(uint8_t bus_index) { + if (bus_index >= I2C_COUNT) { + return NULL; + } + + i2c_bus_t* bus = &g_i2c_bus_driver[bus_index]; + + if (bus->refcount == 0) { + if (!i2c_bus_init(bus, bus_index)) { + i2c_bus_deinit(bus); + return NULL; + } + } + + ++bus->refcount; + + return bus; +} + +void i2c_bus_close(i2c_bus_t* bus) { + if (!i2c_bus_ptr_valid(bus)) { + return; + } + + if (bus->refcount > 0) { + if (--bus->refcount == 0) { + i2c_bus_deinit(bus); + } + } +} + +i2c_status_t i2c_packet_status(i2c_packet_t* packet) { + uint32_t irq_state = disable_irq(); + i2c_status_t status = packet->status; + enable_irq(irq_state); + return status; +} + +i2c_status_t i2c_packet_wait(i2c_packet_t* packet) { + while (true) { + i2c_status_t status = i2c_packet_status(packet); + + if (status != I2C_STATUS_PENDING) { + return status; + } + + // Enter sleep mode and wait for any interrupt + __WFI(); + } +} + +static uint8_t i2c_bus_read_buff(i2c_bus_t* bus) { + if (bus->transfer_size > 0) { + while (bus->buff_size == 0 && bus->transfer_op < bus->next_op) { + i2c_op_t* op = &bus->queue_head->ops[bus->transfer_op++]; + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + } + + --bus->transfer_size; + + if (bus->buff_size > 0) { + --bus->buff_size; + return *bus->buff_ptr++; + } + } + + return 0; +} + +static void i2c_bus_write_buff(i2c_bus_t* bus, uint8_t data) { + if (bus->transfer_size > 0) { + while (bus->buff_size == 0 && bus->transfer_op < bus->next_op) { + i2c_op_t* op = &bus->queue_head->ops[bus->transfer_op++]; + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + } + + --bus->transfer_size; + + if (bus->buff_size > 0) { + *bus->buff_ptr++ = data; + --bus->buff_size; + } + } +} + +// Invokes the packet completion callback +static inline void i2c_bus_invoke_callback(i2c_bus_t* bus, i2c_packet_t* packet, + i2c_status_t status) { + packet->status = status; + if (packet->callback) { + bus->callback_executed = true; + packet->callback(packet->context, packet); + bus->callback_executed = false; + } +} + +// Appends the packet to the end of the queue +// Returns true if the queue was empty before +// Expects disabled IRQ or calling from IRQ context +static inline bool i2c_bus_add_packet(i2c_bus_t* bus, i2c_packet_t* packet) { + if (bus->queue_tail == NULL) { + bus->queue_head = packet; + bus->queue_tail = packet; + return true; + } else { + bus->queue_tail->next = packet; + bus->queue_tail = packet; + return false; + } +} + +// Removes the packet from the queue (if present) +// Returns true if the removed we removed head of the queue +// Expects disabled IRQ or calling from IRQ context +static inline bool i2c_bus_remove_packet(i2c_bus_t* bus, i2c_packet_t* packet) { + if (packet == bus->queue_head) { + // Remove head of the queue + bus->queue_head = packet->next; + // If the removed paacket was also the tail, reset the tail + if (bus->queue_tail == packet) { + bus->queue_tail = NULL; + } + packet->next = NULL; + return true; + } + + // Remove from the middle or tail of the queue + i2c_packet_t* p = bus->queue_head; + while (p->next != NULL && p->next != packet) { + p = p->next; + } + + if (p->next == packet) { + // The packet found in the queue, remove it + p->next = packet->next; + // Update the tail if necessary + if (bus->queue_tail == packet) { + bus->queue_tail = p; + } + packet->next = NULL; + } + + return false; +} + +i2c_status_t i2c_bus_submit(i2c_bus_t* bus, i2c_packet_t* packet) { + if (!i2c_bus_ptr_valid(bus) || packet == NULL) { + // Invalid bus or packet + return I2C_STATUS_ERROR; + } + + if (packet->next != NULL) { + // Packet is already queued + return I2C_STATUS_ERROR; + } + + packet->status = I2C_STATUS_PENDING; + + // Insert packet into the queue + uint32_t irq_state = disable_irq(); + if (i2c_bus_add_packet(bus, packet)) { + // The queue was empty, start the operation + if (!bus->callback_executed && !bus->abort_pending) { + i2c_bus_head_continue(bus); + } + } + enable_irq(irq_state); + + return I2C_STATUS_OK; +} + +void i2c_bus_abort(i2c_bus_t* bus, i2c_packet_t* packet) { + if (!i2c_bus_ptr_valid(bus) || packet == NULL) { + // Invalid bus or packet + return; + } + + uint32_t irq_state = disable_irq(); + + if (packet->status == I2C_STATUS_PENDING) { + if (i2c_bus_remove_packet(bus, packet) && bus->next_op > 0) { + // The packet was being processed + + if (bus->transfer_size > 0) { + bus->dummy_data = i2c_bus_read_buff(bus); + } + + // Reset internal state + bus->next_op = 0; + bus->buff_ptr = NULL; + bus->buff_size = 0; + bus->transfer_size = 0; + bus->transfer_op = 0; + bus->stop_requested = false; + + // Inform interrupt handler about pending abort + bus->abort_pending = true; + + // Abort operation may fail if the bus is busy or noisy + // so we need to set a timeout. + systimer_set(bus->timer, I2C_BUS_TIMEOUT(2)); + } + + packet->status = I2C_STATUS_ABORTED; + } + + enable_irq(irq_state); +} + +// Completes the current packet by removing it from the queue +// an invoking the completion callback +// +// Must be called with IRQ disabled or from IRQ context +// Expects the operation is finished +static void i2c_bus_head_complete(i2c_bus_t* bus, i2c_status_t status) { + i2c_packet_t* packet = bus->queue_head; + if (packet != NULL) { + // Remove packet from the queue + i2c_bus_remove_packet(bus, packet); + + // Reset internal state + bus->next_op = 0; + bus->buff_ptr = NULL; + bus->buff_size = 0; + bus->transfer_size = 0; + bus->transfer_op = 0; + bus->stop_requested = false; + bus->abort_pending = false; + + systimer_unset(bus->timer); + + // Invoke the completion callback + i2c_bus_invoke_callback(bus, packet, status); + } +} + +// Starts the next operation in the packet by +// programming the I2C controller +// +// Must be called with IRQ disabled or from IRQ context +// Expects no other operation is being processed +static void i2c_bus_head_continue(i2c_bus_t* bus) { + if (bus->abort_pending) { + systimer_unset(bus->timer); + bus->abort_pending = false; + } + + if (bus->queue_head != NULL) { + i2c_packet_t* packet = bus->queue_head; + + if (bus->next_op < packet->op_count) { + i2c_op_t* op = &packet->ops[bus->next_op++]; + I2C_TypeDef* regs = bus->def->regs; + + uint32_t cr2 = regs->CR2; + cr2 &= ~(I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | + I2C_CR2_AUTOEND | I2C_CR2_RD_WRN | I2C_CR2_SADD_Msk); + + // Set device address + cr2 |= ((packet->address & 0x7F) << 1) << I2C_CR2_SADD_Pos; + + // Get data ptr and its length + if (op->flags & I2C_FLAG_EMBED) { + bus->buff_ptr = op->data; + bus->buff_size = MIN(op->size, sizeof(op->data)); + } else { + bus->buff_ptr = op->ptr; + bus->buff_size = op->size; + } + + // Calculate transfer size + bus->transfer_size = bus->buff_size; + bus->transfer_op = bus->next_op; + + // Include following operations in the transfer if: + // 1) We are not processing the last operation + // 2) STOP condition is not requested in the current operation + // 3) START condition is not requested in the next operation + // 4) The next operation has the same direction + + while ((bus->next_op != packet->op_count) && + ((op->flags & I2C_FLAG_STOP) == 0) && + (((op + 1)->flags & I2C_FLAG_START) == 0) && + (((op + 1)->flags & I2C_FLAG_TX) == (op->flags & I2C_FLAG_TX))) { + // Move to the next operation + op = &packet->ops[bus->next_op++]; + + if (op->flags & I2C_FLAG_EMBED) { + bus->transfer_size += MIN(op->size, sizeof(op->data)); + } else { + bus->transfer_size += op->size; + } + } + + if (bus->transfer_size > 0) { + // I2C controller can handle only 255 bytes at once + // More data will be handled by the TCR interrupt + cr2 |= MIN(255, bus->transfer_size) << I2C_CR2_NBYTES_Pos; + + if (bus->transfer_size > 255) { + cr2 |= I2C_CR2_RELOAD; + } + + if (op->flags & I2C_FLAG_TX) { + // Transmitting has priority over receive. + // Flush TXDR register possibly filled by some previous + // invalid operation or abort. + regs->ISR = I2C_ISR_TXE; + } else if (op->flags & I2C_FLAG_RX) { + // Receive data from the device + cr2 |= I2C_CR2_RD_WRN; + } + } + + // STOP conditition: + // 1) if it is explicitly requested + // 2) if it is the last operation in the packet + bus->stop_requested = ((op->flags & I2C_FLAG_STOP) != 0) || + (bus->next_op == packet->op_count); + + bus->nack = false; + + // START condition + cr2 |= I2C_CR2_START; + + // Guard time between operations STOP and START condition + if (bus->def->guard_time > 0) { + while (systick_us() - bus->stop_time < bus->def->guard_time) + ; + } + + regs->CR2 = cr2; + + // Each operation has its own timeout calculated + // based on the number of bytes to transfer and the bus speed + + // expected operation overhead + systimer_set(bus->timer, + I2C_BUS_TIMEOUT(bus->transfer_size) + packet->timeout); + } + } +} + +// Timer callback handling I2C bus timeout +static void i2c_bus_timer_callback(void* context) { + i2c_bus_t* bus = (i2c_bus_t*)context; + + if (bus->abort_pending) { + // Packet abort was not completed in time (STOPF was not detected) + // This may be cause by the bus being busy/noisy. + I2C_TypeDef* regs = bus->def->regs; + + // Reset the I2C controller + regs->CR1 &= ~I2C_CR1_PE; + regs->CR1 |= I2C_CR1_PE; + + // Continue with the next packet + i2c_bus_head_continue(bus); + } else { + // Timeout during normal operation occurred + i2c_packet_t* packet = bus->queue_head; + if (packet != NULL) { + // Determine the status based on the current bus state + I2C_TypeDef* regs = bus->def->regs; + i2c_status_t status; + if ((regs->CR2 & I2C_CR2_START) && (regs->ISR & I2C_ISR_BUSY)) { + // START condition was issued but the bus is still busy + status = I2C_STATUS_BUSY; + } else { + status = I2C_STATUS_TIMEOUT; + } + + // Abort pending packet + i2c_bus_abort(bus, packet); + + // Invoke the completion callback + i2c_bus_invoke_callback(bus, packet, status); + } + } +} + +// I2C bus event interrupt handler +static void i2c_bus_ev_handler(i2c_bus_t* bus) { + I2C_TypeDef* regs = bus->def->regs; + + uint32_t isr = regs->ISR; + + if (isr & I2C_ISR_RXNE) { + // I2C controller receive buffer is not empty. + // The interrupt flag is cleared by reading the RXDR register. + uint8_t received_byte = regs->RXDR; + if (bus->next_op > 0 && bus->transfer_size > 0) { + i2c_bus_write_buff(bus, received_byte); + } else if (bus->abort_pending) { + regs->CR2 |= I2C_CR2_STOP; + } else { + // Invalid state, ignore + } + } + + if (isr & I2C_ISR_TXIS) { + // I2C controller transmit buffer is empty. + // The interrupt flag is cleared by writing the TXDR register. + if (bus->next_op > 0 && bus->transfer_size > 0) { + regs->TXDR = i2c_bus_read_buff(bus); + } else { + regs->TXDR = bus->dummy_data; + if (bus->abort_pending) { + regs->CR2 |= I2C_CR2_STOP; + } else { + // Invalid state, ignore + } + } + } + + if (isr & I2C_ISR_TCR) { + // Data transfer is partially completed and RELOAD is required + if (bus->abort_pending) { + // Packet is being aborted, issue STOP condition + regs->CR2 &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD); + regs->CR2 |= I2C_CR2_STOP; + } else if (bus->transfer_size > 0) { + // There are still some bytes left in the current operation buffer + uint32_t cr2 = regs->CR2 & ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD); + + cr2 |= MIN(bus->transfer_size, 255) << I2C_CR2_NBYTES_Pos; + + if (bus->transfer_size > 255) { + // Set RELOAD if we the remaining data is still over + // the 255 bytes limit + cr2 |= I2C_CR2_RELOAD; + } + regs->CR2 = cr2; + } else if (bus->queue_head != NULL) { + // Data transfer is split between two or more operations, + // continues in the next operation + i2c_bus_head_continue(bus); + } else { + // Invalid state, clear the TCR flag + regs->CR2 &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD); + regs->CR2 |= I2C_CR2_STOP; + } + } + + if (isr & I2C_ISR_TC) { + // Trasfer complete + if (bus->stop_requested || bus->abort_pending) { + // Issue stop condition and wait for ISR_STOPF flag + regs->CR2 |= I2C_CR2_STOP; + } else if (bus->queue_head != NULL) { + // Continue with the next operation + i2c_bus_head_continue(bus); + } else { + // Invalid state, clear the TC flag + regs->CR2 |= I2C_CR2_STOP; + } + } + + if (isr & I2C_ISR_NACKF) { + // Clear the NACKF flag + regs->ICR = I2C_ICR_NACKCF; + bus->nack = true; + // STOP condition is automatically generated + // by the hardware and the STOPF is set later. + } + + if (isr & I2C_ISR_STOPF) { + // Clear the STOPF flag + regs->ICR = I2C_ICR_STOPCF; + + if (bus->def->guard_time > 0) { + bus->stop_time = systick_us(); + } + + if (bus->next_op > 0 && bus->next_op == bus->queue_head->op_count) { + // Last operation in the packet + i2c_bus_head_complete(bus, bus->nack ? I2C_STATUS_NACK : I2C_STATUS_OK); + } + + // Continue with the next operation + // or complete the pending packet and move to the next + i2c_bus_head_continue(bus); + } +} + +// I2C bus error interrupt handler +static void i2c_bus_er_handler(i2c_bus_t* bus) { + I2C_TypeDef* regs = bus->def->regs; + + uint32_t isr = regs->ISR; + + // Clear error flags + regs->ICR = I2C_ICR_BERRCF | I2C_ICR_ARLOCF | I2C_ICR_OVRCF; + + if (isr & I2C_ISR_BERR) { + // Bus error + // Ignore and continue with pending operation + } + + if (isr & I2C_ISR_ARLO) { + if (bus->next_op > 0) { + // Arbitration lost, complete packet with error + i2c_bus_head_complete(bus, I2C_STATUS_ERROR); + // Start the next packet + i2c_bus_head_continue(bus); + } else { + // Packet aborted or invalid state + } + } + + if (isr & I2C_ISR_OVR) { + // This should not happed in master mode + } +} + +// Interrupt handlers + +#ifdef I2C_INSTANCE_0 +void I2C_INSTANCE_0_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[0]); +} + +void I2C_INSTANCE_0_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[0]); +} +#endif + +#ifdef I2C_INSTANCE_1 +void I2C_INSTANCE_1_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[1]); +} + +void I2C_INSTANCE_1_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[1]); +} +#endif + +#ifdef I2C_INSTANCE_2 +void I2C_INSTANCE_2_EV_IRQHandler(void) { + i2c_bus_ev_handler(&g_i2c_bus_driver[2]); +} + +void I2C_INSTANCE_2_ER_IRQHandler(void) { + i2c_bus_er_handler(&g_i2c_bus_driver[2]); +} +#endif diff --git a/core/site_scons/models/D001/discovery.py b/core/site_scons/models/D001/discovery.py index b0533b567..b9fb1ec64 100644 --- a/core/site_scons/models/D001/discovery.py +++ b/core/site_scons/models/D001/discovery.py @@ -37,6 +37,10 @@ def configure( "embed/models/D001/model_D001_layout.c", ] + sources += [ + "embed/trezorhal/stm32f4/i2c_bus.c" + ] + if "new_rendering" in features_wanted: sources += [ "embed/trezorhal/xdisplay_legacy.c", diff --git a/core/site_scons/models/D002/discovery2.py b/core/site_scons/models/D002/discovery2.py index cba3537d1..16365f45a 100644 --- a/core/site_scons/models/D002/discovery2.py +++ b/core/site_scons/models/D002/discovery2.py @@ -44,6 +44,10 @@ def configure( "embed/models/D002/model_D002_layout.c", ] + sources += [ + "embed/trezorhal/stm32u5/i2c_bus.c" + ] + if "new_rendering" in features_wanted: sources += [ "embed/trezorhal/xdisplay_legacy.c", diff --git a/core/site_scons/models/T2B1/trezor_r_v10.py b/core/site_scons/models/T2B1/trezor_r_v10.py index 9cb1723ea..d3128924b 100644 --- a/core/site_scons/models/T2B1/trezor_r_v10.py +++ b/core/site_scons/models/T2B1/trezor_r_v10.py @@ -48,7 +48,10 @@ def configure( else: sources += [f"embed/trezorhal/stm32f4/displays/{display}"] - sources += ["embed/trezorhal/stm32f4/i2c.c"] + sources += [ + "embed/trezorhal/stm32f4/i2c.c", + "embed/trezorhal/stm32f4/i2c_bus.c", + ] if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/button.c"] diff --git a/core/site_scons/models/T2T1/trezor_t.py b/core/site_scons/models/T2T1/trezor_t.py index de100f8c1..e8b62c745 100644 --- a/core/site_scons/models/T2T1/trezor_t.py +++ b/core/site_scons/models/T2T1/trezor_t.py @@ -42,6 +42,11 @@ def configure( "embed/models/T2T1/model_T2T1_layout.c", "embed/models/T2T1/compat_settings.c", ] + + sources += [ + "embed/trezorhal/stm32f4/i2c_bus.c" + ] + if "new_rendering" in features_wanted: sources += ["embed/trezorhal/xdisplay_legacy.c"] sources += ["embed/trezorhal/stm32f4/xdisplay/st-7789/display_nofb.c"] diff --git a/core/site_scons/models/T3B1/trezor_t3b1_revB.py b/core/site_scons/models/T3B1/trezor_t3b1_revB.py index 67e3f1bfc..7bcf0a686 100644 --- a/core/site_scons/models/T3B1/trezor_t3b1_revB.py +++ b/core/site_scons/models/T3B1/trezor_t3b1_revB.py @@ -43,6 +43,10 @@ def configure( "embed/models/T3B1/model_T3B1_layout.c", ] + sources += [ + "embed/trezorhal/stm32u5/i2c_bus.c", + ] + if "new_rendering" in features_wanted: sources += ["embed/trezorhal/xdisplay_legacy.c"] sources += ["embed/trezorhal/stm32u5/xdisplay/vg-2864/display_driver.c"] diff --git a/core/site_scons/models/T3T1/trezor_t3t1_revE.py b/core/site_scons/models/T3T1/trezor_t3t1_revE.py index 85edb12c5..b8923c013 100644 --- a/core/site_scons/models/T3T1/trezor_t3t1_revE.py +++ b/core/site_scons/models/T3T1/trezor_t3t1_revE.py @@ -47,6 +47,10 @@ def configure( "embed/models/T3T1/model_T3T1_layout.c", ] + sources += [ + "embed/trezorhal/stm32u5/i2c_bus.c" + ] + if "new_rendering" in features_wanted: sources += ["embed/trezorhal/xdisplay_legacy.c"] sources += ["embed/trezorhal/stm32u5/xdisplay/st-7789/display_fb.c"] diff --git a/core/site_scons/models/T3T1/trezor_t3t1_v4.py b/core/site_scons/models/T3T1/trezor_t3t1_v4.py index 4d8e3e59b..9f7cea6ba 100644 --- a/core/site_scons/models/T3T1/trezor_t3t1_v4.py +++ b/core/site_scons/models/T3T1/trezor_t3t1_v4.py @@ -46,7 +46,10 @@ def configure( sources += [ "embed/models/T3T1/model_T3T1_layout.c", ] - sources += [f"embed/trezorhal/stm32u5/displays/{display}"] + sources += [ + f"embed/trezorhal/stm32u5/displays/{display}", + "embed/trezorhal/stm32u5/i2c_bus.c" + ] if "new_rendering" in features_wanted: sources += ["embed/trezorhal/xdisplay_legacy.c"]