IES/src/platforms/common/lib/i2c/smbus/fm_platform_smbus.c
DataHoarder 82bd01ed92
All checks were successful
continuous-integration/drone/push Build is passing
Remove #include <sys/io.h>, prevents arm64 build
2021-10-28 08:04:13 +02:00

461 lines
15 KiB
C

/* vim:ts=4:sw=4:expandtab
* (No tabs, indent level is 4 spaces) */
/*****************************************************************************
* File: fm_platform_smbus.c
* Creation Date: June 3, 2013
* Description: SMBUS functions.
*
* Copyright (c) 2013 - 2015, Intel Corporation
*
* 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 following disclaimer.
* * 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.
* * Neither the name of Intel Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
*****************************************************************************/
#include <fm_sdk.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#ifdef I2C_SMBUS
#include <linux/i2c.h>
#else
/* Temporary fix for compile error on some dev platforms */
#define I2C_SLAVE 0x703
#define I2C_SMBUS 0x720
#define I2C_FUNCS 0x705 /* Get the adapter functionality */
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)
#define I2C_SMBUS_WRITE 0
#define I2C_SMBUS_READ 1
#define I2C_SMBUS_BYTE 1
#define I2C_SMBUS_BYTE_DATA 2
#define I2C_SMBUS_WORD_DATA 3
#define I2C_SMBUS_BLOCK_DATA 5
#define I2C_SMBUS_I2C_BLOCK_BROKEN 6
#define I2C_SMBUS_BLOCK_PROC_CALL 7 /* SMBus 2.0 */
#define I2C_SMBUS_I2C_BLOCK_DATA 8
#define I2C_SMBUS_BLOCK_MAX 32 /* As specified in SMBus standard */
union i2c_smbus_data
{
__u8 byte;
__u16 word;
__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
/* and one more for user-space compatibility */
};
#endif
/*****************************************************************************
* Macros, Constants & Types
*****************************************************************************/
/*****************************************************************************
* Global Variables
*****************************************************************************/
/*****************************************************************************
* Local Variables
*****************************************************************************/
/*****************************************************************************
* Local function prototypes.
*****************************************************************************/
/*****************************************************************************
* Local Functions
*****************************************************************************/
/*****************************************************************************/
/* I2CSmbusAccess
* \ingroup intPlatformSMBus
*
* \desc Perform SMBUS IOCTL access to the kernel.
*
* \param[in] fd is the file handle to the SMBUS device.
*
* \param[in] readWrite specifies whether to read or write.
*
* \param[in] command is the SMBUS command byte.
*
* \param[in] size if the SMBUS size to read or write.
*
* \param[in,out] data is the pointer to the i2c_smbus_data structure.
*
* \return FM_OK if successful.
* \return Other ''Status Codes'' as appropriate in case of
* failure.
*
*****************************************************************************/
static inline __s32 I2CSmbusAccess(int fd, fm_char readWrite, fm_byte command,
fm_int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args;
args.read_write = readWrite;
args.command = command;
args.size = size;
args.data = data;
return ioctl(fd, I2C_SMBUS, &args);
} /* I2CSmbusAccess */
/*****************************************************************************
* Public Functions
*****************************************************************************/
/*****************************************************************************/
/* fmPlatformSMBusI2cInit
* \ingroup intPlatformSMBus
*
* \desc Open the SMBUS I2C device to access the platform peripheral.
*
* \param[in] devName is the SMBUS device name to open.
*
* \param[out] i2cFd points to caller allocated storage where the file
* descriptor of the I2C device will be written.
*
* \return FM_OK if successful.
* \return Other ''Status Codes'' as appropriate in case of
* failure.
*
*****************************************************************************/
fm_status fmPlatformSMBusI2cInit(fm_text devName, int *i2cFd)
{
int fd;
char strErrBuf[FM_STRERROR_BUF_SIZE];
errno_t strErrNum;
FM_LOG_ENTRY(FM_LOG_CAT_PLATFORM, "devName = %s\n", devName);
*i2cFd = -1;
fd = open(devName, O_RDWR);
if (fd < 0)
{
strErrNum = FM_STRERROR_S(strErrBuf, FM_STRERROR_BUF_SIZE, errno);
if (strErrNum)
{
FM_SNPRINTF_S(strErrBuf, FM_STRERROR_BUF_SIZE, "%d", errno);
}
FM_LOG_FATAL(FM_LOG_CAT_PLATFORM,
"%s: Unable to open '%s'.\n",
strErrBuf,
devName);
FM_LOG_EXIT(FM_LOG_CAT_PLATFORM, FM_FAIL);
}
*i2cFd = fd;
FM_LOG_EXIT(FM_LOG_CAT_PLATFORM, FM_OK);
} /* end fmPlatformSMBusI2cInit */
/*****************************************************************************/
/* fmPlatformSMBusIsI2cBlockSupported
* \ingroup intPlatformSMBus
*
* \desc Indicates whether the I2C_SMBUS_I2C_BLOCK_DATA transaction
* type is supported by the linux SMBus driver associated
* to the given file descriptor.
*
* \param[in] fd is the file descriptor to the SMbus device.
*
* \param[out] i2cSupport points to caller allocated storage where the
* support status will be written.
*
* \return FM_OK if successful.
* \return Other ''Status Codes'' as appropriate in case of
* failure.
*
*****************************************************************************/
fm_status fmPlatformSMBusIsI2cBlockSupported(int fd, int *i2cSupport)
{
unsigned long funcs;
fm_status status;
FM_LOG_ENTRY(FM_LOG_CAT_PLATFORM, "fd = %d\n", fd);
if (fd <= 0 || i2cSupport == NULL)
{
status = FM_ERR_INVALID_ARGUMENT;
FM_LOG_ABORT_ON_ERR(FM_LOG_CAT_PLATFORM, status);
}
/* Default to not supported */
*i2cSupport = 0;
if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
{
status = FM_ERR_UNSUPPORTED;
FM_LOG_ABORT_ON_ERR(FM_LOG_CAT_PLATFORM, status);
}
if ((funcs & I2C_FUNC_SMBUS_I2C_BLOCK) == I2C_FUNC_SMBUS_I2C_BLOCK)
{
/* Indicate I2C_FUNC_SMBUS_I2C_BLOCK is supported */
*i2cSupport = 1;
}
status = FM_OK;
ABORT:
FM_LOG_EXIT(FM_LOG_CAT_PLATFORM, status);
} /* end fmPlatformSMBusIsI2cBlockSupported */
/*****************************************************************************/
/* fmPlatformSMBusI2cWriteRead
* \ingroup intPlatformSMBus
*
* \desc Write to then immediately read from an SMBUS device using
* SMBUS interface.
*
* \param[in] fd is the file descriptor to the USB COM device.
*
* \param[in] device is the SMBUS device address (0x00 - 0x7F).
*
* \param[in,out] data points to an array from which data is written and
* into which data is read.
*
* \param[in] wl is the number of bytes to write.
*
* \param[in] rl is the number of bytes to read.
*
* \return FM_OK if successful.
* \return Other ''Status Codes'' as appropriate in case of
* failure.
*
*****************************************************************************/
fm_status fmPlatformSMBusI2cWriteRead(int fd,
fm_int device,
fm_byte *data,
fm_int wl,
fm_int rl)
{
int ret;
fm_int cnt;
fm_status status = FM_OK;
union i2c_smbus_data smbusData;
fm_int len;
fm_int off;
FM_LOG_ENTRY(FM_LOG_CAT_PLATFORM, "dev 0x%02x wl %d rl %d data %02x%02x\n",
device, wl, rl, (wl > 0) ? data[0]:0, (wl > 1) ? data[1]:0);
if (fd <= 0)
{
status = FM_ERR_UNSUPPORTED;
FM_LOG_ABORT_ON_ERR(FM_LOG_CAT_PLATFORM, status);
}
/* This code is process safe. Two processes can access different devices
* on the same I2C bus without requiring locking */
if (ioctl(fd, I2C_SLAVE, device) < 0)
{
status = FM_ERR_UNSUPPORTED;
FM_LOG_ABORT_ON_ERR(FM_LOG_CAT_PLATFORM, status);
}
if (wl == 0 && rl == 1)
{
if ( I2CSmbusAccess(fd, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &smbusData) )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
else
{
data[0] = 0x0FF & smbusData.byte;
}
}
else if (rl == 0)
{
if (wl == 1)
{
if ( ( ret = I2CSmbusAccess(fd, I2C_SMBUS_WRITE, data[0],
I2C_SMBUS_BYTE, NULL) ) < 0 )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
}
else if (wl == 2)
{
smbusData.byte = data[1];
if ( ( ret = I2CSmbusAccess(fd, I2C_SMBUS_WRITE, data[0],
I2C_SMBUS_BYTE_DATA, &smbusData) ) < 0 )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
}
else if (wl == 3)
{
smbusData.word = (data[2] << 8) | data[1];
if ( ( ret = I2CSmbusAccess(fd, I2C_SMBUS_WRITE, data[0],
I2C_SMBUS_WORD_DATA, &smbusData) ) < 0 )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
}
else if ( wl <= (I2C_SMBUS_BLOCK_MAX + 1) )
{
smbusData.block[0] = wl - 1;
for (cnt = 1 ; cnt <= smbusData.block[0] ; cnt++)
{
smbusData.block[cnt] = data[cnt];
}
if ( I2CSmbusAccess(fd, I2C_SMBUS_WRITE, data[0],
I2C_SMBUS_I2C_BLOCK_DATA,
&smbusData) )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
}
else
{
FM_LOG_ERROR(FM_LOG_CAT_PLATFORM, "SmbusWriteRead wl %d rl %d not supported\n", wl, rl);
status = FM_ERR_INVALID_ARGUMENT;
}
}
else if (wl == 1 && rl == 1)
{
if ( I2CSmbusAccess(fd, I2C_SMBUS_READ, data[0],
I2C_SMBUS_BYTE_DATA,
&smbusData) )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
else
{
data[0] = smbusData.byte & 0xFF;
}
}
else if (wl == 1 && rl == 2)
{
if ( I2CSmbusAccess(fd, I2C_SMBUS_READ, data[0],
I2C_SMBUS_WORD_DATA,
&smbusData) )
{
status = FM_ERR_I2C_NO_RESPONSE;
}
else
{
data[0] = smbusData.word & 0xFF;
data[1] = (smbusData.word >> 8) & 0xFF;
}
}
else if (wl == 1 && rl > 0)
{
len = 0;
off = data[0];
/* For EEPROM read, need to split into 32 chunks
* The rest should always be less than 32 bytes read */
while (len < rl)
{
smbusData.block[0] = rl - len;
if ( I2CSmbusAccess(fd, I2C_SMBUS_READ, off + len,
I2C_SMBUS_I2C_BLOCK_DATA,
&smbusData) )
{
status = FM_ERR_I2C_NO_RESPONSE;
break;
}
else if (smbusData.block[0] <= 0)
{
/* Avoid possible infinite loop */
status = FM_ERR_I2C_NO_RESPONSE;
break;
}
else
{
for (cnt = 1 ; cnt <= smbusData.block[0] ; cnt++)
{
data[len++] = smbusData.block[cnt];
if (len >= rl)
{
break;
}
}
}
}
}
else if (wl == 3 && rl == 4)
{
/* Reading FM6000 register requires a write of 3 bytes for address and read back
* of 4 bytes for value. However there is no such SMBUS command defined.
* A method is to write 3 bytes for address, followed by a write read with writing of
* 1 byte of zero. FM6000 will ignore the 1 byte write and return the 4 bytes register value */
status = fmPlatformSMBusI2cWriteRead(fd, device, data, 3, 0);
FM_LOG_ABORT_ON_ERR(FM_LOG_CAT_PLATFORM, status);
data[0] = 0;
status = fmPlatformSMBusI2cWriteRead(fd, device, data, 1, rl);
}
else
{
FM_LOG_ERROR(FM_LOG_CAT_PLATFORM, "SmbusWriteRead wl %d rl %d not supported\n", wl, rl);
/* Need to add support here if feature is required */
status = FM_ERR_UNSUPPORTED;
}
ABORT:
FM_LOG_EXIT(FM_LOG_CAT_PLATFORM, status);
} /* end fmPlatformSMBusI2cWriteRead */