TWIHS Driver improved and GPIO-Driver fixed for Open-Drain Pins

- sam_gpioread: Now the actual line level from the pin is read back.  This is extremely important for Open-Drain Pins, which can be used bidirectionally
 - Re-Implemented twi_reset-function and enhanced it so it can be called from inside the driver (see next point)
 - Glitch-Filter: Added a configuration option to enable the twi-built-in glitch filter
 - Added a "Single Master Mode": In EMC Testing the TWI-Bus got stuck because the TWI-Master detected a Multi-Master access (but there is no second master). With the option "Single Master" we detect these events and automatically trigger a twi_reset. We also do an automatic recovery if a slave got stuck (SDA stays low).

With the above changes I²C-Bus reliability in harsh environments (eg. EMC) is greatly improved.

The small change in the GPIO-Driver was necessary because otherwise you cannot read back the correct line status of Open-Drain Outputs and this is needed by the twi_reset function.
This commit is contained in:
Michael Spahlinger 2016-06-24 10:33:51 -06:00 committed by Gregory Nutt
parent 315e22e4df
commit 26f7b8c9e5
4 changed files with 247 additions and 65 deletions

View File

@ -213,6 +213,7 @@ config ARCH_CHIP_SAMV7
select ARCH_HAVE_RAMFUNCS
select ARCH_HAVE_TICKLESS
select ARMV7M_HAVE_STACKCHECK
select ARCH_HAVE_I2CRESET
---help---
Atmel SAMV7 (ARM Cortex-M7) architectures

View File

@ -957,16 +957,85 @@ config SAMV7_TWIHS0_FREQUENCY
default 100000
depends on SAMV7_TWIHS0
config SAMV7_TWIHS0_GLITCH_FILTER
int "TWIHS0 Glitch Filter Time"
default 1
range 0 7
depends on SAMV7_TWIHS0
---help---
Apply filtering on TWIHS Inputs. Given number is the maximum pulse width
(defined in peripheral CLKs) of spikes to be suppressed by the input filter.
Setting this value to zero will disable glitch filtering.
config SAMV7_TWIHS0_SINGLE_MASTER
bool "TWIHS0 Single Master Mode"
default y
depends on SAMV7_TWIHS0
depends on I2C_RESET
---help---
Enables a mode, where errors on the I2C-Bus (e.g. by EMC or
stuck slaves) are automatically handled by the driver.
In an error-case the I2C-Bus is reset so further communication
on the bus can take place.
This option is default on because the TWI-Driver can't handle
Multi-Master I2C anyways.
config SAMV7_TWIHS1_FREQUENCY
int "TWIHS1 Frequency"
default 100000
depends on SAMV7_TWIHS1
config SAMV7_TWIHS1_GLITCH_FILTER
int "TWIHS1 Glitch Filter Time"
default 1
range 0 7
depends on SAMV7_TWIHS1
---help---
Apply filtering on TWIHS Inputs. Given number is the maximum pulse width
(defined in peripheral CLKs) of spikes to be suppressed by the input filter.
Setting this value to zero will disable glitch filtering.
config SAMV7_TWIHS1_SINGLE_MASTER
bool "TWIHS1 Single Master Mode"
default y
depends on SAMV7_TWIHS1
depends on I2C_RESET
---help---
Enables a mode, where errors on the I2C-Bus (e.g. by EMC or
stuck slaves) are automatically handled by the driver.
In an error-case the I2C-Bus is reset so further communication
on the bus can take place.
This option is default on because the TWI-Driver can't handle
Multi-Master I2C anyways.
config SAMV7_TWIHS2_FREQUENCY
int "TWIHS2 Frequency"
default 100000
depends on SAMV7_TWIHS2
config SAMV7_TWIHS2_GLITCH_FILTER
int "TWIHS2 Glitch Filter Time"
default 1
range 0 7
depends on SAMV7_TWIHS2
---help---
Apply filtering on TWIHS Inputs. Given number is the maximum pulse width
(defined in peripheral CLKs) of spikes to be suppressed by the input filter.
Setting this value to zero will disable glitch filtering.
config SAMV7_TWIHS2_SINGLE_MASTER
bool "TWIHS2 Single Master Mode"
default y
depends on SAMV7_TWIHS2
depends on I2C_RESET
---help---
Enables a mode, where errors on the I2C-Bus (e.g. by EMC or
stuck slaves) are automatically handled by the driver.
In an error-case the I2C-Bus is reset so further communication
on the bus can take place.
This option is default on because the TWI-Driver can't handle
Multi-Master I2C anyways.
config SAMV7_TWIHS_REGDEBUG
bool "TWIHS register level debug"
depends on DEBUG_I2C_INFO

View File

@ -543,15 +543,11 @@ bool sam_gpioread(gpio_pinset_t pinset)
uint32_t pin = sam_gpio_pinmask(pinset);
uint32_t regval;
if ((pinset & GPIO_MODE_MASK) == GPIO_OUTPUT)
{
regval = getreg32(base + SAM_PIO_ODSR_OFFSET);
}
else
{
regval = getreg32(base + SAM_PIO_PDSR_OFFSET);
}
/* Always read the Pin Data Status Register. Otherwise an Open-Drain
* Output pin will not be read back correctly.
*/
regval = getreg32(base + SAM_PIO_PDSR_OFFSET);
return (regval & pin) != 0;
}

View File

@ -116,13 +116,13 @@
#define TWIHS_MAX_FREQUENCY 66000000 /* Maximum TWIHS frequency */
/* Macros to convert a I2C pin to a PIO open-drain output */
/* Macros to convert a I2C pin to a GPIO open-drain output */
#define I2C_INPUT (PIO_INPUT | PIO_CFG_PULLUP)
#define I2C_OUTPUT (PIO_OUTPUT | PIO_CFG_OPENDRAIN | PIO_OUTPUT_SET)
#define I2C_INPUT (GPIO_INPUT | GPIO_CFG_PULLUP)
#define I2C_OUTPUT (GPIO_OUTPUT | GPIO_CFG_OPENDRAIN | GPIO_OUTPUT_SET)
#define MKI2C_INPUT(p) (((p) & (PIO_PORT_MASK | PIO_PIN_MASK)) | I2C_INPUT)
#define MKI2C_OUTPUT(p) (((p) & (PIO_PORT_MASK | PIO_PIN_MASK)) | I2C_OUTPUT)
#define MKI2C_INPUT(p) (((p) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | I2C_INPUT)
#define MKI2C_OUTPUT(p) (((p) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | I2C_OUTPUT)
/****************************************************************************
* Private Types
@ -134,6 +134,8 @@ struct twi_attr_s
uint8_t twi; /* TWIHS device number (for debug output) */
uint8_t pid; /* TWIHS peripheral ID */
uint16_t irq; /* IRQ number for this TWIHS bus */
uint8_t glitchfltr; /* Pulse width of a glich to be suppressed by the filter */
uint8_t s_master; /* Single-Master Mode active */
gpio_pinset_t sclcfg; /* TWIHS CK pin configuration (SCL in I2C-ese) */
gpio_pinset_t sdacfg; /* TWIHS D pin configuration (SDA in I2C-ese) */
uintptr_t base; /* Base address of TWIHS registers */
@ -219,6 +221,7 @@ static void twi_startmessage(struct twi_dev_s *priv, struct i2c_msg_s *msg);
static int twi_transfer(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
static int twi_reset_internal(FAR struct i2c_master_s *dev);
static int twi_reset(FAR struct i2c_master_s * dev);
#endif
@ -237,6 +240,12 @@ static const struct twi_attr_s g_twi0attr =
.twi = 0,
.pid = SAM_PID_TWIHS0,
.irq = SAM_IRQ_TWIHS0,
.glitchfltr = CONFIG_SAMV7_TWIHS0_GLITCH_FILTER,
#ifdef CONFIG_SAMV7_TWIHS0_SINGLE_MASTER
.s_master = 1,
#else
.s_master = 0,
#endif
.sclcfg = GPIO_TWIHS0_CK,
.sdacfg = GPIO_TWIHS0_D,
.base = SAM_TWIHS0_BASE,
@ -252,6 +261,12 @@ static const struct twi_attr_s g_twi1attr =
.twi = 1,
.pid = SAM_PID_TWIHS1,
.irq = SAM_IRQ_TWIHS1,
.glitchfltr = CONFIG_SAMV7_TWIHS1_GLITCH_FILTER,
#ifdef CONFIG_SAMV7_TWIHS1_SINGLE_MASTER
.s_master = 1,
#else
.s_master = 0,
#endif
.sclcfg = GPIO_TWIHS1_CK,
.sdacfg = GPIO_TWIHS1_D,
.base = SAM_TWIHS1_BASE,
@ -267,6 +282,12 @@ static const struct twi_attr_s g_twi2attr =
.twi = 2,
.pid = SAM_PID_TWIHS2,
.irq = SAM_IRQ_TWIHS2,
.glitchfltr = CONFIG_SAMV7_TWIHS2_GLITCH_FILTER,
#ifdef CONFIG_SAMV7_TWIHS0_SINGLE_MASTER
.s_master = 1,
#else
.s_master = 0,
#endif
.sclcfg = GPIO_TWIHS2_CK,
.sdacfg = GPIO_TWIHS2_D,
.base = SAM_TWIHS2_BASE,
@ -312,7 +333,7 @@ static void twi_takesem(sem_t *sem)
* awakened by a signal.
*/
ASSERT(errno == EINTR);
DEBUGASSERT(errno == EINTR);
}
}
@ -494,6 +515,24 @@ static int twi_wait(struct twi_dev_s *priv, unsigned int size)
* all further interrupts for the TWIHS have been disabled.
*/
/* Check if an Arbitration Lost has occured */
if (priv->result == -EUSERS)
{
/* Something bad happened on the bus so force a reset */
priv->result = twi_reset_internal(&priv->dev);
/* Although the reset was successful tell the higher driver that it's
* transfer has failed and should be repeated.
*/
if (priv->result == OK)
{
priv->result = -EIO;
}
}
return priv->result;
}
@ -607,6 +646,20 @@ static int twi_interrupt(struct twi_dev_s *priv)
}
}
/* If Single-Master Mode is enabled and we lost arbitration (someone else or
* an EMC-Pulse did something on the bus) something went very wrong. So we end
* the current transfer with an EUSERS. The wait function will then reset
* the bus so further communication can take place.
*/
else if ((priv->attr->s_master) && ((pending & TWIHS_INT_ARBLST) != 0))
{
/* Wake up the thread with an Arbitration Lost error indication */
i2cllerr("ERROR: TWIHS%d Arbitration Lost\n");
twi_wakeup(priv, -EUSERS);
}
/* Check for errors. We must check for errors *before* checking TXRDY or
* TXCMP because the error can be signaled in combination with TXRDY or
* TXCOMP.
@ -856,6 +909,7 @@ static int twi_transfer(FAR struct i2c_master_s *dev,
struct twi_dev_s *priv = (struct twi_dev_s *)dev;
irqstate_t flags;
unsigned int size;
uint32_t sr;
int i;
int ret;
@ -891,6 +945,22 @@ static int twi_transfer(FAR struct i2c_master_s *dev,
twi_setfrequency(priv, msgs->frequency);
/* When we are in Single Master Mode check if the bus is ready (no stuck
* DATA or CLK line).
* Otherwise initiate a bus reset.
*/
if (priv->attr->s_master)
{
sr = twi_getrel(priv, SAM_TWIHS_SR_OFFSET);
if (((sr & TWIHS_INT_SDA) == 0) || ((sr & TWIHS_INT_SCL) == 0))
{
ret = twi_reset_internal(&priv->dev);
if (ret != OK)
goto errout;
}
}
/* Initiate the transfer. The rest will be handled from interrupt
* logic. Interrupts must be disabled to prevent re-entrance from the
* interrupt level.
@ -904,46 +974,46 @@ static int twi_transfer(FAR struct i2c_master_s *dev,
*/
ret = twi_wait(priv, size);
leave_critical_section(flags);
errout:
if (ret < 0)
{
i2cerr("ERROR: Transfer failed: %d\n", ret);
}
leave_critical_section(flags);
twi_givesem(&priv->exclsem);
return ret;
}
/************************************************************************************
* Name: twi_reset
*
* Description:
* Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
************************************************************************************/
* Name: twi_reset_internal
*
* Description:
* Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
* This function can be called from inside the driver while the TWIHS device is
* already locked, so we must not handle any semapores inside.
* To initiate a bus reset from outside the driver use twi_reset(dev).
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
************************************************************************************/
#ifdef CONFIG_I2C_RESET
static int twi_reset(FAR struct i2c_master_s *dev)
static int twi_reset_internal(FAR struct i2c_master_s *dev)
{
struct twi_dev_s *priv = (struct twi_dev_s *)dev;
unsigned int clockcnt;
unsigned int stretchcnt;
uint32_t sclpin;
uint32_t sdapin;
uint8_t wait_us;
int ret;
ASSERT(priv);
/* Get exclusive access to the TWIHS device */
twi_takesem(&priv->exclsem);
/* Disable TWIHS interrupts */
up_disable_irq(priv->attr->irq);
@ -960,28 +1030,31 @@ static int twi_reset(FAR struct i2c_master_s *dev)
sam_configgpio(sclpin);
sam_configgpio(sdapin);
/* Peripheral clocking must be enabled in order to read valid data from
* the output pin (clocking is enabled automatically for pins configured
* as inputs).
*/
sam_pio_forceclk(sclpin, true);
sam_pio_forceclk(sdapin, true);
/* Clock the bus until any slaves currently driving it low let it float.
* Reading from the output will return the actual sensed level on the
* SDA pin (not the level that we wrote).
*/
/* Set the wait-time according to the TWI-Bus-Frequency */
if (priv->frequency >= 330000)
{
wait_us = 3;
}
else
{
wait_us = 10;
}
clockcnt = 0;
while (sam_pioread(sdapin) == false)
while (sam_gpioread(sdapin) == false)
{
/* Give up if we have tried too hard */
if (clockcnt++ > 10)
{
ret = -ETIMEDOUT;
goto errout_with_lock;
goto errout;
}
/* Sniff to make sure that clock stretching has finished. SCL should
@ -991,55 +1064,85 @@ static int twi_reset(FAR struct i2c_master_s *dev)
*/
stretchcnt = 0;
while (sam_pioread(sclpin) == false)
while (sam_gpioread(sclpin) == false)
{
/* Give up if we have tried too hard */
if (stretchcnt++ > 10)
{
ret = -EAGAIN;
goto errout_with_lock;
goto errout;
}
up_udelay(10);
up_udelay(wait_us);
}
/* Drive SCL low */
sam_piowrite(sclpin, false);
up_udelay(10);
sam_gpiowrite(sclpin, false);
up_udelay(wait_us);
/* Drive SCL high (floating) again */
sam_piowrite(sclpin, true);
up_udelay(10);
sam_gpiowrite(sclpin, true);
up_udelay(wait_us);
}
/* Generate a start followed by a stop to reset slave
* state machines.
*/
sam_piowrite(sdapin, false);
up_udelay(10);
sam_piowrite(sclpin, false);
up_udelay(10);
sam_gpiowrite(sdapin, false);
up_udelay(wait_us);
sam_gpiowrite(sclpin, false);
up_udelay(wait_us);
sam_piowrite(sclpin, true);
up_udelay(10);
sam_piowrite(sdapin, true);
up_udelay(10);
/* Clocking is no longer forced */
sam_pio_forceclk(sclpin, false);
sam_pio_forceclk(sdapin, false);
sam_gpiowrite(sclpin, true);
up_udelay(wait_us);
sam_gpiowrite(sdapin, true);
up_udelay(wait_us);
/* Re-initialize the port hardware */
twi_hw_initialize(priv, priv->frequency);
ret = OK;
errout_with_lock:
errout:
return ret;
}
#endif /* CONFIG_I2C_RESET */
/************************************************************************************
* Name: twi_reset
*
* Description:
* Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
* This function can be called from outside the driver, so lock the TWIHS Device
* and then let the internal reset function do the work.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
************************************************************************************/
#ifdef CONFIG_I2C_RESET
static int twi_reset(FAR struct i2c_master_s *dev)
{
struct twi_dev_s *priv = (struct twi_dev_s *)dev;
int ret;
DEBUGASSERT(priv != NULL);
/* Get exclusive access to the TWIHS device */
twi_takesem(&priv->exclsem);
/* Do the reset-procedure */
ret = twi_reset_internal(dev);
/* Release our lock on the bus */
@ -1217,6 +1320,19 @@ static void twi_hw_initialize(struct twi_dev_s *priv, uint32_t frequency)
regval |= PMC_PCR_PID(priv->attr->pid) | PMC_PCR_CMD | PMC_PCR_EN;
twi_putabs(priv, SAM_PMC_PCR, regval);
/* Set the TWIHS Input Filters */
if (priv->attr->glitchfltr)
{
regval = TWIHS_FILTR_FILT | TWIHS_FILTR_THRES(priv->attr->glitchfltr);
}
else
{
regval = 0;
}
twi_putrel(priv, SAM_TWIHS_FILTR_OFFSET, regval);
/* Set the initial TWIHS data transfer frequency */
priv->frequency = 0;