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