使用者空間spi驅動
轉自:http://armbedded.eu/node/318
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; }