Using SPI with Linux

Introduction

SPI (Synchronous Peripheral Interface) is a synchronous serial interface with which to connect peripheral chips like ADCs, EEPROMS, Sensors or other Micro-Controllers.
SPI works in master and slave mode, while the master provides the clock signal and each slave has a dedicated chipselect. On our AT91SAM9 based devices a Linux driver is provided. As most peripheral chips are slaves this driver only works in master mode. To connect a spi chip four signals are needed: CLK, MISO (master in, slave out), MOSI (master out, slave in) and a chipselect.
This how-to describes how to configure and use the SPI user-mode device driver (spi-dev).

Preparing the Kernel

On some of our products, the driver is already activated, which are listed below:

  • PortuxG20: Chipselects, which are exported on the PXB are already configured in the kernel. It is necessary to activate the driver.
  • Ledato NanosG20: Chipselects, which are exported on the I/O interface are configured. The driver is also already activated. Unless you have the need to change the standard settings, you can skip this chapter

Configuring the Kernel

Activate the driver in menuconfig (make menuconfig):

Device Drivers ---> SPI
Device Drivers ---> SPI ---> Atmel SPI Controller
Device Drivers ---> SPI ---> User mode SPI device driver support

Add SPI board support

Add the following structure to your arch/arm/mach-at91/board-xxx.c:

static struct spi_board_info stamp9g20_spi_devices[] = {
    {
        .modalias    = "spidev",
        .chip_select    = 0,
        .max_speed_hz    = 1 * 1000 * 1000,
        .bus_num    = 1,
	.mode = SPI_MODE_3,
    }, 
    {
        .modalias    = "spidev",
        .chip_select    = 2,
        .max_speed_hz    = 1 * 1000 * 1000,
        .bus_num    = 1,
	.mode = SPI_MODE_3,
    },
}; 

Of course this structure can be adapted according to your needs. The details of the members are explained below:

  • .modalias This tells which spi-device driver to use. Setting it to "spidev" will use the spi user mode device driver, but there are other device drivers in the kernel, e.g. for the ADS7843 Touchscreen. If you want to use one of these you have to set the relevant modalias for this driver and of course activate it in your configuration.
  • .chip_select This tells your device driver which chipselect to use.
  • .max_speed_hz This is the maximum possible speed configured for this chip select. The speed of the SPI should be between MCK and MCK divided by 255. Take care to not use a value here which cannot be supported by your platform.
    Processor MCK (Hz) SPI Min
    AT91SAM9261 99,993,600 392,132
    AT91SAM9G20 132,096,000 518,024
    AT91SAM9G10 120,000,000 471,000
    AT91SAM9G45 132,096,000 518,024
  • .bus_num This chooses if SPI0 or SPI1 of the AT91SAM9xx is used.
  • .mode There are four different SPI modes, mode three is most commonly used by peripherals which can be only slaves. The mode of master and slave have to be the same, so when in doubt consult the datasheet of your device. There are two relevant parameters, the clock phase (CPHA) and clock polarity (CPOL). Is the phase zero (CPHA = 0), then data is sampled at rising edge with CPOL=0 and falling edge with CPOL=1. This behaviour switches with CPHA=1, then data is sampled at falling edge with CPOL=0 and rising edge with CPOL=1.
    SPI Mode CPOL CPHA
    0 0 0
    1 0 1
    2 1 0
    3 1 1

Additionally to adding the structure, you have to add a call to at91_add_device_spi in your xxx_board_init function:

at91_add_device_spi(stamp9g20_spi_devices, ARRAY_SIZE(stamp9g20_spi_devices));

After compiling the kernel according to the instructions of your platform and flashing it to your board you should see these SPI devices in your /dev directory:

/dev # ls -al /dev/spi*
crw-rw----    1 root     root     153,   0 Jan  1 00:20 /dev/spidev1.0
crw-rw----    1 root     root     153,   1 Jan  1 00:20 /dev/spidev1.2

Using the SPI User Mode Device Driver

There are two ways of of using the user mode spi device driver. You can call either the read/write functions or an ioctl(). With calling read/write you can only read or write at a time. If you need full-duplex read and write, you have to call the ioctl's. Examples for both are provided.
This is the read/write example. You can compile it either with the cross-compiler of your platform or with the native compiler on your board:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

#define ARRAY_SIZE(array) sizeof(array)/sizeof(array[0])


int main(int argc, char **argv) 
{
	int i,fd;
	char wr_buf[]={0xff,0x00,0x1f,0x0f};
	char rd_buf[10];;

	if (argc<2) {
		printf("Usage:\n%s [device]\n", argv[0]);
		exit(1);
	}
   	
	fd = open(argv[1], O_RDWR);
	if (fd<=0) { 
		printf("%s: Device %s not found\n", argv[0], argv[1]);
		exit(1);
	}
	
	if (write(fd, wr_buf, ARRAY_SIZE(wr_buf)) != ARRAY_SIZE(wr_buf))
		perror("Write Error");
	if (read(fd, rd_buf, ARRAY_SIZE(rd_buf)) != ARRAY_SIZE(rd_buf))
		perror("Read Error");
	else
		for (i=0;i<ARRAY_SIZE(rd_buf);i++) {
		printf("0x%02X ", rd_buf[i]);
		if (i%2)
			printf("\n");
	}

	close(fd);
	return 0;
}

For synchronous transfer, you can have a look at the example from Documentation/spi. It is slightly changed, as the at91-spi driver does not support to change speed or bits per word via the ioctl-transfer interface. You can cross-compile it
with your-cross-gcc -o spidev-test -I/path-to-cross-kernel-include spidev-test.c. Of course you can also use your compiler on the target board (Make sure you have the package linux-libc-dev installed). Later you can run it like this: spidev-test -D /dev/spidev1.0.

/*
 * SPI testing utility (using spidev driver)
 *
 * Copyright (c) 2007  MontaVista Software, Inc.
 * Copyright (c) 2007  Anton Vorontsov 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
 *
 */

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static void pabort(const char *s)
{
	perror(s);
	abort();
}

static const char *device = "/dev/spidev1.1";
static uint8_t mode = 3;
static uint8_t bits = 8;
static uint32_t speed = 1000000;
static uint16_t delay;

static void transfer(int fd)
{
	int ret;
	uint8_t tx[] = {
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD,
		0xF0, 0x0D,
	};
	uint8_t rx[ARRAY_SIZE(tx)] = {0, };
	struct spi_ioc_transfer tr = {
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = ARRAY_SIZE(tx),
		.delay_usecs = delay,
		.speed_hz = 0,
		.bits_per_word = 0,
	};

	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret == 1)
		pabort("can't send spi message");

	for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
		if (!(ret % 6))
			puts("");
		printf("%.2X ", rx[ret]);
	}
	puts("");
}

void print_usage(const char *prog)
{
	printf("Usage: %s [-DsbdlHOLC3]\n", prog);
	puts("  -D --device   device to use (default /dev/spidev1.1)\n"
	     "  -s --speed    max speed (Hz)\n"
	     "  -d --delay    delay (usec)\n"
	     "  -b --bpw      bits per word \n"
	     "  -l --loop     loopback\n"
	     "  -H --cpha     clock phase\n"
	     "  -O --cpol     clock polarity\n"
	     "  -L --lsb      least significant bit first\n"
	     "  -C --cs-high  chip select active high\n"
	     "  -3 --3wire    SI/SO signals shared\n");
	exit(1);
}

void parse_opts(int argc, char *argv[])
{
	while (1) {
		static const struct option lopts[] = {
			{ "device",  1, 0, 'D' },
			{ "speed",   1, 0, 's' },
			{ "delay",   1, 0, 'd' },
			{ "bpw",     1, 0, 'b' },
			{ "loop",    0, 0, 'l' },
			{ "cpha",    0, 0, 'H' },
			{ "cpol",    0, 0, 'O' },
			{ "lsb",     0, 0, 'L' },
			{ "cs-high", 0, 0, 'C' },
			{ "3wire",   0, 0, '3' },
			{ NULL, 0, 0, 0 },
		};
		int c;

		c = getopt_long(argc, argv, "D:s:d:b:lHOLC3", lopts, NULL);

		if (c == -1)
			break;

		switch (c) {
		case 'D':
			device = optarg;
			break;
		case 's':
			speed = atoi(optarg);
			break;
		case 'd':
			delay = atoi(optarg);
			break;
		case 'b':
			bits = atoi(optarg);
			break;
		case 'l':
			mode |= SPI_LOOP;
			break;
		case 'H':
			mode |= SPI_CPHA;
			break;
		case 'O':
			mode |= SPI_CPOL;
			break;
		case 'L':
			mode |= SPI_LSB_FIRST;
			break;
		case 'C':
			mode |= SPI_CS_HIGH;
			break;
		case '3':
			mode |= SPI_3WIRE;
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
}

int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;

	parse_opts(argc, argv);

	fd = open(device, O_RDWR);
	if (fd < 0)
		pabort("can't open device");

	/*
	 * spi mode
	 */
	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
	if (ret == -1)
		pabort("can't set spi mode");

	ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
		pabort("can't get spi mode");

	/*
	 * bits per word
	 */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't set bits per word");

	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't get bits per word");

	/*
	 * max speed hz
	 */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't set max speed hz");

	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't get max speed hz");

	printf("spi mode: %d\n", mode);
	printf("bits per word: %d\n", bits);
	printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);

	transfer(fd);

	close(fd);

	return ret;
}