Adding SPI Driver Support for Cubieboard2

0. Introduction

Since the official stable kernel linux-3.4 for Cubieboard2 doesn’t include native SPI driver support, we need to modify the kernel source code, compile and put it into NAND to replace the original uImage, and add the new kernel module files. Additionally, we need to modify system files to fully support full-duplex communication.

1. Flash Debian System to NAND (Windows)

First, download the customized Debian system for Cubieboard2 from: http://dl.cubieboard.org/software/a20-cubieboard/debian/nand/debian-nand.img.gz

Download Phoenixsuit and open the downloaded image through it. With the Cubieboard2 unpowered, hold down the FEL button while connecting it to the computer via an OTG cable. In Device Manager, install the driver for this unrecognized device, specifying the Phoenixsuit installation directory as the driver location, and confirm installation.

Then format and flash the image.

2. Log in to the System

Connect the Cubieboard2 to an Ethernet cable. The Debian system is set to a static IP address 192.168.1.124 by default, which you can access via SSH. You can also log in through a TTL line from UART, change to DHCP mode, then check the IP address from the router before logging in. The router username and password are root/cubieboard.

3. Modify the Kernel (Ubuntu)

The current Linux kernel doesn’t support SPI drivers for Cubieboard2. First, download the linux-3.4 branch kernel source: https://github.com/linux-sunxi/linux-sunxi. Then add SPI support to the kernel (not recommended); for details, see http://blog.csdn.net/u010352603/article/details/51657265#. Instead, directly download a pre-modified version with SPI support from https://github.com/linanwx/cubieboard2-spi-support. After downloading, delete the drivers/spi directory in the kernel directory and place the downloaded spi folder in its place.

4. Compile the Kernel (Ubuntu)

Prepare an arm-linux-gnueabihf-gcc compiler, version not higher than 5.0.

In the kernel directory, using the terminal, enter:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- sun7i_defconfig

Enter:

gedit .config

Find the following options and change them to:

CONFIG_SPI=y
CONFIG_SUNXI_NAND_PARTITION=y
CONFIG_SUNXI_NAND=y

Enter the following command to start compiling the kernel:

make -j4ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- uImage modules

Since CONFIG_SPI and CONFIG_SUNXI_NAND support are enabled, a series of settings are required. When you encounter spi support, CONFIG_SPI_SUN7I, CONFIG_SUN7I_SPI_NDMA, enter y; in other cases, press enter.

Check if the uImage file exists in the path arch/arm/boot

Enter the following command to generate kernel modules:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=output modules_install

Check if the output/lib/modules/3.4.104 directory has been created.

5. Replace the Kernel (Cubieboard2)

On Cubieboard2, open the terminal and enter:

mkdir /media/nanda
mount /dev/nanda /media/nanda

Copy the uImage to the /media/nanda directory of the Cubieboard2 Debian system, overwriting the original kernel.

Use the tar command to pack the output/lib/modules/3.4.104 directory, transfer this file to Cubieboard2’s /lib/modules directory, then extract it.

6. Check Compilation Success

Restart. If the system enters normally, it was successful.

7. Modify System Files to Enable SPI and Support Full-Duplex SPI (Cubieboard2)

Remount /dev/nanda to the /media/nanda/ directory and enter it.

Enter the following commands:

bin2fex script.bin script.fex
nano script.fex

Find [spi0_para], change the line with “used” to 1, and add the following code:

[spi_devices]
spi_dev_num =1

[spi_board0]
modalias ="spidev"
max_speed_hz =100000
bus_num =0
chip_select =0
mode =0
full_duplex =1
manual_cs =0

Enter the following commands:

fex2bin script.fex script.bin
nano /usr/include/linux/spi/spidev.h

Add the following line above __u16 delay_usecs;:

__u16 interbyte_usecs;

8. Test SPI

Restart Cubieboard2. Check if the spidev device exists in the /dev path. Short-circuit pins 46 and 48; pin diagram available at http://docs.cubieboard.org/products/a10_cubieboard/expansion_ports. Create a new text file with the following code:

/* 
 * SPI testing utility (using spidev driver) 
 * 
 * Copyright (c) 2007  MontaVista Software, Inc. 
 * Copyright (c) 2007  Anton Vorontsov <avorontsov@ru.mvista.com> 
 * 
 * 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/spidev0.0";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
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 = speed,
        .bits_per_word = bits,
    };

    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("");
}

static void print_usage(const char *prog)
{
    printf("Usage: %s [-DsbdlHOLC3]\n", prog);
    puts("  -D --device   device to use (default /dev/spidev0.0)\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);
}

static 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'},
            {"no-cs", 0, 0, 'N'},
            {"ready", 0, 0, 'R'},
            {NULL, 0, 0, 0},
        };
        int c;

        c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", 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;
        case 'N':
            mode |= SPI_NO_CS;
            break;
        case 'R':
            mode |= SPI_READY;
            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;
}

Compile this file and run it. If the output looks like the following, the modification was successful:

spi mode: 0
bits per word: 8
max speed: 500000 Hz (500 KHz)

FF FF FF FF FF FF 
40 00 00 00 00 95 
FF FF FF FF FF FF 
FF FF FF FF FF FF 
FF FF FF FF FF FF 
DE AD BE EF BA AD 
F0 0D 
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy