mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-26 06:12:01 +00:00
feat(core/embed): introduce non-blocking i2c drivers
[no changelog]
This commit is contained in:
parent
9e4a6d08e5
commit
91649dc7cb
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
185
core/embed/trezorhal/i2c_bus.h
Normal file
185
core/embed/trezorhal/i2c_bus.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_I2C_BUS_H
|
||||
#define TREZORHAL_I2C_BUS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
@ -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
|
||||
|
||||
|
936
core/embed/trezorhal/stm32f4/i2c_bus.c
Normal file
936
core/embed/trezorhal/stm32f4/i2c_bus.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include TREZOR_BOARD
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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
|
@ -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 {
|
||||
|
894
core/embed/trezorhal/stm32u5/i2c_bus.c
Normal file
894
core/embed/trezorhal/stm32u5/i2c_bus.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include TREZOR_BOARD
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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
|
@ -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")
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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"]
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user