diff options
Diffstat (limited to 'libmaple/i2c.c')
-rw-r--r-- | libmaple/i2c.c | 488 |
1 files changed, 216 insertions, 272 deletions
diff --git a/libmaple/i2c.c b/libmaple/i2c.c index e3f3199..9c93d3f 100644 --- a/libmaple/i2c.c +++ b/libmaple/i2c.c @@ -2,6 +2,7 @@ * The MIT License * * Copyright (c) 2010 Perry Hung. + * Copyright (c) 2012 LeafLabs, LLC. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,49 +26,28 @@ *****************************************************************************/ /** - * @file i2c.c + * @file libmaple/i2c.c + * @author Perry Hung <perry@leaflabs.com> * @brief Inter-Integrated Circuit (I2C) support. * * Currently, only master mode is supported. */ -#include "libmaple.h" -#include "rcc.h" -#include "gpio.h" -#include "nvic.h" -#include "i2c.h" -#include "string.h" -#include "systick.h" - -static i2c_dev i2c_dev1 = { - .regs = I2C1_BASE, - .gpio_port = &gpiob, - .sda_pin = 7, - .scl_pin = 6, - .clk_id = RCC_I2C1, - .ev_nvic_line = NVIC_I2C1_EV, - .er_nvic_line = NVIC_I2C1_ER, - .state = I2C_STATE_DISABLED -}; -/** I2C1 device */ -i2c_dev* const I2C1 = &i2c_dev1; - -static i2c_dev i2c_dev2 = { - .regs = I2C2_BASE, - .gpio_port = &gpiob, - .sda_pin = 11, - .scl_pin = 10, - .clk_id = RCC_I2C2, - .ev_nvic_line = NVIC_I2C2_EV, - .er_nvic_line = NVIC_I2C2_ER, - .state = I2C_STATE_DISABLED -}; -/** I2C2 device */ -i2c_dev* const I2C2 = &i2c_dev2; +#include "i2c_private.h" + +#include <libmaple/libmaple.h> +#include <libmaple/rcc.h> +#include <libmaple/gpio.h> +#include <libmaple/nvic.h> +#include <libmaple/i2c.h> +#include <libmaple/systick.h> + +#include <string.h> static inline int32 wait_for_state_change(i2c_dev *dev, i2c_state state, uint32 timeout); +static void set_ccr_trise(i2c_dev *dev, uint32 flags); /** * @brief Fill data register with slave address @@ -125,10 +105,191 @@ enum { }; /** - * @brief IRQ handler for I2C master. Handles transmission/reception. + * @brief Reset an I2C bus. + * + * Reset is accomplished by clocking out pulses until any hung slaves + * release SDA and SCL, then generating a START condition, then a STOP + * condition. + * + * @param dev I2C device + */ +void i2c_bus_reset(const i2c_dev *dev) { + /* Release both lines */ + i2c_master_release_bus(dev); + + /* + * Make sure the bus is free by clocking it until any slaves release the + * bus. + */ + while (!gpio_read_bit(sda_port(dev), dev->sda_pin)) { + /* Wait for any clock stretching to finish */ + while (!gpio_read_bit(scl_port(dev), dev->scl_pin)) + ; + delay_us(10); + + /* Pull low */ + gpio_write_bit(scl_port(dev), dev->scl_pin, 0); + delay_us(10); + + /* Release high again */ + gpio_write_bit(scl_port(dev), dev->scl_pin, 1); + delay_us(10); + } + + /* Generate start then stop condition */ + gpio_write_bit(sda_port(dev), dev->sda_pin, 0); + delay_us(10); + gpio_write_bit(scl_port(dev), dev->scl_pin, 0); + delay_us(10); + gpio_write_bit(scl_port(dev), dev->scl_pin, 1); + delay_us(10); + gpio_write_bit(sda_port(dev), dev->sda_pin, 1); +} + +/** + * @brief Initialize an I2C device and reset its registers to their + * default values. + * @param dev Device to initialize. + */ +void i2c_init(i2c_dev *dev) { + rcc_reset_dev(dev->clk_id); + rcc_clk_enable(dev->clk_id); +} + +/* Hack for deprecated bit of STM32F1 functionality */ +#ifndef _I2C_HAVE_DEPRECATED_I2C_REMAP +#define _i2c_handle_remap(dev, flags) ((void)0) +#endif + +/** + * @brief Initialize an I2C device as bus master + * @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: (deprecated, STM32F1 only) Remap I2C1 to SCL/PB8 + * SDA/PB9. + */ +void i2c_master_enable(i2c_dev *dev, uint32 flags) { + /* PE must be disabled to configure the device */ + ASSERT(!(dev->regs->CR1 & I2C_CR1_PE)); + + /* Ugh */ + _i2c_handle_remap(dev, flags); + + /* Reset the bus. Clock out any hung slaves. */ + if (flags & I2C_BUS_RESET) { + i2c_bus_reset(dev); + } + + /* Turn on clock and set GPIO modes */ + i2c_init(dev); + i2c_config_gpios(dev); + + /* Configure clock and rise time */ + set_ccr_trise(dev, flags); + + /* Enable event and buffer interrupts */ + nvic_irq_enable(dev->ev_nvic_line); + nvic_irq_enable(dev->er_nvic_line); + i2c_enable_irq(dev, I2C_IRQ_EVENT | I2C_IRQ_BUFFER | I2C_IRQ_ERROR); + + /* Make it go! */ + i2c_peripheral_enable(dev); + + dev->state = I2C_STATE_IDLE; +} + +/** + * @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 in between 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. */ -static void i2c_irq_handler(i2c_dev *dev) { +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; + dev->timestamp = systick_uptime(); + dev->state = I2C_STATE_BUSY; + + i2c_enable_irq(dev, I2C_IRQ_EVENT); + i2c_start_condition(dev); + + rc = wait_for_state_change(dev, I2C_STATE_XFER_DONE, timeout); + if (rc < 0) { + goto out; + } + + dev->state = I2C_STATE_IDLE; +out: + return rc; +} + +/** + * @brief Wait for an I2C event, or time out 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, a negative value on error. + */ +static inline int32 wait_for_state_change(i2c_dev *dev, + i2c_state state, + uint32 timeout) { + i2c_state tmp; + + while (1) { + tmp = dev->state; + + 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; + } + } + } +} + +/* + * Private API + */ + +/* + * IRQ handler for I2C master. Handles transmission/reception. + */ +void _i2c_irq_handler(i2c_dev *dev) { + /* WTFs: + * - Where is I2C_MSG_10BIT_ADDR handled? + */ i2c_msg *msg = dev->msg; uint8 read = msg->flags & I2C_MSG_READ; @@ -214,7 +375,7 @@ static void i2c_irq_handler(i2c_dev *dev) { /* * This should be impossible... */ - throb(); + ASSERT(0); } sr1 = sr2 = 0; } @@ -288,20 +449,11 @@ static void i2c_irq_handler(i2c_dev *dev) { } } -void __irq_i2c1_ev(void) { - i2c_irq_handler(&i2c_dev1); -} - -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 +/* + * Interrupt handler for I2C error conditions. Aborts any pending I2C + * transactions. */ -static void i2c_irq_error_handler(i2c_dev *dev) { +void _i2c_irq_error_handler(i2c_dev *dev) { I2C_CRUMB(ERROR_ENTRY, dev->regs->SR1, dev->regs->SR2); dev->error_flags = dev->regs->SR2 & (I2C_SR1_BERR | @@ -317,125 +469,34 @@ static void i2c_irq_error_handler(i2c_dev *dev) { dev->state = I2C_STATE_ERROR; } -void __irq_i2c1_er(void) { - i2c_irq_error_handler(&i2c_dev1); -} - -void __irq_i2c2_er(void) { - i2c_irq_error_handler(&i2c_dev2); -} - -/** - * @brief Reset an I2C bus. - * - * Reset is accomplished by clocking out pulses until any hung slaves - * release SDA and SCL, then generating 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); - gpio_set_mode(dev->gpio_port, dev->scl_pin, GPIO_OUTPUT_OD); - gpio_set_mode(dev->gpio_port, dev->sda_pin, GPIO_OUTPUT_OD); - - /* - * Make sure the bus is free by clocking it until any slaves release the - * bus. - */ - while (!gpio_read_bit(dev->gpio_port, dev->sda_pin)) { - /* Wait for any clock stretching to finish */ - while (!gpio_read_bit(dev->gpio_port, dev->scl_pin)) - ; - delay_us(10); - - /* Pull low */ - gpio_write_bit(dev->gpio_port, dev->scl_pin, 0); - delay_us(10); - - /* Release high again */ - gpio_write_bit(dev->gpio_port, dev->scl_pin, 1); - delay_us(10); - } - - /* Generate start then stop condition */ - gpio_write_bit(dev->gpio_port, dev->sda_pin, 0); - delay_us(10); - gpio_write_bit(dev->gpio_port, dev->scl_pin, 0); - delay_us(10); - gpio_write_bit(dev->gpio_port, dev->scl_pin, 1); - delay_us(10); - gpio_write_bit(dev->gpio_port, dev->sda_pin, 1); -} - -/** - * @brief Initialize an I2C device and reset its registers to their - * default values. - * @param dev Device to initialize. +/* + * CCR/TRISE configuration helper */ -void i2c_init(i2c_dev *dev) { - rcc_reset_dev(dev->clk_id); - rcc_clk_enable(dev->clk_id); -} +static void set_ccr_trise(i2c_dev *dev, uint32 flags) { + uint32 ccr = 0; + uint32 trise = 0; + uint32 clk_mhz = _i2c_bus_clk(dev); + uint32 clk_hz = clk_mhz * (1000 * 1000); -/** - * @brief Initialize an I2C device as bus master - * @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 (STM32_PCLK1/1000000) - 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. */ - if (flags & I2C_BUS_RESET) { - i2c_bus_reset(dev); - } - - /* Turn on clock and set GPIO modes */ - i2c_init(dev); - gpio_set_mode(dev->gpio_port, dev->sda_pin, GPIO_AF_OUTPUT_OD); - gpio_set_mode(dev->gpio_port, dev->scl_pin, GPIO_AF_OUTPUT_OD); - - /* I2C1 and I2C2 are fed from APB1, clocked at 36MHz */ - i2c_set_input_clk(dev, I2C_CLK); + i2c_set_input_clk(dev, clk_mhz); if (flags & I2C_FAST_MODE) { ccr |= I2C_CCR_FS; if (flags & I2C_DUTY_16_9) { /* Tlow/Thigh = 16/9 */ - ccr |= I2C_CCR_DUTY; - ccr |= STM32_PCLK1/(400000 * 25); + ccr |= I2C_CCR_DUTY_16_9; + ccr |= clk_hz / (400000 * 25); } else { /* Tlow/Thigh = 2 */ - ccr |= STM32_PCLK1/(400000 * 3); + ccr |= clk_hz / (400000 * 3); } - trise = (300 * (I2C_CLK)/1000) + 1; + trise = (300 * clk_mhz / 1000) + 1; } else { /* Tlow/Thigh = 1 */ - ccr = STM32_PCLK1/(100000 * 2); - trise = I2C_CLK + 1; + ccr = clk_hz / (100000 * 2); + trise = clk_mhz + 1; } /* Set minimum required value if CCR < 1*/ @@ -445,121 +506,4 @@ void i2c_master_enable(i2c_dev *dev, uint32 flags) { 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); - i2c_enable_irq(dev, I2C_IRQ_EVENT | I2C_IRQ_BUFFER | I2C_IRQ_ERROR); - - /* - * Important STM32 Errata: - * - * See STM32F10xx8 and STM32F10xxB Errata sheet (Doc ID 14574 Rev 8), - * Section 2.11.1, 2.11.2. - * - * 2.11.1: - * When the EV7, EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3 events are not - * managed before the current byte is being transferred, problems may be - * encountered such as receiving an extra byte, reading the same data twice - * or missing data. - * - * 2.11.2: - * In Master Receiver mode, when closing the communication using - * method 2, the content of the last read data can be corrupted. - * - * If the user software is not able to read the data N-1 before the STOP - * condition is generated on the bus, the content of the shift register - * (data N) will be corrupted. (data N is shifted 1-bit to the left). - * - * ---------------------------------------------------------------------- - * - * In order to ensure that events are not missed, the i2c interrupt must - * not be preempted. We set the i2c interrupt priority to be the highest - * interrupt in the system (priority level 0). All other interrupts have - * been initialized to priority level 16. See nvic_init(). - */ - nvic_irq_set_priority(dev->ev_nvic_line, 0); - nvic_irq_set_priority(dev->er_nvic_line, 0); - - /* Make it go! */ - i2c_peripheral_enable(dev); - - dev->state = I2C_STATE_IDLE; -} - - -/** - * @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 in between 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; - dev->timestamp = systick_uptime(); - dev->state = I2C_STATE_BUSY; - - i2c_enable_irq(dev, I2C_IRQ_EVENT); - i2c_start_condition(dev); - - rc = wait_for_state_change(dev, I2C_STATE_XFER_DONE, timeout); - if (rc < 0) { - goto out; - } - - dev->state = I2C_STATE_IDLE; -out: - return rc; -} - - -/** - * @brief Wait for an I2C event, or time out 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, a negative value on error. - */ -static inline int32 wait_for_state_change(i2c_dev *dev, - i2c_state state, - uint32 timeout) { - i2c_state tmp; - - while (1) { - tmp = dev->state; - - 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; - } - } - } } |