From 432ab15ef87735cce089856d255d5c19c4edd0f4 Mon Sep 17 00:00:00 2001 From: aleph Date: Tue, 31 May 2011 15:53:53 +0000 Subject: [PATCH] sam3: add TWI (i2c) driver. git-svn-id: https://src.develer.com/svnoss/bertos/trunk@4940 38d2e660-2303-0410-9eaa-f027e97ec537 --- bertos/cpu/cortex-m3/drv/i2c_cm3.h | 2 + bertos/cpu/cortex-m3/drv/i2c_sam3.c | 262 ++++++++++++++++++++++++++++ bertos/cpu/cortex-m3/drv/i2c_sam3.h | 53 ++++++ 3 files changed, 317 insertions(+) create mode 100644 bertos/cpu/cortex-m3/drv/i2c_sam3.c create mode 100644 bertos/cpu/cortex-m3/drv/i2c_sam3.h diff --git a/bertos/cpu/cortex-m3/drv/i2c_cm3.h b/bertos/cpu/cortex-m3/drv/i2c_cm3.h index 956d1655..1af140d2 100644 --- a/bertos/cpu/cortex-m3/drv/i2c_cm3.h +++ b/bertos/cpu/cortex-m3/drv/i2c_cm3.h @@ -42,6 +42,8 @@ #include "i2c_lm3s.h" #elif CPU_CM3_STM32 #include "i2c_stm32.h" +#elif CPU_CM3_SAM3 + #include "i2c_sam3.h" /*#elif Add other Cortex-M3 CPUs here */ #else #error Unknown CPU diff --git a/bertos/cpu/cortex-m3/drv/i2c_sam3.c b/bertos/cpu/cortex-m3/drv/i2c_sam3.c new file mode 100644 index 00000000..9e9ba6f4 --- /dev/null +++ b/bertos/cpu/cortex-m3/drv/i2c_sam3.c @@ -0,0 +1,262 @@ +/** + * \file + * + * + * \brief TWI driver for SAM3 (implementation) + * + * Only master mode is supported. + * + * \author Stefano Fedrigo + */ + + +#include "cfg/cfg_i2c.h" + +#define LOG_LEVEL I2C_LOG_LEVEL +#define LOG_FORMAT I2C_LOG_FORMAT + +#include + +#include // CPU_FREQ +#include +#include // BV() +#include +#include +#include +#include +#include +#include +#include + + +struct I2cHardware +{ + uint32_t base; + bool first_xtranf; +}; + + +INLINE bool waitTxRdy(I2c *i2c, time_t ms_timeout) +{ + ticks_t start = timer_clock(); + + while (!(HWREG(i2c->hw->base + TWI_SR_OFF) & TWI_SR_TXRDY)) + { + if (timer_clock() - start > ms_to_ticks(ms_timeout)) + return false; + cpu_relax(); + } + + return true; +} + +INLINE bool waitRxRdy(I2c *i2c, time_t ms_timeout) +{ + ticks_t start = timer_clock(); + + while (!(HWREG(i2c->hw->base + TWI_SR_OFF) & TWI_SR_RXRDY)) + { + if (timer_clock() - start > ms_to_ticks(ms_timeout)) + return false; + cpu_relax(); + } + + return true; +} + +INLINE void waitXferComplete(I2c *i2c) +{ + while (!(HWREG(i2c->hw->base + TWI_SR_OFF) & TWI_SR_TXCOMP)) + cpu_relax(); +} + +/* + * Send STOP condition. + */ +INLINE void sendStop(I2c *i2c) +{ + HWREG(i2c->hw->base + TWI_CR_OFF) |= TWI_CR_STOP; +} + +/* + * The start is not performed when we call the start function + * because the hardware should know the first data byte to send. + * Generally to perform a byte send we should write the slave address + * in slave address register and the first byte to send in data registry. + * After then we can perform the start write procedure, and send really + * the our data. To use common bertos i2c api the really start will be + * performed when the user "put" or "send" its data. These tricks are hide + * from the driver implementation. + */ +static void i2c_sam3_start(struct I2c *i2c, uint16_t slave_addr) +{ + i2c->hw->first_xtranf = true; + + if (I2C_TEST_START(i2c->flags) == I2C_START_R) + HWREG(i2c->hw->base + TWI_MMR_OFF) = TWI_MMR_DADR(slave_addr) | TWI_MMR_MREAD; + else + HWREG(i2c->hw->base + TWI_MMR_OFF) = TWI_MMR_DADR(slave_addr); +} + +static void i2c_sam3_putc(I2c *i2c, const uint8_t data) +{ + if (!waitTxRdy(i2c, CONFIG_I2C_START_TIMEOUT)) + { + LOG_ERR("i2c: txready timeout\n"); + i2c->errors |= I2C_START_TIMEOUT; + return; + } + + HWREG(i2c->hw->base + TWI_THR_OFF) = data; + + // On first byte sent wait for start timeout + if (i2c->hw->first_xtranf && !waitTxRdy(i2c, CONFIG_I2C_START_TIMEOUT)) + { + LOG_ERR("i2c: write start timeout\n"); + i2c->errors |= I2C_START_TIMEOUT; + sendStop(i2c); + waitXferComplete(i2c); + return; + } + i2c->hw->first_xtranf = false; + + if ((i2c->xfer_size == 1) && (I2C_TEST_STOP(i2c->flags) == I2C_STOP)) + { + sendStop(i2c); + waitXferComplete(i2c); + } +} + +static uint8_t i2c_sam3_getc(I2c *i2c) +{ + if (i2c->hw->first_xtranf) + { + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_START; + i2c->hw->first_xtranf = false; + } + + if ((i2c->xfer_size == 1) && (I2C_TEST_STOP(i2c->flags) == I2C_STOP)) + sendStop(i2c); + + if (!waitRxRdy(i2c, CONFIG_I2C_START_TIMEOUT)) + { + LOG_ERR("i2c: read start timeout\n"); + i2c->errors |= I2C_START_TIMEOUT; + return 0xFF; + } + + return HWREG(i2c->hw->base + TWI_RHR_OFF); +} + +static void i2c_setClock(I2c *i2c, int clock) +{ + uint32_t ck_div = 0; + uint32_t cl_div; + + for (;;) + { + cl_div = ((CPU_FREQ / (2 * clock)) - 4) / (1 << ck_div); + + if (cl_div <= 255) + break; + + ck_div++; + } + + ASSERT(ck_div < 8); + LOG_INFO("i2c: using CKDIV = %lu and CLDIV/CHDIV = %lu\n\n", ck_div, cl_div); + + HWREG(i2c->hw->base + TWI_CWGR_OFF) = 0; + HWREG(i2c->hw->base + TWI_CWGR_OFF) = (ck_div << 16) | (cl_div << 8) | cl_div; +} + + +static const I2cVT i2c_sam3_vt = +{ + .start = i2c_sam3_start, + .getc = i2c_sam3_getc, + .putc = i2c_sam3_putc, + .write = i2c_genericWrite, + .read = i2c_genericRead, +}; + +struct I2cHardware i2c_sam3_hw[I2C_CNT]; + + +/** + * Initialize I2C module. + */ +void i2c_hw_init(I2c *i2c, int dev, uint32_t clock) +{ + uint8_t dummy; + + ASSERT(dev < I2C_CNT); + + i2c->hw = &i2c_sam3_hw[dev]; + i2c->vt = &i2c_sam3_vt; + + // Configure I/O pins + pmc_periphEnable(PIOA_ID); + + switch (dev) + { + case I2C0: + i2c->hw->base = TWI0_BASE; + PIO_PERIPH_SEL(TWI0_PORT, BV(TWI0_TWD) | BV(TWI0_TWCK), TWI0_PERIPH); + HWREG(TWI0_PORT + PIO_PDR_OFF) = BV(TWI0_TWD) | BV(TWI0_TWCK); + pmc_periphEnable(TWI0_ID); + break; + case I2C1: + i2c->hw->base = TWI1_BASE; + PIO_PERIPH_SEL(TWI1_PORT, BV(TWI1_TWD) | BV(TWI1_TWCK), TWI1_PERIPH); + HWREG(TWI1_PORT + PIO_PDR_OFF) = BV(TWI1_TWD) | BV(TWI1_TWCK); + pmc_periphEnable(TWI1_ID); + break; + default: + ASSERT(!"i2c: invalid dev number"); + return; + } + + /* + * Reset sequence: enable slave mode, reset, read RHR, + * disable slave and master modes. + */ + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_SVEN; + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_SWRST; + dummy = HWREG(i2c->hw->base + TWI_RHR_OFF); + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_SVDIS; + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_MSDIS; + + // Set master mode + HWREG(i2c->hw->base + TWI_CR_OFF) = TWI_CR_MSEN; + + i2c_setClock(i2c, clock); +} diff --git a/bertos/cpu/cortex-m3/drv/i2c_sam3.h b/bertos/cpu/cortex-m3/drv/i2c_sam3.h new file mode 100644 index 00000000..5ecbc251 --- /dev/null +++ b/bertos/cpu/cortex-m3/drv/i2c_sam3.h @@ -0,0 +1,53 @@ +/** + * \file + * + * + * \brief TWI driver for SAM3 (interface) + * + * \author Stefano Fedrigo + */ + +#ifndef DRV_I2C_SAM3_H +#define DRV_I2C_SAM3_H + +/** + * \name I2C devices enum + */ +enum +{ + I2C0, + I2C1, + + I2C_CNT /**< Number of ports */ +}; + +#endif /* DRV_I2C_SAM3_H */ + -- 2.25.1