You've already forked M5Stack_Linux_Libs
mirror of
https://github.com/m5stack/M5Stack_Linux_Libs.git
synced 2026-05-20 11:01:38 -07:00
462 lines
12 KiB
C
462 lines
12 KiB
C
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <errno.h>
|
|
#include "linuxi2c.h"
|
|
|
|
/* I2C default delay */
|
|
#define I2C_DEFAULT_DELAY 1
|
|
|
|
/* I2C internal address max length */
|
|
#define INT_ADDR_MAX_BYTES 4
|
|
|
|
/* I2C page max bytes */
|
|
#define PAGE_MAX_BYTES 4096
|
|
|
|
#define GET_I2C_DELAY(delay) (delay)
|
|
#define GET_I2C_FLAGS(tenbit, flags) ((tenbit) ? ((flags) | I2C_M_TEN) : (flags))
|
|
#define GET_WRITE_SIZE(addr, remain, page_bytes) ((addr) + (remain) > (page_bytes) ? (page_bytes) - (addr) : remain)
|
|
|
|
static void linuxi2c_delay(unsigned char delay);
|
|
|
|
/*
|
|
** @brief : Open i2c bus
|
|
** #bus_name : i2c bus name such as: /dev/i2c-1
|
|
** @return : failed return -1, success return i2c bus fd
|
|
*/
|
|
int linuxi2c_open(const char *bus_name)
|
|
{
|
|
int fd;
|
|
unsigned long funcs;
|
|
int ret;
|
|
/* Open i2c-bus devcice */
|
|
if ((fd = open(bus_name, O_RDWR)) == -1) {
|
|
|
|
return -errno;
|
|
}
|
|
|
|
ret = ioctl(fd, I2C_FUNCS, &funcs);
|
|
if (ret < 0) {
|
|
ret = -errno;
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
/* I2C driver must support I2C_RDWR ioctl */
|
|
if (!(funcs & I2C_FUNC_I2C)) {
|
|
ret = -ENOSYS;
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
|
|
void linuxi2c_close(int bus)
|
|
{
|
|
close(bus);
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : Initialize LINUXI2CDevice with defualt value
|
|
** #device : LINUXI2CDevice struct
|
|
*/
|
|
void linuxi2c_init_device(LINUXI2CDevice *device)
|
|
{
|
|
/* 7 bit device address */
|
|
device->tenbit = 0;
|
|
|
|
/* no delay */
|
|
device->delay = 0;
|
|
|
|
/* 8 bytes per page */
|
|
device->page_bytes = 32;
|
|
|
|
/* 1 byte internal(word) address */
|
|
device->iaddr_bytes = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : Get LINUXI2CDevice struct desc
|
|
** #device : LINUXI2CDevice struct
|
|
** #buf : Description message buffer
|
|
** #size : #buf size
|
|
** @return : return i2c device desc
|
|
*/
|
|
char *linuxi2c_get_device_desc(const LINUXI2CDevice *device, char *buf, size_t size)
|
|
{
|
|
memset(buf, 0, size);
|
|
snprintf(buf, size, "Device address: 0x%x, tenbit: %s, internal(word) address: %d bytes, page max %d bytes, delay: %dms",
|
|
device->addr, device->tenbit ? "True" : "False", device->iaddr_bytes, device->page_bytes, device->delay);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*
|
|
** i2c_ioctl_read/write
|
|
** I2C bus top layer interface to operation different i2c devide
|
|
** This function will call XXX:ioctl system call and will be related
|
|
** i2c-dev.c i2cdev_ioctl to operation i2c device.
|
|
** 1. it can choice ignore or not ignore i2c bus ack signal (flags set I2C_M_IGNORE_NAK)
|
|
** 2. it can choice ignore or not ignore i2c internal address
|
|
**
|
|
*/
|
|
ssize_t linuxi2c_ioctl_read(const LINUXI2CDevice *device, unsigned int iaddr, void *buf, size_t len)
|
|
{
|
|
struct i2c_msg ioctl_msg[2];
|
|
struct i2c_rdwr_ioctl_data ioctl_data;
|
|
unsigned char addr[INT_ADDR_MAX_BYTES];
|
|
unsigned short flags = GET_I2C_FLAGS(device->tenbit, device->flags);
|
|
|
|
memset(addr, 0, sizeof(addr));
|
|
memset(ioctl_msg, 0, sizeof(ioctl_msg));
|
|
memset(&ioctl_data, 0, sizeof(ioctl_data));
|
|
|
|
/* Target have internal address */
|
|
if (device->iaddr_bytes) {
|
|
|
|
linuxi2c_iaddr_convert(iaddr, device->iaddr_bytes, addr);
|
|
|
|
/* First message is write internal address */
|
|
ioctl_msg[0].len = device->iaddr_bytes;
|
|
ioctl_msg[0].addr = device->addr;
|
|
ioctl_msg[0].buf = addr;
|
|
ioctl_msg[0].flags = flags;
|
|
|
|
/* Second message is read data */
|
|
ioctl_msg[1].len = len;
|
|
ioctl_msg[1].addr = device->addr;
|
|
ioctl_msg[1].buf = buf;
|
|
ioctl_msg[1].flags = flags | I2C_M_RD;
|
|
|
|
/* Package to i2c message to operation i2c device */
|
|
ioctl_data.nmsgs = 2;
|
|
ioctl_data.msgs = ioctl_msg;
|
|
}
|
|
/* Target did not have internal address */
|
|
else {
|
|
|
|
/* Direct send read data message */
|
|
ioctl_msg[0].len = len;
|
|
ioctl_msg[0].addr = device->addr;
|
|
ioctl_msg[0].buf = buf;
|
|
ioctl_msg[0].flags = flags | I2C_M_RD;
|
|
|
|
/* Package to i2c message to operation i2c device */
|
|
ioctl_data.nmsgs = 1;
|
|
ioctl_data.msgs = ioctl_msg;
|
|
}
|
|
|
|
/* Using ioctl interface operation i2c device */
|
|
if (ioctl(device->bus, I2C_RDWR, (unsigned long)&ioctl_data) == -1) {
|
|
|
|
perror("Ioctl read i2c error:");
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
ssize_t linuxi2c_ioctl_write(const LINUXI2CDevice *device, unsigned int iaddr, const void *buf, size_t len)
|
|
{
|
|
ssize_t remain = len;
|
|
size_t size = 0, cnt = 0;
|
|
const unsigned char *buffer = buf;
|
|
unsigned char delay = GET_I2C_DELAY(device->delay);
|
|
unsigned short flags = GET_I2C_FLAGS(device->tenbit, device->flags);
|
|
|
|
struct i2c_msg ioctl_msg;
|
|
struct i2c_rdwr_ioctl_data ioctl_data;
|
|
unsigned char tmp_buf[PAGE_MAX_BYTES + INT_ADDR_MAX_BYTES];
|
|
|
|
while (remain > 0) {
|
|
|
|
size = GET_WRITE_SIZE(iaddr % device->page_bytes, remain, device->page_bytes);
|
|
|
|
/* Convert i2c internal address */
|
|
memset(tmp_buf, 0, sizeof(tmp_buf));
|
|
linuxi2c_iaddr_convert(iaddr, device->iaddr_bytes, tmp_buf);
|
|
|
|
/* Connect write data after device internal address */
|
|
memcpy(tmp_buf + device->iaddr_bytes, buffer, size);
|
|
|
|
/* Fill kernel ioctl i2c_msg */
|
|
memset(&ioctl_msg, 0, sizeof(ioctl_msg));
|
|
memset(&ioctl_data, 0, sizeof(ioctl_data));
|
|
|
|
ioctl_msg.len = device->iaddr_bytes + size;
|
|
ioctl_msg.addr = device->addr;
|
|
ioctl_msg.buf = tmp_buf;
|
|
ioctl_msg.flags = flags;
|
|
|
|
ioctl_data.nmsgs = 1;
|
|
ioctl_data.msgs = &ioctl_msg;
|
|
|
|
if (ioctl(device->bus, I2C_RDWR, (unsigned long)&ioctl_data) == -1) {
|
|
|
|
perror("Ioctl write i2c error:");
|
|
return -1;
|
|
}
|
|
|
|
/* XXX: Must have a little time delay */
|
|
linuxi2c_delay(delay);
|
|
|
|
cnt += size;
|
|
iaddr += size;
|
|
buffer += size;
|
|
remain -= size;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : read #len bytes data from #device #iaddr to #buf
|
|
** #device : LINUXI2CDevice struct, must call i2c_device_init first
|
|
** #iaddr : i2c_device internal address will read data from this address, no address set zero
|
|
** #buf : i2c data will read to here
|
|
** #len : how many data to read, lenght must less than or equal to buf size
|
|
** @return : success return read data length, failed -1
|
|
*/
|
|
ssize_t linuxi2c_read(const LINUXI2CDevice *device, unsigned int iaddr, void *buf, size_t len)
|
|
{
|
|
ssize_t cnt;
|
|
unsigned char addr[INT_ADDR_MAX_BYTES];
|
|
unsigned char delay = GET_I2C_DELAY(device->delay);
|
|
|
|
/* Set i2c slave address */
|
|
if (linuxi2c_select(device->bus, device->addr, device->tenbit) == -1) {
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Convert i2c internal address */
|
|
memset(addr, 0, sizeof(addr));
|
|
linuxi2c_iaddr_convert(iaddr, device->iaddr_bytes, addr);
|
|
|
|
/* Write internal address to devide */
|
|
if (write(device->bus, addr, device->iaddr_bytes) != device->iaddr_bytes) {
|
|
|
|
perror("Write i2c internal address error");
|
|
return -1;
|
|
}
|
|
|
|
/* Wait a while */
|
|
linuxi2c_delay(delay);
|
|
|
|
/* Read count bytes data from int_addr specify address */
|
|
if ((cnt = read(device->bus, buf, len)) == -1) {
|
|
|
|
perror("Read i2c data error");
|
|
return -1;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : write #buf data to i2c #device #iaddr address
|
|
** #device : LINUXI2CDevice struct, must call i2c_device_init first
|
|
** #iaddr : i2c_device internal address, no address set zero
|
|
** #buf : data will write to i2c device
|
|
** #len : buf data length without '/0'
|
|
** @return : success return write data length, failed -1
|
|
*/
|
|
ssize_t linuxi2c_write(const LINUXI2CDevice *device, unsigned int iaddr, const void *buf, size_t len)
|
|
{
|
|
ssize_t remain = len;
|
|
ssize_t ret;
|
|
size_t cnt = 0, size = 0;
|
|
const unsigned char *buffer = buf;
|
|
unsigned char delay = GET_I2C_DELAY(device->delay);
|
|
unsigned char tmp_buf[PAGE_MAX_BYTES + INT_ADDR_MAX_BYTES];
|
|
|
|
/* Set i2c slave address */
|
|
if (linuxi2c_select(device->bus, device->addr, device->tenbit) == -1) {
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Once only can write less than 4 byte */
|
|
while (remain > 0) {
|
|
|
|
size = GET_WRITE_SIZE(iaddr % device->page_bytes, remain, device->page_bytes);
|
|
|
|
/* Convert i2c internal address */
|
|
memset(tmp_buf, 0, sizeof(tmp_buf));
|
|
linuxi2c_iaddr_convert(iaddr, device->iaddr_bytes, tmp_buf);
|
|
|
|
/* Copy data to tmp_buf */
|
|
memcpy(tmp_buf + device->iaddr_bytes, buffer, size);
|
|
|
|
/* Write to buf content to i2c device length is address length and
|
|
write buffer length */
|
|
ret = write(device->bus, tmp_buf, device->iaddr_bytes + size);
|
|
if (ret == -1 || (size_t)ret != device->iaddr_bytes + size)
|
|
{
|
|
perror("I2C write error:");
|
|
return -1;
|
|
}
|
|
|
|
/* XXX: Must have a little time delay */
|
|
linuxi2c_delay(delay);
|
|
|
|
/* Move to next #size bytes */
|
|
cnt += size;
|
|
iaddr += size;
|
|
buffer += size;
|
|
remain -= size;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : i2c internal address convert
|
|
** #iaddr : i2c device internal address
|
|
** #len : i2c device internal address length
|
|
** #addr : save convert address
|
|
*/
|
|
void linuxi2c_iaddr_convert(unsigned int iaddr, unsigned int len, unsigned char *addr)
|
|
{
|
|
union {
|
|
unsigned int iaddr;
|
|
unsigned char caddr[INT_ADDR_MAX_BYTES];
|
|
} convert;
|
|
|
|
/* I2C internal address order is big-endian, same with network order */
|
|
convert.iaddr = htonl(iaddr);
|
|
|
|
/* Copy address to addr buffer */
|
|
int i = len - 1;
|
|
int j = INT_ADDR_MAX_BYTES - 1;
|
|
|
|
while (i >= 0 && j >= 0) {
|
|
|
|
addr[i--] = convert.caddr[j--];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** @brief : Select i2c address @i2c bus
|
|
** #bus : i2c bus fd
|
|
** #dev_addr : i2c device address
|
|
** #tenbit : i2c device address is tenbit
|
|
** #return : success return 0, failed return -1
|
|
*/
|
|
int linuxi2c_select(int bus, unsigned long dev_addr, unsigned long tenbit)
|
|
{
|
|
/* Set i2c device address bit */
|
|
if (ioctl(bus, I2C_TENBIT, tenbit)) {
|
|
|
|
perror("Set I2C_TENBIT failed");
|
|
return -1;
|
|
}
|
|
|
|
/* Set i2c device as slave ans set it address */
|
|
if (ioctl(bus, I2C_SLAVE, dev_addr)) {
|
|
|
|
perror("Set i2c device address failed");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_rw(const LINUXI2CDevice *device, void *offset, size_t offset_len, void *buf,
|
|
size_t buf_len, int write_flag)
|
|
{
|
|
struct i2c_msg msgs[2];
|
|
struct i2c_rdwr_ioctl_data ioctl_data;
|
|
unsigned int idx, count;
|
|
int ret;
|
|
|
|
if (!device)
|
|
return -EINVAL;
|
|
|
|
idx = 0;
|
|
count = 0;
|
|
|
|
/* Send the device's register/memory address to be read/written */
|
|
if (offset_len) {
|
|
msgs[0].addr = device->addr;
|
|
msgs[0].flags = 0; /* write */
|
|
msgs[0].len = offset_len;
|
|
msgs[0].buf = (unsigned char*)offset;
|
|
idx++;
|
|
count++;
|
|
}
|
|
|
|
/* Read/write from/to the device's register/memory */
|
|
if (buf_len) {
|
|
msgs[idx].addr = device->addr;
|
|
msgs[idx].flags = write_flag ? 0 : I2C_M_RD;
|
|
msgs[idx].len = buf_len;
|
|
msgs[idx].buf = (unsigned char*)buf;
|
|
count++;
|
|
}
|
|
|
|
if (count) {
|
|
ioctl_data.msgs = msgs;
|
|
ioctl_data.nmsgs = count;
|
|
|
|
ret = ioctl(device->bus, I2C_RDWR, &ioctl_data);
|
|
if (ret < 0)
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** @brief : Read specified bytes number of bytes from I2C device
|
|
** #device : LINUXI2CDevice struct, must call i2c_device_init first
|
|
** #offset : Offset within I2C device to start reading
|
|
** #offset_len : Length in bytes of 'offset' parameter
|
|
** #buf : Address of buffer where data will be placed
|
|
** #buf_len : Length in bytes of the buffer pointed to by 'buf'
|
|
** @return : 0 on success, negative errno on failure
|
|
*/
|
|
ssize_t linuxi2c_primitive_read(const LINUXI2CDevice *device, void *offset, size_t offset_len, void *buf, size_t buf_len)
|
|
{
|
|
return i2c_rw(device, offset, offset_len, buf, buf_len, 0);
|
|
}
|
|
|
|
/*
|
|
** @brief : write #buf data to i2c #device #iaddr address
|
|
** #device : LINUXI2CDevice struct, must call i2c_device_init first
|
|
** #offset : Offset within I2C device to start writing
|
|
** #offset_len : Length in bytes of 'offset' parameter
|
|
** #buf : Address of buffer containing data to write
|
|
** #buf_len : Length in bytes of the data pointed to by 'buf'
|
|
** @return : 0 on success, negative errno on failure
|
|
*/
|
|
ssize_t linuxi2c_primitive_write(const LINUXI2CDevice *device, void *offset, size_t offset_len, void *buf, size_t buf_len)
|
|
{
|
|
return i2c_rw(device, offset, offset_len, buf, buf_len, 1);
|
|
}
|
|
|
|
/*
|
|
** @brief : i2c delay
|
|
** #msec : milliscond to be delay
|
|
*/
|
|
static void linuxi2c_delay(unsigned char msec)
|
|
{
|
|
if(msec)
|
|
usleep(msec * 1000);
|
|
}
|