From fa9644e655ca8304a27a85a82a457bbc5785fdcc Mon Sep 17 00:00:00 2001 From: flabbergast <s3+flabbergast@sdfeu.org> Date: Mon, 18 Apr 2016 21:09:29 +0100 Subject: [KINETIS] Slightly rewrite and comment i2c driver. --- os/hal/ports/KINETIS/LLD/hal_i2c_lld.c | 199 +++++++++++++++++++++++---------- 1 file changed, 143 insertions(+), 56 deletions(-) (limited to 'os/hal/ports/KINETIS') diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c index 1095737..0831eac 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c @@ -63,7 +63,7 @@ void config_frequency(I2CDriver *i2cp) { * divider used to generate the SCL clock from the main * system clock. */ - uint16_t icr_table[] = { + const uint16_t icr_table[] = { /* 0x00 - 0x0F */ 20,22,24,26,28,30,34,40,28,32,36,40,44,48,56,68, /* 0x10 - 0x1F */ @@ -117,53 +117,116 @@ static void serve_interrupt(I2CDriver *i2cp) { I2C_TypeDef *i2c = i2cp->i2c; intstate_t state = i2cp->intstate; - if (i2c->S & I2Cx_S_ARBL) { - - i2cp->errors |= I2C_ARBITRATION_LOST; - i2c->S |= I2Cx_S_ARBL; - - } else if (state == STATE_SEND) { - - if (i2c->S & I2Cx_S_RXAK) - i2cp->errors |= I2C_ACK_FAILURE; - else if (i2cp->txbuf != NULL && i2cp->txidx < i2cp->txbytes) - i2c->D = i2cp->txbuf[i2cp->txidx++]; - else - i2cp->intstate = STATE_STOP; - - } else if (state == STATE_DUMMY) { - - if (i2c->S & I2Cx_S_RXAK) - i2cp->errors |= I2C_ACK_FAILURE; - else { - i2c->C1 &= ~I2Cx_C1_TX; - - if (i2cp->rxbytes > 1) - i2c->C1 &= ~I2Cx_C1_TXAK; - else - i2c->C1 |= I2Cx_C1_TXAK; - (void) i2c->D; - i2cp->intstate = STATE_RECV; - } - - } else if (state == STATE_RECV) { - - if (i2cp->rxbytes > 1) { - if (i2cp->rxidx == (i2cp->rxbytes - 2)) - i2c->C1 |= I2Cx_C1_TXAK; - else - i2c->C1 &= ~I2Cx_C1_TXAK; - } + /* check if we're master or slave */ + if (i2c->C1 & I2Cx_C1_MST) { + /* master */ + + if (i2c->S & I2Cx_S_ARBL) { + /* check if we lost arbitration */ + i2cp->errors |= I2C_ARBITRATION_LOST; + i2c->S |= I2Cx_S_ARBL; + /* TODO: may need to do more here, reset bus? */ + /* Perhaps clear MST? */ + + } else if (i2c->S & I2Cx_S_TCF) { + /* just completed byte transfer */ + if (i2c->C1 & I2Cx_C1_TX) { + /* the byte was transmitted */ + + if (state == STATE_SEND) { + /* currently sending stuff */ + + if (i2c->S & I2Cx_S_RXAK) { + /* slave did not ACK */ + i2cp->errors |= I2C_ACK_FAILURE; + /* the thread will be woken up at the end of ISR and release the bus */ + + } else if (i2cp->txbuf != NULL && i2cp->txidx < i2cp->txbytes) { + /* slave ACK'd and we want to send more */ + i2c->D = i2cp->txbuf[i2cp->txidx++]; + } else { + /* slave ACK'd and we are done sending */ + i2cp->intstate = STATE_STOP; + /* this wakes up the waiting thread at the end of ISR */ + } + + } else if (state == STATE_RECV) { + /* should be receiving stuff, so we've just sent the address */ + + if (i2c->S & I2Cx_S_RXAK) { + /* slave did not ACK */ + i2cp->errors |= I2C_ACK_FAILURE; + /* the thread will be woken up and release the bus */ + + } else { + /* slave ACK'd, we should be receiving next */ + i2c->C1 &= ~I2Cx_C1_TX; + + if (i2cp->rxbytes > 1) { + /* multi-byte read, send ACK after next transfer */ + i2c->C1 &= ~I2Cx_C1_TXAK; + } else { + /* only 1 byte remaining, send NAK */ + i2c->C1 |= I2Cx_C1_TXAK; + } + + (void) i2c->D; /* dummy read; triggers next receive */ + } + + } /* possibly check other states here - should not happen! */ + + } else { + /* the byte was received */ + + if (state == STATE_RECV) { + /* currently receiving stuff */ + /* the received byte is now in D */ + + if (i2cp->rxbytes > 1) { + /* expecting at least one byte after this one */ + if (i2cp->rxidx == (i2cp->rxbytes - 2)) { + /* expecting exactly one byte after this one, NAK that one */ + i2c->C1 |= I2Cx_C1_TXAK; + } else { + /* expecting more than one after this one, respond with ACK */ + i2c->C1 &= ~I2Cx_C1_TXAK; + } + } + + if (i2cp->rxidx == i2cp->rxbytes - 1) { + /* D is the last byte we're expecting */ + /* release bus: switch to RX mode, send STOP */ + /* need to do it now otherwise the I2C module will wait for another byte */ + // delayMicroseconds(1); + i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + i2cp->intstate = STATE_STOP; + /* this wakes up the waiting thread at the end of ISR */ + } + + /* get the data from D; this triggers the next receive */ + i2cp->rxbuf[i2cp->rxidx++] = i2c->D; + + // if (i2cp->rxidx == i2cp->rxbytes) { + /* done receiving */ + // } + } /* possibly check other states here - should not happen! */ + } - if (i2cp->rxidx == i2cp->rxbytes - 1) - i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + } /* possibly check other interrupt flags here */ - i2cp->rxbuf[i2cp->rxidx++] = i2c->D; + } else { + /* slave */ - if (i2cp->rxidx == i2cp->rxbytes) - i2cp->intstate = STATE_STOP; + /* Not implemented yet */ } + /* Reset other interrupt sources */ +#if defined(I2Cx_FLT_STOPF) /* extra flags on KL26Z and KL27Z */ + i2cp->i2c->FLT |= I2Cx_FLT_STOPF; +#endif +#if defined(I2Cx_FLT_STARTF) /* extra flags on KL27Z */ + i2cp->i2c->FLT |= I2Cx_FLT_STARTF; +#endif /* Reset interrupt flag */ i2c->S |= I2Cx_S_IICIF; @@ -260,8 +323,9 @@ void i2c_lld_start(I2CDriver *i2cp) { } config_frequency(i2cp); - i2cp->i2c->C1 |= I2Cx_C1_IICEN | I2Cx_C1_IICIE; - i2cp->intstate = STATE_STOP; + i2cp->i2c->C1 = I2Cx_C1_IICEN | I2Cx_C1_IICIE; // reset I2C, enable interrupts + i2cp->i2c->S = I2Cx_S_IICIF | I2Cx_S_ARBL; // clear status flags just in case + i2cp->intstate = STATE_STOP; // internal state } /** @@ -315,35 +379,58 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, i2cp->rxbytes = rxbytes; i2cp->rxidx = 0; - /* send START */ - i2cp->i2c->C1 |= I2Cx_C1_MST; - i2cp->i2c->C1 |= I2Cx_C1_TX; - - /* FIXME: should not use busy waiting! */ - while (!(i2cp->i2c->S & I2Cx_S_BUSY)); + /* clear status flags */ +#if defined(I2Cx_FLT_STOPF) /* extra flags on KL26Z and KL27Z */ + i2cp->i2c->FLT |= I2Cx_FLT_STOPF; +#endif +#if defined(I2Cx_FLT_STARTF) /* extra flags on KL27Z */ + i2cp->i2c->FLT |= I2Cx_FLT_STARTF; +#endif + i2cp->i2c->S = I2Cx_S_IICIF|I2Cx_S_ARBL; + + /* acquire the bus */ + /* check to see if we already have the bus */ + if(i2cp->i2c->C1 & I2Cx_C1_MST) { + /* send repeated start */ + i2cp->i2c->C1 |= I2Cx_C1_RSTA | I2Cx_C1_TX; + } else { + /* wait until the bus is released */ + /* TODO: implement timeout here */ + /* FIXME: should not use busy waiting! */ + while (i2cp->i2c->S & I2Cx_S_BUSY); + /* send START */ + i2cp->i2c->C1 |= I2Cx_C1_MST|I2Cx_C1_TX; + } + /* send slave address */ i2cp->i2c->D = addr << 1 | op; + /* wait for the ISR to signal that the transmission (or receive if no transmission) phase is complete */ msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); /* FIXME */ //if (i2cp->i2c->S & I2Cx_S_RXAK) // i2cp->errors |= I2C_ACK_FAILURE; - if (msg == MSG_OK && txbuf != NULL && rxbuf != NULL && rxbytes > 0) { + /* the transmitting (or receiving if no transmission) phase has finished, + * do we expect to receive something? */ + if (msg == MSG_OK && rxbuf != NULL && rxbytes > 0 && i2cp->rxidx < rxbytes) { + /* send repeated start */ i2cp->i2c->C1 |= I2Cx_C1_RSTA; /* FIXME */ - while (!(i2cp->i2c->S & I2Cx_S_BUSY)); + // while (!(i2cp->i2c->S & I2Cx_S_BUSY)); - i2cp->intstate = STATE_DUMMY; + i2cp->intstate = STATE_RECV; i2cp->i2c->D = i2cp->addr << 1 | 1; msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); } + /* release bus - RX mode, send STOP */ + // other kinetis I2C drivers wait here for 1us. is this needed? i2cp->i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); /* FIXME */ - while (i2cp->i2c->S & I2Cx_S_BUSY); + // while (i2cp->i2c->S & I2Cx_S_BUSY); return msg; } @@ -373,7 +460,7 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, uint8_t *rxbuf, size_t rxbytes, systime_t timeout) { - i2cp->intstate = STATE_DUMMY; + i2cp->intstate = STATE_RECV; return _i2c_txrx_timeout(i2cp, addr, NULL, 0, rxbuf, rxbytes, timeout); } -- cgit v1.2.3 From 0a37322265bb0e109d8f4a840ccc1ccbb19ffdfa Mon Sep 17 00:00:00 2001 From: flabbergast <s3+flabbergast@sdfeu.org> Date: Mon, 18 Apr 2016 22:53:11 +0100 Subject: [KINETIS] I2C driver: implement timeout. --- os/hal/ports/KINETIS/LLD/hal_i2c_lld.c | 31 +++++++++++++++++++++++++------ os/hal/ports/KINETIS/LLD/hal_i2c_lld.h | 7 +++++++ 2 files changed, 32 insertions(+), 6 deletions(-) (limited to 'os/hal/ports/KINETIS') diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c index 0831eac..6085941 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c @@ -363,8 +363,8 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, uint8_t *rxbuf, size_t rxbytes, systime_t timeout) { - (void)timeout; msg_t msg; + systime_t start, end; uint8_t op = (i2cp->intstate == STATE_SEND) ? 0 : 1; @@ -394,10 +394,29 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, /* send repeated start */ i2cp->i2c->C1 |= I2Cx_C1_RSTA | I2Cx_C1_TX; } else { + /* unlock during the wait, so that tasks with + * higher priority can get attention */ + osalSysUnlock(); + /* wait until the bus is released */ - /* TODO: implement timeout here */ - /* FIXME: should not use busy waiting! */ - while (i2cp->i2c->S & I2Cx_S_BUSY); + /* Calculating the time window for the timeout on the busy bus condition.*/ + start = osalOsGetSystemTimeX(); + end = start + OSAL_MS2ST(KINETIS_I2C_BUSY_TIMEOUT); + + while(true) { + osalSysLock(); + /* If the bus is not busy then the operation can continue, note, the + loop is exited in the locked state.*/ + if(!(i2cp->i2c->S & I2Cx_S_BUSY)) + break; + /* If the system time went outside the allowed window then a timeout + condition is returned.*/ + if (!osalOsIsTimeWithinX(osalOsGetSystemTimeX(), start, end)) { + return MSG_TIMEOUT; + } + osalSysUnlock(); + } + /* send START */ i2cp->i2c->C1 |= I2Cx_C1_MST|I2Cx_C1_TX; } @@ -406,7 +425,7 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, i2cp->i2c->D = addr << 1 | op; /* wait for the ISR to signal that the transmission (or receive if no transmission) phase is complete */ - msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); + msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); /* FIXME */ //if (i2cp->i2c->S & I2Cx_S_RXAK) @@ -423,7 +442,7 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, i2cp->intstate = STATE_RECV; i2cp->i2c->D = i2cp->addr << 1 | 1; - msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); + msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); } /* release bus - RX mode, send STOP */ diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h index a7214c5..2b25df2 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h @@ -77,6 +77,13 @@ #define KINETIS_I2C_I2C1_PRIORITY 12 #endif +/** + * @brief Timeout for external clearing BUSY bus (in ms). + */ +#if !defined(KINETIS_I2C_BUSY_TIMEOUT) || defined(__DOXYGEN__) +#define KINETIS_I2C_BUSY_TIMEOUT 50 +#endif + /*===========================================================================*/ /* Derived constants and error checks. */ /*===========================================================================*/ -- cgit v1.2.3 From 2897589bf3b8fbbc2bfbda787bd42aa6b71b7bff Mon Sep 17 00:00:00 2001 From: flabbergast <s3+flabbergast@sdfeu.org> Date: Tue, 19 Apr 2016 09:57:00 +0100 Subject: [KINETIS] Fix I2C clock divisor computation. --- os/hal/ports/KINETIS/LLD/hal_i2c_lld.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'os/hal/ports/KINETIS') diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c index 6085941..ce59627 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c @@ -80,9 +80,9 @@ void config_frequency(I2CDriver *i2cp) { uint16_t best, diff; if (i2cp->config != NULL) - divisor = KINETIS_SYSCLK_FREQUENCY / i2cp->config->clock; + divisor = KINETIS_BUSCLK_FREQUENCY / i2cp->config->clock; else - divisor = KINETIS_SYSCLK_FREQUENCY / 100000; + divisor = KINETIS_BUSCLK_FREQUENCY / 100000; best = ~0; index = 0; -- cgit v1.2.3 From 9107b150b0d1fd5a2bdcc080b3493aefd8c56b46 Mon Sep 17 00:00:00 2001 From: flabbergast <s3+flabbergast@sdfeu.org> Date: Tue, 19 Apr 2016 21:51:34 +0100 Subject: [KINETIS] Add I2C workaround for KL27Z. --- os/hal/ports/KINETIS/LLD/hal_i2c_lld.c | 67 +++++++++++++++++++++++++++++++++- os/hal/ports/KINETIS/LLD/hal_i2c_lld.h | 10 ++++- 2 files changed, 74 insertions(+), 3 deletions(-) (limited to 'os/hal/ports/KINETIS') diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c index ce59627..c6b3d11 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.c @@ -127,8 +127,20 @@ static void serve_interrupt(I2CDriver *i2cp) { i2c->S |= I2Cx_S_ARBL; /* TODO: may need to do more here, reset bus? */ /* Perhaps clear MST? */ + } + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + else if ((i2cp->rsta_workaround == RSTA_WORKAROUND_ON) && (i2cp->i2c->FLT & I2Cx_FLT_STARTF)) { + i2cp->rsta_workaround = RSTA_WORKAROUND_OFF; + /* clear+disable STARTF/STOPF interrupts and wake up the thread */ + i2cp->i2c->FLT |= I2Cx_FLT_STOPF|I2Cx_FLT_STARTF; + i2cp->i2c->FLT &= ~I2Cx_FLT_SSIE; + i2c->S |= I2Cx_S_IICIF; + _i2c_wakeup_isr(i2cp); + } +#endif /* KL27Z RST workaround */ - } else if (i2c->S & I2Cx_S_TCF) { + else if (i2c->S & I2Cx_S_TCF) { /* just completed byte transfer */ if (i2c->C1 & I2Cx_C1_TX) { /* the byte was transmitted */ @@ -213,7 +225,6 @@ static void serve_interrupt(I2CDriver *i2cp) { } } /* possibly check other interrupt flags here */ - } else { /* slave */ @@ -379,6 +390,10 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, i2cp->rxbytes = rxbytes; i2cp->rxidx = 0; +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + i2cp->rsta_workaround = RSTA_WORKAROUND_OFF; +#endif /* KL27Z RST workaround */ + /* clear status flags */ #if defined(I2Cx_FLT_STOPF) /* extra flags on KL26Z and KL27Z */ i2cp->i2c->FLT |= I2Cx_FLT_STOPF; @@ -391,8 +406,34 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, /* acquire the bus */ /* check to see if we already have the bus */ if(i2cp->i2c->C1 & I2Cx_C1_MST) { + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + /* need to wait for STARTF interrupt after issuing repeated start, + * otherwise the double buffering mechanism sends the last sent byte + * instead of the slave address. + * https://community.freescale.com/thread/377611 + */ + i2cp->rsta_workaround = RSTA_WORKAROUND_ON; + /* clear any interrupt bits and enable STARTF/STOPF interrupts */ + i2cp->i2c->FLT |= I2Cx_FLT_STOPF|I2Cx_FLT_STARTF; + i2cp->i2c->S |= I2Cx_S_IICIF|I2Cx_S_ARBL; + i2cp->i2c->FLT |= I2Cx_FLT_SSIE; +#endif /* KL27Z RST workaround */ + /* send repeated start */ i2cp->i2c->C1 |= I2Cx_C1_RSTA | I2Cx_C1_TX; + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + /* wait for the STARTF interrupt */ + msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); + /* abort if this didn't go well (timed out) */ + if (msg != MSG_OK) { + /* release bus - RX mode, send STOP */ + i2cp->i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + return msg; + } +#endif /* KL27Z RST workaround */ + } else { /* unlock during the wait, so that tasks with * higher priority can get attention */ @@ -434,8 +475,30 @@ static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, /* the transmitting (or receiving if no transmission) phase has finished, * do we expect to receive something? */ if (msg == MSG_OK && rxbuf != NULL && rxbytes > 0 && i2cp->rxidx < rxbytes) { + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + /* the same KL27Z RST workaround as above */ + i2cp->rsta_workaround = RSTA_WORKAROUND_ON; + /* clear any interrupt bits and enable STARTF/STOPF interrupts */ + i2cp->i2c->FLT |= I2Cx_FLT_STOPF|I2Cx_FLT_STARTF; + i2cp->i2c->S |= I2Cx_S_IICIF|I2Cx_S_ARBL; + i2cp->i2c->FLT |= I2Cx_FLT_SSIE; +#endif /* KL27Z RST workaround */ + /* send repeated start */ i2cp->i2c->C1 |= I2Cx_C1_RSTA; + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + /* wait for the STARTF interrupt */ + msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); + /* abort if this didn't go well (timed out) */ + if (msg != MSG_OK) { + /* release bus - RX mode, send STOP */ + i2cp->i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + return msg; + } +#endif /* KL27Z RST workaround */ + /* FIXME */ // while (!(i2cp->i2c->S & I2Cx_S_BUSY)); diff --git a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h index 2b25df2..3576b60 100644 --- a/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h +++ b/os/hal/ports/KINETIS/LLD/hal_i2c_lld.h @@ -34,7 +34,11 @@ #define STATE_STOP 0x00 #define STATE_SEND 0x01 #define STATE_RECV 0x02 -#define STATE_DUMMY 0x03 + +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ +#define RSTA_WORKAROUND_OFF 0x00 +#define RSTA_WORKAROUND_ON 0x01 +#endif /* KL27Z RST workaround */ /*===========================================================================*/ /* Driver pre-compile time settings. */ @@ -188,6 +192,10 @@ struct I2CDriver { intstate_t intstate; /* @brief Low-level register access. */ I2C_TypeDef *i2c; +#if defined(KL27Zxxx) || defined(KL27Zxx) /* KL27Z RST workaround */ + /* @brief Auxiliary variable for KL27Z repeated start workaround. */ + intstate_t rsta_workaround; +#endif /* KL27Z RST workaround */ }; /*===========================================================================*/ -- cgit v1.2.3