diff options
Diffstat (limited to 'libmaple/i2c.c')
-rw-r--r-- | libmaple/i2c.c | 227 |
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; + } } } } |