文章目录

  • 10. ecspi基本知识
  • 10.1 spi物理总线
  • 10.2 spi时序
  • 10.3 spi通信模式
  • 10.4 ecspi驱动框架
  • 10.5 关键数据结构
  • 10.5 ecspi设备驱动
  • 10.5.1 ecspi核心函数
  • 10.5.2 ecspi同步与互斥
  • 10.5.3 spi数据收发流程



10. ecspi基本知识

ecSPI是 Enhanced Configurable SPI 直译为增强可配置SPI,可以理解为是功能更强的SPI接口

ecspi驱动和i2c驱动非常像,它们都是按照总线、设备驱动模型编写。 ecspi总线驱动由NXP官方编写,ecspi设备驱动需要我们根据实际连接的设备编写对应的驱动,例如spi接口的oled屏,spi接口的外置AD转换新芯片等等。
这里主要介绍的是在 linux 下,实现 spi 传输的原理以及涉及到的 api 函数

10.1 spi物理总线

i2c总线和spi总线都可以挂载多个设备,spi支持一主多从,全双工通信,最大速率可达上百MHz。其中四根控制线包括:

  • SCK:时钟线,数据收发同步
  • MOSI:数据线,主设备数据发送、从设备数据接收
  • MISO:数据线,从设备数据发送,主设备数据接收
  • NSS、CS:片选信号线
    i2c通过i2c设备地址选择通信设备,而spi通过片选引脚选中要通信的设备。

10.2 spi时序

SPI子系统—ecspi驱动_嵌入式硬件

  • 起始信号:NSS(输出) 信号线由高变低
  • 停止信号:NSS(输出)信号由低变高
  • 数据传输:在 SCK的每个时钟周期 MOSI和 MISO同时传输一位数据,高/低位传输没有硬性规定
  • 传输单位:8位或16位 , 单位数量:允许无限长的数据传输

10.3 spi通信模式

总线空闲时 SCK 的时钟状态以及数据采样时刻

SPI子系统—ecspi驱动_驱动开发_02

  • 时钟极性 CPOL:指 SPI 通讯设备处于空闲状态时,SCK信号线的电平信号:
  • CPOL=0时,SCK在空闲状态时为低电平; CPOL=1时,SCK在空闲状态时为高电平
  • 时钟相位 CPHA:数据的采样的时刻:
  • CPHA=0时,数据在SCK时钟线的“奇数边沿”被采样; CPHA=1时,数据在SCK时钟线的“偶数边沿”被采样

10.4 ecspi驱动框架

ecspi设备驱动和i2c设备驱动非常相似,可对比学习。

SPI子系统—ecspi驱动_c语言_03

如框架图所示,ecspi可分为spi总线驱动和spi设备驱动。spi总线驱动已经由芯片厂商提供,我们适当了解其实现机制。 而spi设备驱动由我们自己编写,则需要明白其中的原理。spi设备驱动涉及到字符设备驱动、SPI核心层、SPI主机驱动,具体功能如下。

  • SPI核心层:提供SPI控制器驱动和设备驱动的注册方法、注销方法、SPI通信硬件无关接口函数。
  • SPI主机驱动:主要包含SPI硬件体系结构中适配器(spi控制器)的控制,用于产生SPI 读写时序。
  • SPI设备驱动:通过SPI主机驱动与CPU交换数据。

10.5 关键数据结构

这里对整个ecspi驱动框架所涉及的关键数据结构进行整理,可先跳过,后续代码中遇到这些数据结构时再回来看详细定义。

  1. spi_transfer
    在spi设备驱动程序中,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;

    dma_addr_t      tx_dma;
    dma_addr_t      rx_dma;
    struct sg_table tx_sg;
    struct sg_table rx_sg;

    unsigned        cs_change:1;
    unsigned        tx_nbits:3;
    unsigned        rx_nbits:3;
#define     SPI_NBITS_SINGLE        0x01 /* 1bit transfer */
#define     SPI_NBITS_DUAL          0x02 /* 2bits transfer */
#define     SPI_NBITS_QUAD          0x04 /* 4bits transfer */
    u8              bits_per_word;
    u16             delay_usecs;
    u32             speed_hz;

    struct list_head transfer_list;
};

传输结构体的成员较多,需要我们自己设置的很少,这里只介绍我们常用的配置项。

  • tx_buf: 发送缓冲区,用于指定要发送的数据地址。
  • rx_buf: 接收缓冲区,用于保存接收得到的数据,如果不接收不用设置或设置为NULL.
  • len: 要发送和接收的长度,根据SPI特性发送、接收长度相等。
  • tx_dma、rx_dma: 如果使用了DAM,用于指定tx或rx DMA地址。
  • bits_per_word: speed_hz,分别用于设置每个字节多少位、发送频率。如果我们不设置这些参数那么会使用默认的配置,也就是我初始化spi是设置的参数。
  1. spi_master
    spi_master会在SPI主机驱动中使用到。 spi_controller实际是一个宏,指向spi_controller结构体。
  2. spi_message
    总的来说spi_transfer结构体保存了要发送(或接收)的数据,而在SPI设备驱动中数据是以“消息”的形式发送。 spi_message是消息结构体,我们把它称为消息结构体,发送一个消息分四步, 依次为定义消息结构体、初始化消息结构体、“绑定”要发送的数据(也就是初始化好的spi_transfer结构)、执行发送。
    spi_message结构体定义如下所示:
struct spi_message {
	struct    list_head 	transfers;
	struct    spi_device    *spi;
	unsigned  				is_dma_mapped:1;
    /* completion is reported through a callback */
	void 		(*complete)(void *context);
	void 		*context;
	unsigned 	frame_length;
	unsigned 	actual_length;
	int status;
	/* 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;
};

spi_message结构体成员我们比较陌生,如果我们不考虑具体的发送细节我们可以不用了解这些成员的含义,因为spi_message的初始化以及“绑定”spi_transfer传输结构体都是由内核函数实现。 唯一要说明的是第二个成员“spi”,它是一个spi_device类型的指针,我们讲解spi_device结构体时说过,一个spi设备对应一个spi_device结构体,这个成员就是用于指定消息来自哪个设备。

10.5 ecspi设备驱动

10.5.1 ecspi核心函数

  1. ecspi设备的注册和注销函数ecspi设备的注册和注销函数分别在驱动入口和出口函数调用,这与平台设备驱动、I2C设备驱动相同。
    ecspi设备注册和注销函数如下:
int spi_register_driver(struct spi_driver *sdrv)
static inline void spi_unregister_driver(struct spi_driver *sdrv)
/*
*	参数:spi spi_driver类型的结构体(spi设备驱动结构体),一个spi_driver结构体就代表了一个ecspi设备驱动
*	   成功: 0    失败: 其他任何值都为错误码
*/

对比i2c设备的注册和注销函数,不难发现把“spi”换成“i2c”就是i2c设备的注册和注销函数了,并且用法相同。

  1. spi_setup()函数
    函数设置spi设备的片选信号、传输单位、最大传输速率等,函数中调用spi控制器的成员controller->setup(), 也就是spi_imx->bitbang.master->setup(),在函数spi_imx_probe()中我们将spi_imx_setup赋予该结构体。
int spi_setup(struct spi_device *spi)
/**
*	参数: spi spi_device spi设备结构体
*	成功: 0    失败: 其他任何值都为错误码
**/
  1. spi_message_init()函数,初始化spi_message
static inline void spi_message_init(struct spi_message *m)
{
    memset(m, 0, sizeof *m);
    spi_message_init_no_memset(m);
}
/**
*	m spi_message 结构体指针, spi_message是消息结构体,我们把它称为消息结构体,发送一个消息分四步,
*	依次为定义消息结构体、初始化消息结构体、“绑定”要发送的数据(也就是初始化好的spi_transfer结构)、执行发送。
**/
  1. spi_message_add_tail()函数
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队列的末尾。
**/

10.5.2 ecspi同步与互斥

  1. SPI同步传输数据
    阻塞当前线程进行数据传输,spi_sync()内部调用__spi_sync()函数,mutex_lock()和mutex_unlock()为互斥锁的加锁和解锁。
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
    int ret;

    mutex_lock(&spi->controller->bus_lock_mutex);
    ret = __spi_sync(spi, message);
    mutex_unlock(&spi->controller->bus_lock_mutex);

    return ret;
}
//阻塞当前线程,当message发送完成时结束阻塞。
  1. SPI异步传输数据
int spi_async(struct spi_device *spi, struct spi_message *message)
{
    ...
    ret = __spi_async(spi, message);
    ...
}
/*在驱动程序中调用async时不会阻塞当前进程,只是把当前message结构体添加到当前spi控制器成员queue的末尾。 
然后在内核中新增加一个工作,这个工作的内容就是去处理这个message结构体。*/

10.5.3 spi数据收发流程

该过程涉及到两个重要的结构体两个重要结构体 spi_message 和 spi_transfer

  1. 申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据,最后设置 len 成员变量,也就是要进行数据通信的长度。
  2. 使用 spi_message_init 函数初始化 spi_message。
  3. 使用spi_message_add_tail函数将前面设置好的 spi_transfer 添加到 spi_message 队列中。
  4. 使用 spi_sync 函数完成 spi 数据同步传输。
void spi_message_init(struct spi_message *m)
// 初始化 spi_message
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
// 将 spi_transfer 添加到 spi_message 队列中
int spi_sync(struct spi_device *spi, struct spi_message *message)
// 同步传输函数
int spi_async(struct spi_device *spi, struct spi_message *message)
// 异步传输函数