Sample code/C/SPI/kernel
From GumstixDocsWiki
Here are 3 SPI slave drivers:
- for Robostix
- The driver is primarily used to read ADC. Achieved speed is ~800 Hz
- getting samples of all 8 ADC inputs at max resolution.
- That translates into 6.4 kSPS - compare to the maximum of 15 kSPS
- declared in ATmega128 manual. I believe higher rates can be achieved with double-buffering
- on the Robostix side, but for practical purposes I need to implement a throttle
- to do fewer SPS (Samples Per Second) in order not to waste resources much beyond application needs.
- For fun I also made the driver understand ioctls of the i2c-io so that
- with very little modifications i2c-io turns into spi-io.
- for PNI-11096 3-axis magneto-inductive sensor
- This device is a bit strange as far as SPI bus goes
- as it requires an extra line for reset which has to be brought hight
- then low with chip select asserted. That can't be done in the driver alone
- and requires [trivial] hack in the existing SPI core and master drivers.
- for LIS3LV02DQ 3-axis linear accelerometer
- There's still an unexplained problem with this device (or the driver, or my hardware), which isn't a show-stopper,
- but pretty annoying. Search for BUG_WORKAROUND in lis3lv02dq_spi.c
All these drivers are based on
drivers/spi/spi.c (SPI core) and
drivers/spi/pxa2xx_spi.c (SPI master)
They should also work (in theory) with a different SPI master (like bitbang).
Unless otherwise noted, all paths mentioned below are relative
to the root of Linux source tree for the Gumstix
(something like gumstix-buildroot/build_arm_nofpu/linux-xxx)
Contents |
Source files
Drop robostix_spi.c, pni11096_spi.c, lis3lv02dq.h and lis3lv02dq_spi.c in drivers/spi
drivers/spi/Kconfig
Edit drivers/spi/Kconfig to include
config SPI_LIS3LV02DQ
tristate "LIS3LV02DQ 3-axis linear accelerometer SPI slave"
depends on SPI_MASTER && EXPERIMENTAL
help
This enables using STMicroelectronics
LIS3LV02DQ 3-axis +-2/6g linear accelerometer.
config SPI_PNI11096
tristate "PNI-11096 3-axis magneto-inductive sensor SPI slave"
depends on SPI_MASTER && EXPERIMENTAL
help
This enables using PNI-11096 3-axis magneto-inductive sensor.
config SPI_ROBOSTIX
tristate "Robostix SPI slave"
depends on SPI_MASTER && EXPERIMENTAL
help
This enables using Robostix as SPI slave.
drivers/spi/Makefile
Edit drivers/spi/Makefile to include
obj-$(CONFIG_SPI_LIS3LV02DQ) += lis3lv02dq_spi.o obj-$(CONFIG_SPI_PNI11096) += pni11096_spi.o obj-$(CONFIG_SPI_ROBOSTIX) += robostix_spi.o
arch/arm/mach-pxa/gumstix.c
Edit arch/arm/mach-pxa/gumstix.c to include
#include <linux/autoconf.h>
#include <linux/spi/spi.h>
#include <asm/arch/pxa2xx_spi.h>
#if defined(CONFIG_SPI_ROBOSTIX) || defined(CONFIG_SPI_ROBOSTIX_MODULE)
static
void robostix_cs_control(u32 command)
{ /* uses NSSPSFRM pin configured as GPIO out */
if (command & PXA2XX_CS_ASSERT)
GPCR(82) = GPIO_bit(82);
else
GPSR(82) = GPIO_bit(82);
}
static
struct pxa2xx_spi_chip robostix_chip_info = {
.tx_threshold = 4, /* SSP hardware FIFO threshold */
.rx_threshold = 12, /* SSP hardware FIFO threshold */
.dma_burst_size = 8, /* byte wide transfers used so 8 byte bursts */
.timeout_microsecs = 235, /* wait to handle trailing */
#warning some versions have just .timeout instead of .timeout_microsecs
.cs_control = robostix_cs_control,
};
#endif
#if defined(CONFIG_SPI_LIS3LV02DQ) || defined(CONFIG_SPI_LIS3LV02DQ_MODULE)
static
void lis3lv02dq_cs_control(u32 command)
{
if (command & PXA2XX_CS_ASSERT)
GPCR(71) = GPIO_bit(71);
else
GPSR(71) = GPIO_bit(71);
}
static
struct pxa2xx_spi_chip lis3lv02dq_chip_info = {
.tx_threshold = 4, /* SSP hardware FIFO threshold */
.rx_threshold = 12, /* SSP hardware FIFO threshold */
.dma_burst_size = 8, /* byte wide transfers used so 8 byte bursts */
.timeout_microsecs = 235, /* wait to handle trailing */
#warning some versions have just .timeout instead of .timeout_microsecs
.cs_control = lis3lv02dq_cs_control,
};
#endif
#if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
static
void pni11096_cs_control(u32 command)
{
if (command & PXA2XX_CS_ASSERT) {
GPCR(67) = GPIO_bit(67);
if (command & PXA2XX_CS_RESET) {
GPSR(77) = GPIO_bit(77);
GPCR(77) = GPIO_bit(77);
}
}
else
GPSR(67) = GPIO_bit(67);
}
static
struct pxa2xx_spi_chip pni11096_chip_info = {
.tx_threshold = 4, /* SSP hardware FIFO threshold */
.rx_threshold = 12, /* SSP hardware FIFO threshold */
.dma_burst_size = 8, /* byte wide transfers used so 8 byte bursts */
.timeout_microsecs = 235, /* wait to handle trailing */
#warning some versions have just .timeout instead of .timeout_microsecs
.cs_control = pni11096_cs_control,
};
#endif
#if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)
static
struct resource pxa_spi_nssp_resources[] = {
[0] = {
.start = __PREG(SSCR0_P(2)), /* Start address of NSSP */
.end = __PREG(SSCR0_P(2)) + 0x2c, /* Range of registers */
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_NSSP, /* NSSP IRQ */
.end = IRQ_NSSP,
.flags = IORESOURCE_IRQ,
},
};
static
struct pxa2xx_spi_master pxa_nssp_master_info = {
.ssp_type = PXA25x_NSSP, /* Type of SSP */
.clock_enable = CKEN9_NSSP, /* NSSP Peripheral clock */
.num_chipselect = 3, /* Matches the number of chips attached to NSSP */
.enable_dma = 1, /* Enables NSSP DMA */
};
static
struct platform_device pxa_spi_nssp = {
.name = "pxa2xx-spi", /* MUST BE THIS VALUE, so device match driver */
.id = 2, /* Bus number, MUST MATCH SSP number 1..n */
.resource = pxa_spi_nssp_resources,
.num_resources = ARRAY_SIZE(pxa_spi_nssp_resources),
.dev = {
.platform_data = &pxa_nssp_master_info, /* Passed to driver */
},
};
static
struct spi_board_info gumstix_spi_board_info[] = {
#if defined(CONFIG_SPI_ROBOSTIX) || defined(CONFIG_SPI_ROBOSTIX_MODULE)
{
.modalias = "robostix-spi",
.max_speed_hz = 3686400, /* run SSP as fast as possbile */
.bus_num = 2,
.chip_select = 0,
.controller_data = &robostix_chip_info,
.irq = IRQ_GPIO(69),
.mode = SPI_MODE_3,
},
#endif
#if defined(CONFIG_SPI_LIS3LV02DQ) || defined(CONFIG_SPI_LIS3LV02DQ_MODULE)
{
.modalias = "lis3lv02dq-spi",
.max_speed_hz = 3686400, /* run SSP as fast as possbile */
.bus_num = 2,
.chip_select = 1,
.controller_data = &lis3lv02dq_chip_info,
.irq = IRQ_GPIO(68),
.mode = SPI_MODE_3,
},
#endif
#if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
{
.modalias = "pni11096-spi",
.max_speed_hz = 3686400, /* run SSP as fast as possbile */
.bus_num = 2,
.chip_select = 2,
.controller_data = &pni11096_chip_info,
.irq = IRQ_GPIO(66),
.mode = SPI_MODE_3,
},
#endif
};
#endif
Modify the following two pieces of code: add the #if...#endif parts.
static struct platform_device *devices[] __initdata = {
&gum_audio_device,
#if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)
&pxa_spi_nssp,
#endif
};
static void __init gumstix_init(void)
{
pxa_set_mci_info(&gumstix_mci_platform_data);
pxa_set_udc_info(&gumstix_udc_info);
#if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)
spi_register_board_info(gumstix_spi_board_info,
ARRAY_SIZE(gumstix_spi_board_info));
#endif
(void) platform_add_devices(devices, ARRAY_SIZE(devices));
}
All this just follows the guidelines in Documentation/spi/pxa2xx
and is pxa2xx-specific.
If you use different SPI master driver (like bitbang), then you need to make proper changes.
PNI-11096 hack
If you intend to use PNI-11096 driver, also make the following changes.
In include/linux/spi/spi.h in the definition of the
struct spi_transfer after unsigned cs_change:1;
add the following:
#if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
unsigned cs_reset:1;
#endif
In include/asm-arm/arch-pxa/pxa2xx_spi.h under #define PXA2XX_CS_DEASSERT (0x02)
add the following:
#if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE) #define PXA2XX_CS_RESET (0x03) #endif
In drivers/spi/pxa2xx_spi.c there are two instances of
drv_data->cs_control(PXA2XX_CS_ASSERT);
Add the following immediately before in both places:
#if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
if (transfer->cs_reset)
drv_data->cs_control(PXA2XX_CS_ASSERT | PXA2XX_CS_RESET);
else
#endif
Configure and compile kernel
Run 'menuconfig' on the Linux kernel:
make ARCH=arm CROSS_COMPILE=`pwd`/../staging_dir/bin/arm-linux- menuconfig
Go to Device Drivers --> SPI support
Select SPI support, PXA2xx SSP SPI master and some of the
LIS3LV02DQ 3-axis linear accelerometer SPI slave
PNI-11096 3-axis magneto-inductive sensor SPI slave
Robostix SPI slave
Make them modules (<M>) - except SPI support.
Compile Linux kernel:
make ARCH=arm CROSS_COMPILE=`pwd`/../staging_dir/bin/arm-linux-
A new uImage may not be created automatically:
make ARCH=arm CROSS_COMPILE=`pwd`/../staging_dir/bin/arm-linux- uImage
Install uImage from arch/arm/boot and the compiled modules (*.ko) from drivers/spi
You may need to edit /lib/modules/2.6.18gum/modules.dep to let kernel
know about the new drivers.
GPIO configuration script
You need to configure Gumstix' GPIO pins used by your SPI chips before loading drivers.
For example, create (in Gumstix) /etc/init.d/spi
with the following (or similar) content:
#!/bin/sh
start() {
echo "enable SPI..."
echo -n "NSSPSCLK "; echo "AF1 out" > /proc/gpio/GPIO81
#echo -n "NSSPSFRM "; echo "GPIO out set" > /proc/gpio/GPIO82
echo -n "NSSPTXD "; echo "AF1 out" > /proc/gpio/GPIO83
echo -n "NSSPRXD "; echo "AF2 in" > /proc/gpio/GPIO84
# robostix
echo -n "robostix CS "; echo "GPIO out set" > /proc/gpio/GPIO82
echo -n "robostix INT "; echo "GPIO in" > /proc/gpio/GPIO69
modprobe robostix_spi
# accelerometer
echo -n "lis3lv02dq CS "; echo "GPIO out set" > /proc/gpio/GPIO71
echo -n "lis3lv02dq INT "; echo "GPIO in" > /proc/gpio/GPIO68
modprobe lis3lv02dq_spi
echo 3 > /proc/lis3lv02dq-spi2.1/decimation_factor
echo 1 > /proc/lis3lv02dq-spi2.1/block_data_update
# magnetometer
echo -n "pni11096 CS "; echo "GPIO out set" > /proc/gpio/GPIO67
echo -n "pni11096 INT "; echo "GPIO in" > /proc/gpio/GPIO66
echo -n "pni11096 RESET "; echo "GPIO out clear" > /proc/gpio/GPIO77
modprobe pni11096_spi
}
stop() {
echo "disable SPI..."
rmmod pni11096_spi
rmmod lis3lv02dq_spi
rmmod robostix_spi
echo -n "NSSPSCLK "; echo "GPIO in" > /proc/gpio/GPIO81
echo -n "NSSPSFRM "; echo "GPIO in" > /proc/gpio/GPIO82
echo -n "NSSPTXD "; echo "GPIO in" > /proc/gpio/GPIO83
echo -n "NSSPRXD "; echo "GPIO in" > /proc/gpio/GPIO84
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
Then make it executable and make a symbolic link to it:
in /etc/init.d do
chmod +x spi
ln -s spi S25
I placed pxa2xx_spi in /etc/modules, alternatively it can be in /etc/init.d/spi above.
Using the drivers
All three drivers have similar purpose and interface: they where designed for data acquisition from sensors.
Robostix requires corresponding firmware.
Each driver creates one /dev/xxx file and one /proc/xxx directory per configured device.
Each xxx has a form of yyy-spiA.B where yyy is driver's base name, A is SPI bus number
(2 for NSSP) and B is chip number on that bus, as configured by arch/arm/mach-pxa/gumstix.c.
/dev/xxx is a character device to read the data from and it may be used for ioctl (like Robostix).
When reading from any of these /dev/xxx files you shall provide a buffer large enough for the largest sample
as for simplicity none of the drivers do internal buffering of the data.
For example, don't try to read a character
at a time - the driver will likely crash.
In binary mode size of the buffer is easy to figure: it's just the size of the data you expect
from the device: 16 bytes for reading Robostix' ADC (8 16-bit values),
6 bytes for the other 2 devices (2 bytes per each of 3 axis).
Although not tested, select() and non-blocking reads are supposed to work.
As a shortcut for what shall be otherwise done by a user-mode application through ioctls, /proc/xxx
directory contains various files to control device and get some information about the device.
Do
cat /dev/xxx
to see data from the device.
Format of the output can be chosen between dec, hex and bin by
echo Z > /proc/xxx/mode
where Z is one of d, h or b (or anything that starts with these letters).
Do
cat /dev/xxx > /dev/null &
then you can check performance and statistics with
cat /proc/xxx/perf and
cat /proc/xxx/stat
cat /proc/xxx/perf1 prints performance once while
cat /proc/xxx/perf keeps printing every second until interrupted.
Reset statistics by
echo 0 > /proc/xxx/stat
cat /proc/xxx/whoami
prints brief information about the device and the driver.
There can be other device-specific entries in /proc/xxx
Robostix firmware and user-mode app
Robostix SPI driver works with modified i2c-io program.
Follow instructions there to download, compile, install and run i2c-io
application on Gumstix/Robostix.
A quick summary of the procedure:
$ cd ~/gumstix-buildroot
$ svn co -r<gs version> http://svn.gumstix.com/gumstix-buildroot/branches/projects/robostix robostix
$ make
If you succeed, continue with the following.
Note: paths here are relative to wherever you installed Robostix
project tree described in here.
Save spi-io.c in robostix/i2c-io, then modify Makefile there to include (just before the line 'all: svn-version.h')
CONFIG_SPI_IO=1 CONFIG_SPI_ADC=1 #CFLAGS += -g -Wa,-a,-ad ifeq ($(CONFIG_SPI_IO),1) COMMON_OBJS += spi-io.o Crc8.o CPPFLAGS += -DCONFIG_SPI_IO=1 ifeq ($(CONFIG_SPI_ADC),1) CPPFLAGS += -DCONFIG_SPI_ADC=1 endif endif
Modify i2c-io.c:
Under #include "svn-version.h" add
#ifdef CONFIG_SPI_IO void spi_init_slave(void); #endif
Above I2C_SlaveBootInit add
#ifdef CONFIG_SPI_IO
spi_init_slave();
#endif
Make and flash resulting i2c-io.hex according to Robostix i2c-io:
$ make
Modify robostix/gumstix/i2c-io/i2c-io.c:
put #define CONFIG_SPI_IO 1 somewhere at the top of the file.
In main() closer to the top, after int cmdIdx; put
#ifdef CONFIG_SPI_IO
char* spi = strstr(argv[0], "spi-io");
if (spi && spi[6])
spi = 0;
#endif
Go down in the main() to gI2cAddr = strtol( argv[ 0 ], NULL, 0 );
Above that add the following:
#ifdef CONFIG_SPI_IO
if (spi) {
static char dev_name[32];
sprintf(dev_name, "/dev/robostix-spi%s", *argv);
i2cDevName = dev_name;
gI2cAddr = 1;
} else
#endif
Compile and install i2c-io on Gumstix, then do
ln -s i2c-io spi-io
The commands
i2c-io 0x0b <whatever> and
spi-io 2.0 <whatever>
shall produce identical results, where appropriate.
'2' is SPI bus number and '0' is Robostix' SPI 'chip-select' number,
as configured in arch/arm/mach-pxa/gumstix.c.
Troubleshooting
Check out this patch.
Don't know if the patch made it to the trunk, I'm not using the latest rev.
Have no idea what it fixes, but I applied the patch, seems doesn't hurt.
You may get some idea about your problem by examining pxa255 NSSP registers with pxaregs.
Enable "SPI debug" option when doing 'menuconfig' - you may get extra messages in the log file (usually /var/log/messages).
LIS3LV02DQ (accelerometer) driver, when built in debug mode, creates entries in its /proc/lis3lv02dq-spiA.B for
each internal register and individual bit-field.
By default, the SPI code on the robostix will set the ADC to use the external reference pin (AREF) rather than using the internal one (which the i2c code uses). So if you find all your ADC values maxing out to 1023, change:
in spi-io.c, inside the spi_init_slave() function:
ADMUX = 0;
to
ADMUX = (1<<REFS0);

