From 3a0cd56623b4b77efa976c8dec184805176ba0c2 Mon Sep 17 00:00:00 2001 From: Yuichi Nakamura Date: Fri, 5 Nov 2021 15:49:38 +0900 Subject: [PATCH] rp2040: support I2C_RESET --- arch/arm/Kconfig | 1 + arch/arm/src/rp2040/rp2040_i2c.c | 179 +++++++++++++++++++++++++++---- 2 files changed, 162 insertions(+), 18 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 56b9a4f8113..03fa17ce6f0 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -231,6 +231,7 @@ config ARCH_CHIP_RP2040 select ARCH_HAVE_RAMVECTORS select ARCH_HAVE_MULTICPU select ARCH_HAVE_TESTSET + select ARCH_HAVE_I2CRESET select ARM_HAVE_WFE_SEV ---help--- Raspberry Pi RP2040 architectures (ARM dual Cortex-M0+). diff --git a/arch/arm/src/rp2040/rp2040_i2c.c b/arch/arm/src/rp2040/rp2040_i2c.c index 3df00c4141e..d2c56771f62 100644 --- a/arch/arm/src/rp2040/rp2040_i2c.c +++ b/arch/arm/src/rp2040/rp2040_i2c.c @@ -45,6 +45,8 @@ #include "rp2040_i2c.h" #include "hardware/rp2040_i2c.h" +#include "hardware/rp2040_resets.h" +#include "rp2040_gpio.h" #ifdef CONFIG_RP2040_I2C @@ -124,6 +126,7 @@ static inline void i2c_reg_rmw(struct rp2040_i2cdev_s *dev, uint32_t val, uint32_t mask); static int rp2040_i2c_disable(struct rp2040_i2cdev_s *priv); +static void rp2040_i2c_init(struct rp2040_i2cdev_s *priv); static void rp2040_i2c_enable(struct rp2040_i2cdev_s *priv); static int rp2040_i2c_interrupt(int irq, FAR void *context, FAR void *arg); @@ -639,7 +642,143 @@ static int rp2040_i2c_transfer(FAR struct i2c_master_s *dev, #ifdef CONFIG_I2C_RESET static int rp2040_i2c_reset(FAR struct i2c_master_s *dev) { - return OK; + FAR struct rp2040_i2cdev_s *priv = (struct rp2040_i2cdev_s *)dev; + unsigned int clock_count; + unsigned int stretch_count; + uint32_t scl_gpio; + uint32_t sda_gpio; + uint32_t subsys; + uint32_t frequency; + int pin; + int ret; + + DEBUGASSERT(dev); + + /* Our caller must own a ref */ + + DEBUGASSERT(priv->refs > 0); + + /* Lock out other clients */ + + i2c_takesem(&priv->mutex); + + ret = -EIO; + + /* De-init the port */ + + rp2040_i2c_disable(priv); + + /* Use GPIO configuration to un-wedge the bus */ + + pin = rp2040_gpio_get_function_pin(RP2040_GPIO_FUNC_I2C, priv->port); + if (pin < 0) + { + goto out_without_reinit; + } + + sda_gpio = pin; + scl_gpio = pin + 1; + subsys = (priv->port == 0) ? RP2040_RESETS_RESET_I2C0 : + RP2040_RESETS_RESET_I2C1; + + rp2040_gpio_init(sda_gpio); + rp2040_gpio_setdir(sda_gpio, true); + rp2040_gpio_set_pulls(sda_gpio, true, false); /* Pull up */ + rp2040_gpio_put(sda_gpio, true); + + rp2040_gpio_init(scl_gpio); + rp2040_gpio_setdir(scl_gpio, true); + rp2040_gpio_set_pulls(scl_gpio, true, false); + rp2040_gpio_put(scl_gpio, true); + + /* Let SDA go high */ + + rp2040_gpio_put(sda_gpio, true); + + /* Clock the bus until any slaves currently driving it let it go. */ + + clock_count = 0; + while (!rp2040_gpio_get(sda_gpio)) + { + /* Give up if we have tried too hard */ + + if (clock_count++ > 10) + { + goto out; + } + + /* Sniff to make sure that clock stretching has finished. If the bus + * never relaxes, the reset has failed. + */ + + stretch_count = 0; + while (!rp2040_gpio_get(scl_gpio)) + { + /* Give up if we have tried too hard */ + + if (stretch_count++ > 10) + { + goto out; + } + + up_udelay(10); + } + + /* Drive SCL low */ + + rp2040_gpio_put(scl_gpio, false); + up_udelay(10); + + /* Drive SCL high again */ + + rp2040_gpio_put(scl_gpio, true); + up_udelay(10); + } + + /* Generate a start followed by a stop to reset slave state machines. */ + + rp2040_gpio_put(sda_gpio, false); + up_udelay(10); + rp2040_gpio_put(scl_gpio, false); + up_udelay(10); + rp2040_gpio_put(scl_gpio, true); + up_udelay(10); + rp2040_gpio_put(sda_gpio, true); + up_udelay(10); + + ret = OK; + +out: + + /* Revert the GPIO configuration. */ + + rp2040_gpio_set_function(sda_gpio, RP2040_GPIO_FUNC_I2C); + rp2040_gpio_set_function(scl_gpio, RP2040_GPIO_FUNC_I2C); + + /* Reset I2C subsystem */ + + setbits_reg32(subsys, RP2040_RESETS_RESET); + clrbits_reg32(subsys, RP2040_RESETS_RESET); + while ((getreg32(RP2040_RESETS_RESET_DONE) & subsys) == 0) + ; + + /* Re-init the port */ + + rp2040_i2c_disable(priv); + rp2040_i2c_init(priv); + + /* Restore the frequency */ + + frequency = priv->frequency; + priv->frequency = 0; + rp2040_i2c_setfrequency(priv, frequency); + +out_without_reinit: + + /* Release the port for re-use by other clients */ + + i2c_givesem(&priv->mutex); + return ret; } #endif /* CONFIG_I2C_RESET */ @@ -694,6 +833,26 @@ static int rp2040_i2c_disable(struct rp2040_i2cdev_s *priv) return 0; } +static void rp2040_i2c_init(struct rp2040_i2cdev_s *priv) +{ + i2c_reg_write(priv, RP2040_I2C_IC_INTR_MASK_OFFSET, 0x00); + i2c_reg_read(priv, RP2040_I2C_IC_CLR_INTR_OFFSET); + + /* set threshold level of the Rx/Tx FIFO */ + + i2c_reg_write(priv, RP2040_I2C_IC_RX_TL_OFFSET, 0xff); + i2c_reg_write(priv, RP2040_I2C_IC_TX_TL_OFFSET, 0); + + /* set hold time for margin */ + + i2c_reg_write(priv, RP2040_I2C_IC_SDA_HOLD_OFFSET, 1); + + i2c_reg_write(priv, RP2040_I2C_IC_CON_OFFSET, + (RP2040_I2C_IC_CON_IC_SLAVE_DISABLE | + RP2040_I2C_IC_CON_MASTER_MODE | + RP2040_I2C_IC_CON_TX_EMPTY_CTRL)); +} + static void rp2040_i2c_enable(struct rp2040_i2cdev_s *priv) { i2c_reg_write(priv, RP2040_I2C_IC_INTR_MASK_OFFSET, I2C_INTR_ENABLE); @@ -758,23 +917,7 @@ struct i2c_master_s *rp2040_i2cbus_initialize(int port) priv->base_freq = BOARD_PERI_FREQ; rp2040_i2c_disable(priv); - - i2c_reg_write(priv, RP2040_I2C_IC_INTR_MASK_OFFSET, 0x00); - i2c_reg_read(priv, RP2040_I2C_IC_CLR_INTR_OFFSET); - - /* set threshold level of the Rx/Tx FIFO */ - - i2c_reg_write(priv, RP2040_I2C_IC_RX_TL_OFFSET, 0xff); - i2c_reg_write(priv, RP2040_I2C_IC_TX_TL_OFFSET, 0); - - /* set hold time for margin */ - - i2c_reg_write(priv, RP2040_I2C_IC_SDA_HOLD_OFFSET, 1); - - i2c_reg_write(priv, RP2040_I2C_IC_CON_OFFSET, - (RP2040_I2C_IC_CON_IC_SLAVE_DISABLE | - RP2040_I2C_IC_CON_MASTER_MODE | - RP2040_I2C_IC_CON_TX_EMPTY_CTRL)); + rp2040_i2c_init(priv); rp2040_i2c_setfrequency(priv, I2C_DEFAULT_FREQUENCY); leave_critical_section(flags);