From cccaf5bb8ab5563c17c620f2daa004740c71e5fb Mon Sep 17 00:00:00 2001 From: batt Date: Wed, 9 Apr 2008 14:38:11 +0000 Subject: [PATCH] Add SAM7 I2C driver. git-svn-id: https://src.develer.com/svnoss/bertos/trunk@1221 38d2e660-2303-0410-9eaa-f027e97ec537 --- bertos/cpu/arm/drv/twi_at91.c | 266 ++++++++++++++++++++++++++++++++++ bertos/cpu/arm/drv/twi_at91.h | 54 +++++++ 2 files changed, 320 insertions(+) create mode 100644 bertos/cpu/arm/drv/twi_at91.c create mode 100644 bertos/cpu/arm/drv/twi_at91.h diff --git a/bertos/cpu/arm/drv/twi_at91.c b/bertos/cpu/arm/drv/twi_at91.c new file mode 100644 index 00000000..b0b84be5 --- /dev/null +++ b/bertos/cpu/arm/drv/twi_at91.c @@ -0,0 +1,266 @@ +/** + * \file + * + * + * \brief Driver for the AT91 ARM TWI (implementation) + * + * \version $Id$ + * + * \author Francesco Sacchi + */ + +#include "twi_at91.h" + +#include +#include +#include +#include + +#include + +#include + +/** + * Timeout for ACK slave waiting. + */ +#define TWI_TIMEOUT ms_to_ticks(50) + +/** + * Send \a size bytes over the twi line to slave \a id. + * If the device requires internal addressing before writing, \a byte1 \a byte2 and \a byte3 can + * be specified. Internal addressign bytes not used *must* be set to TWI_NO_IADDR. If 1 or 2 bytes + * are required for internal addressing you *must* first use \a byte1 and than \a byte2. + * \note Atmel TWI implementation is broken so it was not possible to supply a better + * interface. Additionally NACK handling is also broken, so if the i2c device reply nack + * this function will return after TWI_TIMEOUT. + * \return true if ok, false on slave timeout. + */ +bool twi_write(uint8_t id, twi_iaddr_t byte1, twi_iaddr_t byte2, twi_iaddr_t byte3, const void *_buf, size_t size) +{ + uint8_t addr_size = 0; + const uint8_t *buf = (const uint8_t *)_buf; + ticks_t start; + + /* At least 1 byte *must* be transmitted, thanks to crappy hw implementation */ + ASSERT(size >= 1); + + /* Check internal byte address presence */ + if (byte1 != TWI_NO_IADDR) + addr_size++; + + if (byte2 != TWI_NO_IADDR) + { + ASSERT(addr_size == 1); + addr_size++; + } + + if (byte3 != TWI_NO_IADDR) + { + ASSERT(addr_size == 2); + addr_size++; + } + + start = timer_clock(); + /* Wait tx buffer empty */ + while (!(TWI_SR & BV(TWI_TXRDY))) + { + if (timer_clock() - start > TWI_TIMEOUT) + return false; + } + + /* Set slave address and (optional) internal slave addresses */ + TWI_MMR = (uint32_t)id << TWI_DADR_SHIFT | (uint32_t)addr_size << TWI_IADRSZ_SHIFT; + + TWI_IADR = ((uint32_t)(byte3 & 0xff) << 16) | ((uint32_t)(byte2 & 0xff) << 8) | ((uint32_t)(byte1 & 0xff)); + + while (size--) + { + /* Send data */ + TWI_THR = *buf++; + + start = timer_clock(); + /* Wait tx buffer empty */ + while (!(TWI_SR & BV(TWI_TXRDY))) + { + if (timer_clock() - start > TWI_TIMEOUT) + return false; + } + } + + /* Wait transmit complete bit */ + start = timer_clock(); + while (!(TWI_SR & BV(TWI_TXCOMP))) + { + if (timer_clock() - start > TWI_TIMEOUT) + return false; + } + + return true; +} + + +/** + * Read \a size bytes from the twi line from slave \a id. + * If the device requires internal addressing before reading, \a byte1 \a byte2 and \a byte3 must + * be specified. Internal addressign bytes not used *must* be set to TWI_NO_IADDR. If 1 or 2 bytes + * are required for internal addressing you *must* first use \a byte1 and than \a byte2. + * \note Atmel TWI implementation is broken so it was not possible to supply a better + * interface. Additionally NACK handling is also broken, so if the i2c device reply nack + * this function will return after TWI_TIMEOUT. + * \return true if ok, false on slave timeout. + */ +bool twi_read(uint8_t id, twi_iaddr_t byte1, twi_iaddr_t byte2, twi_iaddr_t byte3, void *_buf, size_t size) +{ + uint8_t addr_size = 0; + uint8_t *buf = (uint8_t *)_buf; + bool stopped = false; + ticks_t start; + + /* At least 1 byte *must* be transmitted, thanks to crappy twi implementation */ + ASSERT(size >= 1); + + /* Check internal byte address presence */ + if (byte1 != TWI_NO_IADDR) + addr_size++; + + if (byte2 != TWI_NO_IADDR) + { + ASSERT(addr_size == 1); + addr_size++; + } + + if (byte3 != TWI_NO_IADDR) + { + ASSERT(addr_size == 2); + addr_size++; + } + + /* Wait tx buffer empty */ + start = timer_clock(); + while (!(TWI_SR & BV(TWI_TXRDY))) + { + if (timer_clock() - start > TWI_TIMEOUT) + return false; + } + + + /* Set slave address and (optional) internal slave addresses */ + TWI_MMR = ((uint32_t)id << TWI_DADR_SHIFT) | BV(TWI_MREAD) | ((uint32_t)addr_size << TWI_IADRSZ_SHIFT); + + TWI_IADR = ((uint32_t)(byte3 & 0xff) << 16) | ((uint32_t)(byte2 & 0xff) << 8) | ((uint32_t)(byte1 & 0xff)); + + /* + * Start reception. + * Kludge: if we want to receive only 1 byte, the stop but *must* be set here + * (thanks to crappy twi implementation again). + */ + if (size == 1) + { + TWI_CR = BV(TWI_START) | BV(TWI_STOP); + stopped = true; + } + else + TWI_CR = BV(TWI_START); + + while (size--) + { + /* If we are at the last byte, inform the crappy hw that we + want to stop the reception. */ + if (!size && !stopped) + TWI_CR = BV(TWI_STOP); + + /* Wait until a byte is received */ + start = timer_clock(); + while (!(TWI_SR & BV(TWI_RXRDY))) + { + if (timer_clock() - start > TWI_TIMEOUT) + { + TWI_CR = BV(TWI_STOP); + return false; + } + } + + + *buf++ = TWI_RHR; + } + + /* Wait transmit complete bit */ + start = timer_clock(); + while (!(TWI_SR & BV(TWI_TXCOMP))) + { + if (timer_clock() - start > TWI_TIMEOUT) + return false; + } + + return true; +} + +MOD_DEFINE(twi); + +/** + * Init the (broken) sam7 twi driver. + */ +void twi_init(void) +{ + /* Disable PIO on TWI pins */ + PIOA_PDR = BV(TWD) | BV(TWCK); + + /* Enable oper drain on TWI pins */ + PIOA_MDER = BV(TWD); + + /* Disable all irqs */ + TWI_IDR = 0xFFFFFFFF; + + TWI_CR = BV(TWI_SWRST); + + /* Enable master mode */ + TWI_CR = BV(TWI_MSEN); + + PMC_PCER = BV(TWI_ID); + + /* + * Compute twi clock. + * CLDIV = ((Tlow * 2^CKDIV) -3) * Tmck + * CHDIV = ((THigh * 2^CKDIV) -3) * Tmck + * Only CLDIV is computed since CLDIV = CHDIV (50% duty cycle) + */ + uint16_t cldiv, ckdiv = 0; + while ((cldiv = ((CLOCK_FREQ / (2 * CONFIG_TWI_FREQ)) - 3) / (1 << ckdiv)) > 255) + ckdiv++; + + /* Atmel errata states that ckdiv *must* be less than 5 for unknown reason */ + ASSERT(ckdiv < 5); + + TWI_CWGR = ((uint32_t)ckdiv << TWI_CKDIV_SHIFT) | (cldiv << TWI_CLDIV_SHIFT) | (cldiv << TWI_CHDIV_SHIFT); + TRACEMSG("TWI_CWGR [%08lx]", TWI_CWGR); + + MOD_INIT(twi); +} diff --git a/bertos/cpu/arm/drv/twi_at91.h b/bertos/cpu/arm/drv/twi_at91.h new file mode 100644 index 00000000..ecf4974e --- /dev/null +++ b/bertos/cpu/arm/drv/twi_at91.h @@ -0,0 +1,54 @@ +/** + * \file + * + * + * \brief Driver for the AT91 ARM TWI (implementation) + * + * \version $Id$ + * + * \author Francesco Sacchi + */ + + +#ifndef DRV_AT91_TWI_H +#define DRV_AT91_TWI_H + +#include + +typedef int16_t twi_iaddr_t; + +#define TWI_NO_IADDR (-1) + +void twi_init(void); +bool twi_read(uint8_t id, twi_iaddr_t byte1, twi_iaddr_t byte2, twi_iaddr_t byte3, void *_buf, size_t len); +bool twi_write(uint8_t id, twi_iaddr_t byte1, twi_iaddr_t byte2, twi_iaddr_t byte3, const void *_buf, size_t len); + +#endif /* DRV_AT91_TWI_H */ -- 2.25.1