rp2040: support I2C_RESET

This commit is contained in:
Yuichi Nakamura 2021-11-05 15:49:38 +09:00 committed by David Sidrane
parent c109262a7f
commit 3a0cd56623
2 changed files with 162 additions and 18 deletions

View file

@ -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+).

View file

@ -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);