mbed-os

Fork of mbed-os by erkin yucel

targets/TARGET_ONSEMI/TARGET_NCS36510/ncs36510_i2c.c

Committer:
elessair
Date:
2016-10-23
Revision:
0:f269e3021894

File content as of revision 0:f269e3021894:

/**
 ******************************************************************************
 * @file i2c.c
 * @brief I2C driver
 * @internal
 * @author ON Semiconductor
 * $Rev:  $
 * $Date: 2016-04-12 $
 ******************************************************************************
 * Copyright 2016 Semiconductor Components Industries LLC (d/b/a “ON Semiconductor”).
 * All rights reserved.  This software and/or documentation is licensed by ON Semiconductor
 * under limited terms and conditions.  The terms and conditions pertaining to the software
 * and/or documentation are available at http://www.onsemi.com/site/pdf/ONSEMI_T&C.pdf
 * (“ON Semiconductor Standard Terms and Conditions of Sale, Section 8 Software”) and
 * if applicable the software license agreement.  Do not use this software and/or
 * documentation unless you have carefully read and you agree to the limited terms and
 * conditions.  By using this software and/or documentation, you agree to the limited
 * terms and conditions.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
 * ON SEMICONDUCTOR SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL,
 * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 * @endinternal
 *
 * @ingroup i2c
 *
 * @details
 *
 * <h1> Reference document(s) </h1>
 * <p>
 * IPC7208 APB I2C Master Design Specification v1.3
 * </p>
 * The I2C bus is an industry-standard two-wire (clock and data) serial communication bus between master(initiator) and slave device.
 * Within the procedure of the I2C-bus, unique situations arise which are defined as START and STOP conditions .A HIGH to LOW transition on
 * the SDA line while SCL is HIGH is one such unique case. This situation indicates a START condition.A LOW to HIGH transition on the
 * SDA line while SCL is HIGH defines a STOP condition.START and STOP conditions are always generated by the master. The bus is considered
 * to be busy after the START condition. The bus is considered to be free again a certain time after the STOP condition.
 * A master may start a transfer only if the bus is free. Two or more masters may generate a START condition.
 * Every byte put on the SDA line must be 8-bits long.Each byte has to be followed by an acknowledge bit.
 * This APB(Advanced peripheral bus) I2C Master is an APB Slave peripheral that can also serves as an I2C bus Master. The Command register
 * is the programming interface to the I2C Engine. The commands arrive at the I2C Engine via the Command FIFO,so the first valid command
 * that is written to the Command register is the first I2C instruction implemented on the I2C bus.Because the command interface provides
 * the basic building blocks for any I2C transaction, access to a wide range of I2C slave devices is supported.
 * I2C can be enabled by setting bit 7 of the control register .
 * There is a generated clock (a divided version of the APB clock) in this module that may be used as the I2C System Clock.
 * There are two FIFO in the I2C; Command FIFO and Read data FIFO
 * The commands(I2C instructions) and data arrive at the I2C Engine via the Command FIFO.
 * if the command FIFO is empty , up to 32 commands can be written to the command interface , it is programmer's responsibility to keep
 * the track of command FIFO's status either by interrupt or by polling method by reading status register, which represents Operational
 * Status of the I2C Module and its sub-modules.The action from the processor may be necessary after reading the status register.Reading
 * the Status register clears the blkInt Interrupt signal.Read data FIFO is where data read by the processor from I2C slave is placed .
 *
 *
 * <h1> Functional description (internal) </h1>
 * <p>
 *
 * </p>
 */
#if DEVICE_I2C
#include "i2c.h"

/* See i2c.h for details */
void fI2cInit(i2c_t *obj,PinName sda,PinName scl)
{
    uint32_t clockDivisor;
    /* determine the I2C to use */
    I2CName i2c_sda = (I2CName)pinmap_peripheral(sda, PinMap_I2C_SDA);
    I2CName i2c_scl = (I2CName)pinmap_peripheral(scl, PinMap_I2C_SCL);
    obj->membase = (I2cIpc7208Reg_pt)pinmap_merge(i2c_sda, i2c_scl);
    MBED_ASSERT((int)obj->membase != NC);

    /* By default disbale interrupts */
    obj->membase->IER.WORD = False;

    /* enable interrupt associated with the device */
    if(obj->membase == I2C1REG) {
        CLOCK_ENABLE(CLOCK_I2C);             /* enable i2c peripheral */
        NVIC_ClearPendingIRQ(I2C_IRQn);
        NVIC_EnableIRQ(I2C_IRQn);
    } else {
        CLOCK_ENABLE(CLOCK_I2C2);            /* enable i2c peripheral */
        NVIC_ClearPendingIRQ(I2C2_IRQn);
        NVIC_EnableIRQ(I2C2_IRQn);
    }

    /*select I2C clock source */
    obj->membase->CR.BITS.I2C_CLK_SRC = True;

    /* enable I2C clock divider */
    obj->membase->CR.BITS.I2C_APB_CD_EN = True;

    /* set default baud rate at 100k */
    clockDivisor = ((fClockGetPeriphClockfrequency() / 100000) >> 2) - 2;
    obj->membase->CR.BITS.CD_VAL = (clockDivisor & I2C_CLOCKDIVEDER_VAL_MASK);
    obj->membase->PRE_SCALE_REG = (clockDivisor & I2C_APB_CLK_DIVIDER_VAL_MASK) >> 5; /**< Zero pre-scale value not allowed */

    /* Cross bar setting */
    pinmap_pinout(sda, PinMap_I2C_SDA);
    pinmap_pinout(scl, PinMap_I2C_SCL);

    /*Enable open drain & pull up for sda & scl pin */
    pin_mode(sda, OpenDrainPullUp);
    pin_mode(scl, OpenDrainPullUp);

    /* PAD drive strength */
    PadReg_t *padRegSda = (PadReg_t*)(PADREG_BASE + (sda * PAD_REG_ADRS_BYTE_SIZE));
    PadReg_t *padRegScl = (PadReg_t*)(PADREG_BASE + (scl * PAD_REG_ADRS_BYTE_SIZE));

    CLOCK_ENABLE(CLOCK_PAD);
    padRegSda->PADIO0.BITS.POWER = 1; /* sda: Drive strength */
    padRegScl->PADIO0.BITS.POWER = 1; /* scl: Drive strength */
    CLOCK_DISABLE(CLOCK_PAD);

    CLOCK_ENABLE(CLOCK_GPIO);
    GPIOREG->W_OUT |= ((True << sda) | (True << scl));
    CLOCK_DISABLE(CLOCK_GPIO);

    /* Enable i2c module */
    obj->membase->CR.BITS.I2C_MODULE_EN = True;
}

/* See i2c.h for details */
void fI2cFrequency(i2c_t *obj, uint32_t hz)
{
    /* Set user baud rate */
    uint32_t clockDivisor;
    clockDivisor = ((fClockGetPeriphClockfrequency() / hz) >> 2) - 2;
    obj->membase->CR.BITS.CD_VAL = (clockDivisor & I2C_CLOCKDIVEDER_VAL_MASK);
    obj->membase->PRE_SCALE_REG = (clockDivisor & I2C_APB_CLK_DIVIDER_VAL_MASK) >> 5; /**< Zero pre-scale value not allowed */
}

/* See i2c.h for details */
int32_t fI2cStart(i2c_t *obj)
{
    /* Send start bit */
    obj->membase->CMD_REG = I2C_CMD_START;
    return I2C_API_STATUS_SUCCESS;
}

/* See i2c.h for details */
int32_t fI2cStop(i2c_t *obj)
{
    /* Send stop bit */
    obj->membase->CMD_REG = I2C_CMD_STOP;
    if (obj->membase->STATUS.WORD & (I2C_STATUS_CMD_FIFO_FULL_BIT |
                                     I2C_STATUS_CMD_FIFO_OFL_BIT |
                                     I2C_STATUS_BUS_ERR_BIT)) {
        /* I2c error occured */
        return I2C_ERROR_BUS_BUSY;
    }
    return I2C_API_STATUS_SUCCESS;
}

/* See i2c.h for details */
int32_t fI2cReadB(i2c_t *d, char *buf, int len)
{
    int32_t read = 0;

    while (read < len) {
        /* Send read command */
        d->membase->CMD_REG = I2C_CMD_RDAT8;
        while(!RD_DATA_READY) {
            if (I2C_BUS_ERR_CHECK) {
                /* Bus error occured */
                return I2C_ERROR_BUS_BUSY;
            }
        }
        buf[read++] = d->membase->RD_FIFO_REG; /**< Reading 'read FIFO register' will clear status register */

        if(!(read>=len)) {  /* No ACK will be generated for the last read, upper level I2C protocol should generate */
            d->membase->CMD_REG=I2C_CMD_WDAT0; /* TODO based on requirement generate ACK or NACK Based on the requirement. */
        }

        /* check for FIFO underflow */
        if(I2C_UFL_CHECK) {
            return I2C_ERROR_NO_SLAVE; /* TODO No error available for this in i2c_api.h */
        }
        if(I2C_BUS_ERR_CHECK) {
            /* Bus error */
            return I2C_ERROR_BUS_BUSY;
        }
    }

    return read;
}

/* See i2c.h for details */
int32_t fI2cWriteB(i2c_t *d, const char *buf, int len)
{
    int32_t write = 0;

    while (write < len) {
        /* Send write command */
        d->membase->CMD_REG = I2C_CMD_WDAT8;
        if(buf[write] == I2C_CMD_RDAT8) {
            /* SW work around to counter FSM issue. If the only command in the CMD FIFO is the WDAT8 command (data of 0x13)
            then as the command is read out (i.e. the FIFO goes empty), the WDAT8 command will be misinterpreted as a
            RDAT8 command by the data FSM; resulting in an I2C bus error (NACK instead of an ACK). */
            /* Send 0x13 bit wise */
            d->membase->CMD_REG = I2C_CMD_WDAT0;
            d->membase->CMD_REG = I2C_CMD_WDAT0;
            d->membase->CMD_REG = I2C_CMD_WDAT0;
            d->membase->CMD_REG = I2C_CMD_WDAT1;

            d->membase->CMD_REG = I2C_CMD_WDAT0;
            d->membase->CMD_REG = I2C_CMD_WDAT0;
            d->membase->CMD_REG = I2C_CMD_WDAT1;
            d->membase->CMD_REG = I2C_CMD_WDAT1;
        } else {
            /* Send data */
            d->membase->CMD_REG = buf[write++];
        }
        d->membase->CMD_REG = I2C_CMD_VRFY_ACK; /* TODO Verify ACK based on requirement, Do we need? */

        while(FIFO_OFL_CHECK); /* Wait till command overflow ends */

        if (I2C_BUS_ERR_CHECK) {
            /* Bus error */
            return I2C_ERROR_BUS_BUSY;
        }
    }

    return write;
}

#endif /* DEVICE_I2C */