mirror of https://github.com/ARMmbed/mbed-os.git
232 lines
8.9 KiB
C
232 lines
8.9 KiB
C
/**
|
|
******************************************************************************
|
|
* @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"
|
|
#include "mbed_wait_api.h"
|
|
|
|
/* See i2c.h for details */
|
|
void fI2cInit(i2c_t *obj,PinName sda,PinName scl)
|
|
{
|
|
/* 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 */
|
|
fI2cFrequency(obj, 100000);
|
|
|
|
/* 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 = 3; /* sda: Drive strength */
|
|
padRegScl->PADIO0.BITS.POWER = 3; /* 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 */
|
|
SEND_COMMAND(I2C_CMD_START);
|
|
return I2C_API_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* See i2c.h for details */
|
|
int32_t fI2cStop(i2c_t *obj)
|
|
{
|
|
/* Send stop bit */
|
|
SEND_COMMAND(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 *obj, char *buf, int len)
|
|
{
|
|
int32_t read = 0;
|
|
|
|
while (read < len) {
|
|
|
|
while(FIFO_OFL_CHECK); /* Wait till command overflow ends */
|
|
|
|
/* Send read command */
|
|
SEND_COMMAND(I2C_CMD_RDAT8);
|
|
while(!RD_DATA_READY) {
|
|
if (I2C_BUS_ERR_CHECK) {
|
|
/* Bus error occured */
|
|
return I2C_ERROR_BUS_BUSY;
|
|
}
|
|
}
|
|
buf[read++] = obj->membase->RD_FIFO_REG; /**< Reading 'read FIFO register' will clear status register */
|
|
|
|
if(!(read>=len)) {
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
} else {
|
|
/* No ack */
|
|
SEND_COMMAND(I2C_CMD_WDAT1);
|
|
}
|
|
|
|
/* check for FIFO underflow */
|
|
if(I2C_UFL_CHECK) {
|
|
return I2C_EVENT_ERROR;
|
|
}
|
|
if(I2C_BUS_ERR_CHECK) {
|
|
/* Bus error */
|
|
return I2C_ERROR_BUS_BUSY;
|
|
}
|
|
}
|
|
|
|
return read;
|
|
}
|
|
|
|
/* See i2c.h for details */
|
|
int32_t fI2cWriteB(i2c_t *obj, const char *buf, int len)
|
|
{
|
|
int32_t write = 0;
|
|
|
|
while (write < len) {
|
|
|
|
while(FIFO_OFL_CHECK); /* Wait till command overflow ends */
|
|
|
|
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 */
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
SEND_COMMAND(I2C_CMD_WDAT1);
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
SEND_COMMAND(I2C_CMD_WDAT0);
|
|
SEND_COMMAND(I2C_CMD_WDAT1);
|
|
SEND_COMMAND(I2C_CMD_WDAT1);
|
|
write++;
|
|
} else {
|
|
/* Send data */
|
|
SEND_COMMAND(I2C_CMD_WDAT8);
|
|
SEND_COMMAND(buf[write++]);
|
|
}
|
|
SEND_COMMAND(I2C_CMD_VRFY_ACK);
|
|
|
|
if (I2C_BUS_ERR_CHECK) {
|
|
/* Bus error */
|
|
return I2C_ERROR_BUS_BUSY;
|
|
}
|
|
}
|
|
return write;
|
|
}
|
|
|
|
#endif /* DEVICE_I2C */
|