spidev相关

背景

有时候会发现,一些外挂的spi的flash或者外设用了spidev这个驱动,没有适配特定的驱动,到底spidev是个啥呢?

spidev:SPI userspace API
下面是内核官方文档解释:

SPI devices have a limited userspace API, supporting basic half-duplex
read() and write() access to SPI slave devices. Using ioctl() requests,
full duplex transfers and device I/O configuration are also available.

它主要包括核内的spidev驱动,相当于spi设备的万能通用驱动, 类似于通用phy驱动,主要代码:

  • drivers/spi/spidev.c
  • include/uapi/linux/spi/spidev.h

使用

dts匹配驱动

设备匹配通用的spidev驱动即可,其他属性参数根据设备硬件特性来写,都是通用的spi属性,和spi.h头文件里面的对应,也可以通过下面的ioctl的接口去重写或读取。

&spi0 {
    status = "okay";
    num-cs = <1>;
    ...

    aaa: bbb@0 {
        status = "okay";
        reg = <0>;
        compatible = "spidev";	/* 使用"spidev"匹配spidev驱动即可 */
        ...
        spi-cpol;
        spi-cpha;
        spi-tx-bus-width = <1>;
        spi-rx-bus-width = <1>;
        spi-max-frequency = <5000000>;
    };
};

正常情况下会有以下几种节点,对于SPI总线 B 上的片选为 C 的设备而言:

  • /dev/spidevB.C

    character special device, major number 153 with a dynamically chosen minor device number. This is the node that userspace programs will open, created by “udev” or “mdev”.

  • /sys/devices/.../spiB.C

    as usual, the SPI device node will be a child of its SPI master controller.

  • /sys/class/spidev/spidevB.C

    created when the “spidev” driver binds to that device. (Directory or symlink, based on whether or not you enabled the “deprecated sysfs files” Kconfig option.)

核外操作

核外操作主要针对/dev/spidevB.C字符设备节点,通过open() ioctl() read()``close()等接口操作

相关参数读写

/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE			_IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE			_IOW(SPI_IOC_MAGIC, 1, __u8)

/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST		_IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST		_IOW(SPI_IOC_MAGIC, 2, __u8)

/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD	_IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD	_IOW(SPI_IOC_MAGIC, 3, __u8)

/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ		_IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ		_IOW(SPI_IOC_MAGIC, 4, __u32)

/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32		_IOR(SPI_IOC_MAGIC, 5, __u32)
#define SPI_IOC_WR_MODE32		_IOW(SPI_IOC_MAGIC, 5, __u32)

注: 支持配置的相关SPI mode(SPI_IOC_RD_MODE32 / SPI_IOC_WR_MODE32)

#define	SPI_CPHA		_BITUL(0)	/* clock phase */
#define	SPI_CPOL		_BITUL(1)	/* 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_MODE_X_MASK		(SPI_CPOL|SPI_CPHA)

#define	SPI_CS_HIGH		_BITUL(2)	/* chipselect active high? */
#define	SPI_LSB_FIRST		_BITUL(3)	/* per-word bits-on-wire */
#define	SPI_3WIRE		_BITUL(4)	/* SI/SO signals shared */
#define	SPI_LOOP		_BITUL(5)	/* loopback mode */
#define	SPI_NO_CS		_BITUL(6)	/* 1 dev/bus, no chipselect */
#define	SPI_READY		_BITUL(7)	/* slave pulls low to pause */
#define	SPI_TX_DUAL		_BITUL(8)	/* transmit with 2 wires */
#define	SPI_TX_QUAD		_BITUL(9)	/* transmit with 4 wires */
#define	SPI_RX_DUAL		_BITUL(10)	/* receive with 2 wires */
#define	SPI_RX_QUAD		_BITUL(11)	/* receive with 4 wires */
#define	SPI_CS_WORD		_BITUL(12)	/* toggle cs after each word */
#define	SPI_TX_OCTAL		_BITUL(13)	/* transmit with 8 wires */
#define	SPI_RX_OCTAL		_BITUL(14)	/* receive with 8 wires */
#define	SPI_3WIRE_HIZ		_BITUL(15)	/* high impedance turnaround */

数据读写操作

对于数据的读写支持半双工和全双工。
基本的read()/write()仅仅支持半双工,而此时片选是不可用的;
全双工需要使用SPI_IOC_MESSAGE(N)ioctl请求,不需要去激活片选

/* not all platforms use <asm-generic/ioctl.h> or _IOC_TYPECHECK() ... */
#define SPI_MSGSIZE(N) \
	((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
		? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])

注:

  • 每次I/O请求有个默认的传输字节数限制:1 page,但可以使用模块参数进行修改
  • 目前暂不知道异步I/O操作,都是同步的

参考代码

以数据传输为例:

static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
    int ret;
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = len,
        .delay_usecs = delay,
        .speed_hz = speed,
        .bits_per_word = bits,
    };

    if (mode & SPI_TX_OCTAL)
        tr.tx_nbits = 8;
    else if (mode & SPI_TX_QUAD)
        tr.tx_nbits = 4;
    else if (mode & SPI_TX_DUAL)
        tr.tx_nbits = 2;
    if (mode & SPI_RX_OCTAL)
        tr.rx_nbits = 8;
    else if (mode & SPI_RX_QUAD)
        tr.rx_nbits = 4;
    else if (mode & SPI_RX_DUAL)
        tr.rx_nbits = 2;
    if (!(mode & SPI_LOOP)) {
        if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))
            tr.rx_buf = 0;
        else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))
            tr.tx_buf = 0;
    }

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 1)
        pabort("can't send spi message");
}

参考

  • 测试用例:内核源码/tools/spi/spidev_test.cspidev_fdx.c(全双工)
  • 内核文档:内核源码/Documentation/spi/spidev.rst