/* ---------------------------------------------------------------------------- | |
* SAM Software Package License | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2015, Atmel Corporation | |
* | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the disclaimer below. | |
* | |
* Atmel's name may not be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* ---------------------------------------------------------------------------- | |
*/ | |
/** \addtogroup twi_module Working with TWI | |
* \section Purpose | |
* The TWI driver provides the interface to configure and use the TWI | |
* peripheral. | |
* | |
* \section Usage | |
* <ul> | |
* <li> Configures a TWI peripheral to operate in master mode, at the given | |
* frequency (in Hz) using twi_configure(). </li> | |
* <li> Sends a STOP condition on the TWI using twi_stop().</li> | |
* <li> Starts a read operation on the TWI bus with the specified slave using | |
* twi_start_read(). Data must then be read using twi_read_byte() whenever | |
* a byte is available (poll using twi_is_byte_received()).</li> | |
* <li> Starts a write operation on the TWI to access the selected slave using | |
* twi_start_write(). A byte of data must be provided to start the write; | |
* other bytes are written next.</li> | |
* <li> Sends a byte of data to one of the TWI slaves on the bus using twi_write_byte(). | |
* This function must be called once before twi_start_write() with the first byte of data | |
* to send, then it shall be called repeatedly after that to send the remaining bytes.</li> | |
* <li> Check if a byte has been received and can be read on the given TWI | |
* peripheral using twi_is_byte_received().< | |
* Check if a byte has been sent using twi_byte_sent().</li> | |
* <li> Check if the current transmission is complete (the STOP has been sent) | |
* using twi_is_transfer_complete().</li> | |
* <li> Enables & disable the selected interrupts sources on a TWI peripheral | |
* using twi_enable_it() and twi_enable_it().</li> | |
* <li> Get current status register of the given TWI peripheral using | |
* twi_get_status(). Get current status register of the given TWI peripheral, but | |
* masking interrupt sources which are not currently enabled using | |
* twi_get_masked_status().</li> | |
* </ul> | |
* For more accurate information, please look at the TWI section of the | |
* Datasheet. | |
* | |
* Related files :\n | |
* \ref twi.c\n | |
* \ref twi.h.\n | |
*/ | |
/*@{*/ | |
/*@}*/ | |
/** | |
* \file | |
* | |
* Implementation of Two Wire Interface (TWI). | |
* | |
*/ | |
/*---------------------------------------------------------------------------- | |
* Headers | |
*----------------------------------------------------------------------------*/ | |
#include "chip.h" | |
#include "peripherals/twi.h" | |
#include "peripherals/pmc.h" | |
#include "trace.h" | |
#include "timer.h" | |
#include "io.h" | |
#include <stddef.h> | |
#include <assert.h> | |
/*---------------------------------------------------------------------------- | |
* Exported functions | |
*----------------------------------------------------------------------------*/ | |
/** | |
* \brief Configures a TWI peripheral to operate in master mode, at the given | |
* frequency (in Hz). The duty cycle of the TWI clock is set to 50%. | |
* \param twi Pointer to an Twi instance. | |
* \param twi_clock Desired TWI clock frequency. | |
*/ | |
void twi_configure_master(Twi * pTwi, uint32_t twi_clock) | |
{ | |
uint32_t ck_div, cl_div, hold, ok, clock; | |
uint32_t id = get_twi_id_from_addr(pTwi); | |
trace_debug("twi_configure_master(%u)\n\r", (unsigned)twi_clock); | |
assert(pTwi); | |
assert(id < ID_PERIPH_COUNT); | |
/* SVEN: TWI Slave Mode Enabled */ | |
pTwi->TWI_CR = TWI_CR_SVEN; | |
/* Reset the TWI */ | |
pTwi->TWI_CR = TWI_CR_SWRST; | |
pTwi->TWI_RHR; | |
timer_sleep(10); | |
/* TWI Slave Mode Disabled, TWI Master Mode Disabled. */ | |
pTwi->TWI_MMR = 0; | |
pTwi->TWI_CR = TWI_CR_SVDIS; | |
pTwi->TWI_CR = TWI_CR_MSDIS; | |
clock = pmc_get_peripheral_clock(id); | |
/* Compute clock */ | |
ck_div = 0; ok = 0; | |
while (!ok) { | |
cl_div = ((clock / (2 * twi_clock)) - 3) >> ck_div; | |
if (cl_div <= 255) | |
ok = 1; | |
else | |
ck_div++; | |
} | |
twi_clock = ROUND_INT_DIV(clock, (((cl_div * 2) << ck_div) + 3)); | |
assert(ck_div < 8); | |
trace_debug("twi: CKDIV=%u CLDIV=CHDIV=%u -> TWI Clock %uHz\n\r", | |
(unsigned)ck_div, (unsigned)cl_div, (unsigned)twi_clock); | |
/* Compute holding time (I2C spec requires 300ns) */ | |
hold = ROUND_INT_DIV((uint32_t)(0.3 * clock), 1000000) - 3; | |
trace_debug("twi: HOLD=%u -> Holding Time %uns\n\r", | |
(unsigned)hold, (unsigned)((1000000 * (hold + 3)) / (clock / 1000))); | |
/* Configure clock */ | |
pTwi->TWI_CWGR = 0; | |
pTwi->TWI_CWGR = TWI_CWGR_CKDIV(ck_div) | TWI_CWGR_CHDIV(cl_div) | | |
TWI_CWGR_CLDIV(cl_div) | TWI_CWGR_HOLD(hold); | |
/* Set master mode */ | |
pTwi->TWI_CR = TWI_CR_MSEN; | |
timer_sleep(10); | |
assert((pTwi->TWI_CR & TWI_CR_SVDIS) != TWI_CR_MSDIS); | |
} | |
/** | |
* \brief Configures a TWI peripheral to operate in slave mode. | |
* \param twi Pointer to an Twi instance. | |
* \param slaveAddress Slave address. | |
*/ | |
void twi_configure_slave(Twi * pTwi, uint8_t slave_address) | |
{ | |
trace_debug("twi_configure_slave()\n\r"); | |
assert(pTwi); | |
/* TWI software reset */ | |
pTwi->TWI_CR = TWI_CR_SWRST; | |
pTwi->TWI_RHR; | |
/* Wait at least 10 ms */ | |
timer_sleep(10); | |
/* TWI Slave Mode Disabled, TWI Master Mode Disabled */ | |
pTwi->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS; | |
/* Configure slave address. */ | |
pTwi->TWI_SMR = 0; | |
pTwi->TWI_SMR = TWI_SMR_SADR(slave_address); | |
/* SVEN: TWI Slave Mode Enabled */ | |
pTwi->TWI_CR = TWI_CR_SVEN; | |
/* Wait at least 10 ms */ | |
timer_sleep(10); | |
assert((pTwi->TWI_CR & TWI_CR_SVDIS) != TWI_CR_SVDIS); | |
} | |
/** | |
* \brief Sends a STOP condition on the TWI. | |
* \param twi Pointer to an Twi instance. | |
*/ | |
void twi_stop(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
pTwi->TWI_CR = TWI_CR_STOP; | |
} | |
/** | |
* \brief Starts a read operation on the TWI bus with the specified slave, it returns | |
* immediately. Data must then be read using twi_read_byte() whenever a byte is | |
* available (poll using twi_is_byte_received()). | |
* \param twi Pointer to an Twi instance. | |
* \param address Slave address on the bus. | |
* \param iaddress Optional internal address bytes. | |
* \param isize Number of internal address bytes. | |
*/ | |
void twi_start_read(Twi * pTwi, uint8_t address, | |
uint32_t iaddress, uint8_t isize) | |
{ | |
assert(pTwi != NULL); | |
assert((address & 0x80) == 0); | |
assert((iaddress & 0xFF000000) == 0); | |
assert(isize < 4); | |
/* Set slave address and number of internal address bytes. */ | |
pTwi->TWI_MMR = 0; | |
pTwi->TWI_MMR = (isize << 8) | TWI_MMR_MREAD | (address << 16); | |
/* Set internal address bytes */ | |
pTwi->TWI_IADR = 0; | |
pTwi->TWI_IADR = iaddress; | |
/* Send START condition */ | |
pTwi->TWI_CR = TWI_CR_START; | |
} | |
/** | |
* \brief Reads a byte from the TWI bus. The read operation must have been started | |
* using twi_start_read() and a byte must be available (check with twi_is_byte_received()). | |
* \param twi Pointer to an Twi instance. | |
* \return byte read. | |
*/ | |
uint8_t twi_read_byte(Twi * twi) | |
{ | |
assert(twi != NULL); | |
uint8_t value; | |
readb(&twi->TWI_RHR, &value); | |
return value; | |
} | |
/** | |
* \brief Sends a byte of data to one of the TWI slaves on the bus. | |
* \note This function must be called once before twi_start_write() with | |
* the first byte of data to send, then it shall be called repeatedly | |
* after that to send the remaining bytes. | |
* \param twi Pointer to an Twi instance. | |
* \param byte Byte to send. | |
*/ | |
void twi_write_byte(Twi * twi, uint8_t byte) | |
{ | |
assert(twi != NULL); | |
writeb(&twi->TWI_THR, byte); | |
} | |
/** | |
* \brief Starts a write operation on the TWI to access the selected slave, then | |
* returns immediately. A byte of data must be provided to start the write; | |
* other bytes are written next. | |
* after that to send the remaining bytes. | |
* \param twi Pointer to an Twi instance. | |
* \param address Address of slave to acccess on the bus. | |
* \param iaddress Optional slave internal address. | |
* \param isize Number of internal address bytes. | |
* \param byte First byte to send. | |
*/ | |
void twi_start_write(Twi * pTwi, uint8_t address, uint32_t iaddress, | |
uint8_t isize, uint8_t byte) | |
{ | |
assert(pTwi != NULL); | |
assert((address & 0x80) == 0); | |
assert((iaddress & 0xFF000000) == 0); | |
assert(isize < 4); | |
/* Set slave address and number of internal address bytes. */ | |
pTwi->TWI_MMR = 0; | |
pTwi->TWI_MMR = (isize << 8) | (address << 16); | |
/* Set internal address bytes. */ | |
pTwi->TWI_IADR = 0; | |
pTwi->TWI_IADR = iaddress; | |
/* Write first byte to send. */ | |
twi_write_byte(pTwi, byte); | |
} | |
/** | |
* \brief Check if a byte have been receiced from TWI. | |
* \param twi Pointer to an Twi instance. | |
* \return 1 if a byte has been received and can be read on the given TWI | |
* peripheral; otherwise, returns 0. This function resets the status register. | |
*/ | |
uint8_t twi_is_byte_received(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
return ((pTwi->TWI_SR & TWI_SR_RXRDY) == TWI_SR_RXRDY); | |
} | |
/** | |
* \brief Check if a byte have been sent to TWI. | |
* \param twi Pointer to an Twi instance. | |
* \return 1 if a byte has been sent so another one can be stored for | |
* transmission; otherwise returns 0. This function clears the status register. | |
*/ | |
uint8_t twi_byte_sent(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
return ((pTwi->TWI_SR & TWI_SR_TXRDY) == TWI_SR_TXRDY); | |
} | |
/** | |
* \brief Check if current transmission is complet. | |
* \param twi Pointer to an Twi instance. | |
* \return 1 if the current transmission is complete (the STOP has been sent); | |
* otherwise returns 0. | |
*/ | |
uint8_t twi_is_transfer_complete(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
return ((pTwi->TWI_SR & TWI_SR_TXCOMP) == TWI_SR_TXCOMP); | |
} | |
/** | |
* \brief Enables the selected interrupts sources on a TWI peripheral. | |
* \param twi Pointer to an Twi instance. | |
* \param sources Bitwise OR of selected interrupt sources. | |
*/ | |
void twi_enable_it(Twi * pTwi, uint32_t sources) | |
{ | |
assert(pTwi != NULL); | |
pTwi->TWI_IER = sources; | |
} | |
/** | |
* \brief Disables the selected interrupts sources on a TWI peripheral. | |
* \param twi Pointer to an Twi instance. | |
* \param sources Bitwise OR of selected interrupt sources. | |
*/ | |
void twi_disable_it(Twi * pTwi, uint32_t sources) | |
{ | |
assert(pTwi != NULL); | |
pTwi->TWI_IDR = sources; | |
} | |
/** | |
* \brief Get the current status register of the given TWI peripheral. | |
* \note This resets the internal value of the status register, so further | |
* read may yield different values. | |
* \param twi Pointer to an Twi instance. | |
* \return TWI status register. | |
*/ | |
uint32_t twi_get_status(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
return pTwi->TWI_SR; | |
} | |
/** | |
* \brief Returns the current status register of the given TWI peripheral, but | |
* masking interrupt sources which are not currently enabled. | |
* \note This resets the internal value of the status register, so further | |
* read may yield different values. | |
* \param twi Pointer to an Twi instance. | |
*/ | |
uint32_t twi_get_masked_status(Twi * pTwi) | |
{ | |
uint32_t status; | |
assert(pTwi != NULL); | |
status = pTwi->TWI_SR; | |
status &= pTwi->TWI_IMR; | |
return status; | |
} | |
/** | |
* \brief Sends a STOP condition. STOP Condition is sent just after completing | |
* the current byte transmission in master read mode. | |
* \param twi Pointer to an Twi instance. | |
*/ | |
void twi_send_stop_condition(Twi * pTwi) | |
{ | |
assert(pTwi != NULL); | |
pTwi->TWI_CR |= TWI_CR_STOP; | |
} | |
#ifdef CONFIG_HAVE_TWI_ALTERNATE_CMD | |
void twi_init_write_transfert(Twi * twi, uint8_t addr, uint32_t iaddress, | |
uint8_t isize, uint8_t len) | |
{ | |
twi->TWI_RHR; | |
twi->TWI_CR = TWI_CR_MSDIS; | |
twi->TWI_CR = TWI_CR_MSEN; | |
twi->TWI_CR = TWI_CR_MSEN | TWI_CR_SVDIS | TWI_CR_ACMEN; | |
twi->TWI_ACR = 0; | |
twi->TWI_ACR = TWI_ACR_DATAL(len); | |
twi->TWI_MMR = 0; | |
twi->TWI_MMR = TWI_MMR_DADR(addr) | TWI_MMR_IADRSZ(isize); | |
/* Set internal address bytes. */ | |
twi->TWI_IADR = 0; | |
twi->TWI_IADR = iaddress; | |
} | |
void twi_init_read_transfert(Twi * twi, uint8_t addr, uint32_t iaddress, | |
uint8_t isize, uint8_t len) | |
{ | |
twi->TWI_RHR; | |
twi->TWI_CR = TWI_CR_MSEN | TWI_CR_SVDIS | TWI_CR_ACMEN; | |
twi->TWI_ACR = 0; | |
twi->TWI_ACR = TWI_ACR_DATAL(len) | TWI_ACR_DIR; | |
twi->TWI_MMR = 0; | |
twi->TWI_MMR = TWI_MMR_DADR(addr) | TWI_MMR_MREAD | |
| TWI_MMR_IADRSZ(isize); | |
/* Set internal address bytes. */ | |
twi->TWI_IADR = 0; | |
twi->TWI_IADR = iaddress; | |
twi->TWI_CR = TWI_CR_START; | |
while(twi->TWI_SR & TWI_SR_TXCOMP); | |
} | |
#endif | |
#ifdef CONFIG_HAVE_TWI_FIFO | |
void twi_fifo_configure(Twi* twi, uint8_t tx_thres, | |
uint8_t rx_thres, | |
uint32_t ready_modes) | |
{ | |
/* Disable TWI master and slave mode and activate FIFO */ | |
twi->TWI_CR = TWI_CR_MSDIS | TWI_CR_SVDIS | TWI_CR_FIFOEN; | |
/* Configure FIFO */ | |
twi->TWI_FMR = TWI_FMR_TXFTHRES(tx_thres) | TWI_FMR_RXFTHRES(rx_thres) | |
| ready_modes; | |
} | |
uint32_t twi_fifo_rx_size(Twi *twi) | |
{ | |
return (twi->TWI_FLR & TWI_FLR_RXFL_Msk) >> TWI_FLR_RXFL_Pos; | |
} | |
uint32_t twi_fifo_tx_size(Twi *twi) | |
{ | |
return (twi->TWI_FLR & TWI_FLR_TXFL_Msk) >> TWI_FLR_TXFL_Pos; | |
} | |
uint32_t twi_read_stream(Twi *twi, uint32_t addr, uint32_t iaddr, | |
uint32_t isize, const void *stream, uint8_t len) | |
{ | |
const uint8_t* buffer = stream; | |
uint8_t left = len; | |
twi_init_read_transfert(twi, addr, iaddr, isize, len); | |
if (twi_get_status(twi) & TWI_SR_NACK) { | |
trace_error("twid2: command NACK!\r\n"); | |
return 0; | |
} | |
while (left > 0) { | |
if ((twi->TWI_SR & TWI_SR_RXRDY) == 0) continue; | |
/* Get FIFO free size (int octet) and clamp it */ | |
uint32_t buf_size = twi_fifo_rx_size(twi); | |
buf_size = buf_size > left ? left : buf_size; | |
/* Fill the FIFO as must as possible */ | |
while (buf_size > sizeof(uint32_t)) { | |
*(uint32_t*)buffer = twi->TWI_RHR; | |
buffer += sizeof(uint32_t); | |
left -= sizeof(uint32_t); | |
buf_size -= sizeof(uint32_t); | |
} | |
while (buf_size >= sizeof(uint8_t)) { | |
readb(&twi->TWI_RHR, (uint8_t*)buffer); | |
buffer += sizeof(uint8_t); | |
left -= sizeof(uint8_t); | |
buf_size -= sizeof(uint8_t); | |
} | |
} | |
return len - left; | |
} | |
uint32_t twi_write_stream(Twi *twi, uint32_t addr, uint32_t iaddr, | |
uint32_t isize, const void *stream, uint8_t len) | |
{ | |
const uint8_t* buffer = stream; | |
uint8_t left = len; | |
int32_t fifo_size = get_peripheral_fifo_depth(twi); | |
if (fifo_size < 0) | |
return 0; | |
twi_init_write_transfert(twi, addr, iaddr, isize, len); | |
if (twi_get_status(twi) & TWI_SR_NACK) { | |
trace_error("twid2: command NACK!\r\n"); | |
return 0; | |
} | |
while (left > 0) { | |
if ((twi->TWI_SR & TWI_SR_TXRDY) == 0) continue; | |
/* Get FIFO free size (int octet) and clamp it */ | |
uint32_t buf_size = fifo_size - twi_fifo_tx_size(twi); | |
buf_size = buf_size > left ? left : buf_size; | |
/* /\* Fill the FIFO as must as possible *\/ */ | |
while (buf_size >= sizeof(uint8_t)) { | |
writeb(&twi->TWI_THR,*buffer); | |
buffer += sizeof(uint8_t); | |
left -= sizeof(uint8_t); | |
buf_size -= sizeof(uint8_t); | |
} | |
} | |
return len - left; | |
} | |
#endif |