Newer
Older
mbed-os / targets / TARGET_Atmel / TARGET_SAM_CortexM4 / drivers / twi / twi.c
@Christopher Haster Christopher Haster on 30 Sep 2016 18 KB restructure - Moved targets out to top level
/**
 * \file
 *
 * \brief Two-Wire Interface (TWI) driver for SAM.
 *
 * Copyright (c) 2011-2015 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * 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
 * EXPRESSLY AND SPECIFICALLY 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.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */

#include "twi.h"

/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
extern "C" {
#endif
/**INDENT-ON**/
/// @endcond

    /**
     * \defgroup sam_drivers_twi_group Two-Wire Interface (TWI)
     *
     * Driver for the TWI (Two-Wire Interface). This driver provides access to the main
     * features of the TWI controller.
     * The TWI interconnects components on a unique two-wire bus.
     * The TWI is programmable as a master or a slave with sequential or single-byte access.
     * Multiple master capability is supported.
     *
     * \par Usage
     *
     * -# Enable the TWI peripheral clock in the PMC.
     * -# Enable the required TWI PIOs (see pio.h).
     * -# Enable TWI master mode by calling twi_enable_master_mode if it is a master on the I2C bus.
     * -# Configure the TWI in master mode by calling twi_master_init.
     * -# Send data to a slave device on the I2C bus by calling twi_master_write.
     * -# Receive data from a slave device on the I2C bus by calling the twi_master_read.
     * -# Enable TWI slave mode by calling twi_enable_slave_mode if it is a slave on the I2C bus.
     * -# Configure the TWI in slave mode by calling twi_slave_init.
     *
     * @{
     */

#define I2C_FAST_MODE_SPEED  400000
#define TWI_CLK_DIVIDER      2
#define TWI_CLK_CALC_ARGU    4
#define TWI_CLK_DIV_MAX      0xFF
#define TWI_CLK_DIV_MIN      7

#define TWI_WP_KEY_VALUE TWI_WPMR_WPKEY_PASSWD

    /**
     * \brief Enable TWI master mode.
     *
     * \param p_twi Pointer to a TWI instance.
     */
    void twi_enable_master_mode(Twi *p_twi)
{
    /* Set Master Disable bit and Slave Disable bit */
    p_twi->TWI_CR = TWI_CR_MSDIS;
    p_twi->TWI_CR = TWI_CR_SVDIS;

    /* Set Master Enable bit */
    p_twi->TWI_CR = TWI_CR_MSEN;
}

/**
 * \brief Disable TWI master mode.
 *
 * \param p_twi Pointer to a TWI instance.
 */
void twi_disable_master_mode(Twi *p_twi)
{
    /* Set Master Disable bit */
    p_twi->TWI_CR = TWI_CR_MSDIS;
}

/**
 * \brief Initialize TWI master mode.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_opt Options for initializing the TWI module (see \ref twi_options_t).
 *
 * \return TWI_SUCCESS if initialization is complete, error code otherwise.
 */
uint32_t twi_master_init(Twi *p_twi, const twi_options_t *p_opt)
{
    uint32_t status = TWI_SUCCESS;

    /* Disable TWI interrupts */
    p_twi->TWI_IDR = ~0UL;

    /* Dummy read in status register */
    p_twi->TWI_SR;

    /* Reset TWI peripheral */
    twi_reset(p_twi);

    twi_enable_master_mode(p_twi);

    /* Select the speed */
    if (twi_set_speed(p_twi, p_opt->speed, p_opt->master_clk) == FAIL) {
        /* The desired speed setting is rejected */
        status = TWI_INVALID_ARGUMENT;
    }

    if (p_opt->smbus == 1) {
        p_twi->TWI_CR = TWI_CR_QUICK;
    }

    return status;
}

/**
 * \brief Set the I2C bus speed in conjunction with the clock frequency.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_speed The desired I2C bus speed (in Hz).
 * \param ul_mck Main clock of the device (in Hz).
 *
 * \retval PASS New speed setting is accepted.
 * \retval FAIL New speed setting is rejected.
 */
uint32_t twi_set_speed(Twi *p_twi, uint32_t ul_speed, uint32_t ul_mck)
{
    uint32_t ckdiv = 0;
    uint32_t c_lh_div;

    if (ul_speed > I2C_FAST_MODE_SPEED) {
        return FAIL;
    }

    c_lh_div = ul_mck / (ul_speed * TWI_CLK_DIVIDER) - TWI_CLK_CALC_ARGU;

    /* cldiv must fit in 8 bits, ckdiv must fit in 3 bits */
    while ((c_lh_div > TWI_CLK_DIV_MAX) && (ckdiv < TWI_CLK_DIV_MIN)) {
        /* Increase clock divider */
        ckdiv++;
        /* Divide cldiv value */
        c_lh_div /= TWI_CLK_DIVIDER;
    }

    /* set clock waveform generator register */
    p_twi->TWI_CWGR =
        TWI_CWGR_CLDIV(c_lh_div) | TWI_CWGR_CHDIV(c_lh_div) |
        TWI_CWGR_CKDIV(ckdiv);

    return PASS;
}

/**
 * \brief Test if a chip answers a given I2C address.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param uc_slave_addr Address of the remote chip to search for.
 *
 * \return TWI_SUCCESS if a chip was found, error code otherwise.
 */
uint32_t twi_probe(Twi *p_twi, uint8_t uc_slave_addr)
{
    twi_packet_t packet;
    uint8_t data = 0;

    /* Data to send */
    packet.buffer = &data;
    /* Data length */
    packet.length = 1;
    /* Slave chip address */
    packet.chip = (uint32_t) uc_slave_addr;
    /* Internal chip address */
    packet.addr[0] = 0;
    /* Address length */
    packet.addr_length = 0;

    /* Perform a master write access */
    return (twi_master_write(p_twi, &packet));
}


/**
 * \internal
 * \brief Construct the TWI module address register field
 *
 * The TWI module address register is sent out MSB first. And the size controls
 * which byte is the MSB to start with.
 *
 * Please see the device datasheet for details on this.
 */
static uint32_t twi_mk_addr(const uint8_t *addr, int len)
{
    uint32_t val;

    if (len == 0)
        return 0;

    val = addr[0];
    if (len > 1) {
        val <<= 8;
        val |= addr[1];
    }
    if (len > 2) {
        val <<= 8;
        val |= addr[2];
    }
    return val;
}

/**
 * \brief Read multiple bytes from a TWI compatible slave device.
 *
 * \note This function will NOT return until all data has been read or error occurs.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_packet Packet information and data (see \ref twi_packet_t).
 *
 * \return TWI_SUCCESS if all bytes were read, error code otherwise.
 */
uint32_t twi_master_read(Twi *p_twi, twi_packet_t *p_packet)
{
    uint32_t status;
    uint32_t cnt = p_packet->length;
    uint8_t *buffer = p_packet->buffer;
    uint8_t stop_sent = 0;
    uint32_t timeout = TWI_TIMEOUT;;

    /* Check argument */
    if (cnt == 0) {
        return TWI_INVALID_ARGUMENT;
    }

    /* Set read mode, slave address and 3 internal address byte lengths */
    p_twi->TWI_MMR = 0;
    p_twi->TWI_MMR = TWI_MMR_MREAD | TWI_MMR_DADR(p_packet->chip) |
                     ((p_packet->addr_length << TWI_MMR_IADRSZ_Pos) &
                      TWI_MMR_IADRSZ_Msk);

    /* Set internal address for remote chip */
    p_twi->TWI_IADR = 0;
    p_twi->TWI_IADR = twi_mk_addr(p_packet->addr, p_packet->addr_length);

    /* Send a START condition */
    if (cnt == 1) {
        p_twi->TWI_CR = TWI_CR_START | TWI_CR_STOP;
        stop_sent = 1;
    } else {
        p_twi->TWI_CR = TWI_CR_START;
        stop_sent = 0;
    }

    while (cnt > 0) {
        status = p_twi->TWI_SR;
        if (status & TWI_SR_NACK) {
            return TWI_RECEIVE_NACK;
        }

        if (!timeout--) {
            return TWI_ERROR_TIMEOUT;
        }

        /* Last byte ? */
        if (cnt == 1  && !stop_sent) {
            p_twi->TWI_CR = TWI_CR_STOP;
            stop_sent = 1;
        }

        if (!(status & TWI_SR_RXRDY)) {
            continue;
        }
        *buffer++ = p_twi->TWI_RHR;

        cnt--;
        timeout = TWI_TIMEOUT;
    }

    while (!(p_twi->TWI_SR & TWI_SR_TXCOMP)) {
    }

    p_twi->TWI_SR;

    return TWI_SUCCESS;
}

/**
 * \brief Write multiple bytes to a TWI compatible slave device.
 *
 * \note This function will NOT return until all data has been written or error occurred.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_packet Packet information and data (see \ref twi_packet_t).
 *
 * \return TWI_SUCCESS if all bytes were written, error code otherwise.
 */
uint32_t twi_master_write(Twi *p_twi, twi_packet_t *p_packet)
{
    uint32_t status;
    uint32_t cnt = p_packet->length;
    uint8_t *buffer = p_packet->buffer;

    /* Check argument */
    if (cnt == 0) {
        return TWI_INVALID_ARGUMENT;
    }

    /* Set write mode, slave address and 3 internal address byte lengths */
    p_twi->TWI_MMR = 0;
    p_twi->TWI_MMR = TWI_MMR_DADR(p_packet->chip) |
                     ((p_packet->addr_length << TWI_MMR_IADRSZ_Pos) &
                      TWI_MMR_IADRSZ_Msk);

    /* Set internal address for remote chip */
    p_twi->TWI_IADR = 0;
    p_twi->TWI_IADR = twi_mk_addr(p_packet->addr, p_packet->addr_length);

    /* Send all bytes */
    while (cnt > 0) {
        status = p_twi->TWI_SR;
        if (status & TWI_SR_NACK) {
            return TWI_RECEIVE_NACK;
        }

        if (!(status & TWI_SR_TXRDY)) {
            continue;
        }
        p_twi->TWI_THR = *buffer++;

        cnt--;
    }

    while (1) {
        status = p_twi->TWI_SR;
        if (status & TWI_SR_NACK) {
            return TWI_RECEIVE_NACK;
        }

        if (status & TWI_SR_TXRDY) {
            break;
        }
    }

    p_twi->TWI_CR = TWI_CR_STOP;

    while (!(p_twi->TWI_SR & TWI_SR_TXCOMP)) {
    }

    return TWI_SUCCESS;
}

/**
 * \brief Enable TWI interrupts.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_sources Interrupts to be enabled.
 */
void twi_enable_interrupt(Twi *p_twi, uint32_t ul_sources)
{
    /* Enable the specified interrupts */
    p_twi->TWI_IER = ul_sources;
}

/**
 * \brief Disable TWI interrupts.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_sources Interrupts to be disabled.
 */
void twi_disable_interrupt(Twi *p_twi, uint32_t ul_sources)
{
    /* Disable the specified interrupts */
    p_twi->TWI_IDR = ul_sources;
    /* Dummy read */
    p_twi->TWI_SR;
}

/**
 * \brief Get TWI interrupt status.
 *
 * \param p_twi Pointer to a TWI instance.
 *
 * \retval TWI interrupt status.
 */
uint32_t twi_get_interrupt_status(Twi *p_twi)
{
    return p_twi->TWI_SR;
}

/**
 * \brief Read TWI interrupt mask.
 *
 * \param p_twi Pointer to a TWI instance.
 *
 * \return The interrupt mask value.
 */
uint32_t twi_get_interrupt_mask(Twi *p_twi)
{
    return p_twi->TWI_IMR;
}

/**
 * \brief Reads a byte from the TWI bus.
 *
 * \param p_twi Pointer to a TWI instance.
 *
 * \return The byte read.
 */
uint8_t twi_read_byte(Twi *p_twi)
{
    return p_twi->TWI_RHR;
}

/**
 * \brief Sends a byte of data to one of the TWI slaves on the bus.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param byte The byte to send.
 */
void twi_write_byte(Twi *p_twi, uint8_t uc_byte)
{
    p_twi->TWI_THR = uc_byte;
}

/**
 * \brief Enable TWI slave mode.
 *
 * \param p_twi Pointer to a TWI instance.
 */
void twi_enable_slave_mode(Twi *p_twi)
{
    /* Set Master Disable bit and Slave Disable bit */
    p_twi->TWI_CR = TWI_CR_MSDIS;
    p_twi->TWI_CR = TWI_CR_SVDIS;

    /* Set Slave Enable bit */
    p_twi->TWI_CR = TWI_CR_SVEN;
}

/**
 * \brief Disable TWI slave mode.
 *
 * \param p_twi Pointer to a TWI instance.
 */
void twi_disable_slave_mode(Twi *p_twi)
{
    /* Set Slave Disable bit */
    p_twi->TWI_CR = TWI_CR_SVDIS;
}

/**
 * \brief Initialize TWI slave mode.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_device_addr Device address of the SAM slave device on the I2C bus.
 */
void twi_slave_init(Twi *p_twi, uint32_t ul_device_addr)
{
    /* Disable TWI interrupts */
    p_twi->TWI_IDR = ~0UL;
    p_twi->TWI_SR;

    /* Reset TWI */
    twi_reset(p_twi);

    /* Set slave address in slave mode */
    p_twi->TWI_SMR = TWI_SMR_SADR(ul_device_addr);

    /* Enable slave mode */
    twi_enable_slave_mode(p_twi);
}

/**
 * \brief Set TWI slave address.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_device_addr Device address of the SAM slave device on the I2C bus.
 */
void twi_set_slave_addr(Twi *p_twi, uint32_t ul_device_addr)
{
    /* Set slave address */
    p_twi->TWI_SMR = TWI_SMR_SADR(ul_device_addr);
}

/**
 * \brief Read data from master.
 *
 * \note This function will NOT return until master sends a STOP condition.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_data Pointer to the data buffer where data received will be stored.
 *
 * \return Number of bytes read.
 */
uint32_t twi_slave_read(Twi *p_twi, uint8_t *p_data)
{
    uint32_t status, cnt = 0;

    do {
        status = p_twi->TWI_SR;
        if (status & TWI_SR_SVACC) {
            if (!(status & TWI_SR_GACC) &&
                    ((status & (TWI_SR_SVREAD | TWI_SR_RXRDY))
                     == (TWI_SR_SVREAD | TWI_SR_RXRDY))) {
                *p_data++ = (uint8_t) p_twi->TWI_RHR;
                cnt++;
            }
        } else if ((status & (TWI_SR_EOSACC | TWI_SR_TXCOMP))
                   == (TWI_SR_EOSACC | TWI_SR_TXCOMP)) {
            break;
        }
    } while (1);

    return cnt;
}

/**
 * \brief Write data to TWI bus.
 *
 * \note This function will NOT return until master sends a STOP condition.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_data Pointer to the data buffer to be sent.
 *
 * \return Number of bytes written.
 */
uint32_t twi_slave_write(Twi *p_twi, uint8_t *p_data)
{
    uint32_t status, cnt = 0;

    do {
        status = p_twi->TWI_SR;
        if (status & TWI_SR_SVACC) {
            if (!(status & (TWI_SR_GACC | TWI_SR_SVREAD)) &&
                    (status & TWI_SR_TXRDY)) {
                p_twi->TWI_THR = *p_data++;
                cnt++;
            }
        } else if ((status & (TWI_SR_EOSACC | TWI_SR_TXCOMP))
                   == (TWI_SR_EOSACC | TWI_SR_TXCOMP)) {
            break;
        }
    } while (1);

    return cnt;
}

/**
 * \brief Reset TWI.
 *
 * \param p_twi Pointer to a TWI instance.
 */
void twi_reset(Twi *p_twi)
{
    /* Set SWRST bit to reset TWI peripheral */
    p_twi->TWI_CR = TWI_CR_SWRST;
    p_twi->TWI_RHR;
}

/**
 * \brief Get TWI PDC base address.
 *
 * \param p_twi Pointer to a TWI instance.
 *
 * \return TWI PDC registers base for PDC driver to access.
 */
Pdc *twi_get_pdc_base(Twi *p_twi)
{
    Pdc *p_pdc_base = NULL;
#if !SAMG
    if (p_twi == TWI0) {
        p_pdc_base = PDC_TWI0;
    } else
#endif
#ifdef PDC_TWI1
        if (p_twi == TWI1) {
            p_pdc_base = PDC_TWI1;
        } else
#endif
#ifdef PDC_TWI2
            if (p_twi == TWI2) {
                p_pdc_base = PDC_TWI2;
            } else
#endif
            {
                Assert(false);
            }

    return p_pdc_base;
}

#if (SAM4E || SAM4C || SAMG || SAM4CP || SAM4CM)
/**
 * \brief Enables/Disables write protection mode.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param flag ture for enable, false for disable.
 */
void twi_set_write_protection(Twi *p_twi, bool flag)
{

    p_twi->TWI_WPMR = (flag ? TWI_WPMR_WPEN : 0) | TWI_WP_KEY_VALUE;
}

/**
 * \brief Read the write protection status.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param p_status Pointer to save the status.
 */
void twi_read_write_protection_status(Twi *p_twi, uint32_t *p_status)
{

    *p_status = p_twi->TWI_WPSR;
}
#endif

#if SAMG55
/**
 * \brief Set the prescaler, TLOW:SEXT, TLOW:MEXT and clock high max cycles for SMBUS mode.
 *
 * \param p_twi   Base address of the TWI instance.
 * \param ul_timing Parameter for prescaler, TLOW:SEXT, TLOW:MEXT and clock high max cycles.
 */
void twi_smbus_set_timing(Twi *p_twi, uint32_t ul_timing)
{
    p_twi->TWI_SMBTR = ul_timing;;
}

/**
 * \brief Set length/direction/PEC for alternative command mode.
 *
 * \param p_twi   Base address of the TWI instance.
 * \param ul_alt_cmd Alternative command parameters.
 */
void twi_set_alternative_command(Twi *p_twi, uint32_t ul_alt_cmd)
{
    p_twi->TWI_ACR = ul_alt_cmd;;
}

/**
 * \brief Set the filter for TWI.
 *
 * \param p_twi   Base address of the TWI instance.
 * \param ul_filter   Filter value.
 */
void twi_set_filter(Twi *p_twi, uint32_t ul_filter)
{
    p_twi->TWI_FILTR = ul_filter;;
}

/**
 * \brief A mask can be applied on the slave device address in slave mode in order to allow multiple
 * address answer. For each bit of the MASK field set to one the corresponding SADR bit will be masked.
 *
 * \param p_twi   Base address of the TWI instance.
 * \param ul_mask  Mask value.
 */
void twi_mask_slave_addr(Twi *p_twi, uint32_t ul_mask)
{
    p_twi->TWI_SMR |= TWI_SMR_MASK(ul_mask);
}

/**
 * \brief Set sleepwalking match mode.
 *
 * \param p_twi Pointer to a TWI instance.
 * \param ul_matching_addr1   Address 1 value.
 * \param ul_matching_addr2   Address 2 value.
 * \param ul_matching_addr3   Address 3 value.
 * \param ul_matching_data   Data value.
 * \param flag1 ture for set, false for no.
 * \param flag2 ture for set, false for no.
 * \param flag3 ture for set, false for no.
 * \param flag ture for set, false for no.
 */
void twi_set_sleepwalking(Twi *p_twi,
                          uint32_t ul_matching_addr1, bool flag1,
                          uint32_t ul_matching_addr2, bool flag2,
                          uint32_t ul_matching_addr3, bool flag3,
                          uint32_t ul_matching_data, bool flag)
{
    uint32_t temp = 0;

    if (flag1) {
        temp |= TWI_SWMR_SADR1(ul_matching_addr1);
    }

    if (flag2) {
        temp |= TWI_SWMR_SADR2(ul_matching_addr2);
    }

    if (flag3) {
        temp |= TWI_SWMR_SADR3(ul_matching_addr3);
    }

    if (flag) {
        temp |= TWI_SWMR_DATAM(ul_matching_data);
    }

    p_twi->TWI_SWMR = temp;
}
#endif
//@}

/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
}
#endif
/**INDENT-ON**/
/// @endcond