summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOnlyoutzZ <[email protected]>2026-09-14 17:37:53 +0800
committerRbb666 <[email protected]>2026-09-16 09:06:34 +0800
commit51be4c4589a5b0e57f57a42e2759c962b86f125c (patch)
tree4a50e1bbd3993e929e21e174d49e30df20f89e0c
parentbd501fa9e32fba11076df475c776eb4a317d92e9 (diff)
[bsp][n32] n32hxxx: fix H7xx hard I2C abort and add the missing SDK header
The N32H7xx branch of drv_hard_i2c.c had never been compiled. The file takes its peripheral declarations from board.h, and only n32h497zgl7-evb includes n32h49x_i2c.h, so on H7xx every I2C symbol was undefined and the option could not build at all. Include the peripheral header from the driver itself, the way drv_sdram.c already does. I2C_ABORT_ON_TIMEOUT also degraded to a bare `state = I2C_READY` on H7xx, at the four rt_completion_wait() timeouts in n32_i2c_master_xfer(). The H49x implementation is written against the older IP - I2C_ClrIntPendingBit, I2C_STS2_BUSY, I2C_CTRL1_STOPGEN - none of which exist in the H7xx SDK, so give H7xx its own teardown. It disables the interrupt and DMA request sources, disables the DMA channel that would otherwise stay armed against the caller's buffer, releases the bus and clears the status residue. Only flags with a matching INTCLR bit may be passed to I2C_ClrFlag(), which is a plain "INTCLR |= flag" write; TFC (0x40) and BUSY (0x8000) have none. I2C_Configuration() was called from n32_i2c_init() but defined nowhere in the tree. It is a board hook for pin mux, which is not the driver's job - the H49x path configures no pins either - so drop the call. Verified by building n32h760zil7-stb with BSP_USING_HARD_I2C1 and its TX/RX DMA enabled, which now compiles and links with no warnings, and n32h497zgl7-evb with I2C1/I2C2 DMA enabled, whose image size is unchanged. Neither board routes I2C pins yet, so this is build-verified only, not hardware-verified.
-rw-r--r--bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c118
1 files changed, 112 insertions, 6 deletions
diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c
index ec106e4d7e..38acff1ba7 100644
--- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c
+++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c
@@ -11,6 +11,19 @@
#include <rtthread.h>
#include <rthw.h>
#include <board.h>
+
+/* The driver owns the peripheral registers, so it pulls in the peripheral
+ * header itself instead of relying on board.h to have declared it. Only the
+ * H49x board happens to include it today.
+ */
+#if defined(SOC_SERIES_N32H7xx)
+#include <n32h7xx_i2c.h>
+#elif defined(SOC_SERIES_N32H49x)
+#include <n32h49x_i2c.h>
+#elif defined(SOC_SERIES_N32H47x_48x)
+#include <n32h47x_48x_i2c.h>
+#endif
+
#include "drv_hard_i2c.h"
#include "drv_config.h"
#include <string.h>
@@ -187,11 +200,106 @@ static rt_err_t n32_i2c_master_start_addr(struct n32_i2c *i2c, uint8_t DevAddres
}
#endif /* defined(SOC_SERIES_N32H49x) */
-#if defined(SOC_SERIES_N32H49x) || defined(SOC_SERIES_N32H47x_48x)
+#if defined(SOC_SERIES_N32H7xx)
+/* Wait for the bus to fall idle before asserting a new STOP or START.
+ *
+ * Asserting either while a previous STOP is still on the wire wedges the state
+ * machine: BUSY stays set, the address never goes out and the transfer stalls.
+ */
+static rt_err_t n32_i2c_wait_bus_idle(I2C_Module *Instance)
+{
+ rt_uint32_t timeout = 1000000U;
+
+ while ((I2C_GetFlag(Instance, I2C_FLAG_BUSY) != RESET) && (--timeout > 0U))
+ {
+ }
+
+ return (timeout != 0U) ? RT_EOK : -RT_ETIMEOUT;
+}
+
+/* Clear residue left by an aborted transfer before the next one starts.
+ *
+ * A STOPGEN that was latched with no frame to terminate stays pending and would
+ * tear down the transfer we are about to set up. Stale status flags would
+ * likewise fire the moment the new transfer re-enables the error interrupt.
+ *
+ * I2C_ClrFlag() is a plain "INTCLR |= flag" write, so only flags that actually
+ * have a matching INTCLR bit may be passed. TFC (0x40) and BUSY (0x8000) have
+ * none - passing them lands in reserved bits and clears nothing.
+ */
+static void n32_i2c_prepare_start(I2C_Module *Instance)
+{
+ I2C_GenerateStop(Instance, DISABLE);
+
+ I2C_ClrFlag(Instance, I2C_FLAG_NAKF | I2C_FLAG_BSER | I2C_FLAG_ABLO |
+ I2C_FLAG_OVF | I2C_FLAG_TMOUT | I2C_FLAG_ALRT |
+ I2C_FLAG_STOPF | I2C_FLAG_ADR | I2C_FLAG_CRCERR);
+}
+
+/* Tear down a transfer that the waiting thread has given up on.
+ *
+ * Must disarm the ISR before returning: master_xfer's callers pass stack
+ * buffers, so a late read writing through transfer.pBuffPtr would corrupt a
+ * stack frame that no longer exists once the caller unwinds. The DMA channel
+ * targets that same buffer, so it is disabled here as well - dropping the
+ * request enable alone leaves the channel armed.
+ */
+static void n32_i2c_abort_transfer(struct n32_i2c *i2c_obj)
+{
+ I2C_Module *Instance = i2c_obj->config->Instance;
+ I2C_StateTypeDef was_state = i2c_obj->transfer.state;
+
+ /* Disable interrupts and DMA first - stop the ISR touching pBuffPtr */
+ Instance->CTRL1 &= ~(I2C_CTRL1_TFCIE | I2C_CTRL1_WDRIE | I2C_CTRL1_RDRIE |
+ I2C_CTRL1_STOPIE | I2C_CTRL1_NAKIE | I2C_CTRL1_ERRIE);
+
+ if ((was_state == I2C_BUSY_TX) &&
+ ((i2c_obj->i2c_dma_flag & I2C_USING_TX_DMA_FLAG) == I2C_USING_TX_DMA_FLAG))
+ {
+ Instance->CTRL1 &= ~I2C_CTRL1_DMAWREN;
+
+ DMA_ChannelCmd(i2c_obj->config->dma_tx->Instance,
+ i2c_obj->config->dma_tx->dma_channel, DISABLE);
+ DMA_ChannelEventCmd(i2c_obj->config->dma_tx->Instance,
+ i2c_obj->config->dma_tx->dma_channel,
+ DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE, DISABLE);
+ DMA_ClearChannelEventStatus(i2c_obj->config->dma_tx->Instance,
+ i2c_obj->config->dma_tx->dma_channel,
+ DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE);
+ }
+ else if ((was_state == I2C_BUSY_RX) &&
+ ((i2c_obj->i2c_dma_flag & I2C_USING_RX_DMA_FLAG) == I2C_USING_RX_DMA_FLAG))
+ {
+ Instance->CTRL1 &= ~I2C_CTRL1_DMARDEN;
+
+ DMA_ChannelCmd(i2c_obj->config->dma_rx->Instance,
+ i2c_obj->config->dma_rx->dma_channel, DISABLE);
+ DMA_ChannelEventCmd(i2c_obj->config->dma_rx->Instance,
+ i2c_obj->config->dma_rx->dma_channel,
+ DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE, DISABLE);
+ DMA_ClearChannelEventStatus(i2c_obj->config->dma_rx->Instance,
+ i2c_obj->config->dma_rx->dma_channel,
+ DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE);
+ }
+
+ i2c_obj->i2c_isr_callback = RT_NULL;
+ i2c_obj->transfer.pBuffPtr = RT_NULL;
+ i2c_obj->transfer.XferCount = 0;
+ i2c_obj->transfer.XferSize = 0;
+ i2c_obj->transfer.state = I2C_READY;
+
+ /* Release the bus only if we still own it, then clear residue */
+ if (I2C_GetFlag(Instance, I2C_FLAG_BUSY) != RESET)
+ {
+ I2C_GenerateStop(Instance, ENABLE);
+ (void)n32_i2c_wait_bus_idle(Instance);
+ }
+
+ n32_i2c_prepare_start(Instance);
+}
+#endif /* defined(SOC_SERIES_N32H7xx) */
+
#define I2C_ABORT_ON_TIMEOUT(obj) n32_i2c_abort_transfer(obj)
-#else
-#define I2C_ABORT_ON_TIMEOUT(obj) ((obj)->transfer.state = I2C_READY)
-#endif
enum
{
@@ -487,8 +595,6 @@ static rt_err_t n32_i2c_init(struct n32_i2c *i2c_drv)
#if defined(SOC_SERIES_N32H7xx)
I2C_InitType I2C_InitStructure;
uint32_t BusTim_Reg;
- /* Call I2C_Configuration() from an external file */
- I2C_Configuration();
/* Disable IIC */
I2C_Enable(cfg->Instance, DISABLE);
/* Get IIC BUSTM Register value */