SPI使用参考

version 1.2


1. 概述

本文档介绍了如何在Linux用户空间中使用spi驱动程序和uboot。


2. SPI控制


2.1. SPI概述

Sigmastar Takoyaki平台支持主SPI控制器(spi0)。


2.2. SPI Pads

SPI pad modes 可设定的mode 和脚位对应如下表。

表2-1

SPI0_CZ SPI0_CK SPI0_DI(MOSI) SPI0_DO(MISO)
Pad mode=1 PAD_SD_CMD PAD_SD_CLK PAD_SD_D0 PAD_SD_D1
Pad mode=2 PAD_TTL16 PAD_TTL17 PAD_TTL18 PAD_TTL19
Pad mode=3 PAD_GPIO7 PAD_GPIO6 PAD_GPIO5 PAD_GPIO4
Pad mode=4 PAD_FUART_RX PAD_FUART_TX PAD_FUART_CTS PAD_FUART_RTS
Pad mode=5 PAD_GPIO8 PAD_GPIO9 PAD_GPIO10 PAD_GPIO11
Pad mode=6 PAD_GPIO0 PAD_GPIO1 PAD_GPIO2 PAD_GPIO3

2.3. 在Linux用户空间中使用SPI

2.3.1. Enable spi

测试spi前需要在kernel打开如下两个config:

CONFIG_SPI_SPIDEV
CONFIG_MS_SPI_INFINITY

2.3.2. SPI Pad Mode设定

Enable spi后,还需要把spi pin设定正确才可以工作,实际使用的pad可透过平台对应的infinity2m_xxxx_padmux.dtsi中对应PINMUX_FOR_SPI0_MODE_x 来指定:

<PAD_GPIO8            PINMUX_FOR_SPI0_MODE_5      MDRV_PUSE_SPI0_CZ>,
<PAD_GPIO9            PINMUX_FOR_SPI0_MODE_5      MDRV_PUSE_SPI0_CK>,
<PAD_GPIO10           PINMUX_FOR_SPI0_MODE_5      MDRV_PUSE_SPI0_DI>,
<PAD_GPIO11           PINMUX_FOR_SPI0_MODE_5      MDRV_PUSE_SPI0_DO>,

2.3.3. SPI device

/dev/spidev0.0

2.3.4. Sample code

static const char *device = "/dev/spidev0.0";

static uint8_t mode = 0; /* SPI通信使用全双工,设置CPOL=0,CPHA=0。 */

static uint8_t bits = 8; /* 8bits读写,MSB first。*/

static uint32_t speed = 60*1000*1000;/* 设置传输速度 */

static uint16_t delay = 0;

static int g_SPI_Fd = 0;

static void pabort(const char *s)

{

    perror(s);

    abort();

}


/**

* 功 能:同步数据传输

* 入口参数 :

* TxBuf -> 发送数据首地址

* len -> 交换数据的长度

* 出口参数:

* RxBuf -> 接收数据缓冲区

* 返回值:0 成功

* 开发人员:Lzy 2013-5-22

*/

int SPI_Transfer(const uint8_t *TxBuf, uint8_t *RxBuf, int len)

{

    int ret;

    int fd = g_SPI_Fd;


    struct spi_ioc_transfer tr ={

        .tx_buf = (unsigned long) TxBuf,

        .rx_buf = (unsigned long) RxBuf,

        .len =len,

        .delay_usecs = delay,

    };


    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

    if (ret < 1)

        perror("can't send spi message\n");

    else

    {

        #if SPI_DEBUG

        int i;

        printf("nsend spi message Succeed\n");

        printf("nSPI Send [Len:%d]: \n", len);

        for (i = 0; i < len; i++)

        {

            if (i % 8 == 0)

            printf("nt\n");

            printf("0x%02X \n", TxBuf[i]);

        }

        printf("n");


        printf("SPI Receive [len:%d]:\n", len);

        for (i = 0; i < len; i++)

        {

            if (i % 8 == 0)

            printf("nt\n");

            printf("0x%02X \n", RxBuf[i]);

        }

        printf("\n");

        #endif

    }

    return ret;

}


/**

* 功 能:发送数据

* 入口参数 :

* TxBuf -> 发送数据首地址

*len -> 发送与长度

*返回值:0 成功

* 开发人员:Lzy 2013-5-22

*/

int SPI_Write(uint8_t *TxBuf, int len)

{

    int ret;

    int fd = g_SPI_Fd;


    ret = write(fd, TxBuf, len);

    if (ret < 0)

    perror("SPI Write error\n");

    else

    {

        #if SPI_DEBUG

        int i;

        printf("SPI Write [Len:%d]: \n", len);

        for (i = 0; i < len; i++)

        {

            if (i % 8 == 0)

            printf("\n\t");

            printf("0x%02X \n", TxBuf[i]);

        }

        printf("\n");
        #endif

    }

    return ret;

}


/**

* 功 能:接收数据

* 出口参数:

* RxBuf -> 接收数据缓冲区

* rtn -> 接收到的长度

* 返回值:>=0 成功

* 开发人员:Lzy 2013-5-22

*/

int SPI_Read(uint8_t *RxBuf, int len)

{

    int ret;

    int fd = g_SPI_Fd;

    ret = read(fd, RxBuf, len);

    if (ret < 0)

    printf("SPI Read error\n");

    else

    {

        #if SPI_DEBUG

        int i;

        printf("SPI Read [len:%d]:\n", len);

        for (i = 0; i < len; i++)

        {

            if (i % 8 == 0)

            printf("\n\t");

            printf("0x%02X \n", RxBuf[i]);

        }

        printf("\n");

        #endif

    }

    return ret;

}


/**

* 功 能:打开设备 并初始化设备

* 入口参数 :

* 出口参数:

* 返回值:0 表示已打开 0XF1 表示SPI已打开 其它出错

* 开发人员:Lzy 2013-5-22

*/

int SPI_Open(void)

{

    int fd;

    int ret = 0;


    if (g_SPI_Fd != 0) /* 设备已打开 */

        return 0xF1;

    fd = open(device, O_RDWR);

    if (fd < 0)

        pabort("can't open device\n");

    else

        printf("SPI - Open Succeed. Start Init SPI...\n");


    g_SPI_Fd = fd;

    /*

    * spi mode

    */

    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);

    if (ret == -1)

        pabort("can't set spi mode\n");


    ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);

    if (ret == -1)

        pabort("can't get spi mode\n");


    /*

    * bits per word

    */

    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);

    if (ret == -1)

        pabort("can't set bits per word\n");


    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);

    if (ret == -1)

        pabort("can't get bits per word\n");


    /*

    * max speed hz

    */

    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    if (ret == -1)

        pabort("can't set max speed hz\n");


    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);

    if (ret == -1)

        pabort("can't get max speed hz\n");


    printf("spi mode: %d\n", mode);

    printf("bits per word: %d\n", bits);

    printf("max speed: %d KHz (%d MHz)\n", speed / 1000, speed / 1000 / 1000);


    return ret;

}


/**

* 功 能:关闭SPI模块

*/

int SPI_Close(void)

{

    int fd = g_SPI_Fd;


    if (fd == 0) /* SPI是否已经打开*/

        return 0;

    close(fd);

    g_SPI_Fd = 0;


    return 0;

}

/**

* 功 能:自发自收测试程序

* 接收到的数据与发送的数据如果不一样 ,则失败

* 说明:

* 在硬件上需要把输入与输出引脚短跑

* 开发人员:Lzy 2013-5-22

*/

int SPI_LookBackTest(void)

{

    int ret, i;

    const int BufSize = 16;

    uint8_t tx[BufSize], rx[BufSize];

    bzero(rx, sizeof(rx));

    for (i = 0; i < BufSize; i++)

        tx[i] = i;

    printf("nSPI - LookBack Mode Test...\n");

    ret = SPI_Transfer(tx, rx, BufSize);

    if (ret > 1)

    {

        ret = memcmp(tx, rx, BufSize);

        if (ret != 0)

        {

            printf("tx:\n");

            for (i = 0; i < BufSize; i++)

            {

                printf("%d ", tx[i]);

            }

            printf("\n");

            printf("rx:\n");

            for (i = 0; i < BufSize; i++)

            {

                printf("%d ", rx[i]);

            }

            printf("\n");

            perror("LookBack Mode Test error\n");

        }

        else

            printf("SPI - LookBack Mode OK\n");

    }

    return ret;

}

2.4. 在uboot中使用SPI

2.4.1. Enable spi

操作spi前需要在uboot的config文件中打开如下config开启spi

CONFIG_CMD_SPI
CONFIG_SSTAR_MSPI

2.4.2. SPI Pad Mode设定

开启spi后,还需要把spi pin设定正确才能正常工作。实际使用的pad可透过修改uboot 下的drivers/mstar/spi/infinity2m/mspi.c 中的 #define MSPI0_PADMUX_MODE 5可设定的数值为 1 ~ 6。

2.4.3. 使用uboot命令行控制SPI设备

可以透过 uboot command line 来控制/测试 spi:

The u-boot command sspi Usage:

sspi - SPI utility command

Usage:

sspi [<bus>:]<cs>[.<mode>] <bit_len> <dout> - Send and receive bits
<bus> - Identifies the SPI bus
<cs> - Identifies the chip select
<mode> - Identifies the SPI mode to use
<bit_len> - Number of bits to send (base 10)
<dout> - Hexadecimal string that gets sent
<dout> is in hex but without the prefix "0x". All others are in decimal.

The following is SPI mode defined in u-boot/include/spi.h. But still depends on the mode that SPI controller/driver can handle:
/* SPI mode flags */
#define SPI_CPHA        0x01                    /* clock phase */
#define SPI_CPOL        0x02                    /* clock polarity */
#define SPI_MODE_0      (0|0)                   /* (original MicroWire) */
#define SPI_MODE_1      (0|SPI_CPHA)
#define SPI_MODE_2      (SPI_CPOL|0)
#define SPI_MODE_3      (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH     0x04                    /* CS active high */
#define SPI_LSB_FIRST   0x08                    /* per-word bits-on-wire */
#define SPI_3WIRE       0x10                    /* SI/SO signals shared */
#define SPI_LOOP        0x20                    /* loopback mode */
#define SPI_SLAVE       0x40                    /* slave mode */
#define SPI_PREAMBLE    0x80                    /* Skip preamble bytes */

2.4.4. SPI API

uboot 支持uboot 标准 spi API,调用 API 请加:

#include <spi.h>

API 说明如下(引用 spi.h 说明):

  • spi_setup_slave

    /**
    * Set up communications parameters for a SPI slave.
    * This must be called once for each slave. Note that this function
    * usually doesn't touch any actual hardware, it only initializes the
    * contents of spi_slave so that the hardware can be easily
    * initialized later.
    *
    * @bus: Bus ID of the slave chip.
    * @cs:      Chip select ID of the slave chip on the specified bus.
    * @max_hz:  Maximum SCK rate in Hz.
    * @mode:    Clock polarity, clock phase and other parameters.
    *
    * Returns: A spi_slave reference that can be used in subsequent SPI
    * calls, or NULL if one or more of the parameters are not supported.
    */
    struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
            unsigned int max_hz, unsigned int mode);
    
  • spi_free_slave

    /**
    * Free any memory associated with a SPI slave.
    *
    * @slave:   The SPI slave
    */
    void spi_free_slave(struct spi_slave *slave);
    
  • spi_claim_bus

    /**
    * Claim the bus and prepare it for communication with a given slave.
    *
    * This must be called before doing any transfers with a SPI slave. It
    * will enable and initialize any SPI hardware as necessary, and make
    * sure that the SCK line is in the correct idle state. It is not
    * allowed to claim the same bus for several slaves without releasing
    * the bus in between.
    *
    * @slave:   The SPI slave
    *
    * Returns: 0 if the bus was claimed successfully, or a negative value
    * if it wasn't.
    */
    int spi_claim_bus(struct spi_slave *slave);
    
  • spi_release_bus

    /**
    * Release the SPI bus
    *
    * This must be called once for every call to spi_claim_bus() after
    * all transfers have finished. It may disable any SPI hardware as
    * appropriate.
    *
    * @slave:   The SPI slave
    */
    void spi_release_bus(struct spi_slave *slave);
    
  • spi_xfer

    /**
    * SPI transfer
    *
    * This writes "bitlen" bits out the SPI MOSI port and simultaneously clocks
    * "bitlen" bits in the SPI MISO port.  That's just the way SPI works.
    *
    * The source of the outgoing bits is the "dout" parameter and the
    * destination of the input bits is the "din" parameter.  Note that "dout"
    * and "din" can point to the same memory location, in which case the
    * input data overwrites the output data (since both are buffered by
    * temporary variables, this is OK).
    *
    * spi_xfer() interface:
    * @slave:   The SPI slave which will be sending/receiving the data.
    * @bitlen:  How many bits to write and read.
    * @dout:    Pointer to a string of bits to send out.  The bits are
    *       held in a byte array and are sent MSB first.
    * @din: Pointer to a string of bits that will be filled in.
    * @flags:   A bitwise combination of SPI_XFER_* flags.
    *
    * Returns: 0 on success, not 0 on failure
    */
    int  spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
            void *din, unsigned long flags);
    

2.4.5. SPI API使用示例

#include <spi.h>

static unsigned int bus = 0;
static unsigned int cs = 0;
static unsigned int mode = 0;
static int          bitlen = 32;
static uchar        dout[MAX_SPI_BYTES];
static uchar        din[MAX_SPI_BYTES];
#define SPI_MAX_SPEED_HZ       60000000   //1000000

int sstar_spi_xfer(int bus, int cs, unsigned int mod, int bitlen , uchar *dout, uchar *din )
{
    struct spi_slave *slave;
    int ret = 0;

    slave = spi_setup_slave(bus, cs, SPI_MAX_SPEED_HZ, mode);
    if (!slave) {
        printf("Invalid device %d:%d\n", bus, cs);
        return -EINVAL;
    }

    ret = spi_claim_bus(slave);
    if (ret)
        goto done;
    ret = spi_xfer(slave, bitlen, dout, din, SPI_XFER_BEGIN | SPI_XFER_END);
    if (ret) {
        printf("Error %d during SPI transaction\n", ret);
    } 
done:
    spi_release_bus(slave);
    return ret;
}