diff --git a/apps/lang/slovak.lang b/apps/lang/slovak.lang
index 3f4ee59cf3..6589e70551 100644
--- a/apps/lang/slovak.lang
+++ b/apps/lang/slovak.lang
@@ -4920,11 +4920,11 @@
*: none
- recording: "Počet bitov"
+ recording: "Bitová rýchlosť"
*: none
- recording: "Počet bitov"
+ recording: "Bitová rýchlosť"
diff --git a/firmware/SOURCES b/firmware/SOURCES
index fbd92742ba..16209d3f78 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -1953,6 +1953,7 @@ target/arm/stm32/echoplayer/audiohw-echoplayer.c
target/arm/stm32/echoplayer/backlight-echoplayer.c
target/arm/stm32/echoplayer/button-echoplayer.c
target/arm/stm32/echoplayer/clock-echoplayer.c
+target/arm/stm32/echoplayer/i2c-echoplayer.c
target/arm/stm32/echoplayer/lcd-echoplayer.c
target/arm/stm32/echoplayer/power-echoplayer.c
target/arm/stm32/echoplayer/sdmmc-echoplayer.c
diff --git a/firmware/export/config/ondavx767.h b/firmware/export/config/ondavx767.h
index 61f79111b3..769e417bd6 100644
--- a/firmware/export/config/ondavx767.h
+++ b/firmware/export/config/ondavx767.h
@@ -33,16 +33,19 @@
#define MODEL_NUMBER 64
//#define HAVE_ATA_SD
-//#define HAVE_HOTSWAP
+#define HAVE_HOTSWAP
+#define INCLUDE_TIMEOUT_API
//#define CONFIG_STORAGE (STORAGE_NAND | STORAGE_SD)
-#define CONFIG_STORAGE STORAGE_SD /* Multivolume currently handled at firmware/target/ level */
+#define CONFIG_STORAGE STORAGE_SD
#define CONFIG_NAND NAND_CC
-#define HAVE_MULTIDRIVE
-#define NUM_DRIVES 2
-
+//#define HAVE_MULTIDRIVE
+//#define NUM_DRIVES 2
+#define HAVE_HOTSWAP
+#define HAVE_MULTIVOLUME
+#define HAVE_HOTSWAP_STORAGE_AS_MAIN
@@ -159,6 +162,7 @@
#define HAVE_USBSTACK
#define USB_VENDOR_ID 0x07C4
#define USB_PRODUCT_ID 0xA4A5
+#define HAVE_BOOTLOADER_USB_MODE
/* Define this if a programmable hotkey is mapped */
//#define HAVE_HOTKEY
diff --git a/firmware/export/config/ondavx777.h b/firmware/export/config/ondavx777.h
index f5bf2af4fa..6074d480ac 100644
--- a/firmware/export/config/ondavx777.h
+++ b/firmware/export/config/ondavx777.h
@@ -192,6 +192,7 @@ No access to the NAND yet..
#define USB_STATUS_BY_EVENT
#define USB_VENDOR_ID 0x07C4
#define USB_PRODUCT_ID 0xA4A5
+#define HAVE_BOOTLOADER_USB_MODE
/* Define this if a programmable hotkey is mapped */
//#define HAVE_HOTKEY
diff --git a/firmware/export/config/xduoox3.h b/firmware/export/config/xduoox3.h
index 23f8f6dc7d..742354f788 100644
--- a/firmware/export/config/xduoox3.h
+++ b/firmware/export/config/xduoox3.h
@@ -128,7 +128,7 @@
#define CONFIG_STORAGE (STORAGE_SD /* | STORAGE_NAND */)
#define HAVE_MULTIDRIVE
-#define CONFIG_STORAGE_MULTI
+//#define CONFIG_STORAGE_MULTI
#define NUM_DRIVES 2
/* Define this if media can be exchanged on the fly */
diff --git a/firmware/reggen/stm32h743.regs b/firmware/reggen/stm32h743.regs
index 2746fc2759..7dc6153c2c 100644
--- a/firmware/reggen/stm32h743.regs
+++ b/firmware/reggen/stm32h743.regs
@@ -1401,3 +1401,111 @@ block DMAMUX {
DMAMUX1 @ 0x40020800 : DMAMUX
DMAMUX2 @ 0x58025800 : DMAMUX
+
+// I2C controller
+block I2C {
+ CR1 @ 0x00 : reg {
+ -- 23 PECEN
+ -- 22 ALERTEN
+ -- 21 SMBDEN
+ -- 20 SMBHEN
+ -- 19 GCEN
+ -- 18 WUPEN
+ -- 17 NOSTRETCH
+ -- 16 SBC
+ -- 15 RXDMAEN
+ -- 14 TXDMAEN
+ -- 12 ANFOFF
+ 11 08 DNF
+ -- 07 ERRIE
+ -- 06 TCIE
+ -- 05 STOPIE
+ -- 04 NACKIE
+ -- 03 ADDRIE
+ -- 02 RXIE
+ -- 01 TXIE
+ -- 00 PE
+ }
+
+ CR2 @ 0x04 : reg {
+ -- 26 PECBYTE
+ -- 25 AUTOEND
+ -- 24 RELOAD
+ 23 16 NBYTES
+ -- 15 NACK
+ -- 14 STOP
+ -- 13 START
+ -- 12 HEAD10R
+ -- 11 ADD10
+ -- 10 RD_WRN
+ 09 00 SADD
+ }
+
+ OAR1 @ 0x08 : reg {
+ -- 15 OA1EN
+ -- 10 OA1MODE
+ 09 00 OA1
+ }
+
+ OAR2 @ 0x0c : reg {
+ -- 15 OA2EN
+ 10 08 OA2MSK
+ 07 01 OA2
+ }
+
+ TIMINGR @ 0x10 : reg {
+ 31 28 PRESC
+ 23 20 SCLDEL
+ 19 16 SDADEL
+ 15 08 SCLH
+ 07 00 SCLL
+ }
+
+ TIMEOUTR @ 0x14 : reg {
+ -- 31 TEXTEN
+ 27 16 TIMEOUTB
+ -- 15 TIMOUTEN
+ -- 12 TIDLE
+ 11 00 TIMEOUTA
+ }
+
+ ISR @ 0x18 : reg {
+ 23 17 ADDCODE
+ -- 16 DIR
+ -- 15 BUSY
+ -- 14 ALERT
+ -- 12 TIMEOUT
+ -- 11 PECERR
+ -- 10 OVR
+ -- 09 ARLO
+ -- 08 BERR
+ -- 07 TCR
+ -- 06 TC
+ -- 05 STOPF
+ -- 04 NACKF
+ -- 03 ADDR
+ -- 02 RXNE
+ -- 01 TXIS
+ -- 00 TXE
+ }
+
+ ICR @ 0x1c : reg {
+ -- 13 ALERTCF
+ -- 12 TIMOUTCF
+ -- 11 PECCF
+ -- 10 OVRCF
+ -- 09 ARLOCF
+ -- 08 BERRCF
+ -- 05 STOPCF
+ -- 04 NACKCF
+ -- 03 ADDRCF
+ }
+
+ RXDR @ 0x24 : reg
+ TXDR @ 0x28 : reg
+}
+
+I2C1 @ 0x40005400 : I2C
+I2C2 @ 0x40005800 : I2C
+I2C3 @ 0x40005C00 : I2C
+I2C4 @ 0x58001C00 : I2C
diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c
index d42c12ae5f..f83314a2e6 100644
--- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c
+++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c
@@ -165,6 +165,7 @@ INIT_ATTR static void init_periph_clock(void)
{
reg_writef(RCC_D1CCIPR, SDMMCSEL_V(PLL1Q));
reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE));
+ reg_writef(RCC_D2CCIP2R, I2C123SEL_V(HSI));
/* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */
reg_writef(RCC_AHB3LPENR, AXISRAMEN(1));
@@ -203,3 +204,11 @@ const struct stm32_clock spi5_ker_clock = {
.lpen_reg = ITA_RCC_APB2LPENR,
.lpen_bit = BM_RCC_APB2ENR_SPI5EN,
};
+
+const struct stm32_clock i2c1_ker_clock = {
+ .frequency = STM32_HSI_FREQ,
+ .en_reg = ITA_RCC_APB1LENR,
+ .en_bit = BM_RCC_APB1LENR_I2C1EN,
+ .lpen_reg = ITA_RCC_APB1LLPENR,
+ .lpen_bit = BM_RCC_APB1LENR_I2C1EN,
+};
diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h
index e6f3624537..d75b294e93 100644
--- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h
+++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h
@@ -28,5 +28,6 @@ void echoplayer_clock_init(void) INIT_ATTR;
extern struct stm32_clock sdmmc1_ker_clock;
extern struct stm32_clock ltdc_ker_clock;
extern struct stm32_clock spi5_ker_clock;
+extern struct stm32_clock i2c1_ker_clock;
#endif /* __CLOCK_ECHOPLAYER_H__ */
diff --git a/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.c b/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.c
new file mode 100644
index 0000000000..39cdea33ab
--- /dev/null
+++ b/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.c
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2026 by Aidan MacDonald
+ *
+ * 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "i2c-stm32h7.h"
+#include "clock-echoplayer.h"
+#include "nvic-arm.h"
+#include "regs/stm32h743/i2c.h"
+
+static const struct stm32_i2c_config i2c1_conf INITDATA_ATTR = {
+ .instance = ITA_I2C1,
+ .ker_clock = &i2c1_ker_clock,
+ .bus_freq_hz = 400000,
+ .scl_low_min_ns = 1300,
+ .scl_high_min_ns = 600,
+ .t_vd_dat_max_ns = 900,
+ .t_su_dat_max_ns = 100,
+ .rise_time_max_ns = 300,
+ .fall_time_max_ns = 300,
+};
+
+struct stm32_i2c_controller i2c1_ctl;
+
+void i2c_init(void)
+{
+ stm32_i2c_init(&i2c1_ctl, &i2c1_conf);
+ nvic_enable_irq(NVIC_IRQN_I2C1_EV);
+ nvic_enable_irq(NVIC_IRQN_I2C1_ER);
+}
+
+void i2c1_irq_handler(void)
+{
+ stm32_i2c_irq_handler(&i2c1_ctl);
+}
diff --git a/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.h b/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.h
new file mode 100644
index 0000000000..7b0ef57eeb
--- /dev/null
+++ b/firmware/target/arm/stm32/echoplayer/i2c-echoplayer.h
@@ -0,0 +1,28 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2026 by Aidan MacDonald
+ *
+ * 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef __I2C_ECHOPLAYER_H__
+#define __I2C_ECHOPLAYER_H__
+
+#include "i2c-stm32h7.h"
+
+extern struct stm32_i2c_controller i2c1_ctl;
+
+#endif /* __I2C_ECHOPLAYER_H__ */
diff --git a/firmware/target/arm/stm32/i2c-stm32h7.c b/firmware/target/arm/stm32/i2c-stm32h7.c
index 87f3f17bda..1d06290f03 100644
--- a/firmware/target/arm/stm32/i2c-stm32h7.c
+++ b/firmware/target/arm/stm32/i2c-stm32h7.c
@@ -18,8 +18,291 @@
* KIND, either express or implied.
*
****************************************************************************/
-#include "i2c.h"
+#include "i2c-stm32h7.h"
+#include "kernel.h"
+#include "panic.h"
+#include "regs/stm32h743/rcc.h"
+#include "regs/stm32h743/i2c.h"
-void i2c_init(void)
+#define FLAG_WRITE_REG_ADDR 0x01
+#define FLAG_WAIT_REG_ADDR 0x02
+#define FLAG_READ_DATA 0x04
+#define FLAG_WRITE_DATA 0x08
+
+static uint32_t stm32_i2c_get_timingr(const struct stm32_i2c_config *cfg)
{
+ /* Kernel clock period and target I2C bus clock period */
+ int t_ker = 1000000 / (stm32_clock_get_frequency(cfg->ker_clock) / 1000);
+ int t_scl = 1000000 / (cfg->bus_freq_hz / 1000);
+
+ /* Analog filter delays */
+ int af_min = 50;
+ int af_max = 260;
+
+ /* Digital filter delays */
+ int df = 0;
+
+ /* Synchronization delays excluding rise/fall time */
+ int sync_min = af_min + df + (2 * t_ker);
+ int sync_max = af_max + df + (4 * (t_ker + 1));
+
+ /* Calculate bounds for SCLDEL/SDADEL */
+ int min_scldel_ns = cfg->rise_time_max_ns + cfg->t_su_dat_max_ns;
+ int min_sdadel_ns = cfg->fall_time_max_ns - sync_min;
+ int max_sdadel_ns = cfg->t_vd_dat_max_ns - cfg->rise_time_max_ns - sync_max;
+
+ /* Find prescaler setting which produces the least error */
+ uint32_t best_timingr = 0;
+ int best_error = INT_MAX;
+
+ for (int presc = 16; presc > 0; --presc)
+ {
+ /* Prescaler clock period */
+ int t_presc_min = presc * t_ker;
+ int t_presc_max = presc * (t_ker + 1);
+ int t_presc_avg = (t_presc_min + t_presc_max) / 2;
+
+ /* Compute SCLDEL setting */
+ int scldel = (min_scldel_ns + t_presc_min - 1) / t_presc_min;
+ if (scldel > 16)
+ continue;
+
+ /* Compute SDADEL setting */
+ int min_sdadel = (min_sdadel_ns + t_presc_min - 1) / t_presc_min;
+ int max_sdadel = max_sdadel_ns / t_presc_max;
+ int sdadel = (min_sdadel + max_sdadel) / 2;
+ if (min_sdadel > 15 || max_sdadel == 0 || min_sdadel > max_sdadel)
+ continue;
+
+ /* Find minimum SCLL and SCLH settings */
+ int scll_min = (cfg->scl_low_min_ns + t_presc_min - 1) / t_presc_min;
+ int sclh_min = (cfg->scl_high_min_ns + t_presc_min - 1) / t_presc_min;
+ if (scll_min > 256 || scll_min > 256)
+ continue;
+
+ /* Find slack time to add to SCLL/SCLH to meet target clock */
+ int t_scl_slack = t_scl;
+ t_scl_slack -= sync_min * 2;
+ t_scl_slack -= cfg->rise_time_max_ns + cfg->fall_time_max_ns;
+ t_scl_slack -= (scll_min + sclh_min) * t_presc_min;
+
+ /* Compute real SCLL and SCLH */
+ int scll = scll_min;
+ int sclh = sclh_min;
+
+ /* Divide slack between SCLL/SCLH evenly if possible */
+ if (t_scl_slack >= 2*t_presc_avg)
+ {
+ int add_clocks = t_scl_slack / (2 * t_presc_avg);
+
+ scll += add_clocks;
+ sclh += add_clocks;
+ t_scl_slack -= add_clocks * t_presc_avg;
+ }
+
+ /* Add leftover clock to SCLH if would reduce error */
+ if (t_scl_slack >= t_presc_avg/2)
+ sclh += 1;
+
+ /* Determine SCL clock period from these settings */
+ int approx_scl = (scll + sclh) * t_presc_avg + (2 * sync_min);
+ approx_scl += cfg->rise_time_max_ns + cfg->fall_time_max_ns;
+
+ /* Update best timing value */
+ int err = t_scl - approx_scl;
+ if (err < 0)
+ err = -err;
+
+ if (err < best_error)
+ {
+ best_timingr = __reg_orf(I2C_TIMINGR,
+ PRESC(presc - 1),
+ SDADEL(sdadel - 0),
+ SCLDEL(scldel - 1),
+ SCLL(scll - 1),
+ SCLH(sclh - 1));
+ best_error = err;
+ }
+ }
+
+ if (best_error == INT_MAX)
+ panicf("%s", __func__);
+
+ return best_timingr;
+}
+
+static void stm32_i2c_transfer_complete(struct stm32_i2c_controller *ctl, int err)
+{
+ ctl->error = err;
+
+ reg_writelf(ctl->regs, I2C_CR1, PE(0));
+ semaphore_release(&ctl->bus_sem);
+}
+
+void stm32_i2c_init(struct stm32_i2c_controller *ctl,
+ const struct stm32_i2c_config *cfg)
+{
+ mutex_init(&ctl->bus_lock);
+ semaphore_init(&ctl->bus_sem, 1, 0);
+
+ ctl->regs = cfg->instance;
+ ctl->ker_clock = cfg->ker_clock;
+
+ stm32_clock_enable(ctl->ker_clock);
+
+ reg_writelf(ctl->regs, I2C_CR1,
+ ANFOFF(0),
+ ERRIE(1),
+ TCIE(1),
+ STOPIE(1),
+ NACKIE(1),
+ RXIE(1),
+ TXIE(1));
+ reg_varl(ctl->regs, I2C_TIMINGR) = stm32_i2c_get_timingr(cfg);
+
+ stm32_clock_disable(ctl->ker_clock);
+}
+
+int stm32_i2c_read_mem(struct stm32_i2c_controller *ctl,
+ uint8_t dev_addr,
+ uint8_t reg_addr,
+ void *data, size_t count)
+{
+ if (count > 255)
+ panicf("%s: count>255 unsupported", __func__);
+
+ mutex_lock(&ctl->bus_lock);
+ stm32_clock_enable(ctl->ker_clock);
+
+ ctl->xfer_buf = data;
+ ctl->xfer_count = count;
+ ctl->reg_addr = reg_addr;
+ ctl->flags = FLAG_WRITE_REG_ADDR | FLAG_WAIT_REG_ADDR | FLAG_READ_DATA;
+ arm_dsb();
+
+ /* Start first 1-byte transfer of the register address */
+ reg_writelf(ctl->regs, I2C_CR1, PE(1));
+ reg_writelf(ctl->regs, I2C_CR2,
+ NBYTES(1),
+ RD_WRN(0),
+ AUTOEND(0),
+ START(1),
+ SADD(dev_addr << 1));
+
+ semaphore_wait(&ctl->bus_sem, TIMEOUT_BLOCK);
+
+ stm32_clock_disable(ctl->ker_clock);
+ mutex_unlock(&ctl->bus_lock);
+ return ctl->error;
+}
+
+int stm32_i2c_write_mem(struct stm32_i2c_controller *ctl,
+ uint8_t dev_addr,
+ uint8_t reg_addr,
+ const void *data, size_t count)
+{
+ if (count > 254)
+ panicf("%s: count>254 unsupported", __func__);
+
+ mutex_lock(&ctl->bus_lock);
+ stm32_clock_enable(ctl->ker_clock);
+
+ ctl->xfer_buf = (void *)data;
+ ctl->xfer_count = count;
+ ctl->reg_addr = reg_addr;
+ ctl->flags = FLAG_WRITE_REG_ADDR | FLAG_WRITE_DATA;
+ arm_dsb();
+
+ /* Start transfer of register address + data */
+ reg_writelf(ctl->regs, I2C_CR1, PE(1));
+ reg_writelf(ctl->regs, I2C_CR2,
+ NBYTES(ctl->xfer_count + 1),
+ RD_WRN(0),
+ AUTOEND(1),
+ START(1),
+ SADD(dev_addr << 1));
+
+ semaphore_wait(&ctl->bus_sem, TIMEOUT_BLOCK);
+
+ stm32_clock_disable(ctl->ker_clock);
+ mutex_unlock(&ctl->bus_lock);
+ return ctl->error;
+}
+
+void stm32_i2c_irq_handler(struct stm32_i2c_controller *ctl)
+{
+ uint32_t isr = reg_varl(ctl->regs, I2C_ISR);
+
+ /* Detect errors */
+ if (reg_vreadf(isr, I2C_ISR, ARLO) ||
+ reg_vreadf(isr, I2C_ISR, BERR) ||
+ (reg_vreadf(isr, I2C_ISR, STOPF) && ctl->xfer_count != 0) ||
+ reg_vreadf(isr, I2C_ISR, NACKF))
+ {
+ stm32_i2c_transfer_complete(ctl, -1);
+ return;
+ }
+
+ if (ctl->flags & FLAG_WRITE_REG_ADDR)
+ {
+ /* Write the register address */
+ if (reg_vreadf(isr, I2C_ISR, TXIS))
+ {
+ reg_varl(ctl->regs, I2C_TXDR) = ctl->reg_addr;
+ ctl->flags &= ~FLAG_WRITE_REG_ADDR;
+ }
+ }
+ else if (ctl->flags & FLAG_WAIT_REG_ADDR)
+ {
+ /* Wait for register address write to complete */
+ if (reg_vreadf(isr, I2C_ISR, TC))
+ {
+ ctl->flags &= ~FLAG_WAIT_REG_ADDR;
+
+ if (ctl->flags & FLAG_READ_DATA)
+ {
+ /* Start read portion of the transfer */
+ reg_writelf(ctl->regs, I2C_CR2,
+ NBYTES(ctl->xfer_count),
+ RD_WRN(1),
+ AUTOEND(1),
+ START(1));
+ }
+ else
+ {
+ /* Writes shouldn't get here */
+ panicf("%s", __func__);
+ }
+ }
+ }
+ else if (ctl->flags & FLAG_WRITE_DATA)
+ {
+ if (ctl->xfer_count > 0 && reg_vreadf(isr, I2C_ISR, TXIS))
+ {
+ /* Transmit bytes from buffer */
+ reg_varl(ctl->regs, I2C_TXDR) = *ctl->xfer_buf++;
+ ctl->xfer_count--;
+ }
+ else if (ctl->xfer_count == 0 && reg_vreadf(isr, I2C_ISR, STOPF))
+ {
+ /* Transfer complete without error */
+ ctl->flags &= ~FLAG_WRITE_DATA;
+ stm32_i2c_transfer_complete(ctl, 0);
+ }
+ }
+ else if (ctl->flags & FLAG_READ_DATA)
+ {
+ if (ctl->xfer_count > 0 && reg_vreadf(isr, I2C_ISR, RXNE))
+ {
+ /* Read bytes into buffer */
+ *ctl->xfer_buf++ = reg_varl(ctl->regs, I2C_RXDR);
+ ctl->xfer_count--;
+ }
+ else if (ctl->xfer_count == 0 && reg_vreadf(isr, I2C_ISR, STOPF))
+ {
+ /* Transfer complete without error */
+ ctl->flags &= ~FLAG_READ_DATA;
+ stm32_i2c_transfer_complete(ctl, 0);
+ }
+ }
}
diff --git a/firmware/target/arm/stm32/i2c-stm32h7.h b/firmware/target/arm/stm32/i2c-stm32h7.h
new file mode 100644
index 0000000000..b20dc0e89b
--- /dev/null
+++ b/firmware/target/arm/stm32/i2c-stm32h7.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2026 Aidan MacDonald
+ *
+ * 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef __I2C_STM32H743_H__
+#define __I2C_STM32H743_H__
+
+#include "mutex.h"
+#include "semaphore.h"
+#include "clock-stm32h7.h"
+
+struct stm32_i2c_config
+{
+ /* I2C instance address */
+ uint32_t instance;
+
+ /* I2C kernel clock */
+ const struct stm32_clock *ker_clock;
+
+ int bus_freq_hz; /* Desired I2C bus frequency */
+ int scl_low_min_ns; /* Minimum SCL low time */
+ int scl_high_min_ns; /* Minimum SCL high time */
+ int t_vd_dat_max_ns; /* Maximum SDA valid time */
+ int t_su_dat_max_ns; /* Minimum SDA setup time */
+ int rise_time_max_ns; /* Worst-case rise time for SCL & SDA */
+ int fall_time_max_ns; /* Worst-case fall time for SCL & SDA */
+};
+
+struct stm32_i2c_controller
+{
+ uint32_t regs;
+ const struct stm32_clock *ker_clock;
+
+ struct mutex bus_lock;
+ struct semaphore bus_sem;
+
+ int error;
+
+ uint8_t *xfer_buf;
+ size_t xfer_count;
+
+ uint8_t reg_addr;
+ uint8_t flags;
+};
+
+void stm32_i2c_init(struct stm32_i2c_controller *ctl,
+ const struct stm32_i2c_config *cfg);
+
+int stm32_i2c_read_mem(struct stm32_i2c_controller *ctl,
+ uint8_t dev_addr,
+ uint8_t reg_addr,
+ void *data, size_t nbytes);
+int stm32_i2c_write_mem(struct stm32_i2c_controller *ctl,
+ uint8_t dev_addr,
+ uint8_t reg_addr,
+ const void *data, size_t nbytes);
+
+void stm32_i2c_irq_handler(struct stm32_i2c_controller *ctl);
+
+#endif /* __I2C_STM32H743_H__ */
diff --git a/firmware/target/arm/stm32/irqhandlers-stm32h743.c b/firmware/target/arm/stm32/irqhandlers-stm32h743.c
index 62182d8080..9af063d230 100644
--- a/firmware/target/arm/stm32/irqhandlers-stm32h743.c
+++ b/firmware/target/arm/stm32/irqhandlers-stm32h743.c
@@ -51,7 +51,10 @@ void bdma_ch4_irq_handler(void) ATTR_IRQ_HANDLER;
void bdma_ch5_irq_handler(void) ATTR_IRQ_HANDLER;
void bdma_ch6_irq_handler(void) ATTR_IRQ_HANDLER;
void bdma_ch7_irq_handler(void) ATTR_IRQ_HANDLER;
-void i2c_irq_handler(void) ATTR_IRQ_HANDLER;
+void i2c1_irq_handler(void) ATTR_IRQ_HANDLER;
+void i2c2_irq_handler(void) ATTR_IRQ_HANDLER;
+void i2c3_irq_handler(void) ATTR_IRQ_HANDLER;
+void i2c4_irq_handler(void) ATTR_IRQ_HANDLER;
void lcdc_irq_handler(void) ATTR_IRQ_HANDLER;
void otg_hs_irq_handler(void) ATTR_IRQ_HANDLER;
void sai1_irq_handler(void) ATTR_IRQ_HANDLER;
diff --git a/firmware/target/arm/stm32/vectors-stm32h7.S b/firmware/target/arm/stm32/vectors-stm32h7.S
index 0882ed032b..16fced87a6 100644
--- a/firmware/target/arm/stm32/vectors-stm32h7.S
+++ b/firmware/target/arm/stm32/vectors-stm32h7.S
@@ -58,10 +58,10 @@ __vectors_platform:
.word UIE /* [ 28] */
.word UIE /* [ 29] */
.word UIE /* [ 30] */
- .word i2c_irq_handler /* [ 31] I2C1 event */
- .word i2c_irq_handler /* [ 32] I2C1 error */
- .word i2c_irq_handler /* [ 33] I2C2 event */
- .word i2c_irq_handler /* [ 34] I2C2 error */
+ .word i2c1_irq_handler /* [ 31] I2C1 event */
+ .word i2c1_irq_handler /* [ 32] I2C1 error */
+ .word i2c2_irq_handler /* [ 33] I2C2 event */
+ .word i2c2_irq_handler /* [ 34] I2C2 error */
.word spi1_irq_handler /* [ 35] SPI1 */
.word spi2_irq_handler /* [ 36] SPI2 */
.word UIE /* [ 37] */
@@ -99,8 +99,8 @@ __vectors_platform:
.word dma2_ch6_irq_handler /* [ 69] DMA2 Stream6 */
.word dma2_ch7_irq_handler /* [ 70] DMA2 Stream7 */
.word UIE /* [ 71] */
- .word i2c_irq_handler /* [ 72] I2C3 event */
- .word i2c_irq_handler /* [ 73] I2C3 error */
+ .word i2c3_irq_handler /* [ 72] I2C3 event */
+ .word i2c3_irq_handler /* [ 73] I2C3 error */
.word UIE /* [ 74] */
.word UIE /* [ 75] */
.word UIE /* [ 76] */
@@ -122,8 +122,8 @@ __vectors_platform:
.word UIE /* [ 92] */
.word UIE /* [ 93] */
.word UIE /* [ 94] */
- .word i2c_irq_handler /* [ 95] I2C4 event */
- .word i2c_irq_handler /* [ 96] I2C4 error */
+ .word i2c4_irq_handler /* [ 95] I2C4 event */
+ .word i2c4_irq_handler /* [ 96] I2C4 error */
.word UIE /* [ 97] */
.word UIE /* [ 98] */
.word UIE /* [ 99] */