Many i2c device drivers used similar code to access registers on the ICs they drive. This commit implements that functionality in libi2cdriver and updates the drivers to use the library instead of their own register access functions. The net result is 375+ fewer lines of code and less work for people developing new drivers. The two exceptions were cat24c256 and parts of tda19988. They access the bus in uncommon ways. It doesn't make sense at this time to move their read/write functions into libi2cdriver. Change-Id: Id8280b71af33b710a49944d7f20a7262be9f5988
367 lines
7.1 KiB
C
367 lines
7.1 KiB
C
/* This file contains device independent i2c device driver helpers. */
|
|
|
|
#include <assert.h>
|
|
#include <minix/drivers.h>
|
|
#include <minix/endpoint.h>
|
|
#include <minix/i2c.h>
|
|
#include <minix/i2cdriver.h>
|
|
#include <minix/ipc.h>
|
|
#include <minix/ds.h>
|
|
|
|
void
|
|
i2cdriver_announce(uint32_t bus)
|
|
{
|
|
/* Announce we are up after a fresh start or restart. */
|
|
int r;
|
|
char key[DS_MAX_KEYLEN];
|
|
char label[DS_MAX_KEYLEN];
|
|
char *driver_prefix = "drv.i2c.";
|
|
|
|
/* Callers are allowed to use sendrec to communicate with drivers.
|
|
* For this reason, there may blocked callers when a driver restarts.
|
|
* Ask the kernel to unblock them (if any).
|
|
*/
|
|
#if USE_STATECTL
|
|
if ((r = sys_statectl(SYS_STATE_CLEAR_IPC_REFS)) != OK) {
|
|
panic("chardriver_init: sys_statectl failed: %d", r);
|
|
}
|
|
#endif
|
|
|
|
/* Publish a driver up event. */
|
|
r = ds_retrieve_label_name(label, getprocnr());
|
|
if (r != OK) {
|
|
panic("unable to get own label: %d\n", r);
|
|
}
|
|
/* example key: drv.i2c.1.cat24c245.0x50 */
|
|
snprintf(key, DS_MAX_KEYLEN, "%s%d.%s", driver_prefix, bus, label);
|
|
r = ds_publish_u32(key, DS_DRIVER_UP, DSF_OVERWRITE);
|
|
if (r != OK) {
|
|
panic("unable to publish driver up event: %d\n", r);
|
|
}
|
|
}
|
|
|
|
int
|
|
i2cdriver_env_parse(uint32_t * bus, i2c_addr_t * address,
|
|
i2c_addr_t * valid_addrs)
|
|
{
|
|
/* fill in bus and address with the values passed on the command line */
|
|
int r;
|
|
int found;
|
|
long int busl;
|
|
long int addressl;
|
|
|
|
r = env_parse("bus", "d", 0, &busl, 1, 3);
|
|
if (r != EP_SET) {
|
|
return -1;
|
|
}
|
|
*bus = (uint32_t) busl;
|
|
|
|
r = env_parse("address", "x", 0, &addressl, 0x0000, 0x03ff);
|
|
if (r != EP_SET) {
|
|
return -1;
|
|
}
|
|
*address = addressl;
|
|
|
|
found = 0;
|
|
while (*valid_addrs != 0x0000) {
|
|
|
|
if (*address == *valid_addrs) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
|
|
valid_addrs++;
|
|
}
|
|
|
|
if (!found) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
endpoint_t
|
|
i2cdriver_bus_endpoint(uint32_t bus)
|
|
{
|
|
/* locate the driver for the i2c bus itself */
|
|
int r;
|
|
char *label_prefix = "i2c.";
|
|
char label[DS_MAX_KEYLEN];
|
|
endpoint_t bus_endpoint;
|
|
|
|
snprintf(label, DS_MAX_KEYLEN, "%s%d", label_prefix, bus);
|
|
|
|
r = ds_retrieve_label_endpt(label, &bus_endpoint);
|
|
if (r != OK) {
|
|
return 0;
|
|
}
|
|
|
|
return bus_endpoint;
|
|
}
|
|
|
|
int
|
|
i2cdriver_subscribe_bus_updates(uint32_t bus)
|
|
{
|
|
int r;
|
|
char regex[DS_MAX_KEYLEN];
|
|
|
|
/* only capture events for the specified bus */
|
|
snprintf(regex, DS_MAX_KEYLEN, "drv\\.chr\\.i2c\\.%d", bus);
|
|
|
|
/* Subscribe to driver events from the i2c bus */
|
|
r = ds_subscribe(regex, DSF_INITIAL | DSF_OVERWRITE);
|
|
if (r != OK) {
|
|
return r;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
void
|
|
i2cdriver_handle_bus_update(endpoint_t * bus_endpoint, uint32_t bus,
|
|
i2c_addr_t address)
|
|
{
|
|
char key[DS_MAX_KEYLEN];
|
|
u32_t value;
|
|
int type;
|
|
endpoint_t owner_endpoint, old_endpoint;
|
|
int r;
|
|
|
|
/* check for pending events */
|
|
while ((r = ds_check(key, &type, &owner_endpoint)) == OK) {
|
|
|
|
r = ds_retrieve_u32(key, &value);
|
|
if (r != OK) {
|
|
return;
|
|
}
|
|
|
|
if (value == DS_DRIVER_UP) {
|
|
old_endpoint = *bus_endpoint;
|
|
|
|
/* look up the bus's (potentially new) endpoint */
|
|
*bus_endpoint = i2cdriver_bus_endpoint(bus);
|
|
|
|
/* was updated endpoint? */
|
|
if (old_endpoint != *bus_endpoint) {
|
|
/* re-reserve device to allow the driver to
|
|
* continue working, even through a manual
|
|
* down/up.
|
|
*/
|
|
i2cdriver_reserve_device(*bus_endpoint,
|
|
address);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
i2cdriver_reserve_device(endpoint_t bus_endpoint, i2c_addr_t address)
|
|
{
|
|
int r;
|
|
message m;
|
|
|
|
m.m_type = BUSC_I2C_RESERVE;
|
|
m.DEVICE = address;
|
|
|
|
r = sendrec(bus_endpoint, &m);
|
|
if (r != OK) {
|
|
return EIO;
|
|
}
|
|
|
|
return m.REP_STATUS; /* return reply code OK, EBUSY, EINVAL, etc. */
|
|
}
|
|
|
|
int
|
|
i2cdriver_exec(endpoint_t bus_endpoint, minix_i2c_ioctl_exec_t * ioctl_exec)
|
|
{
|
|
int r;
|
|
message m;
|
|
cp_grant_id_t grant_nr;
|
|
|
|
grant_nr = cpf_grant_direct(bus_endpoint, (vir_bytes) ioctl_exec,
|
|
sizeof(minix_i2c_ioctl_exec_t), CPF_READ | CPF_WRITE);
|
|
|
|
memset(&m, '\0', sizeof(message));
|
|
|
|
m.m_type = BUSC_I2C_EXEC;
|
|
m.IO_GRANT = (char *) grant_nr;
|
|
|
|
r = sendrec(bus_endpoint, &m);
|
|
cpf_revoke(grant_nr);
|
|
if (r != OK) {
|
|
return EIO;
|
|
}
|
|
|
|
return m.REP_STATUS;
|
|
}
|
|
|
|
static int
|
|
__i2creg_read(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t raw,
|
|
uint8_t reg, uint32_t * val, size_t vallen)
|
|
{
|
|
int r, i;
|
|
minix_i2c_ioctl_exec_t ioctl_exec;
|
|
|
|
assert(val != NULL);
|
|
assert(vallen >= 1 && vallen <= 4);
|
|
|
|
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
|
|
|
|
/* Read from chip */
|
|
ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
|
|
ioctl_exec.iie_addr = address;
|
|
|
|
if (!raw) {
|
|
/* write the register address */
|
|
ioctl_exec.iie_cmd[0] = reg;
|
|
ioctl_exec.iie_cmdlen = 1;
|
|
}
|
|
|
|
/* read vallen bytes */
|
|
ioctl_exec.iie_buflen = vallen;
|
|
|
|
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
for (*val = 0, i = 0; i < vallen; i++) {
|
|
*val = ((*val) << 8) | ioctl_exec.iie_buf[i];
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int
|
|
i2creg_raw_read8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t * val)
|
|
{
|
|
int r;
|
|
uint32_t val32;
|
|
|
|
r = __i2creg_read(bus_endpoint, address, 1, 0, &val32, 1);
|
|
*val = val32 & 0xff;
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
i2creg_read8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint8_t * val)
|
|
{
|
|
int r;
|
|
uint32_t val32;
|
|
|
|
r = __i2creg_read(bus_endpoint, address, 0, reg, &val32, 1);
|
|
*val = val32 & 0xff;
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
i2creg_read16(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint16_t * val)
|
|
{
|
|
int r;
|
|
uint32_t val32;
|
|
|
|
r = __i2creg_read(bus_endpoint, address, 0, reg, &val32, 2);
|
|
*val = val32 & 0xffff;
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
i2creg_read24(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint32_t * val)
|
|
{
|
|
return __i2creg_read(bus_endpoint, address, 0, reg, val, 3);
|
|
}
|
|
|
|
static int
|
|
__i2creg_write(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t raw,
|
|
uint8_t reg, uint8_t val)
|
|
{
|
|
int r;
|
|
minix_i2c_ioctl_exec_t ioctl_exec;
|
|
|
|
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
|
|
|
|
/* Write to chip */
|
|
ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
|
|
ioctl_exec.iie_addr = address;
|
|
|
|
if (raw) {
|
|
/* write just the value */
|
|
ioctl_exec.iie_buf[0] = val;
|
|
ioctl_exec.iie_buflen = 1;
|
|
} else {
|
|
/* write the register address and value */
|
|
ioctl_exec.iie_buf[0] = reg;
|
|
ioctl_exec.iie_buf[1] = val;
|
|
ioctl_exec.iie_buflen = 2;
|
|
}
|
|
|
|
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int
|
|
i2creg_write8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint8_t val)
|
|
{
|
|
return __i2creg_write(bus_endpoint, address, 0, reg, val);
|
|
}
|
|
|
|
int
|
|
i2creg_raw_write8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t val)
|
|
{
|
|
return __i2creg_write(bus_endpoint, address, 1, 0, val);
|
|
}
|
|
|
|
int
|
|
i2creg_set_bits8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint8_t bits)
|
|
{
|
|
int r;
|
|
uint8_t val;
|
|
|
|
r = i2creg_read8(bus_endpoint, address, reg, &val);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
val |= bits;
|
|
|
|
r = i2creg_write8(bus_endpoint, address, reg, val);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int
|
|
i2creg_clear_bits8(endpoint_t bus_endpoint, i2c_addr_t address, uint8_t reg,
|
|
uint8_t bits)
|
|
{
|
|
int r;
|
|
uint8_t val;
|
|
|
|
r = i2creg_read8(bus_endpoint, address, reg, &val);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
val &= ~bits;
|
|
|
|
r = i2creg_write8(bus_endpoint, address, reg, val);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
|
|
return OK;
|
|
}
|