This note includes:
I2C (Inter-Integrated Circuit) is a simple serial protocol that connects multiple devices in a master-slave relationship. Multiple master devices may share a single bus. The same device may function as both a master and a slave in different transactions. The I2C specification defines these transfer speed ranges:
The I2C framework is intended to facilitate consistent implementation of I2C interfaces. The framework consists of the following parts:
The most common application of the I2C bus is low-bandwidth access to a slave device's registers, such as:
Typically, only a few bytes are exchanged on the bus.
You can implement the I2C master as a single-threaded resource manager, or as a dedicated application. The primary advantages of a resource manager interface are:
For a dedicated I2C-bus application, a hardware access library is more efficient. The hardware interface, which defines the interface to this library, is useful as a starting point for developers and facilitates maintenance and code portability.
This is the interface to the code that implements the hardware-specific functionality of an I2C master. It's defined in <hw/i2c.h>.
The i2c_master_funcs_t structure is a table of pointers to functions that you can provide for your hardware. The higher-level code calls these functions.
typedef struct { size_t size; /* size of this structure */ int (*version_info)(i2c_libversion_t *version); void *(*init)(int argc, char *argv[]); void (*fini)(void *hdl); i2c_status_t (*send)(void *hdl, void *buf, unsigned int len, unsigned int stop); i2c_status_t (*recv)(void *hdl, void *buf, unsigned int len, unsigned int stop); int (*abort)(void *hdl, int rcvid); int (*set_slave_addr)(void *hdl, unsigned int addr, i2c_addrfmt_t fmt); int (*set_bus_speed)(void *hdl, unsigned int speed, unsigned int *ospeed); int (*driver_info)(void *hdl, i2c_driver_info_t *info); int (*ctl)(void *hdl, int cmd, void *msg, int msglen, int *nbytes, int *info); } i2c_master_funcs_t;
|
The functions are described in the sections that follow:
The higher-level code calls the version_info function to get information about the version of the library. The prototype for this function is:
int version_info( i2c_libversion_t *version );
The version argument is a pointer to a i2c_libversion_t structure that this function must fill in. This structure is defined as follows:
typedef struct { unsigned char major; unsigned char minor; unsigned char revision; } i2c_libversion_t;
This function should set the members of this structure as follows:
version->major = I2CLIB_VERSION_MAJOR; version->minor = I2CLIB_VERSION_MINOR; version->revision = I2CLIB_REVISION;
The function must return:
The init function initializes the master interface. The prototype for this function is:
void *init( int argc, char *argv[]);
The arguments are those passed on the command line.
The function returns a handle that's passed to all other functions, or NULL if an error occurred.
The fini function cleans up the driver and frees any memory associated with the given handle. The prototype for this function is:
void fini( void *hdl);
The argument is the handle that the init function returned.
The send function initiates a master send. An optional event is sent when the transaction is complete (and the data buffer can be released). If this function fails, no transaction has been initiated.
The prototype for this function is:
i2c_status_t send( void *hdl, void *buf, unsigned int len, unsigned int stop );
The arguments are:
The function returns one of the following:
The recv function initiates a master receive. An optional event is sent when the transaction is complete (and the data buffer can be used). If this function fails, no transaction has been initiated.
The prototype for this function is:
i2c_status_t recv( void *hdl, void *buf, unsigned int len, unsigned int stop );
The arguments are:
The function returns one of the following:
The abort function forces the master to free the bus. It returns when the stop condition has been sent. The prototype for this function is:
int abort( void *hdl, int rcvid );
The arguments are:
The function must return:
The set_slave_addr function specifies the target slave address. The prototype for this function is:
int set_slave_addr( void *hdl, unsigned int addr, i2c_addrfmt_t fmt );
The arguments are:
The function must return:
The set_bus_speed function specifies the bus speed. If an invalid bus speed is requested, this function should return a failure and leave the bus speed unchanged. The prototype for this function is:
int set_bus_speed( void *hdl, unsigned int speed, unsigned int *ospeed );
The arguments are:
The function must return:
The driver_info function returns information about the driver. The prototype for this function is:
int driver_info( void *hdl, i2c_driver_info_t *info );
The arguments are:
typedef struct { _Uint32t speed_mode; _Uint32t addr_mode; _Uing32t reserved[2]; } i2c_driver_info_t;
For the speed_mode member, OR together the appropriate values from the following list to indicate the supported speeds:
Set the addr_mode to one of the following to indicate the supported address format:
The function must return:
The ctl function handles a driver-specific devctl() command. The prototype for this function is:
int ctl( void *hdl, int cmd, void *msg, int msglen, int *nbytes, int *info );
The arguments are:
The function must return:
This function is used by higher-level code (such as the resource manager interface) to access the hardware-specific functions. It must be implemented.
int i2c_master_getfuncs(i2c_master_funcs_t *funcs, int tabsize);
This function must fill in the given table with the hardware-specific functions.
The arguments are:
Don't change the size member of the structure. |
To set an entry in the table, use the I2C_ADD_FUNC() macro:
#define I2C_ADD_FUNC(tabletype, table, entry, func, tabsize) …
For example:
I2C_ADD_FUNC( i2c_master_funcs_t, funcs, init, my_init, tabsize);
The function must return:
A typical sequence of hardware library calls is as follows:
#include <hw/i2c.h> i2c_master_funcs_t masterf; i2c_libversion_t version; i2c_status_t status; void *hdl; i2c_master_getfuncs(&masterf, sizeof(masterf)); masterf.version_info(&version); if ((version.major != I2CLIB_VERSION_MAJOR) || (version.minor > I2CLIB_VERSION_MINOR)) { /* error */ ... } hdl = masterf.init(...); masterf.set_bus_speed(hdl, ...); masterf.set_slave_addr(hdl, ...); status = masterf.send(hdl, ...); if (status != I2C_STATUS_DONE) { /* error */ if (!(status & I2C_STATUS_DONE)) masterf.abort(hdl); } status = masterf.recv(hdl, ...); if (status != I2C_STATUS_DONE) { /* error */ ... } masterf.fini(hdl);
When an I2C master device is dedicated for use by a single application, you can compile the hardware interface code as a library and link it to the application.
To increase code portability, the application should call i2c_master_getfuncs() to access the hardware-specific functions. Accessing the hardware library through the function table also makes it easier for an application to load and manage more than one hardware library.
The resource manager interface uses the hardware library in a similar way.
The resource manager interface is designed to mediate accesses to a single master. In a system with multiple masters, a separate instance of the resource manager should be run for each master.
The resource manager layer registers a device name (usually /dev/i2c0). Applications access the I2C master by issuing devctl() commands to the device name.
The supported devctl() commands and their formats are defined in <hw/i2c.h>. The commands include:
The following commands are deprecated:
Many of the devctl() commands use the i2c_addr_t structure, which is defined as:
typedef struct { _Uint32t addr; /* I2C address */ _Uint32t fmt; /* I2C_ADDRFMT_7BIT or I2C_ADDRFMT_10BIT */ } i2c_addr_t;
The DCMD_I2C_DRIVER_INFO command returns information about the hardware library.
If an error occurs, the command returns:
The DCMD_I2C_SEND command executes a master send transaction. It returns when the transaction is complete.
typedef struct { i2c_addr_t slave; /* slave address */ _Uint32t len; /* length of send data in bytes */ _Uint32t stop; /* send stop when complete? (0=no, 1=yes) */ } i2c_send_t;
If an error occurs, the command returns:
The DCMD_I2C_RECV command executes a master receive transaction. It returns when the transaction is complete.
The i2c_recv_t structure is defined as:
typedef struct { i2c_addr_t slave; /* slave address */ _Uint32t len; /* length of receive data in bytes */ _Uint32t stop; /* send stop when complete? (0=no, 1=yes) */ } i2c_recv_t;
If an error occurs, the command returns:
The DCMD_I2C_SENDRECV command executes a send followed by a receive. This sequence is typically used to read a slave device's register value. When multiple applications access the same slave device, it is necessary to execute this sequence atomically to prevent register reads from being interrupted. Although this functionality is also provided by DCMD_I2C_LOCK and DCMD_I2C_UNLOCK, the implementation of this functionality is much simpler.
The i2c_sendrecv_t structure is defined as:
typedef struct { i2c_addr_t slave; /* slave address */ _Uint32t send_len; /* length of send data in bytes */ _Uint32t recv_len; /* length of receive data in bytes */ _Uint32t stop; /* set stop when complete? */ } i2c_sendrecv_t;
If an error occurs, the command returns:
The DCMD_I2C_SET_BUS_SPEED command sets the bus speed for the current connection. You should set the bus speed before attempting a data-transfer operation.
If an error occurs, the command returns:
The DCMD_I2C_SET_SLAVE_ADDR command sets the slave device address for the current connection. You should set the slave device address before attempting a master send or receive transaction.
This command doesn't return any error codes.
The DCMD_I2C_MASTER_SEND command execute a master send transaction, using the slave device address and bus speed specified for the current connection.
typedef struct { _Uint32t len; /* length of data to send/recv, in bytes (not including this header) */ _Uint32t stop; /* send stop when complete? (0=no, 1=yes) */ } i2c_masterhdr_t;
If an error occurs, the command returns:
The DCMD_I2C_MASTER_RECV command executes a master receive transaction, using the slave device address and bus speed specified for the current connection.
The message header is overwritten. |
If an error occurs, the command returns:
The resource manager layer is implemented as a library that's statically linked with the hardware library. We currently provide a single-threaded manager, libi2c-master.
On startup, the resmgr layer does the following:
i2c_master_getfuncs(&masterf) masterf.init() masterf.set_bus_speed()
The resource manager then makes itself run in the background.
Here's how the resource manager handles these devctl() commands:
if (bus_speed has changed) masterf.set_bus_speed() masterf.set_slave_address() masterf.send() or masterf.recv()
The resource manager thread remains occupied until the transaction is complete and replies to the client.
You can terminate the resource manager by sending a SIGTERM to it.