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:
parent
315e22e4df
commit
26f7b8c9e5
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user