265 lines
6.2 KiB
C
265 lines
6.2 KiB
C
/*
|
|
* Copyright 2018 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Pim 2018-04-03: This is a Linux implementation of the mgos_i2c interface
|
|
// which is useful to develop drivers directly on Linux. For Ubuntu/Debian,
|
|
// make sure to install package 'libi2c-dev'.
|
|
|
|
#include "mgos_i2c.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <i2c/smbus.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
struct mgos_i2c
|
|
{
|
|
int fd;
|
|
uint16_t read_timeout_ms; // in msec
|
|
char *filename;
|
|
};
|
|
|
|
static struct mgos_i2c *s_global_i2c_bus = NULL;
|
|
|
|
static size_t i2c_read_timeout(struct mgos_i2c *i2c, void *data, size_t len) {
|
|
uint16_t tries;
|
|
size_t ret=-1;
|
|
|
|
if (!i2c) return -1;
|
|
|
|
ret = read(i2c->fd, data, len);
|
|
|
|
for(tries=i2c->read_timeout_ms; tries && ret!=len && len>0; tries--) {
|
|
usleep(1000);
|
|
ret = read(i2c->fd, data, len);
|
|
}
|
|
if (ret!=len) {
|
|
// LOG(LL_ERROR, ("Read timeout on I2C"));
|
|
return -1;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
bool mgos_i2c_read(struct mgos_i2c *i2c, uint16_t addr, void *data, size_t len, bool stop) {
|
|
size_t ret;
|
|
|
|
if (!i2c) {
|
|
LOG(LL_ERROR, ("No I2C bus"));
|
|
return false;
|
|
}
|
|
|
|
if (ioctl(i2c->fd,I2C_SLAVE,addr) < 0) {
|
|
LOG(LL_ERROR, ("Cannot select slave 0x%04x on I2C bus", addr));
|
|
return false;
|
|
}
|
|
ret = i2c_read_timeout(i2c, data, len);
|
|
if (ret != len) {
|
|
// LOG(LL_DEBUG, ("RECV %ld bytes (wanted %lu) from 0x%02x: %s", ret, len, addr, strerror(errno)));
|
|
return false;
|
|
}
|
|
return true;
|
|
(void)stop;
|
|
}
|
|
|
|
|
|
bool mgos_i2c_write(struct mgos_i2c *i2c, uint16_t addr, const void *data, size_t len, bool stop) {
|
|
size_t ret;
|
|
|
|
if (!i2c) {
|
|
LOG(LL_ERROR, ("No I2C bus"));
|
|
return false;
|
|
}
|
|
|
|
if (ioctl(i2c->fd,I2C_SLAVE,addr) < 0) {
|
|
LOG(LL_ERROR, ("Cannot select slave 0x%04x on I2C bus", addr));
|
|
return false;
|
|
}
|
|
ret = write(i2c->fd, data, len);
|
|
if (ret != len) {
|
|
// LOG(LL_DEBUG, ("XMIT %ld bytes (wanted %lu) from 0x%02x: %s", ret, len, addr, strerror(errno)));
|
|
return false;
|
|
}
|
|
return true;
|
|
(void)stop;
|
|
}
|
|
|
|
|
|
void mgos_i2c_stop(struct mgos_i2c *i2c) {
|
|
return;
|
|
(void)i2c;
|
|
}
|
|
|
|
|
|
int mgos_i2c_get_freq(struct mgos_i2c *i2c) {
|
|
return MGOS_I2C_FREQ_100KHZ;
|
|
(void)i2c;
|
|
}
|
|
|
|
|
|
bool mgos_i2c_set_freq(struct mgos_i2c *i2c, int freq) {
|
|
if (freq==MGOS_I2C_FREQ_100KHZ) return true;
|
|
return false;
|
|
(void)i2c;
|
|
}
|
|
|
|
|
|
int mgos_i2c_read_reg_b(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg) {
|
|
uint8_t value;
|
|
|
|
if (!mgos_i2c_read_reg_n(i2c, addr, reg, 1, &value))
|
|
return -1;
|
|
return value;
|
|
}
|
|
|
|
|
|
int mgos_i2c_read_reg_w(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg) {
|
|
uint16_t value;
|
|
uint8_t data[2];
|
|
|
|
if (!mgos_i2c_read_reg_n(i2c, addr, reg, 2, data))
|
|
return -1;
|
|
value=(data[0]<<8)+data[1];
|
|
return value;
|
|
}
|
|
|
|
|
|
bool mgos_i2c_read_reg_n(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg, size_t n, uint8_t *buf) {
|
|
uint8_t outbuf;
|
|
struct i2c_rdwr_ioctl_data packets;
|
|
struct i2c_msg messages[2];
|
|
|
|
if (!i2c) {
|
|
LOG(LL_ERROR, ("No I2C bus"));
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(i2c->fd,I2C_SLAVE,addr) < 0) {
|
|
LOG(LL_ERROR, ("Cannot select slave 0x%04x on I2C bus: %s", addr, strerror(errno)));
|
|
return -1;
|
|
}
|
|
/*
|
|
* In order to read a register, we first do a "dummy write" by writing
|
|
* 0 bytes to the register we want to read from. This is similar to
|
|
* the packet in set_i2c_register, except it's 1 byte rather than 2.
|
|
*/
|
|
outbuf = reg;
|
|
messages[0].addr = addr;
|
|
messages[0].flags = 0;
|
|
messages[0].len = sizeof(outbuf);
|
|
messages[0].buf = &outbuf;
|
|
|
|
/* The data will get returned in this structure */
|
|
messages[1].addr = addr;
|
|
messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
|
|
messages[1].len = n;
|
|
messages[1].buf = buf;
|
|
|
|
/* Send the request to the kernel and get the result back */
|
|
packets.msgs = messages;
|
|
packets.nmsgs = 2;
|
|
if(ioctl(i2c->fd, I2C_RDWR, &packets) < 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool mgos_i2c_write_reg_w(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg, uint16_t value) {
|
|
uint8_t data[2];
|
|
data[0]=value>>8;
|
|
data[1]=value&0xFF;
|
|
|
|
return mgos_i2c_write_reg_n(i2c, addr, reg, 2, data);
|
|
}
|
|
|
|
|
|
bool mgos_i2c_write_reg_b(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg, uint8_t value) {
|
|
return mgos_i2c_write_reg_n(i2c, addr, reg, 1, &value);
|
|
}
|
|
|
|
|
|
bool mgos_i2c_write_reg_n(struct mgos_i2c *i2c, uint16_t addr, uint8_t reg, size_t n, const uint8_t *buf) {
|
|
unsigned char outbuf[n+1];
|
|
struct i2c_rdwr_ioctl_data packets;
|
|
struct i2c_msg messages[1];
|
|
|
|
if (!i2c) {
|
|
LOG(LL_ERROR, ("No I2C bus"));
|
|
return false;
|
|
}
|
|
|
|
if (ioctl(i2c->fd,I2C_SLAVE,addr) < 0) {
|
|
LOG(LL_ERROR, ("Cannot select slave 0x%04x on I2C bus: %s", addr, strerror(errno)));
|
|
return false;
|
|
}
|
|
|
|
messages[0].addr=addr;
|
|
messages[0].flags=0;
|
|
messages[0].len=sizeof(outbuf);
|
|
messages[0].buf=outbuf;
|
|
|
|
outbuf[0]=reg;
|
|
memcpy(outbuf+1, buf, n);
|
|
|
|
packets.msgs = messages;
|
|
packets.nmsgs = 1;
|
|
if(ioctl(i2c->fd, I2C_RDWR, &packets) < 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void mgos_i2c_close(struct mgos_i2c *i2c) {
|
|
return;
|
|
(void)i2c;
|
|
}
|
|
|
|
|
|
struct mgos_i2c *mgos_i2c_get_global(void) {
|
|
return s_global_i2c_bus;
|
|
}
|
|
|
|
|
|
// User provided function to interface with Linux I2C driver
|
|
bool mgos_i2c_open(int busnr) {
|
|
int fd;
|
|
char filename[20];
|
|
struct mgos_i2c *i2c;
|
|
|
|
sprintf(filename,"/dev/i2c-%d",busnr);
|
|
if ((fd = open(filename,O_RDWR)) < 0) {
|
|
LOG(LL_ERROR, ("Could not open %s", filename));
|
|
return false;
|
|
}
|
|
i2c = calloc(1, sizeof(struct mgos_i2c));
|
|
if (!i2c) {
|
|
LOG(LL_ERROR, ("Could not allocate mgos_i2c handle for %s", filename));
|
|
return false;
|
|
}
|
|
i2c->fd=fd;
|
|
i2c->filename=strdup(filename);
|
|
i2c->read_timeout_ms=1000;
|
|
s_global_i2c_bus=i2c;
|
|
LOG(LL_INFO, ("Opened I2C bus on %s", filename));
|
|
|
|
return true;
|
|
}
|