blob: cb12ac2a14f8db99dba29704514ce81fb7c32383 [file] [log] [blame]
/*
*
* Copyright 2017-2020 NXP
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @par Description
* MCIMX6UL-EVK / MCIMX8M-EVK board specific & Generic i2c code
* @par History
*
**/
#include "i2c_a7.h"
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <linux/version.h>
#include <errno.h>
#include <time.h>
// #define NX_LOG_ENABLE_SMCOM_DEBUG 1
#include "nxLog_smCom.h"
static char* default_axSmDevice_name = "/dev/i2c-0";
static int default_axSmDevice_addr = 0x49; // 7-bit address
#define DEV_NAME_BUFFER_SIZE 64
/**
* Opens the communication channel to I2C device
*/
i2c_error_t axI2CInit(void **conn_ctx, const char *pDevName)
{
unsigned long funcs;
int axSmDevice = 0;
char *pdev_name = NULL;
char *pdev_addr_str = NULL;
U32 dev_addr = 0x00;
char temp[DEV_NAME_BUFFER_SIZE] = { 0, };
if (pDevName != NULL && (strcasecmp("none", pDevName) != 0) ) {
if ((strlen(pDevName) + 1) < DEV_NAME_BUFFER_SIZE) {
memcpy(temp, pDevName, strlen(pDevName));
temp[strlen(pDevName)] = '\0';
}
else {
LOG_E("Connection string passed as argument is too long (%d).", strlen(pDevName));
LOG_I("Pass i2c device address in the format <i2c_port>:<i2c_addr(optional. Default 0x48)>.");
LOG_I("Example ./example /dev/i2c-1:0x48 OR ./example /dev/i2c-1");
}
pdev_name = strtok(temp, ":");
if (pdev_name == NULL) {
perror("Invalid connection string");
LOG_I("Pass i2c device address in the format <i2c_port>:<i2c_addr(optional. Default 0x48)>.");
LOG_I("Example ./example /dev/i2c-1:0x48 OR ./example /dev/i2c-1");
return I2C_FAILED;
}
pdev_addr_str = strtok(NULL, ":");
if (pdev_addr_str != NULL) {
dev_addr = strtol(pdev_addr_str, NULL, 0);
}
else {
dev_addr = default_axSmDevice_addr;
}
}
else {
pdev_name = default_axSmDevice_name;
dev_addr = default_axSmDevice_addr;
}
LOG_D("I2CInit: opening %s\n", pdev_name);
if ((axSmDevice = open(pdev_name, O_RDWR)) < 0)
{
LOG_E("opening failed...");
perror("Failed to open the i2c bus");
LOG_I("Pass i2c device address in the format <i2c_port>:<i2c_addr(optional. Default 0x48)>.");
LOG_I("Example ./example /dev/i2c-1:0x48 OR ./example /dev/i2c-1");
return I2C_FAILED;
}
if (ioctl(axSmDevice, I2C_SLAVE, dev_addr) < 0)
{
LOG_E("I2C driver failed setting address\n");
}
// clear PEC flag
if (ioctl(axSmDevice, I2C_PEC, 0) < 0)
{
LOG_E("I2C driver: PEC flag clear failed\n");
}
else
{
LOG_D("I2C driver: PEC flag cleared\n");
}
// Query functional capacity of I2C driver
if (ioctl(axSmDevice, I2C_FUNCS, &funcs) < 0)
{
LOG_E("Cannot get i2c adapter functionality\n");
close(axSmDevice);
return I2C_FAILED;
}
else
{
if (funcs & I2C_FUNC_I2C)
{
LOG_D("I2C driver supports plain i2c-level commands.\n");
#if defined(SCI2C) //if SCI2C is enabled
if ( (funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA) == I2C_FUNC_SMBUS_READ_BLOCK_DATA )
{
LOG_D("I2C driver supports Read Block.\n");
}
else
{
LOG_E("I2C driver does not support Read Block!\n");
close(axSmDevice);
return I2C_FAILED;
}
#endif
}
else
{
LOG_E("I2C driver CANNOT support plain i2c-level commands!\n");
close(axSmDevice);
return I2C_FAILED;
}
}
*conn_ctx = malloc(sizeof(int));
*(int*)(*conn_ctx) = axSmDevice;
return I2C_OK;
}
/**
* Closes the communication channel to I2C device
*/
void axI2CTerm(void* conn_ctx, int mode)
{
AX_UNUSED_ARG(mode);
// printf("axI2CTerm (enter) i2c device = %d\n", *(int*)(conn_ctx));
if (conn_ctx != NULL) {
if (close(*(int*)(conn_ctx)) != 0) {
LOG_E("Failed to close i2c device %d.\n", *(int*)(conn_ctx));
}
else {
LOG_D("Close i2c device %d.\n", *(int*)(conn_ctx));
}
free(conn_ctx);
}
// printf("axI2CTerm (exit)\n");
return;
}
#if defined(SCI2C)
/**
* Write a single byte to the slave device.
* In the context of the SCI2C protocol, this command is only invoked
* to trigger a wake-up of the attached secure module. As such this
* wakeup command 'wakes' the device, but does not receive a valid response.
* \note \par bus is currently not used to distinguish between I2C masters.
*/
i2c_error_t axI2CWriteByte(void* conn_ctx, unsigned char bus, unsigned char addr, unsigned char * pTx)
{
int nrWritten = -1;
i2c_error_t rv;
int axSmDevice = *(int*)conn_ctx;
if (bus != I2C_BUS_0)
{
LOG_E("axI2CWriteByte on wrong bus %x (addr %x)\n", bus, addr);
}
nrWritten = write(axSmDevice, pTx, 1);
if (nrWritten < 0)
{
// I2C_LOG_PRINTF("Failed writing data (nrWritten=%d).\n", nrWritten);
rv = I2C_FAILED;
}
else
{
if (nrWritten == 1)
{
rv = I2C_OK;
}
else
{
rv = I2C_FAILED;
}
}
return rv;
}
#endif // defined(SCI2C)
#if defined(SCI2C) || defined(T1oI2C)
i2c_error_t axI2CWrite(void* conn_ctx, unsigned char bus, unsigned char addr, unsigned char * pTx, unsigned short txLen)
{
int nrWritten = -1;
i2c_error_t rv;
int axSmDevice = *(int*)conn_ctx;
#ifdef LOG_I2C
int i = 0;
#endif
if (bus != I2C_BUS_0)
{
LOG_E("axI2CWrite on wrong bus %x (addr %x)\n", bus, addr);
}
LOG_MAU8_D("TX (axI2CWrite) > ",pTx,txLen);
nrWritten = write(axSmDevice, pTx, txLen);
if (nrWritten < 0)
{
LOG_E("Failed writing data (nrWritten=%d).\n", nrWritten);
rv = I2C_FAILED;
}
else
{
if (nrWritten == txLen) // okay
{
rv = I2C_OK;
}
else
{
rv = I2C_FAILED;
}
}
LOG_D("Done with rv = %02x ", rv);
return rv;
}
#endif // defined(SCI2C) || defined(T1oI2C)
#if defined(SCI2C)
i2c_error_t axI2CWriteRead(void* conn_ctx, unsigned char bus, unsigned char addr, unsigned char * pTx,
unsigned short txLen, unsigned char * pRx, unsigned short * pRxLen)
{
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
int r = 0;
int i = 0;
int axSmDevice = *(int*)conn_ctx;
if (bus != I2C_BUS_0) // change if bus 0 is not the correct bus
{
LOG_E("axI2CWriteRead on wrong bus %x (addr %x)\n", bus, addr);
}
messages[0].addr = default_axSmDevice_addr;
messages[0].flags = 0;
messages[0].len = txLen;
messages[0].buf = pTx;
// NOTE:
// By setting the 'I2C_M_RECV_LEN' bit in 'messages[1].flags' one ensures
// the I2C Block Read feature is used.
messages[1].addr = default_axSmDevice_addr;
messages[1].flags = I2C_M_RD | I2C_M_RECV_LEN;
messages[1].len = 256;
messages[1].buf = pRx;
messages[1].buf[0] = 1;
// NOTE:
// By passing the two message structures via the packets structure as
// a parameter to the ioctl call one ensures a Repeated Start is triggered.
packets.msgs = messages;
packets.nmsgs = 2;
LOG_MAU8_D("TX (axI2CWriteRead ) > ",&packets.msgs[0].buf[i], txLen);
// Send the request to the kernel and get the result back
r = ioctl(axSmDevice, I2C_RDWR, &packets);
// NOTE:
// The ioctl return value in case of a NACK on the write address is '-1'
// This impacts the error handling routine of the caller.
// If possible distinguish between a general I2C error and a NACK on address
// The way to do this is platform specific (depends on I2C bus driver).
if (r < 0)
{
// LOG_E("axI2CWriteRead: ioctl cmd I2C_RDWR fails with value %d (errno: 0x%08X)\n", r, errno);
// perror("Errorstring: ");
#ifdef PLATFORM_IMX
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)
#define E_NACK_I2C_IMX ENXIO
// #warning "ENXIO"
#else
#define E_NACK_I2C_IMX EIO
// #warning "EIO"
#endif // LINUX_VERSION_CODE
// In case of IMX, errno == E_NACK_I2C_IMX is not exclusively bound to NACK on address,
// it can also signal a NACK on a data byte
if (errno == E_NACK_I2C_IMX) {
// I2C_LOG_PRINTF("axI2CWriteRead: ioctl signal NACK (errno = %d)\n", errno);
return I2C_NACK_ON_ADDRESS;
}
else {
// printf("axI2CWriteRead: ioctl error (errno = %d)\n", errno);
return I2C_FAILED;
}
#else
// I2C_LOG_PRINTF("axI2CWriteRead: ioctl cmd I2C_RDWR fails with value %d (errno: 0x%08X)\n", r, errno);
return I2C_FAILED;
#endif // PLATFORM_IMX
}
else
{
int rlen = packets.msgs[1].buf[0]+1;
//I2C_LOG_PRINTF("packets.msgs[1].len is %d \n", packets.msgs[1].len);
LOG_MAU8_D("RX (axI2CWriteRead) < ",&packets.msgs[1].buf[i], rlen);
for (i = 0; i < rlen; i++)
{
pRx[i] = packets.msgs[1].buf[i];
}
*pRxLen = rlen;
}
return I2C_OK;
}
#endif // defined(SCI2C)
#ifdef T1oI2C
i2c_error_t axI2CRead(void* conn_ctx, unsigned char bus, unsigned char addr, unsigned char * pRx, unsigned short rxLen)
{
int nrRead = -1;
i2c_error_t rv;
int axSmDevice = *(int*)conn_ctx;
if (bus != I2C_BUS_0)
{
LOG_E("axI2CRead on wrong bus %x (addr %x)\n", bus, addr);
}
nrRead = read(axSmDevice, pRx, rxLen);
if (nrRead < 0)
{
//LOG_E("Failed Read data (nrRead=%d).\n", nrRead);
rv = I2C_FAILED;
}
else
{
if (nrRead == rxLen) // okay
{
rv = I2C_OK;
}
else
{
rv = I2C_FAILED;
}
}
LOG_D("Done with rv = %02x ", rv);
LOG_MAU8_D("TX (axI2CRead): ",pRx,rxLen);
return rv;
}
#endif // T1oI2C