| name | spi-driver-dev |
| description | Develop and integrate SPI peripheral drivers on PolarFire SoC using Linux spidev interface. Covers device initialization, frame protocol design, data transfer patterns, and HAL integration. Use when creating SPI drivers, working with AD5664R DAC, extending peripheral support, or implementing new SPI devices. |
SPI Driver Development
Structured approach for creating SPI drivers on PolarFire SoC Discovery Kit using Linux userspace spidev interface.
Quick Start
1. Plan the SPI Protocol
Gather from hardware datasheet:
- Slave address (if applicable; not needed for SPI)
- Speed (Hz): Typical 1–20 MHz for most peripherals
- Mode: SPI_MODE_0/1/2/3 (clock polarity + phase)
- Frame size: 8, 16, 24, or 32 bits
- Command structure: Register map, control bytes, data layout
- Device node:
/dev/spidev<bus>.<chip_select> (e.g., /dev/spidev1.0)
Example: AD5664R DAC
- Bus 1, CS 0 →
/dev/spidev1.0
- 5 MHz, SPI_MODE_1, 24-bit frames
- Frame:
[2 reserved][3 cmd][3 addr][16 data]
1A. SPI Master to Fixed-Length Slave (NUCLEO SPI6 pattern)
Use this pattern when your PolarFire Linux app is the SPI master and an external MCU is the SPI slave with strict frame windows.
- Master drives SCK and CS/NSS.
- Slave expects exactly N bytes while CS stays low (for NUCLEO SPI6: 64 bytes).
- Do not split one frame into multiple CS assertions.
- SPI is still full-duplex, so send dummy MOSI bytes even when only MISO is meaningful.
- Validate framing with a logic analyzer when bringing up a new bus/CS mapping.
Project reference for this workflow:
docs/NUCLEO_SPI6_BRIDGE.md
c:\Projects\PAT_neucleo_firmware_for_86euv89y8\docs\SPI6_HOST_MANUAL.md
2. Create HAL Header (device.h)
#ifndef DEVICE_NAME_H
#define DEVICE_NAME_H
#include <stdint.h>
#define DEVICE_SPI_PATH "/dev/spidev1.0"
#define DEVICE_SPI_SPEED_HZ 5000000
#define DEVICE_SPI_MODE SPI_MODE_1
#define CMD_WRITE_UPDATE 2
#define CMD_READ_STATUS 3
typedef struct {
uint8_t channel;
uint16_t code;
} device_write_t;
int device_init(const char *spidev_path);
void device_deinit(void);
int device_write(const device_write_t *cmd);
int device_read(uint8_t reg, uint8_t *value);
#endif
3. Implement SPI Transfers (device.c)
Include required headers:
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
Core SPI transfer function:
static int spi_fd = -1;
static int spi_transfer(const uint8_t *tx, uint8_t *rx, size_t len) {
struct spi_ioc_transfer xfer = {
.tx_buf = (uintptr_t)tx,
.rx_buf = (uintptr_t)rx,
.len = len,
.speed_hz = DEVICE_SPI_SPEED_HZ,
.bits_per_word = 8,
.delay_usecs = 0,
};
return ioctl(spi_fd, SPI_IOC_MESSAGE(1), &xfer) >= 0 ? 0 : -1;
}
Frame assembly (24-bit example):
static int spi_write_word24(uint32_t word24) {
uint8_t tx[3] = {
(uint8_t)(word24 >> 16),
(uint8_t)(word24 >> 8),
(uint8_t)(word24)
};
return spi_transfer(tx, NULL, 3);
}
4. Device Initialization
int device_init(const char *spidev_path) {
if (spi_fd >= 0) {
printf("[SPI] Already open\n");
return 0;
}
const char *path = spidev_path ? spidev_path : DEVICE_SPI_PATH;
spi_fd = open(path, O_RDWR);
if (spi_fd < 0) {
printf("[ERROR] SPI open failed: %s\n", strerror(errno));
return -1;
}
uint8_t mode = DEVICE_SPI_MODE;
if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0) {
printf("[ERROR] SPI mode set failed: %s\n", strerror(errno));
close(spi_fd);
spi_fd = -1;
return -1;
}
uint32_t speed = DEVICE_SPI_SPEED_HZ;
if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
printf("[ERROR] SPI speed set failed: %s\n", strerror(errno));
close(spi_fd);
spi_fd = -1;
return -1;
}
uint8_t bits = 8;
if (ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
printf("[ERROR] SPI bits set failed: %s\n", strerror(errno));
close(spi_fd);
spi_fd = ;
;
}
(, path, speed, mode);
;
}
{
(spi_fd >= ) {
close(spi_fd);
spi_fd = ;
}
}
5. Device-Specific Commands
int device_write(const device_write_t *cmd) {
if (spi_fd < 0) {
printf("[ERROR] SPI not initialized\n");
return -1;
}
uint32_t frame = 0;
frame |= (CMD_WRITE_UPDATE & 0x7) << 21;
frame |= (cmd->channel & 0x7) << 18;
frame |= (cmd->code & 0xFFFF);
return spi_write_word24(frame);
}
int device_read(uint8_t reg, uint8_t *value) {
if (spi_fd < 0) return -1;
uint8_t tx[2] = {reg, 0x00};
uint8_t rx[2] = {0};
if (spi_transfer(tx, rx, 2) < 0) return -1;
*value = rx[1];
return 0;
}
Hardware Setup
Enable SPI Kernel Module
Check SPI is available:
ls -la /dev/spidev*
If missing, enable in device tree or kernel config:
find /sys/devices -name "*spi*" -type d
Verify GPIO Chip Selects
CS lines are usually GPIO-controlled or native SPI. Verify on your board:
ls /sys/class/spi_master/
ls -la /dev/spidev*
Testing
Unit Test Template
#include "device.h"
#include <assert.h>
int main() {
assert(device_init(NULL) == 0);
printf("✓ Init OK\n");
device_write_t cmd = {.channel = 0, .code = 32768};
assert(device_write(&cmd) == 0);
printf("✓ Write OK\n");
uint8_t status;
assert(device_read(0x01, &status) == 0);
printf("✓ Read OK, status=0x%02x\n", status);
device_deinit();
printf("✓ All tests passed\n");
return 0;
}
Integration into PAT
Place Files
firmware/
├── drivers/
│ └── device_name/
│ ├── device.h
│ ├── device.c
│ └── README.md
└── include/
└── device_name.h (or link from drivers/)
Link in Build System (CMake)
add_library(device_driver
${PROJECT_SOURCE_DIR}/drivers/device_name/device.c
)
target_include_directories(device_driver PUBLIC
${PROJECT_SOURCE_DIR}/drivers/device_name
${PROJECT_SOURCE_DIR}/firmware/include
)
Usage in Application
#include "device.h"
int main() {
if (device_init(NULL) < 0) {
return 1;
}
for (int i = 0; i < 1000; i++) {
device_write_t cmd = {.channel = 0, .code = i * 65};
device_write(&cmd);
usleep(10000);
}
device_deinit();
return 0;
}
Common Pitfalls
| Issue | Cause | Fix |
|---|
| "Device not found" | /dev/spidev* missing | Load kernel module or enable in device tree |
| "Permission denied" | Running as non-root | Use sudo or add user to spi group |
| "SPI ioctl failed" | Invalid speed/mode | Check datasheet; verify values are supported |
| "Corrupted data" | Wrong bit order or frame format | Verify MSB-first, check frame assembly logic |
| "Timeout/no response" | CS pin not asserted | Verify CS control (GPIO or native SPI) |
Next Steps
- Add error handling and retries
- Implement read-modify-write patterns
- Add interrupt handling (if device supports interrupts)
- Create integration tests with real hardware