diff --git a/core/embed/models/D001/boards/stm32f429i-disc1.h b/core/embed/models/D001/boards/stm32f429i-disc1.h
index cd37a97124..28198342d2 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 baf3e71171..059fac72b0 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 b48479d526..24cda1e8cc 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 25a8711b71..60d569648b 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 e91a2952d2..7c57e7623d 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 591ff37b52..425b9afc49 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 bf2c0a00d8..1a8741e0a5 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 0000000000..3203fcaa06
--- /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(const i2c_packet_t* packet);
+
+// Waits until I2C packet is completed and returns its final status
+i2c_status_t i2c_packet_wait(const 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 319342b765..a6b124ae02 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 0000000000..c5769e2e4d
--- /dev/null
+++ b/core/embed/trezorhal/stm32f4/i2c_bus.c
@@ -0,0 +1,936 @@
+/*
+ * 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 SCL clock frequency
+#define I2C_BUS_SCL_FREQ 200000 // Hz
+
+// We expect the I2C bus to be running at ~200kHz
+// and max response time of the device is 1000us
+#define I2C_BUS_CHAR_TIMEOUT (50 + 5) // us
+#define I2C_BUS_OP_TIMEOUT 1000 // us
+
+#define I2C_BUS_TIMEOUT(n) \
+ ((I2C_BUS_CHAR_TIMEOUT * (1 + n) + I2C_BUS_OP_TIMEOUT + 999) / 1000)
+
+// 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(const 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;
+}
+
+// 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.Pin = def->scl_pin;
+ HAL_GPIO_Init(def->scl_port, &GPIO_InitStructure);
+
+ 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 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 = I2C_BUS_SCL_FREQ;
+
+ 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 void i2c_bus_deinit(i2c_bus_t* bus) {
+ const i2c_bus_def_t* def = bus->def;
+
+ systimer_delete(bus->timer);
+
+ if (bus->def == NULL) {
+ return;
+ }
+
+ 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;
+
+ bus->def = NULL;
+}
+
+static bool i2c_bus_init(i2c_bus_t* bus, int bus_index) {
+ memset(bus, 0, sizeof(i2c_bus_t));
+
+ 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:
+ goto cleanup;
+ }
+
+ const i2c_bus_def_t* def = &g_i2c_bus_def[bus_index];
+
+ bus->def = def;
+
+ // Unlocks potentially locked I2C bus by
+ // generating several 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);
+
+ 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) {
+ goto cleanup;
+ }
+
+ return true;
+
+cleanup:
+ i2c_bus_deinit(bus);
+ return false;
+}
+
+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)) {
+ 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(const 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(const 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 packet 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;
+ }
+
+ // Check if the bus is in a faulty state
+ if (bus->queue_head != NULL && bus->next_op == 0) {
+ uint32_t sr2 = regs->SR2;
+
+ if ((sr2 & I2C_SR2_BUSY) && ((sr2 & I2C_SR2_MSL) == 0)) {
+ // the bus is busy but not in master mode.
+ // It may happen if in case of noise or other issues.
+ i2c_bus_reset(bus);
+ }
+ }
+
+ 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 condition:
+ // 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 caused 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 4aacfa8873..723ff781d0 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 0000000000..58b157cf1e
--- /dev/null
+++ b/core/embed/trezorhal/stm32u5/i2c_bus.c
@@ -0,0 +1,894 @@
+/*
+ * 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"
+
+// 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 ~200kHz
+// and max response time of the device is 1000us
+#define I2C_BUS_CHAR_TIMEOUT (50 + 5) // us
+#define I2C_BUS_OP_TIMEOUT 1000 // us
+
+#define I2C_BUS_TIMEOUT(n) \
+ ((I2C_BUS_CHAR_TIMEOUT * (1 + n) + I2C_BUS_OP_TIMEOUT + 999) / 1000)
+
+// 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(const 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;
+}
+
+// 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.Pin = def->scl_pin;
+ HAL_GPIO_Init(def->scl_port, &GPIO_InitStructure);
+
+ 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 void i2c_bus_deinit(i2c_bus_t* bus) {
+ const i2c_bus_def_t* def = bus->def;
+
+ systimer_delete(bus->timer);
+
+ if (bus->def == NULL) {
+ return;
+ }
+
+ 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;
+
+ bus->def = NULL;
+}
+
+static bool i2c_bus_init(i2c_bus_t* bus, int bus_index) {
+ memset(bus, 0, sizeof(i2c_bus_t));
+
+ 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:
+ goto cleanup;
+ }
+
+ const i2c_bus_def_t* def = &g_i2c_bus_def[bus_index];
+
+ bus->def = def;
+
+ // Unlocks potentially locked I2C bus by
+ // generating several 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) {
+ goto cleanup;
+ }
+
+ return true;
+
+cleanup:
+ i2c_bus_deinit(bus);
+ return false;
+}
+
+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)) {
+ 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(const 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(const 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 packet 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 condition:
+ // 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 caused 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) {
+ // Transfer 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 happen 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 b0533b5679..65b332e6ab 100644
--- a/core/site_scons/models/D001/discovery.py
+++ b/core/site_scons/models/D001/discovery.py
@@ -73,6 +73,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/trezorhal/stm32f4/i2c.c"]
+ sources += ["embed/trezorhal/stm32f4/i2c_bus.c"]
sources += ["embed/trezorhal/stm32f4/touch/stmpe811.c"]
features_available.append("touch")
diff --git a/core/site_scons/models/D002/discovery2.py b/core/site_scons/models/D002/discovery2.py
index cba3537d1f..9041123cb1 100644
--- a/core/site_scons/models/D002/discovery2.py
+++ b/core/site_scons/models/D002/discovery2.py
@@ -60,9 +60,8 @@ def configure(
sources += [
"embed/trezorhal/stm32u5/i2c.c",
]
- sources += [
- "embed/trezorhal/stm32u5/touch/sitronix.c",
- ]
+ sources += ["embed/trezorhal/stm32u5/i2c_bus.c"]
+ sources += ["embed/trezorhal/stm32u5/touch/sitronix.c"]
features_available.append("touch")
# if "sd_card" in features_wanted:
diff --git a/core/site_scons/models/T2B1/trezor_r_v10.py b/core/site_scons/models/T2B1/trezor_r_v10.py
index 9cb1723ea6..49c63e50c5 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"]
@@ -79,6 +82,7 @@ def configure(
if "optiga" in features_wanted:
defines += ["USE_OPTIGA=1"]
+ sources += ["embed/trezorhal/stm32f4/i2c_bus.c"]
sources += ["embed/trezorhal/stm32f4/optiga_hal.c"]
sources += ["embed/trezorhal/optiga/optiga.c"]
sources += ["embed/trezorhal/optiga/optiga_commands.c"]
diff --git a/core/site_scons/models/T2T1/trezor_t.py b/core/site_scons/models/T2T1/trezor_t.py
index de100f8c1b..18f4af2dcf 100644
--- a/core/site_scons/models/T2T1/trezor_t.py
+++ b/core/site_scons/models/T2T1/trezor_t.py
@@ -42,6 +42,7 @@ def configure(
"embed/models/T2T1/model_T2T1_layout.c",
"embed/models/T2T1/compat_settings.c",
]
+
if "new_rendering" in features_wanted:
sources += ["embed/trezorhal/xdisplay_legacy.c"]
sources += ["embed/trezorhal/stm32f4/xdisplay/st-7789/display_nofb.c"]
@@ -82,6 +83,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/trezorhal/stm32f4/i2c.c"]
+ sources += ["embed/trezorhal/stm32f4/i2c_bus.c"]
sources += ["embed/trezorhal/stm32f4/touch/ft6x36.c"]
features_available.append("touch")
diff --git a/core/site_scons/models/T3B1/trezor_t3b1_revB.py b/core/site_scons/models/T3B1/trezor_t3b1_revB.py
index 67e3f1bfc4..ae923cdd5f 100644
--- a/core/site_scons/models/T3B1/trezor_t3b1_revB.py
+++ b/core/site_scons/models/T3B1/trezor_t3b1_revB.py
@@ -74,6 +74,7 @@ def configure(
if "optiga" in features_wanted:
defines += ["USE_OPTIGA=1"]
sources += ["embed/trezorhal/stm32u5/i2c.c"]
+ sources += ["embed/trezorhal/stm32u5/i2c_bus.c"]
sources += ["embed/trezorhal/stm32u5/optiga_hal.c"]
sources += ["embed/trezorhal/optiga/optiga.c"]
sources += ["embed/trezorhal/optiga/optiga_commands.c"]
diff --git a/core/site_scons/models/T3T1/trezor_t3t1_revE.py b/core/site_scons/models/T3T1/trezor_t3t1_revE.py
index 85edb12c54..450ab408b8 100644
--- a/core/site_scons/models/T3T1/trezor_t3t1_revE.py
+++ b/core/site_scons/models/T3T1/trezor_t3t1_revE.py
@@ -72,6 +72,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/trezorhal/stm32u5/i2c.c"]
+ sources += ["embed/trezorhal/stm32u5/i2c_bus.c"]
sources += ["embed/trezorhal/stm32u5/touch/ft6x36.c"]
sources += ["embed/trezorhal/stm32u5/touch/panels/lx154a2422cpt23.c"]
features_available.append("touch")
diff --git a/core/site_scons/models/T3T1/trezor_t3t1_v4.py b/core/site_scons/models/T3T1/trezor_t3t1_v4.py
index 4d8e3e59bc..661a44e923 100644
--- a/core/site_scons/models/T3T1/trezor_t3t1_v4.py
+++ b/core/site_scons/models/T3T1/trezor_t3t1_v4.py
@@ -46,7 +46,9 @@ def configure(
sources += [
"embed/models/T3T1/model_T3T1_layout.c",
]
- sources += [f"embed/trezorhal/stm32u5/displays/{display}"]
+ sources += [
+ f"embed/trezorhal/stm32u5/displays/{display}",
+ ]
if "new_rendering" in features_wanted:
sources += ["embed/trezorhal/xdisplay_legacy.c"]
@@ -74,6 +76,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/trezorhal/stm32u5/i2c.c"]
+ sources += ["embed/trezorhal/stm32u5/i2c_bus.c"]
sources += ["embed/trezorhal/stm32u5/touch/ft6x36.c"]
features_available.append("touch")