aboutsummaryrefslogtreecommitdiffstats
path: root/os/hal/ports
diff options
context:
space:
mode:
authorflabbergast <s3+flabbergast@sdfeu.org>2016-04-18 21:09:29 +0100
committerflabbergast <s3+flabbergast@sdfeu.org>2016-04-25 11:53:48 +0100
commitfa9644e655ca8304a27a85a82a457bbc5785fdcc (patch)
treea75010c53e4632db85702e90c01e450670c3ebf2 /os/hal/ports
parent7485ece089bd7fbf20ef57b81e73f41a20153981 (diff)
downloadChibiOS-Contrib-fa9644e655ca8304a27a85a82a457bbc5785fdcc.tar.gz
ChibiOS-Contrib-fa9644e655ca8304a27a85a82a457bbc5785fdcc.tar.bz2
ChibiOS-Contrib-fa9644e655ca8304a27a85a82a457bbc5785fdcc.zip
[KINETIS] Slightly rewrite and comment i2c driver.
Diffstat (limited to 'os/hal/ports')
-rw-r--r--os/hal/ports/KINETIS/LLD/hal_i2c_lld.c199
1 files changed, 143 insertions, 56 deletions
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);
}