I2C driver implementation
This section describes how to implement an I2C driver for QNX using the libi2c-master library. The examples below are simple examples and shouldn't be copied literally.
The libic2-master library provides the main() function, the primary entry point of the driver, which:
Sets up a resource manager for communication with client applications.
Creates an
i2c_master_funcs_tstructure instance that contains pointers to default stub implementations of the functions that interact with the hardware.Calls the i2c_master_getfuncs() function implemented by the device developer, which in turn replaces the stub implementations with the device-specific ones.
Uses functions specified by the
i2c_master_funcs_tinstance to interact with the device while servicing client application requests.
The interface to implement an I2C driver is documented in the "Function table" section of the I2C chapter of the Customizing a BSP guide.
Define a structure
Before making the functions, define a structure that holds all necessary state information. The structure may look like the following:
typedef struct _your_dev {
// Buffer management
uint8_t *buf;
unsigned buflen;
// Hardware registers
unsigned reglen;
uintptr_t regbase;
unsigned physbase;
// Interrupt handling
int intr;
int iid;
// Transfer state
unsigned txrx;
uint32_t status;
unsigned cur_len;
unsigned tot_len;
// I2C configuration
unsigned slave_addr;
i2c_addrfmt_t slave_addr_fmt;
unsigned speed;
} your_dev_t;Driver initialization
Now, you can make your initialization function to fill the structure with relevant data. This initialization function will then return a handler to the struct and be passed to all other relevant functions. Your initialization function may look like the following:
void *your_init(int argc, char *argv[]) {
your_dev_t *dev;
// 1. Allocate device structure
dev = calloc(1, sizeof(*dev));
if (!dev) return NULL;
// 2. Custom function to parse command line options to fill the device structure with data
if (parse_options(dev, argc, argv) != EOK) {
goto fail;
}
// 3. Thread control setup
if (ThreadCtl(_NTO_TCTL_IO, NULL) == -1) {
goto fail;
}
// 4. Map hardware registers
dev->regbase = (uintptr_t)mmap(NULL, dev->reglen, PROT_NOCACHE|PROT_READ|PROT_WRITE, MAP_SHARED|MAP_PHYS, NOFD, dev->physbase);
if (dev->regbase == (uintptr_t)MAP_FAILED) {
goto fail;
}
// 5. Setup interrupt handling
SIGEV_INTR_INIT(&dev->intrevent);
dev->iid = InterruptAttachEvent(dev->intr, &dev->intrevent, 0);
return dev;
fail:
cleanup(dev);
return NULL;
}Set the slave address
Implement a function that sets the slave address of your I2C device, which may look like the following:
// Set slave address
int your_set_slave_addr(void *hdl, unsigned int addr, i2c_addrfmt_t fmt) {
your_dev_t *dev = hdl;
if ((fmt != I2C_ADDRFMT_7BIT) ) {
errno = EINVAL;
return -1;
}
dev->slave_addr = addr;
dev->slave_addr_fmt = fmt;
return 0;
}Send data
Implement a function that sends the data to your I2C device:
i2c_status_t your_send(void *hdl, void *buf, unsigned int len, unsigned int stop) {
your_dev_t *dev = hdl;
/* Initialize transfer state */
dev->tot_len = len;
dev->cur_len = 0;
dev->buf = buf;
dev->txrx = I2C_TX_MODE;
dev->status = 0;
/* Configure slave address */
out32(dev->regbase + YOUR_I2C_SLAVE_ADDR_REG, dev->slave_addr);
/* Set transfer length */
out32(dev->regbase + YOUR_I2C_DATA_LEN_REG, dev->tot_len);
/* Fill buffer initial data */
while (dev->cur_len < dev->tot_len) {
dev->status = in32(dev->regbase + YOUR_I2C_STATREG_OFF) & YOUR_STATREG_MASK;
if (dev->status & YOUR_STATREG_TXD) {
out32(dev->regbase + YOUR_I2C_FIFOREG_OFF, dev->buf[dev->cur_len++]);
} else {
break;
}
}
/* Start transfer */
out32(dev->regbase + YOUR_I2C_CTRL_REG, YOUR_CTRL_I2C_EN | YOUR_CTRL_START);
/* Enable interrupts */
out32(dev->regbase + YOUR_I2C_CTRL_REG,
in32(dev->regbase + YOUR_I2C_CTRL_REG) | YOUR_CTRL_INT_DONE | YOUR_CTRL_INT_TX);
/* Wait for completion */
return wait_for_completion(dev, len);
}You can also implement a wait_for_completion() function to wait for the data transfer to be completed before moving on.
Receive data
Implement a function that receives data from your I2C device:
i2c_status_t your_send(void *hdl, void *buf, unsigned int len, unsigned int stop) {
your_dev_t *dev = hdl;
/* Initialize receive state */
dev->tot_len = len;
dev->cur_len = 0;
dev->buf = buf;
dev->txrx = I2C_RX_MODE;
dev->status = 0;
/* Configure slave address */
out32(dev->regbase + I2C_SLAVE_ADDR_REG, dev->slave_addr);
/* Set receive length */
out32(dev->regbase + I2C_DATA_LEN_REG, dev->tot_len);
/* Start read operation - Note READ bit is set */
out32(dev->regbase + I2C_CTRL_REG,
I2C_ENABLE | I2C_START | I2C_READ | I2C_CLEAR_FIFO);
/* Enable receive interrupts */
out32(dev->regbase + I2C_CTRL_REG,
in32(dev->regbase + I2C_CTRL_REG) | I2C_INT_DONE | I2C_INT_RX);
/* Wait for completion */
return wait_for_completion(dev, len);
}
/*
* NOTE: The following function must be implemented according to your specific hardware:
* - wait_for_completion(): Set the SPI clock rate
*/You can also implement a wait_for_completion() function to wait for the data to be received before moving on.
Clean up the driver
Finally, implement a function to clean up the driver. It may look something like the following:
void your_fini(void *hdl){
your_dev_t *dev = hdl;
if (dev != NULL) {
if (dev->iid != -1) {
InterruptDetach(dev->iid);
}
munmap((void *)dev->regbase, (size_t)dev->reglen);
}
}Integrate the implementations
Once you've implemented these functions, you must integrate these implementations. Here is an example on how you can integrate them:
int i2c_master_getfuncs(i2c_master_funcs_t *funcs, int tabsize){
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
init, your_init, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
fini, your_fini, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
send, your_send, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
recv, your_recv, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
set_slave_addr, your_set_slave_addr, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
set_bus_speed, your_set_bus_speed, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
version_info, your_version_info, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs,
driver_info, your_driver_info, tabsize);
return 0;
}