SPI driver implementation
This section describes how to implement an SPI driver for QNX. The example functions below are simple examples and shouldn't be copied literally.
For more information on the SPI framework, including its essential functions, refer to the SPI Framework Technote in the SDP 8.0 documentation.
Define a structure
Before making the functions, you need to define a structure that holdsĀ all necessary state information. The structure may look like this:
typedef struct {
uint64_t pbase; // Physical base address
uintptr_t vbase; // Virtual base address
uint32_t irq; // Interrupt number
int iid;
uint64_t input_clk; // Input clock frequency
uint8_t *pbuf; // Transfer buffer
uint32_t tnbytes; // Transmit bytes
uint32_t rnbytes; // Receive bytes
uint32_t xlen; // Full transaction length requested by client
uint32_t tcnt; // Transmit counter
uint32_t rcnt; // Receive counter
uint32_t dwidth; // Data width. 1 = 8bits/byte, 2 = 16bits/2 bytes, 4 = 32bits/4 bytes
spi_ctrl_t *spi_ctrl; // The address of spi_ctrl structure
spi_bus_t *bus_node; // The address of bus structure which is passed in by spi_init()
} your_spi_t;Clean up the driver
The following code sample shows how to implement the driver cleanup function:
void your_fini(void *const hdl) {
your_spi_t *const spi = hdl;
if (spi == NULL) {
return;
}
// 1. Disable hardware
if (spi->vbase != (uintptr_t)MAP_FAILED) {
// Disable SPI controller
out32(spi->vbase + YOUR_SPI_CONTROL_REG, 0);
// Unmap registers
munmap_device_memory((void *)spi->vbase, YOUR_SPI_REGLEN);
}
// 2. Free allocated memory
free(spi);
}Set up a config driver
You need a setup config driver function to set the configuration of when the device driver starts, or when the application sets the configuration, which may look something like this:
int your_setcfg(const void *const hdl, spi_dev_t *spi_dev,const spi_cfg_t *const cfg)
{
const your_spi_t *const spi = hdl;
uintptr_t const base = spi->vbase;
uint32_t devctrl = 0;
/* Check hardware limitations for word width */
switch ((cfg->mode & SPI_MODE_WORD_WIDTH_MASK)) {
case SPI_MODE_WORD_WIDTH_32:
case SPI_MODE_WORD_WIDTH_8:
break;
default:
spi_slogf(_SLOG_ERROR, "%s: %d-bit word width is not supported by this controller",
__func__, cfg->mode & SPI_MODE_WORD_WIDTH_MASK);
return EINVAL;
}
/* Gather default CS and clock polarity related configuration */
if (cfg->mode & SPI_MODE_CSPOL_HIGH) {
devctrl |= (SPI_CS_CSPOL_HIGH << SPI_CS_CSPOL); /* CS Polarity: CS active high */
}
if (cfg->mode & SPI_MODE_CPOL_1) {
devctrl |= (SPI_CS_CPOL_HIGH << SPI_CS_CPOL); /* Clock Polarity: Rest state of clock high */
}
if (cfg->mode & SPI_MODE_CPHA_1) {
devctrl |= (SPI_CS_CPHA_BEGIN << SPI_CS_CPHA); /* Clock Phase: SCLK beginning of data bit */
}
/* Set SPI_CLK only when it is different than current clock rate */
if (spi_dev->devinfo.current_clkrate != cfg->clock_rate) {
uint32_t actual_rate = spi_set_clock(spi, cfg, base);
if (actual_rate == 0) {
spi_slogf(_SLOG_ERROR, "%s: Clock configuration failed", __func__);
return EINVAL;
}
/* Update current clock rate with actual achieved rate */
cfg->clock_rate = actual_rate;
spi_dev->devinfo.current_clkrate = actual_rate;
} else {
spi_slogf(_SLOG_DEBUG2, "%s: Device clock rate is already set to %d", __func__, cfg->clock_rate);
}
/* Add Chip Select/Physical device ID */
devctrl |= spi_dev->devinfo.devno;
/* Update the SPI device devctrl reg value */
spi_dev->devctrl = devctrl;
spi_slogf(_SLOG_DEBUG1, "%s: sclk=%d, devctrl=0x%x", __func__,
cfg->clock_rate, devctrl);
return EOK;
}
/*
* NOTE: The following function must be implemented according to your specific hardware:
* - spi_set_clock(): Set the SPI clock rate
*/Exchange data
You need a function to exchange data between the master and the slave. The function may look something like this:
int your_xfer(void *const hdl, spi_dev_t *const spi_dev, uint8_t *const buf, const uint32_t tnbytes, const uint32_t rnbytes)
{
your_spi_t *const spi = hdl;
const uintptr_t base = spi->vbase;
const spi_cfg_t *const cfg = &spi_dev->devinfo.cfg;
int status = EOK;
/* Set transfer parameters based on operation type */
if (tnbytes == 0) {
/* Read only */
spi->tnbytes = rnbytes;
spi->rnbytes = rnbytes;
} else if (rnbytes == 0) {
/* Write only */
spi->tnbytes = tnbytes;
spi->rnbytes = tnbytes;
} else {
/* Bidirectional transaction */
spi->tnbytes = tnbytes;
spi->rnbytes = rnbytes;
}
/* Capture max transaction length requested by client */
spi->xlen = max(spi->tnbytes, spi->rnbytes);
/* Setup transfer parameters */
spi->dwidth = ((cfg->mode & SPI_MODE_WORD_WIDTH_MASK) >> 3);
spi->rcnt = 0;
spi->tcnt = 0;
spi->pbuf = buf;
/* Reset the SPI interface
* Clear both FIFOs
*/
out32((base + YOUR_SPI_CS), YOUR_SPI_RESET);
out32((base + YOUR_SPI_CS), YOU_SPI_FIFO_CLEAR);
/* Enable SPI transfer */
spi_enable(spi, spi_dev->devctrl);
/* Fill TX FIFO with data */
while ((spi->tcnt < spi->tnbytes) && (spi->tcnt < YOUR_SPI_FIFOLEN)) {
spi_write_fifo(spi, *(spi->pbuf + spi->tcnt));
spi->tcnt += spi->dwidth;
}
/* Wait for transfer completion */
status = spi_wait_complete(spi);
/* Disable SPI */
spi_disable(spi);
return status;
}
/*
* NOTE: The following functions must be implemented according to your specific hardware:
* - spi_enable(): Enable SPI
* - spi_write_fifo(): Write a byte to the TX FIFO
* - spi_wait_complete(): Wait for transfer completion with appropriate timeout
* - spi_disable(): Disable SPI controller and interrupts
*/Initialize the driver
Finally, after making the essential functions to make the driver, you can integrate these functions in the spi_init() function to make the driver. This spi_init() function is called when the driver is started by the user. The function may look something like this:
static int init_device(your_spi_t *spi)
{
int status;
uintptr_t base;
spi_bus_t *const bus = spi->bus_node;
spi_dev_t *spi_dev = bus->devlist;
/*
* Map in SPI registers
*/
base = (uintptr_t)mmap(NULL, YOUR_SPI_REGLEN, PROT_READ | PROT_WRITE | PROT_NOCACHE, MAP_PHYS | MAP_SHARED, NOFD, (off_t)spi->pbase);
if (base == (uintptr_t)MAP_FAILED) {
spi_slogf(_SLOG_ERROR, "%s: Failed to map SPI registers(%s)", __func__, strerror(errno));
return errno;
}
spi->vbase = base;
/* Reset the SPI interface
* Clear both FIFOs
*/
out32((base + YOUR_SPI_CS), YOUR_SPI_RESET);
out32((base + YOUR_SPI_CS), YOU_SPI_FIFO_CLEAR);
/*
* Initial device configuration with defaults from config file
*/
while (spi_dev != NULL) {
status = your_setcfg(spi, spi_dev, &spi_dev->devinfo.cfg);
if (status != EOK) {
spi_slogf(_SLOG_ERROR, "%s: your_setcfg failed", __func__);
return status;
}
spi_dev = spi_dev->next;
}
/* Attach SPI interrupt */
spi->iid = InterruptAttachEvent(spi->irq, &bus->evt, _NTO_INTR_FLAGS_TRK_MSK);
return status;
}
int spi_init(spi_bus_t *bus)
{
your_spi_t *spi = NULL;
int status = EOK;
if (bus == NULL) {
spi_slogf(_SLOG_ERROR, "%s: SPI bus structure is NULL!", __func__);
return EINVAL;
}
spi = calloc(1, sizeof(your_spi_t));
/* Save spi_ctrl to driver structure */
spi->spi_ctrl = bus->spi_ctrl;
spi->bus_node = bus;
/* Get other SPI driver functions */
bus->funcs->spi_fini = your_fini;
bus->funcs->drvinfo = your_drvinfo;
bus->funcs->devinfo = your_devinfo;
bus->funcs->setcfg = your_setcfg;
bus->funcs->xfer = your_xfer;
bus->funcs->dma_xfer = NULL;
bus->funcs->dma_allocbuf = NULL;
bus->funcs->dma_freebuf = NULL;
/* Get controller parameters */
spi->pbase = bus->pbase;
spi->irq = bus->irq;
spi->input_clk = bus->input_clk;
/* Update SPI transaction buffer boundary */
bus->buf_max = SPI_BUF_MAX_64K;
spi->iid = -1;
spi->vbase = (uintptr_t)MAP_FAILED;
/* Optional: Process custom arguments to your driver to override the defaults */
if (bus->bs != NULL) {
status = process_args(spi, bus->bs);
if (status != EOK) {
spi_slogf(_SLOG_ERROR, "%s: process_args failed", __func__);
your_fini(spi);
return status;
}
}
/* Init SPI device */
status = init_device(spi);
if (status != EOK) {
spi_slogf(_SLOG_ERROR, "%s: init_device failed: %s", __func__, strerror(status));
your_fini(spi);
return status;
}
/*
* Create SPI chip select devices
*/
status = spi_create_devs(bus->devlist);
if (status != EOK) {
spi_slogf(_SLOG_ERROR, "%s: spi_create_devs for %s failed", __func__, bus->devlist->devinfo.name);
your_fini(spi);
return status;
}
/* Save driver structure to drvhdl */
bus->drvhdl = spi;
return status;
}
/*
* NOTE: Implement the following function if want your driver to have optional parameters
* - process_args(): Process custom arguments to your driver to override the defaults
*/