Sample code/C/SPI/kernel

From GumstixDocsWiki

Jump to: navigation, search

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)

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