stm32h743: implement I2C controller interface

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit 9c5017ea98800b83ff619a0df5015d081456807e
Author: Aidan MacDonald <[email protected]>
Date:   Sun Feb 8 17:48:31 2026 +0000

    stm32h743: implement I2C controller interface
    
    Change-Id: I1d4af5ba8a42d4d2a6a3b1d50059e699b016431b

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/i2c-stm32h7.c b/firmware/target/arm/stm32/i2c-stm32h7.c
index 87f3f17bda..73435663c4 100644
--- a/firmware/target/arm/stm32/i2c-stm32h7.c
+++ b/firmware/target/arm/stm32/i2c-stm32h7.c
@@ -18,8 +18,296 @@
  * 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"
 
+#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);
+        }
+    }
+}
+
+/* TODO: move to target */
 void i2c_init(void)
 {
 }
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] */
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.