From 26f7b8c9e540e57243483b2b4ed2a9a972972b94 Mon Sep 17 00:00:00 2001 From: Michael Spahlinger Date: Fri, 24 Jun 2016 10:33:51 -0600 Subject: [PATCH] TWIHS Driver improved and GPIO-Driver fixed for Open-Drain Pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- arch/arm/Kconfig | 1 + arch/arm/src/samv7/Kconfig | 69 ++++++++++ arch/arm/src/samv7/sam_gpio.c | 12 +- arch/arm/src/samv7/sam_twihs.c | 230 +++++++++++++++++++++++++-------- 4 files changed, 247 insertions(+), 65 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f31f812778..fcc1c9ce98 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -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 diff --git a/arch/arm/src/samv7/Kconfig b/arch/arm/src/samv7/Kconfig index 55eb92d485..9507e0c59c 100644 --- a/arch/arm/src/samv7/Kconfig +++ b/arch/arm/src/samv7/Kconfig @@ -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 diff --git a/arch/arm/src/samv7/sam_gpio.c b/arch/arm/src/samv7/sam_gpio.c index 9b322f8381..cf681e7662 100644 --- a/arch/arm/src/samv7/sam_gpio.c +++ b/arch/arm/src/samv7/sam_gpio.c @@ -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; } diff --git a/arch/arm/src/samv7/sam_twihs.c b/arch/arm/src/samv7/sam_twihs.c index 6a0de18ef4..032d2eaa4a 100644 --- a/arch/arm/src/samv7/sam_twihs.c +++ b/arch/arm/src/samv7/sam_twihs.c @@ -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;