Sample code/C/SPI/kernel

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.

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)
 * 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

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


 * 1) include 
 * 2) include 
 * 3) include 

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); }
 * 1) if defined(CONFIG_SPI_ROBOSTIX) || defined(CONFIG_SPI_ROBOSTIX_MODULE)

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 */ .cs_control = robostix_cs_control, };
 * 1) warning some versions have just .timeout instead of .timeout_microsecs
 * 1) endif

static void lis3lv02dq_cs_control(u32 command) {   if (command & PXA2XX_CS_ASSERT) GPCR(71) = GPIO_bit(71); else GPSR(71) = GPIO_bit(71); }
 * 1) if defined(CONFIG_SPI_LIS3LV02DQ) || defined(CONFIG_SPI_LIS3LV02DQ_MODULE)

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 */ .cs_control = lis3lv02dq_cs_control, };
 * 1) warning some versions have just .timeout instead of .timeout_microsecs
 * 1) endif

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); }
 * 1) if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)

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 */ .cs_control = pni11096_cs_control, };
 * 1) warning some versions have just .timeout instead of .timeout_microsecs
 * 1) endif

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, }, };
 * 1) if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)

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[] = { {       .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, },   {        .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, },   {        .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, }, }; Modify the following two pieces of code: add the #if...#endif parts. static struct platform_device *devices[] __initdata = { &gum_audio_device, &pxa_spi_nssp, };
 * 1) if defined(CONFIG_SPI_ROBOSTIX) || defined(CONFIG_SPI_ROBOSTIX_MODULE)
 * 1) endif
 * 2) if defined(CONFIG_SPI_LIS3LV02DQ) || defined(CONFIG_SPI_LIS3LV02DQ_MODULE)
 * 1) endif
 * 2) if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
 * 1) endif
 * 1) endif
 * 1) if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)
 * 1) endif

static void __init gumstix_init(void) {   pxa_set_mci_info(&gumstix_mci_platform_data); pxa_set_udc_info(&gumstix_udc_info); spi_register_board_info(gumstix_spi_board_info,       ARRAY_SIZE(gumstix_spi_board_info)); (void) platform_add_devices(devices, ARRAY_SIZE(devices)); }
 * 1) if defined(CONFIG_SPI_PXA2XX) || defined(CONFIG_SPI_PXA2XX_MODULE)
 * 1) endif

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 after add the following: unsigned   cs_reset:1; In include/asm-arm/arch-pxa/pxa2xx_spi.h under add the following: In drivers/spi/pxa2xx_spi.c there are two instances of Add the following immediately before in both places: if (transfer->cs_reset) drv_data->cs_control(PXA2XX_CS_ASSERT | PXA2XX_CS_RESET); else
 * 1) if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
 * 1) endif
 * 1) if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
 * 2) define PXA2XX_CS_RESET (0x03)
 * 3) endif
 * 1) if defined(CONFIG_SPI_PNI11096) || defined(CONFIG_SPI_PNI11096_MODULE)
 * 1) endif

Configure and compile kernel
Run 'menuconfig' on the Linux kernel: 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 () - except SPI support. Compile Linux kernel: A new uImage may not be created automatically: 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:
 * 1) !/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

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 to see data from the device. Format of the output can be chosen between dec, hex and bin by where Z is one of d, h or b (or anything that starts with these letters). Do then you can check performance and statistics with and prints performance once while keeps printing every second until interrupted. Reset statistics by 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:

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


 * 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  add void spi_init_slave(void); Above  add spi_init_slave;
 * 1) ifdef CONFIG_SPI_IO
 * 1) endif
 * 1) ifdef CONFIG_SPI_IO
 * 1) endif

Make and flash resulting i2c-io.hex according to Robostix i2c-io:

Modify robostix/gumstix/i2c-io/i2c-io.c: put  somewhere at the top of the file. In  closer to the top, after   put char* spi = strstr(argv[0], "spi-io"); if (spi && spi[6]) spi = 0; Go down in the  to  Above that add the following: if (spi) { static char dev_name[32]; sprintf(dev_name, "/dev/robostix-spi%s", *argv); i2cDevName = dev_name; gI2cAddr = 1; } else Compile and install i2c-io on Gumstix, then do The commands and 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.
 * 1) ifdef CONFIG_SPI_IO
 * 1) endif
 * 1) ifdef CONFIG_SPI_IO
 * 1) endif

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);