SPI驱动理论与实例分析


前言

实例部分分为几个部分:
1、正点原子 imx6ull 陀螺仪模块驱动,非常经典,基本满足所有 SPI 驱动开发;
2、瑞芯微官方 SPI 设备驱动例程;
由于文章长度,以下两个实战例子放在另一篇文章。
3、高通 DACx0501 (ADC)模块驱动;
4、高通 IDT8V97003 (无线发射接收器)模块驱动。

一、SPI总线

SPI总线概述

SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
SPI总线的构成及信号类型如图1-1所示:
MOSI – 主设备数据输出,从设备数据输入 对应MOSI master output slave input
MISO – 主设备数据输入,从设备数据输出 对应MISO master input slave output
CLK – 时钟信号,由主设备产生
nCS – 从设备使能信号,由主设备控制
在这里插入图片描述

SPI总线时序

SPI接口在Master控制下产生的从设备使能信号和时钟信号,两个双向移位寄存器按位传输进行数据交换,传输数据高位在前(MSB first),低位在后。如下图所示,在CLK的下降沿上数据改变,上升沿一位数据被存入移位寄存器。
在这里插入图片描述
在一个SPI时钟周期内,会完成如下操作:
(1)Master通过MOSI线发送1位数据,同时Slave通过MOSI线读取这1位数据;
(2)Slave通过MISO线发送1位数据,同时Master通过MISO线读取这1位数据。
Master和Slave各有一个移位寄存器,而且这两个移位寄存器连接成环状。依照CLK的变化,数据以MSB first的方式依次移出Master寄存器和Slave寄存器,并且依次移入Slave寄存器和Master寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。如图所示:
在这里插入图片描述

SPI总线传输模式

SPI总线传输一共有4中模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。这四种模式的时序图如下图所示:
在这里插入图片描述
模式0:CPOL= 0,CPHA=0。CLK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换
模式1:CPOL= 0,CPHA=1。CLK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
模式2:CPOL= 1,CPHA=0。CLK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
模式3:CPOL= 1,CPHA=1。CLK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换
其中比较常用的模式是模式0和模式3。为了更清晰的描述SPI总线的时序,下面展现了模式0下的SPI时序图:
在这里插入图片描述

SPI总线的优缺点

(1) 在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
(2) SPI接口没有指定的流控制,没有应答机制确认是否接收到数据。

Linux SPI 框架

软件架构

Linux系统对spi设备具有很好的支持,linux系统下的spi驱动程序从逻辑上可以分为3个部分:

spi核心(SPI Core):SPI Core是Linux内核用来维护和管理spi的核心部分,SPI Core提供操作接口函数,允许一个spi master,spi driver和spi device初始化时在SPI Core中进行注册,以及推出时进行注销。
spi控制器驱动(SPI Master Driver):SPI Master针对不同类型的spi控制器硬件,实现spi总线的硬件访问操作。SPI Master通过接口函数向SPI Core注册一个控制器。
spi设备驱动(SPI Device Driver):SPI Driver是对应于spi设备端的驱动程序,通过接口函数向SPI Core进行注册,SPI Driver的作用是将spi设备挂接到spi总线上;
Linux的软件架构图如图所示:
在这里插入图片描述

初始化及退出流程

注册spi控制器

注册spi控制器到内核分为两个阶段:
第一个阶段,使用spi_alloc_master,分配一个spi_master的空间,具体流程如图2-2所示:
在这里插入图片描述
第二阶段,使用spi_register_master将第一阶段分配的spi_master注册到内核中,具体流程如所示:
在这里插入图片描述

注销spi控制器

spi控制器注销的流程如图所示:
在这里插入图片描述

关键数据结构

spi_device

struct spi_device {
    struct device       dev;              /*spi控制器对应的device结构
    struct spi_master   *master;          /*设备使用的master结构,挂在哪个主控制器下*/
    u32                 max_speed_hz;     /*通讯时钟最大频率*/
    u8                  chip_select;      /*片选号,每个master支持多个spi_device  */ 
    u8                  mode;
#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                    /* chipselect 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_NO_CS       0x40                    /* 1 dev/bus, no chipselect */
#define SPI_READY       0x80                    /* slave pulls low to pause */
    u8                  bits_per_word;          /*每个字长的比特数,默认是8*/
    int                 irq;
    void                *controller_state;       /*控制器状态*/
    void                *controller_data;        /*控制器数据*/
    char                modalias[SPI_NAME_SIZE]; /* 设备驱动的名字 */
    int                 cs_gpio;                 /* chip select gpio */
};

spi_device代表一个外围spi设备,由master controller driver注册完成后扫描BSP中注册设备产生的设备链表并向spi_bus注册产生。在内核中,每个spi_device代表一个物理的spi设备。
spi_driver

struct spi_driver {
        const struct spi_device_id *id_table;    /*支持的spi_device设备表*/
        int                     (*probe)(struct spi_device *spi);
        int                     (*remove)(struct spi_device *spi);
        void                    (*shutdown)(struct spi_device *spi);
        int                     (*suspend)(struct spi_device *spi, pm_message_t mesg);
        int                     (*resume)(struct spi_device *spi);
        struct device_driver    driver;
};

spi_driver 代表一个SPI protocol drivers,即外设驱动
spi_master

struct spi_master {
        struct device    dev;     /*spi控制器对应的device结构*/
        struct list_head list;    /*链表
        s16              bus_num; /*总线(或控制器编号)*/
        u16              num_chipselect; /*片选数量*/

        /* some SPI controllers pose alignment requirements on DMAable
         * buffers; let protocol drivers know about these requirements.
         */
        u16              dma_alignment;

        /* spi_device.mode flags understood by this controller driver */
        u16              mode_bits;            /* master支持的设备模式 */

        /* bitmask of supported bits_per_word for transfers */
        u32              bits_per_word_mask;

        /* other constraints relevant to this driver */
        u16              flags; /*用于限定某些限制条件的标志位
#define SPI_MASTER_HALF_DUPLEX  BIT(0)          /* can't do full duplex */
#define SPI_MASTER_NO_RX        BIT(1)          /* can't do buffer read */
#define SPI_MASTER_NO_TX        BIT(2)          /* can't do buffer write */

        /* lock and mutex for SPI bus locking */
        spinlock_t       bus_lock_spinlock;
        struct mutex     bus_lock_mutex;

        /* flag indicating that the SPI bus is locked for exclusive use */
        bool             bus_lock_flag;

        /* Setup mode and clock, etc (spi driver may call many times).
         *
         * IMPORTANT:  this may be called when transfers to another
         * device are active.  DO NOT UPDATE SHARED REGISTERS in ways
         * which could break those transfers.
         */
        int (*setup)(struct spi_device *spi); /*根据spi设备更新硬件配置。设置spi工作模式、时钟等*/

        int (*transfer)(struct spi_device *spi,
                        struct spi_message *mesg);  /*添加消息到队列的方法,此函数不可睡眠。它的职责是安排发生的传送并且调用注册的回调函数complete()*/

        /* called on release() to free memory provided by spi_master */
        void (*cleanup)(struct spi_device *spi);    /*cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。*/

        bool                   queued; 
        struct kthread_worker  kworker; /*用于管理数据传输消息队列的工作队列线程*/
        struct task_struct     *kworker_task;
        struct kthread_work    pump_messages; /*具体实现数据传输队列的工作队列*/
        spinlock_t             queue_lock;
        struct list_head       queue; /*该控制器的消息队列,所有等待传输的队列挂在该链表下*/
        struct spi_message     *cur_msg;/*当前正在处理的消息队列*/
        bool                   busy; /忙状态*/
        bool                   running; /*正在跑*/
        bool                   rt; 
        
        /*回调函数,正式发起传输前会被调用,用于准备硬件资源*/         
        int (*prepare_transfer_hardware)(struct spi_master *master);
        /*单个消息的原子传输回调函数,队列中每个消息都会回调一次该回调来完成传输工作*/
        int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); 
        /*清理回调函数*/
        int (*unprepare_transfer_hardware)(struct spi_master *master); 
        /* gpio chip select */
        int                    *cs_gpios;
};

spi_master代表一个spi控制器。

要完成和SPI设备的数据传输,还需要另外两个数据结构:spi_message和spi_transfer。
spi_message包含了一个的spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。
struct spi_message

struct spi_message {
        struct list_head   transfers;        /*spi_transfer链表队列,此次消息的传输段队列,一个消息可以包含多个传输段。*/

        struct spi_device  *spi;             /*传输的目的设备*/
        unsigned           is_dma_mapped:1;  /*如果为真,此次调用提供dma和cpu虚拟地址。*/
        /* completion is reported through a callback */
        void               (*complete)(void *context); /*异步调用完成后的回调函数*/
        void               *context;         /*回调函数的参数*/
        unsigned           actual_length;    /*实际传输的长度*/
        int                status;           /*该消息的发送结果,成功被置0,否则是一个负的错误码。*/

        /* for optional use by whatever driver currently owns the
         * spi_message ...  between calls to spi_async and then later
         * complete(), that's the spi_master controller driver.
         */
        struct list_head   queue;
        void               *state;
};

链表字段queue用于把该结构挂在代表控制器的spi_master结构的queue字段上,控制器上可以同时被加入多个spi_message进行排队。另一个链表字段transfers则用于链接挂在本message下的spi_tranfer结构。complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据。我们再来看看spi_transfer结构:
spi_transfer

struct spi_transfer {
        /* it's ok if tx_buf == rx_buf (right?)
         * for MicroWire, one buffer must be null
         * buffers must work with dma_*map_single() calls, unless
         *   spi_message.is_dma_mapped reports a pre-existing mapping
         */
        const void     *tx_buf;        /*发送缓冲区*/
        void            *rx_buf;       /*接收缓冲区*/
        unsigned        len;           /*缓冲区长度,tx和rx的大小(字节数)。指它们各自的大小*/
        dma_addr_t      tx_dma;        /*tx的dma地址*/
        dma_addr_t      rx_dma;        /*rx的dma地址*/
        unsigned        cs_change:1;   /*当前spi_transfer发送完成之后重新片选*/
        u8              bits_per_word; /*每个字长的比特数,0代表使用spi_device中的默认值8*/
        u16             delay_usecs;   /*发送完成一个spi_transfer后的延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息*/
        u32             speed_hz;      /*通信时钟。如果是0,使用默认值*/

#ifdef CONFIG_SPI_LOMBO
        struct lombo_spi_operate_para *esop;
#endif

        struct list_head transfer_list; /*用于链接到spi_message,用来连接的双向链接节点*/
};

首先,transfer_list链表字段用于把该transfer挂在一个spi_message结构中,tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址,len则是需要传输数据的长度,tx_dma和rx_dma则给出了dma模式下的缓冲区地址。原则来讲,spi_transfer才是传输的最小单位,之所以又引进了spi_message进行打包,我觉得原因是:有时候希望往spi设备的多个不连续的地址(或寄存器)一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程(工作队列)中完成的,不打包的后果就是会造成更多的进程切换,效率降低,延迟增加,尤其对于多个不连续地址的小规模数据传送而言就更为明显。

spi_board_info

struct spi_board_info {
        /* the device name and module name are coupled, like platform_bus;
         * "modalias" is normally the driver name.
         *
         * platform_data goes to spi_device.dev.platform_data,
         * controller_data goes to spi_device.controller_data,
         * irq is copied too
         */
        char            modalias[SPI_NAME_SIZE]; /*名字*/
        const void      *platform_data; /*平台数据*/
        void            *controller_data; /*控制器数据*/
        int             irq;

        /* slower signaling on noisy or low voltage boards */
        u32             max_speed_hz; /*最大速率*/
        u16             bus_num; /*spi总线编号*/
        u16             chip_select; /*片选*/
        u8              mode; /*模式 */
};

数据传输流程

整体的数据传输流程大致上是这样的:

  1. 定义一个spi_message结构;
  2. 用spi_message_init函数初始化spi_message;
  3. 定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段(tx_buf,rx_buf等);
  4. 通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
  5. 如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();(我调试外设时,只使用过spi_sync

传输示意图如图所示:
在这里插入图片描述
数据准备

static inline void spi_message_init(struct spi_message *m)
{
    memset(m, 0, sizeof *m);
    INIT_LIST_HEAD(&m->transfers);
}

初始化spi_message:清空message,初始化transfers链表头。

static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
    list_add_tail(&t->transfer_list, &m->transfers);
}

将spi_transfer加入到spi_message的链表尾部。

数据传输
SPI数据传输可以有两种方式:同步方式和异步方式。所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。而异步方式则正好相反,数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。
对于SPI控制器来说,要支持异步方式必须要考虑以下两种状况:

  • 对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个message,而这时上一个message还没有处理完。
  • 对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求,首先分析spi_sync()接口的实现流程,如图:
    在这里插入图片描述
    其次分析spi_async_locked接口的实现流程,如图所示:
    在这里插入图片描述
    spi_queued_transfer接口的实现流程如图所示:
    在这里插入图片描述
    spi_pump_messages函数的处理流程如图所示:
    在这里插入图片描述
    图中transfer_one_message是spi控制器驱动要实现的,主要功能是处理spi_message中的每个spi_transfer。
int spi_sync(struct spi_device *spi, struct spi_message *message)

同步传输会阻塞的等待 SPI 数据传输完成。

int spi_async(struct spi_device *spi, struct spi_message *message)

异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。

关键函数解析

struct spi_master *spi_alloc_master(struct device *dev, unsigned size)

功能:
分配一个spi_master结构体指针。
参数:
dev:spi控制器device指针
size :分配的driver-private data大小
返回值 :
成功,返回spi_master指针;否则返回NULL

int spi_register_master(struct spi_master *master) 

功能:注册spi控制器驱动到内核。
参数
master:spi_master指针
返回值:成功,返回0;否则返回错误码

void spi_unregister_master(struct spi_master *master) 

功能:注销spi控制器驱动。
参数:master:spi_master指针

还有一个每个 spi 驱动都要调用的 spi_setup( struct spi_device *spi )

int spi_setup(struct spi_device *spi)
{
	unsigned	bad_bits, ugly_bits;
	int		status;
 
	/* check mode to prevent that DUAL and QUAD set at the same time
	 */
	if (((spi->mode & SPI_TX_DUAL) && (spi->mode & SPI_TX_QUAD)) ||
		((spi->mode & SPI_RX_DUAL) && (spi->mode & SPI_RX_QUAD))) {
		dev_err(&spi->dev,"setup: can not select dual and quad at the same time\n");
		return -EINVAL;
	}
	/* if it is SPI_3WIRE mode, DUAL and QUAD should be forbidden
	 */
	if ((spi->mode & SPI_3WIRE) && (spi->mode &
		(SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD)))
		return -EINVAL;
	/* help drivers fail *cleanly* when they need options
	 * that aren't supported with their current master
	 */
	bad_bits = spi->mode & ~spi->master->mode_bits;
	ugly_bits = bad_bits &
		    (SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD);
	if (ugly_bits) {
		dev_warn(&spi->dev,
			 "setup: ignoring unsupported mode bits %x\n",
			 ugly_bits);
		spi->mode &= ~ugly_bits;
		bad_bits &= ~ugly_bits;
	}
	if (bad_bits) {
		dev_err(&spi->dev, "setup: unsupported mode bits %x\n",
			bad_bits);
		return -EINVAL;
	}
 
	if (!spi->bits_per_word)
		spi->bits_per_word = 8;
 
	status = __spi_validate_bits_per_word(spi->master, spi->bits_per_word);
	if (status)
		return status;
 
	if (!spi->max_speed_hz)
		spi->max_speed_hz = spi->master->max_speed_hz;
 
	if (spi->master->setup)
		status = spi->master->setup(spi);
 
	spi_set_cs(spi, false);
 
	dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s%u bits/w, %u Hz max --> %d\n",
			(int) (spi->mode & (SPI_CPOL | SPI_CPHA)),
			(spi->mode & SPI_CS_HIGH) ? "cs_high, " : "",
			(spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",
			(spi->mode & SPI_3WIRE) ? "3wire, " : "",
			(spi->mode & SPI_LOOP) ? "loopback, " : "",
			spi->bits_per_word, spi->max_speed_hz,
			status);
 
	return status;
}

spi_setup 函数实际上的工作就是去告知 SPI 主控器该怎么配置 SPI 的时序参数。
对于我们只需要在驱动加载之前,将配置好的 spi 设备指针交给 spi_setup 函数就行了。

实例分析:SPI 陀螺仪驱动

先上原理图:
在这里插入图片描述
实验用到的一些寄存器和位:
在这里插入图片描述
在这里插入图片描述

修改设备树

/* IO 复用 */
pinctrl_ecspi3: icm20608 {
 fsl,pins = <
     MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0  /* CS */
     MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
     MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1   /* MISO */
     MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1   /* MOSI */
 >;
};

/* 在SPI设备节点下,增加设备节点 */
&ecspi3 {
 fsl,spi-num-chipselects = <1>;            //片选数量设为 1,只接了一个设备嘛
 cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;    //一个SPI控制器上或许有多个cs,不够的话也可以使用普通的GPIO来控制,用到的话就加到设备节点里就行了
 pinctrl-names = "default";                //默认功能,没有添加其他功能的话,就是 SPI 功能了
 pinctrl-0 = <&pinctrl_ecspi3>;            //设置 IO 要使用的 pinctrl 子节点:pinctrl_ecspi3,就是上面那个
 status = "okay";

 spidev: icm20608@0 {
     compatible = "alientek,icm20608";
     spi-max-frequency = <8000000>;        //这里是8M,根据模块支持的最大时钟频率来改
     reg = <0>;    //spi设备是没有设备地址的, 这里是指使用spi控制器的cs-gpios里的第几个片选io
 };
};

SPI设备树参考资料:https://blog.csdn.net/jklinux/article/details/78701702

编写驱动程序

思路:

  1. 先完成驱动框架
  2. 再完成通信功能
  3. 最后加上业务
    按照之前的寄存器地址表,把寄存器先写好放设备头文件:
#ifndef ICM20608_H
#define ICM20608_H

#define ICM20608G_ID            0XAF    /* ID值 */
#define ICM20608D_ID            0XAE    /* ID值 */

/* ICM20608寄存器 
 *复位后所有寄存器地址都为0,除了
 *Register 107(0X6B) Power Management 1     = 0x40
 *Register 117(0X75) WHO_AM_I                 = 0xAF或0xAE
 */
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define    ICM20_SELF_TEST_X_GYRO        0x00
#define    ICM20_SELF_TEST_Y_GYRO        0x01
#define    ICM20_SELF_TEST_Z_GYRO        0x02
#define    ICM20_SELF_TEST_X_ACCEL       0x0D
#define    ICM20_SELF_TEST_Y_ACCEL       0x0E
#define    ICM20_SELF_TEST_Z_ACCEL       0x0F

/* 陀螺仪静态偏移 */
#define    ICM20_XG_OFFS_USRH            0x13
#define    ICM20_XG_OFFS_USRL            0x14
#define    ICM20_YG_OFFS_USRH            0x15
#define    ICM20_YG_OFFS_USRL            0x16
#define    ICM20_ZG_OFFS_USRH            0x17
#define    ICM20_ZG_OFFS_USRL            0x18

#define    ICM20_SMPLRT_DIV             0x19
#define    ICM20_CONFIG                 0x1A
#define    ICM20_GYRO_CONFIG            0x1B
#define    ICM20_ACCEL_CONFIG           0x1C
#define    ICM20_ACCEL_CONFIG2          0x1D
#define    ICM20_LP_MODE_CFG            0x1E
#define    ICM20_ACCEL_WOM_THR          0x1F
#define    ICM20_FIFO_EN                0x23
#define    ICM20_FSYNC_INT              0x36
#define    ICM20_INT_PIN_CFG            0x37
#define    ICM20_INT_ENABLE             0x38
#define    ICM20_INT_STATUS             0x3A

/* 加速度输出 */
#define    ICM20_ACCEL_XOUT_H           0x3B
#define    ICM20_ACCEL_XOUT_L           0x3C
#define    ICM20_ACCEL_YOUT_H           0x3D
#define    ICM20_ACCEL_YOUT_L           0x3E
#define    ICM20_ACCEL_ZOUT_H           0x3F
#define    ICM20_ACCEL_ZOUT_L           0x40

/* 温度输出 */
#define    ICM20_TEMP_OUT_H             0x41
#define    ICM20_TEMP_OUT_L             0x42

/* 陀螺仪输出 */
#define    ICM20_GYRO_XOUT_H            0x43
#define    ICM20_GYRO_XOUT_L            0x44
#define    ICM20_GYRO_YOUT_H            0x45
#define    ICM20_GYRO_YOUT_L            0x46
#define    ICM20_GYRO_ZOUT_H            0x47
#define    ICM20_GYRO_ZOUT_L            0x48

#define    ICM20_SIGNAL_PATH_RESET      0x68
#define    ICM20_ACCEL_INTEL_CTRL       0x69
#define    ICM20_USER_CTRL              0x6A
#define    ICM20_PWR_MGMT_1             0x6B
#define    ICM20_PWR_MGMT_2             0x6C
#define    ICM20_FIFO_COUNTH            0x72
#define    ICM20_FIFO_COUNTL            0x73
#define    ICM20_FIFO_R_W               0x74
#define    ICM20_WHO_AM_I               0x75

/* 加速度静态偏移 */
#define    ICM20_XA_OFFSET_H            0x77
#define    ICM20_XA_OFFSET_L            0x78
#define    ICM20_YA_OFFSET_H            0x7A
#define    ICM20_YA_OFFSET_L            0x7B
#define    ICM20_ZA_OFFSET_H            0x7D
#define    ICM20_ZA_OFFSET_L            0x7E

#endif

数据定义和数据结构:

#define ICM20608_CNT    1
#define ICM20608_NAME    "icm20608"

struct icm20608_dev {
    dev_t devid;                /* 设备号      */
    struct cdev cdev;            /* cdev     */
    struct class *class;        /* 类         */
    struct device *device;        /* 设备      */
    struct device_node    *nd;     /* 设备节点 */
    int major;                    /* 主设备号 */
    void *private_data;            /* 私有数据         */
    int cs_gpio;                /* 片选所使用的GPIO编号        */
    signed int gyro_x_adc;        /* 陀螺仪X轴原始值      */
    signed int gyro_y_adc;        /* 陀螺仪Y轴原始值        */
    signed int gyro_z_adc;        /* 陀螺仪Z轴原始值         */
    signed int accel_x_adc;        /* 加速度计X轴原始值     */
    signed int accel_y_adc;        /* 加速度计Y轴原始值    */
    signed int accel_z_adc;        /* 加速度计Z轴原始值     */
    signed int temp_adc;        /* 温度原始值             */
};

static struct icm20608_dev icm20608dev;

SPI 读写函数:

/*
 * 功能: 读取icm20608指定寄存器值,读取一个寄存器
 * dev:  icm20608设备;
reg:  要读取的寄存器
 * 返回: 读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    u8 data = 0;
    icm20608_read_regs(dev, reg, &data, 1);
    return data;
}
/*
 * 功能: 从icm20608读取多个寄存器数据

 * reg:  要读取的寄存器首地址
;val:  读取到的数据
;len:  要读取的数据长度

 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret;
    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    gpio_set_value(dev->cs_gpio, 0);                /* 片选拉低,选中ICM20608 */
    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);    /* 申请内存 */

    /* 第1次,发送要读取的寄存地址 */
    txdata[0] = reg | 0x80;                /* 写数据的时候寄存器地址bit8要置1 */
    t->tx_buf = txdata;                    /* 要发送的数据 */
    t->len = 1;                            /* 1个字节 */
    spi_message_init(&m);                /* 初始化spi_message */
    spi_message_add_tail(t, &m);        /* 将spi_transfer添加到spi_message队列 */
    ret = spi_sync(spi, &m);            /* 同步发送 */

    /* 第2次,读取数据 */
    txdata[0] = 0xff;                    /* 随便一个值,此处无意义 */
    t->rx_buf = buf;                    /* 读取到的数据 */
    t->len = len;                        /* 要读取的数据长度 */
    spi_message_init(&m);                /* 初始化spi_message */
    spi_message_add_tail(t, &m);        /* 将spi_transfer添加到spi_message队列 */
    ret = spi_sync(spi, &m);            /* 同步发送 */

    kfree(t);                            /* 释放内存 */
    gpio_set_value(dev->cs_gpio, 1);    /* 片选拉高,释放ICM20608 */

    return ret;
}

/*
 *
 功能: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * dev:  icm20608设备
;reg:  要写的寄存器
;data: 要写入的值

 */    

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 buf = value;
    icm20608_write_regs(dev, reg, &buf, 1);
}
/*
 * @description    : 向icm20608多个寄存器写入数据

 * reg:  要写入的寄存器首地址
;val:  要写入的数据缓冲区
;len:  要写入的数据长度

 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
    int ret;

    unsigned char txdata[len];
    struct spi_message m;
    struct spi_transfer *t;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);    /* 申请内存 */
    gpio_set_value(dev->cs_gpio, 0);            /* 片选拉低 */

    /* 第1次,发送要读取的寄存地址 */
    txdata[0] = reg & ~0x80;            /* 写数据的时候寄存器地址bit8要清零 */
    t->tx_buf = txdata;                    /* 要发送的数据 */
    t->len = 1;                            /* 1个字节 */
    spi_message_init(&m);                /* 初始化spi_message */
    spi_message_add_tail(t, &m);        /* 将spi_transfer添加到spi_message队列 */
    ret = spi_sync(spi, &m);            /* 同步发送 */

    /* 第2次,发送要写入的数据 */
    t->tx_buf = buf;                    /* 要写入的数据 */
    t->len = len;                        /* 写入的字节数 */
    spi_message_init(&m);                /* 初始化spi_message */
    spi_message_add_tail(t, &m);        /* 将spi_transfer添加到spi_message队列 */
    ret = spi_sync(spi, &m);            /* 同步发送 */

    kfree(t);                            /* 释放内存 */
    gpio_set_value(dev->cs_gpio, 1);    /* 片选拉高,释放ICM20608 */
    return ret;
}

业务函数:读取陀螺仪数据

/*
 功能: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
三轴加速度计和内部温度 */
/* 数据读取到设备!!!然后 read 操作的时候,就可以直接从设备结构体传输给应用层了 */
void icm20608_readdata(struct icm20608_dev *dev)
{
    unsigned char data[14];
    icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);

    dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); 
    dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); 
    dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); 
    dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); 
    dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); 
    dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);
    dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}

/* open 操作,把设备指针给文件指针,read 的时候可以直接从文件指针读 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev; /* 设置私有数据 */
    return 0;
}

/* read 操作,把文件指针强转为设备指针,然后数据全部拿走 */
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    signed int data[7];
    long err = 0;
    struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;

    icm20608_readdata(dev);
    data[0] = dev->gyro_x_adc;
    data[1] = dev->gyro_y_adc;
    data[2] = dev->gyro_z_adc;
    data[3] = dev->accel_x_adc;
    data[4] = dev->accel_y_adc;
    data[5] = dev->accel_z_adc;
    data[6] = dev->temp_adc;
    err = copy_to_user(buf, data, sizeof(data));    //传输给应用层
    return 0;
}

补充操作集

static int icm20608_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .read = icm20608_read,
    .release = icm20608_release,
};

业务函数:初始化陀螺仪设备

void icm20608_reginit(void)
{
    u8 value = 0;
    
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
    mdelay(50);
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
    mdelay(50);

    value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
    printk("ICM20608 ID = %#X\r\n", value);    

    icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);     /* 输出速率是内部采样率 */
    icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);     /* 陀螺仪±2000dps量程 */
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);     /* 加速度计±16G量程 */
    icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);         /* 陀螺仪低通滤波BW=20Hz */
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);     /* 打开加速度计和陀螺仪所有轴 */
    icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);     /* 关闭低功耗 */
    icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);        /* 关闭FIFO    */
}

完成 spi 驱动结构体

static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;

    /* 1、构建设备号 */
    if (icm20608dev.major) {
        icm20608dev.devid = MKDEV(icm20608dev.major, 0);
        register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
    } else {
        alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
    }

    /* 2、注册设备 */
    cdev_init(&icm20608dev.cdev, &icm20608_ops);
    cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);

    /* 3、创建类 */
    icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
    if (IS_ERR(icm20608dev.class)) {
        return PTR_ERR(icm20608dev.class);
    }

    /* 4、创建设备 */
    icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
    if (IS_ERR(icm20608dev.device)) {
        return PTR_ERR(icm20608dev.device);
    }

    /* 获取设备树中cs片选信号 */
    icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");//这一步可取的原因是设备树上使能的设备都会在 sysfs 文件系统中出现
    if(icm20608dev.nd == NULL) {
        printk("ecspi3 node not find!\r\n");
        return -EINVAL;
    } 

    /* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
    icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
    if(icm20608dev.cs_gpio < 0) {
        printk("can't get cs-gpio");
        return -EINVAL;
    }

    /* 3、设置GPIO1_IO20为输出,并且输出高电平 */
    ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /*初始化spi_device */
    spi->mode = SPI_MODE_0;    /*MODE0,CPOL=0,CPHA=0*/
    spi_setup(spi);
    icm20608dev.private_data = spi; /* 设置私有数据 */

    /* 初始化ICM20608内部寄存器 */
    icm20608_reginit();        
    return 0;
}



static int icm20608_remove(struct spi_device *spi)
{
    /* 删除设备 */
    cdev_del(&icm20608dev.cdev);
    unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

    /* 注销掉类和设备 */
    device_destroy(icm20608dev.class, icm20608dev.devid);
    class_destroy(icm20608dev.class);
    return 0;
}

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
    { .compatible = "alientek,icm20608" },
    { /* Sentinel */ }
};

/* SPI驱动结构体 */    
static struct spi_driver icm20608_driver = {
    .probe = icm20608_probe,
    .remove = icm20608_remove,
    .driver = {
            .owner = THIS_MODULE,
               .name = "icm20608",
               .of_match_table = icm20608_of_match, 
           },
    .id_table = NULL,
};

最后完成出入口函数:

static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}

static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);

编写测试程序:

int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    signed int databuf[7];
    unsigned char data[14];
    signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
    signed int accel_x_adc, accel_y_adc, accel_z_adc;
    signed int temp_adc;

    float gyro_x_act, gyro_y_act, gyro_z_act;
    float accel_x_act, accel_y_act, accel_z_act;
    float temp_act;

    int ret = 0;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("can't open file %s\r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, databuf, sizeof(databuf));
        if(ret == 0) {             /* 数据读取成功 */
            gyro_x_adc = databuf[0];
            gyro_y_adc = databuf[1];
            gyro_z_adc = databuf[2];
            accel_x_adc = databuf[3];
            accel_y_adc = databuf[4];
            accel_z_adc = databuf[5];
            temp_adc = databuf[6];

            /* 计算实际值 */
            gyro_x_act = (float)(gyro_x_adc)  / 16.4;
            gyro_y_act = (float)(gyro_y_adc)  / 16.4;
            gyro_z_act = (float)(gyro_z_adc)  / 16.4;
            accel_x_act = (float)(accel_x_adc) / 2048;
            accel_y_act = (float)(accel_y_adc) / 2048;
            accel_z_act = (float)(accel_z_adc) / 2048;
            temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;


            printf("\r\n原始值:\r\n");
            printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
            printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
            printf("temp = %d\r\n", temp_adc);
            printf("实际值:");
            printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
            printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
            printf("act temp = %.2f°C\r\n", temp_act);
        }
        usleep(100000); /*100ms */
    }
    close(fd);    /* 关闭文件 */    
    return 0;
}

测试程序用到了浮点数运算,大多数处理器都是支持硬件浮点数运算的,一般都是有使能这个模块,如果没有,就需要手动使能了。
测试结果:
在这里插入图片描述

实例:瑞芯微 SPI 设备驱动

瑞芯微官方SPI主机驱动源码:rk356x_linux/kernel/drivers/spi/spi-rockchip.c
SPI 设备测试源码:spi-rockchip-test.c
对于非原厂的 SPI 工程师我们来看 SPI 设备测试源码:spi-rockchip-test.c

首先是官方建议设备树:

/* dts config */
&spi0 {
    status = "okay";
    max-freq = <48000000>;   //spi internal clk, don't modify
    //dma-names = "tx", "rx";   //enable dma
    pinctrl-names = "default";  //pinctrl according to you board
    pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;
    spi_test@00 {
        compatible = "rockchip,spi_test_bus0_cs0";
        reg = <0>;   //chip select  0:cs0  1:cs1
        id = <0>;
        spi-max-frequency = <24000000>;   //spi output clock
        //spi-cpha;      not support
        //spi-cpol;     //if the property is here it is 1:clk is high, else 0:clk is low  when idle
    };

    spi_test@01 {
        compatible = "rockchip,spi_test_bus0_cs1";
        reg = <1>;
        id = <1>;
        spi-max-frequency = <24000000>;
        spi-cpha;
        spi-cpol;
    };
};

官方提供了实例,也提供了测试方法:

/* how to test spi */
echo write 0 10 255 > /dev/spi_misc_test
echo write 0 10 255 init.rc > /dev/spi_misc_test
echo read 0 10 255 > /dev/spi_misc_test
echo loop 0 10 255 > /dev/spi_misc_test
echo setspeed 0 1000000 > /dev/spi_misc_test

源码分析
依然是先从设定和数据结构开始:

#define MAX_SPI_DEV_NUM 8
#define SPI_MAX_SPEED_HZ    12000000

struct spi_test_data {
    struct device    *dev;
    struct spi_device *spi;
    char *rx_buf;
    int rx_len;
    char *tx_buf;
    int tx_len;
};

static struct spi_test_data *g_spi_test_data[MAX_SPI_DEV_NUM];
static u32 bit_per_word = 8;

SPI 读写函数:

int spi_write_slt(int id, const void *txbuf, size_t n)
{
    int ret = -1;
    struct spi_device *spi = NULL;
    struct spi_transfer     t = {
            .tx_buf         = txbuf,
            .len            = n,
            .bits_per_word = bit_per_word,
        };
    struct spi_message      m;

    if (id >= MAX_SPI_DEV_NUM)
        return ret;
    if (!g_spi_test_data[id]) {
        pr_err("g_spi.%d is NULL\n", id);
        return ret;
    } else {
        spi = g_spi_test_data[id]->spi;
    }

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spi_sync(spi, &m);
}

int spi_read_slt(int id, void *rxbuf, size_t n)
{
    int ret = -1;
    struct spi_device *spi = NULL;
    struct spi_transfer     t = {
            .rx_buf         = rxbuf,
            .len            = n,
            .bits_per_word = bit_per_word,
        };
    struct spi_message      m;

    if (id >= MAX_SPI_DEV_NUM)
        return ret;
    if (!g_spi_test_data[id]) {
        pr_err("g_spi.%d is NULL\n", id);
        return ret;
    } else {
        spi = g_spi_test_data[id]->spi;
    }

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spi_sync(spi, &m);
}

利用基本读写函数拼凑的读写函数:

int spi_write_then_read_slt(int id, const void *txbuf, unsigned n_tx,
        void *rxbuf, unsigned n_rx)
{
    int ret = -1;
    struct spi_device *spi = NULL;

    if (id >= MAX_SPI_DEV_NUM)
        return ret;
    if (!g_spi_test_data[id]) {
        pr_err("g_spi.%d is NULL\n", id);
        return ret;
    } else {
        spi = g_spi_test_data[id]->spi;
    }

    ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx);
    return ret;
}

int spi_write_and_read_slt(int id, const void *tx_buf,
            void *rx_buf, size_t len)
{
    int ret = -1;
    struct spi_device *spi = NULL;
    struct spi_transfer     t = {
            .tx_buf         = tx_buf,
            .rx_buf         = rx_buf,
            .len            = len,
        };
    struct spi_message      m;

    if (id >= MAX_SPI_DEV_NUM)
        return ret;
    if (!g_spi_test_data[id]) {
        pr_err("g_spi.%d is NULL\n", id);
        return ret;
    } else {
        spi = g_spi_test_data[id]->spi;
    }

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spi_sync(spi, &m);
}

业务函数:配合测试用的 write 函数

static ssize_t spi_test_write(struct file *file,
            const char __user *buf, size_t n, loff_t *offset)
{
    int argc = 0, i;
    char tmp[64];
    char *argv[16];
    char *cmd, *data;
    unsigned int id = 0, times = 0, size = 0;
    unsigned long us = 0, bytes = 0;
    char *txbuf = NULL, *rxbuf = NULL;
    ktime_t start_time;
    ktime_t end_time;
    ktime_t cost_time;

    memset(tmp, 0, sizeof(tmp));
    if (copy_from_user(tmp, buf, n))
        return -EFAULT;
    cmd = tmp;
    data = tmp;

    while (data < (tmp + n)) {
        data = strstr(data, " ");
        if (!data)
            break;
        *data = 0;
        argv[argc] = ++data;
        argc++;
        if (argc >= 16)
            break;
    }

    tmp[n - 1] = 0;

    if (!strcmp(cmd, "setspeed")) {
        int id = 0, val;
        struct spi_device *spi = NULL;

        sscanf(argv[0], "%d", &id);
        sscanf(argv[1], "%d", &val);

        if (id >= MAX_SPI_DEV_NUM)
            return n;
        if (!g_spi_test_data[id]) {
            pr_err("g_spi.%d is NULL\n", id);
            return n;
        } else {
            spi = g_spi_test_data[id]->spi;
        }
        spi->max_speed_hz = val;
    } else if (!strcmp(cmd, "write")) {
        char name[64];
        int fd;
        mm_segment_t old_fs = get_fs();

        sscanf(argv[0], "%d", &id);
        sscanf(argv[1], "%d", &times);
        sscanf(argv[2], "%d", &size);
        if (argc > 3) {
            sscanf(argv[3], "%s", name);
            set_fs(KERNEL_DS);
        }

        txbuf = kzalloc(size, GFP_KERNEL);
        if (!txbuf) {
            printk("spi write alloc buf size %d fail\n", size);
            return n;
        }

        if (argc > 3) {
            fd = ksys_open(name, O_RDONLY, 0);
            if (fd < 0) {
                printk("open %s fail\n", name);
            } else {
                ksys_read(fd, (char __user *)txbuf, size);
                ksys_close(fd);
            }
            set_fs(old_fs);
        } else {
            for (i = 0; i < size; i++)
                txbuf[i] = i % 256;
        }

        start_time = ktime_get();
        for (i = 0; i < times; i++)
            spi_write_slt(id, txbuf, size);
        end_time = ktime_get();
        cost_time = ktime_sub(end_time, start_time);
        us = ktime_to_us(cost_time);

        bytes = size * times * 1;
        bytes = bytes * 1000 / us;
        printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

        kfree(txbuf);
    } else if (!strcmp(cmd, "read")) {
        sscanf(argv[0], "%d", &id);
        sscanf(argv[1], "%d", &times);
        sscanf(argv[2], "%d", &size);

        rxbuf = kzalloc(size, GFP_KERNEL);
        if (!rxbuf) {
            printk("spi read alloc buf size %d fail\n", size);
            return n;
        }

        start_time = ktime_get();
        for (i = 0; i < times; i++)
            spi_read_slt(id, rxbuf, size);
        end_time = ktime_get();
        cost_time = ktime_sub(end_time, start_time);
        us = ktime_to_us(cost_time);

        bytes = size * times * 1;
        bytes = bytes * 1000 / us;
        printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
        print_hex_dump(KERN_ERR, "SPI RX: ",
                   DUMP_PREFIX_OFFSET,
                   16,
                   1,
                   rxbuf,
                   size,
                   1);

        kfree(rxbuf);
    } else if (!strcmp(cmd, "loop")) {
        sscanf(argv[0], "%d", &id);
        sscanf(argv[1], "%d", &times);
        sscanf(argv[2], "%d", &size);

        txbuf = kzalloc(size, GFP_KERNEL);
        if (!txbuf) {
            printk("spi tx alloc buf size %d fail\n", size);
            return n;
        }

        rxbuf = kzalloc(size, GFP_KERNEL);
        if (!rxbuf) {
            kfree(txbuf);
            printk("spi rx alloc buf size %d fail\n", size);
            return n;
        }

        for (i = 0; i < size; i++)
            txbuf[i] = i % 256;

        start_time = ktime_get();
        for (i = 0; i < times; i++) {
            spi_write_and_read_slt(id, txbuf, rxbuf, size);
            if (memcmp(txbuf, rxbuf, size)) {
                printk("spi loop test fail\n");
                break;
            }
        }

        end_time = ktime_get();
        cost_time = ktime_sub(end_time, start_time);
        us = ktime_to_us(cost_time);

        bytes = size * times;
        bytes = bytes * 1000 / us;
        printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

        kfree(txbuf);
        kfree(rxbuf);
    } else if (!strcmp(cmd, "config")) {
        int width;

        sscanf(argv[0], "%d", &width);

        if (width == 16)
            bit_per_word = 16;
        else
            bit_per_word = 8;
    } else {
        printk("echo id number size > /dev/spi_misc_test\n");
        printk("echo write 0 10 255 > /dev/spi_misc_test\n");
        printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");
        printk("echo read 0 10 255 > /dev/spi_misc_test\n");
        printk("echo loop 0 10 255 > /dev/spi_misc_test\n");
        printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");
        printk("echo config 8 > /dev/spi_misc_test\n");
    }

    return n;
}

完成文件操作集:

static const struct file_operations spi_test_fops = {
    .write = spi_test_write,
};

static struct miscdevice spi_test_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "spi_misc_test",
    .fops = &spi_test_fops,
};

所以可以明确的是,这个 spi 设备只能写,不能读。

完成 spi 驱动结构体:

static int rockchip_spi_test_probe(struct spi_device *spi)
{
    int ret;
    int id = 0;
    struct spi_test_data *spi_test_data = NULL;

    if (!spi)
        return -ENOMEM;

    if (!spi->dev.of_node)
        return -ENOMEM;

    spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);
    if (!spi_test_data) {
        dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");
        return -ENOMEM;
    }
    spi->bits_per_word = 8;

    spi_test_data->spi = spi;
    spi_test_data->dev = &spi->dev;

    ret = spi_setup(spi);
    if (ret < 0) {
        dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");
        return -1;
    }

    if (of_property_read_u32(spi->dev.of_node, "id", &id)) {
        dev_warn(&spi->dev, "fail to get id, default set 0\n");
        id = 0;
    }

    g_spi_test_data[id] = spi_test_data;

    printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);

    return ret;
}

static int rockchip_spi_test_remove(struct spi_device *spi)
{
    printk("%s\n", __func__);
    return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id rockchip_spi_test_dt_match[] = {
    { .compatible = "rockchip,spi_test_bus0_cs1", },
    {},
};
MODULE_DEVICE_TABLE(of, rockchip_spi_test_dt_match);
#endif /* CONFIG_OF */

static struct spi_driver spi_rockchip_test_driver = {
    .driver = {
        .name    = "spi_test",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(rockchip_spi_test_dt_match),
    },
    .probe = rockchip_spi_test_probe,
    .remove = rockchip_spi_test_remove,
};

编写驱动出入口函数:

static int __init spi_rockchip_test_init(void)
{
    int ret = 0;

    misc_register(&spi_test_misc);
    ret = spi_register_driver(&spi_rockchip_test_driver);
    return ret;
}
module_init(spi_rockchip_test_init);

static void __exit spi_rockchip_test_exit(void)
{
    misc_deregister(&spi_test_misc);
    return spi_unregister_driver(&spi_rockchip_test_driver);
}
module_exit(spi_rockchip_test_exit);

总结

  1. 先完成驱动框架
  2. 再完成通信功能
  3. 最后加上业务

SPI 的传输速度比 IIC 快,但是在嵌入式中属于低速设备,一般用于读取传感器模块数据或者自动化模块的控制。所以业务基本集中在 write 和 read 操作中。
先完成通信功能,整个模块的开发基本就完成了90%。

参考资料:https://blog.csdn.net/daocaokafei/article/details/114377669

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值