aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmaple/i2c.c')
-rw-r--r--libmaple/i2c.c227
1 files changed, 170 insertions, 57 deletions
diff --git a/libmaple/i2c.c b/libmaple/i2c.c
index f4cb522..5c8ee02 100644
--- a/libmaple/i2c.c
+++ b/libmaple/i2c.c
@@ -26,7 +26,8 @@
/**
* @file i2c.c
- * @brief Inter-Integrated Circuit (I2C) support.
+ * @brief Inter-Integrated Circuit (I2C) support. Currently supports only master
+ * mode.
*/
#include "libmaple.h"
@@ -36,8 +37,7 @@
#include "nvic.h"
#include "i2c.h"
#include "string.h"
-
-static inline int32 wait_for_state_change(i2c_dev *dev, i2c_state state);
+#include "systick.h"
static i2c_dev i2c_dev1 = {
.regs = I2C1_BASE,
@@ -47,9 +47,8 @@ static i2c_dev i2c_dev1 = {
.clk_line = RCC_I2C1,
.ev_nvic_line = NVIC_I2C1_EV,
.er_nvic_line = NVIC_I2C1_ER,
- .state = I2C_STATE_IDLE
+ .state = I2C_STATE_DISABLED
};
-
i2c_dev* const I2C1 = &i2c_dev1;
static i2c_dev i2c_dev2 = {
@@ -60,29 +59,52 @@ static i2c_dev i2c_dev2 = {
.clk_line = RCC_I2C2,
.ev_nvic_line = NVIC_I2C2_EV,
.er_nvic_line = NVIC_I2C2_ER,
- .state = I2C_STATE_IDLE
+ .state = I2C_STATE_DISABLED
};
-
i2c_dev* const I2C2 = &i2c_dev2;
-struct crumb {
- uint32 event;
- uint32 sr1;
- uint32 sr2;
-};
+static inline int32 wait_for_state_change(i2c_dev *dev,
+ i2c_state state,
+ uint32 timeout);
+
+/**
+ * @brief Fill data register with slave address
+ * @param dev i2c device
+ * @param addr slave address
+ * @param rw read/write bit
+ */
+static inline void i2c_send_slave_addr(i2c_dev *dev, uint32 addr, uint32 rw) {
+ dev->regs->DR = (addr << 1) | rw;
+}
+
+/*
+ * Simple debugging trail. Define I2C_DEBUG to turn on.
+ */
+#ifdef I2C_DEBUG
-#define NR_CRUMBS 128
+#define NR_CRUMBS 128
static struct crumb crumbs[NR_CRUMBS];
static uint32 cur_crumb = 0;
-static inline void leave_big_crumb(uint32 event, uint32 sr1, uint32 sr2) {
+static inline void i2c_drop_crumb(uint32 event, uint32 arg0, uint32 arg1) {
if (cur_crumb < NR_CRUMBS) {
struct crumb *crumb = &crumbs[cur_crumb++];
crumb->event = event;
- crumb->sr1 = sr1;
- crumb->sr2 = sr2;
+ crumb->arg0 = arg0;
+ crumb->arg1 = arg1;
}
}
+#define I2C_CRUMB(event, arg0, arg1) i2c_drop_crumb(event, arg0, arg1)
+
+#else
+#define I2C_CRUMB(event, arg0, arg1)
+#endif
+
+struct crumb {
+ uint32 event;
+ uint32 arg0;
+ uint32 arg1;
+};
enum {
IRQ_ENTRY = 1,
@@ -100,6 +122,7 @@ enum {
ERROR_ENTRY = 13,
};
+
/**
* @brief IRQ handler for i2c master. Handles transmission/reception.
* @param dev i2c device
@@ -111,7 +134,12 @@ static void i2c_irq_handler(i2c_dev *dev) {
uint32 sr1 = dev->regs->SR1;
uint32 sr2 = dev->regs->SR2;
- leave_big_crumb(IRQ_ENTRY, sr1, sr2);
+ I2C_CRUMB(IRQ_ENTRY, sr1, sr2);
+
+ /*
+ * Reset timeout counter
+ */
+ dev->timestamp = systick_uptime();
/*
* EV5: Start condition sent
@@ -145,10 +173,10 @@ static void i2c_irq_handler(i2c_dev *dev) {
i2c_disable_ack(dev);
if (dev->msgs_left > 1) {
i2c_start_condition(dev);
- leave_big_crumb(RX_ADDR_START, 0, 0);
+ I2C_CRUMB(RX_ADDR_START, 0, 0);
} else {
i2c_stop_condition(dev);
- leave_big_crumb(RX_ADDR_STOP, 0, 0);
+ I2C_CRUMB(RX_ADDR_STOP, 0, 0);
}
}
} else {
@@ -157,8 +185,9 @@ static void i2c_irq_handler(i2c_dev *dev) {
* register. We should get another TXE interrupt
* immediately to fill DR again.
*/
- if (msg->length != 1)
- i2c_write(dev, msg->data[msg->xferred++]);
+ if (msg->length != 1) {
+ i2c_write(dev, msg->data[msg->xferred++]);
+ }
}
sr1 = sr2 = 0;
}
@@ -169,7 +198,7 @@ static void i2c_irq_handler(i2c_dev *dev) {
* byte written.
*/
if ((sr1 & I2C_SR1_TXE) && !(sr1 & I2C_SR1_BTF)) {
- leave_big_crumb(TXE_ONLY, 0, 0);
+ I2C_CRUMB(TXE_ONLY, 0, 0);
if (dev->msgs_left) {
i2c_write(dev, msg->data[msg->xferred++]);
if (msg->xferred == msg->length) {
@@ -194,9 +223,9 @@ static void i2c_irq_handler(i2c_dev *dev) {
* Last byte sent, program repeated start/stop
*/
if ((sr1 & I2C_SR1_TXE) && (sr1 & I2C_SR1_BTF)) {
- leave_big_crumb(TXE_BTF, 0, 0);
+ I2C_CRUMB(TXE_BTF, 0, 0);
if (dev->msgs_left) {
- leave_big_crumb(TEST, 0, 0);
+ I2C_CRUMB(TEST, 0, 0);
/*
* Repeated start insanity: We can't disable ITEVTEN or else SB
* won't interrupt, but if we don't disable ITEVTEN, BTF will
@@ -216,7 +245,7 @@ static void i2c_irq_handler(i2c_dev *dev) {
* me.
*/
i2c_disable_irq(dev, I2C_IRQ_EVENT);
- leave_big_crumb(STOP_SENT, 0, 0);
+ I2C_CRUMB(STOP_SENT, 0, 0);
dev->state = I2C_STATE_XFER_DONE;
}
sr1 = sr2 = 0;
@@ -226,7 +255,7 @@ static void i2c_irq_handler(i2c_dev *dev) {
* EV7: Master Receiver
*/
if (sr1 & I2C_SR1_RXNE) {
- leave_big_crumb(RXNE_ONLY, 0, 0);
+ I2C_CRUMB(RXNE_ONLY, 0, 0);
msg->data[msg->xferred++] = dev->regs->DR;
/*
@@ -238,10 +267,10 @@ static void i2c_irq_handler(i2c_dev *dev) {
i2c_disable_ack(dev);
if (dev->msgs_left > 2) {
i2c_start_condition(dev);
- leave_big_crumb(RXNE_START_SENT, 0, 0);
+ I2C_CRUMB(RXNE_START_SENT, 0, 0);
} else {
i2c_stop_condition(dev);
- leave_big_crumb(RXNE_STOP_SENT, 0, 0);
+ I2C_CRUMB(RXNE_STOP_SENT, 0, 0);
}
} else if (msg->xferred == msg->length) {
dev->msgs_left--;
@@ -249,7 +278,7 @@ static void i2c_irq_handler(i2c_dev *dev) {
/*
* We're done.
*/
- leave_big_crumb(RXNE_DONE, 0, 0);
+ I2C_CRUMB(RXNE_DONE, 0, 0);
dev->state = I2C_STATE_XFER_DONE;
} else {
dev->msg++;
@@ -266,13 +295,25 @@ void __irq_i2c2_ev(void) {
i2c_irq_handler(&i2c_dev2);
}
+
+/**
+ * @brief Interrupt handler for i2c error conditions
+ * @param dev i2c device
+ * @sideeffect Aborts any pending i2c transactions
+ */
static void i2c_irq_error_handler(i2c_dev *dev) {
uint32 sr1 = dev->regs->SR1;
uint32 sr2 = dev->regs->SR2;
- leave_big_crumb(ERROR_ENTRY, sr1, sr2);
+ I2C_CRUMB(ERROR_ENTRY, sr1, sr2);
+
+ /* Clear flags */
+ dev->regs->SR1 = 0;
+ dev->regs->SR2 = 0;
i2c_stop_condition(dev);
i2c_disable_irq(dev, I2C_IRQ_BUFFER | I2C_IRQ_EVENT | I2C_IRQ_ERROR);
+ dev->error_flags = sr2 & (I2C_SR1_BERR | I2C_SR1_ARLO | I2C_SR1_AF |
+ I2C_SR1_OVR);
dev->state = I2C_STATE_ERROR;
}
@@ -284,7 +325,14 @@ void __irq_i2c2_er(void) {
i2c_irq_error_handler(&i2c_dev2);
}
-static void i2c_bus_reset(const i2c_dev *dev) {
+
+/**
+ * @brief Reset an i2c bus by clocking out pulses until any hung
+ * slaves release SDA and SCL, then generate a START condition, then
+ * a STOP condition.
+ * @param dev i2c device
+ */
+void i2c_bus_reset(const i2c_dev *dev) {
/* Release both lines */
gpio_write_bit(dev->gpio_port, dev->scl_pin, 1);
gpio_write_bit(dev->gpio_port, dev->sda_pin, 1);
@@ -335,16 +383,29 @@ void i2c_init(i2c_dev *dev) {
* @param dev Device to enable
* @param flags Bitwise or of the following I2C options:
* I2C_FAST_MODE: 400 khz operation
+ * I2C_DUTY_16_9: 16/9 Tlow/Thigh duty cycle (only applicable for fast mode)
+ * I2C_BUS_RESET: Reset the bus and clock out any hung slaves on initialization
* I2C_10BIT_ADDRESSING: Enable 10-bit addressing
+ * I2C_REMAP: Remap I2C1 to SCL/PB8 SDA/PB9
*/
void i2c_master_enable(i2c_dev *dev, uint32 flags) {
#define I2C_CLK (PCLK1/1000000)
-#define STANDARD_CCR (PCLK1/(100000*2))
-#define STANDARD_TRISE (I2C_CLK+1)
-#define FAST_CCR (I2C_CLK/10)
-#define FAST_TRISE ((I2C_CLK*3)/10+1)
+ uint32 ccr = 0;
+ uint32 trise = 0;
+
+ /* PE must be disabled to configure the device */
+ ASSERT(!(dev->regs->CR1 & I2C_CR1_PE));
+
+ if ((dev == I2C1) && (flags & I2C_REMAP)) {
+ afio_remap(AFIO_REMAP_I2C1);
+ I2C1->sda_pin = 9;
+ I2C1->scl_pin = 8;
+ }
+
/* Reset the bus. Clock out any hung slaves. */
- i2c_bus_reset(dev);
+ if (flags & I2C_BUS_RESET) {
+ i2c_bus_reset(dev);
+ }
/* Turn on clock and set GPIO modes */
i2c_init(dev);
@@ -354,22 +415,33 @@ void i2c_master_enable(i2c_dev *dev, uint32 flags) {
/* I2C1 and I2C2 are fed from APB1, clocked at 36MHz */
i2c_set_input_clk(dev, I2C_CLK);
- if(flags & I2C_FAST_MODE) {
- /* 400 kHz for fast mode, set DUTY and F/S bits */
- i2c_set_clk_control(dev, FAST_CCR|I2C_CCR_DUTY|I2C_CCR_FS);
+ if (flags & I2C_FAST_MODE) {
+ ccr |= I2C_CCR_FS;
- /* Set scl rise time, max rise time in fast mode: 300ns */
- i2c_set_trise(dev, FAST_TRISE);
+ if (flags & I2C_DUTY_16_9) {
+ /* Tlow/Thigh = 16/9 */
+ ccr |= I2C_CCR_DUTY;
+ ccr |= PCLK1/(400000 * 25);
+ } else {
+ /* Tlow/Thigh = 2 */
+ ccr |= PCLK1/(400000 * 3);
+ }
+ trise = (300 * (I2C_CLK)/1000) + 1;
} else {
+ /* Tlow/Thigh = 1 */
+ ccr = PCLK1/(100000 * 2);
+ trise = I2C_CLK + 1;
+ }
- /* 100 kHz for standard mode */
- i2c_set_clk_control(dev, STANDARD_CCR);
-
- /* Max rise time in standard mode: 1000 ns */
- i2c_set_trise(dev, STANDARD_TRISE);
+ /* Set minimum required value if CCR < 1*/
+ if ((ccr & I2C_CCR_CCR) == 0) {
+ ccr |= 0x1;
}
+ i2c_set_clk_control(dev, ccr);
+ i2c_set_trise(dev, trise);
+
/* Enable event and buffer interrupts */
nvic_irq_enable(dev->ev_nvic_line);
nvic_irq_enable(dev->er_nvic_line);
@@ -407,40 +479,81 @@ void i2c_master_enable(i2c_dev *dev, uint32 flags) {
/* Make it go! */
i2c_peripheral_enable(dev);
+
+ dev->state = I2C_STATE_IDLE;
}
-int32 i2c_master_xfer(i2c_dev *dev, i2c_msg *msgs, uint16 num) {
+
+/**
+ * @brief Process an i2c transaction. Transactions are composed of
+ * one or more i2c_msg's and may be read or write tranfers. Multiple i2c_msg's
+ * will generate a repeated start inbetween messages.
+ * @param dev i2c device
+ * @param msgs messages to send/receive
+ * @param num number of messages to send/receive
+ * @param timeout bus idle timeout in milliseconds before aborting the
+ * transfer. 0 denotes no timeout.
+ * @return 0 on success
+ * I2C_ERROR_PROTOCOL if there was a protocol error.
+ * I2C_ERROR_TIMEOUT if the transfer timed out.
+ */
+int32 i2c_master_xfer(i2c_dev *dev,
+ i2c_msg *msgs,
+ uint16 num,
+ uint32 timeout) {
int32 rc;
+ ASSERT(dev->state == I2C_STATE_IDLE);
+
dev->msg = msgs;
dev->msgs_left = num;
-
- while (dev->regs->SR2 & I2C_SR2_BUSY)
- ;
-
+ dev->timestamp = systick_uptime();
dev->state = I2C_STATE_BUSY;
- i2c_enable_irq(dev, I2C_IRQ_EVENT);
+ i2c_enable_irq(dev, I2C_IRQ_EVENT);
i2c_start_condition(dev);
- rc = wait_for_state_change(dev, I2C_STATE_XFER_DONE);
+
+ rc = wait_for_state_change(dev, I2C_STATE_XFER_DONE, timeout);
if (rc < 0) {
goto out;
}
dev->state = I2C_STATE_IDLE;
- rc = num;
out:
return rc;
}
-static inline int32 wait_for_state_change(i2c_dev *dev, i2c_state state) {
+
+/**
+ * @brief Wait for an i2c event, or timeout in case of error
+ * @param dev i2c device
+ * @param state i2c_state state to wait for
+ * @param timeout timeout in milliseconds
+ * @return 0 if target state is reached, <0 on error
+ */
+static inline int32 wait_for_state_change(i2c_dev *dev,
+ i2c_state state,
+ uint32 timeout) {
int32 rc;
i2c_state tmp;
while (1) {
tmp = dev->state;
- if ((tmp == state) || (tmp == I2C_STATE_ERROR)) {
- return (tmp == I2C_STATE_ERROR) ? -1 : 0;
+
+ if (tmp == I2C_STATE_ERROR) {
+ return I2C_STATE_ERROR;
+ }
+
+ if (tmp == state) {
+ return 0;
+ }
+
+ if (timeout) {
+ if (systick_uptime() > (dev->timestamp + timeout)) {
+ /* TODO: overflow? */
+ /* TODO: racy? */
+ return I2C_ERROR_TIMEOUT;
+ }
}
}
}