嵌入式linux SPI驱动

        SPI驱动框架和I2C很类似,都是分为主机控制器和设备驱动,主机控制器也就是SOC的SPI控制器接口。这个也是半导体原厂已经写好了的,所以我们只需要负责种类繁多的SPI设备驱动,使用控制器提供的API函数对驱动设备的寄存器进行配置。

SPI主机驱动

        linux内核中使用spi_master表示SPI主机驱动,i2c_adapter代表I2C主机驱动。spi_master是个结构体,定义在在 include/linux/spi/spi.h 文件中,内容如下(有缩减):

         第393行,transfer函数,和i2c_algorithm中的master_xfer函数一样,控制器数据传输函数。

        第434行,tranfer_one_message函数,也用于SPI数据传输,用于发送一个spi_message,SPI的数据会打包成spi_message,然后以队列方式发送出去。也就是SPI主机端最终会通过transfer函数与SPI设备进行通讯,这个transfer函数就不需要我们管了,这个是原厂已经写好的了,我们只要会用就好。

        SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向linux内核注册spi_master。

1,spi_master的申请与释放

        spi_alloc_master函数用于申请spi_master,函数原型如下:

         dev:设备,一般是platform_device中的dev成员变量

        size:私有数据大小,可以通过spi_master_get_devdata函数获取到这些私有数据

        返回值:申请到的spi_master

        spi_master的释放通过spi_master_put函数完成,当我们删除一个SPI主机驱动的时候就需要释放掉前面申请的spi_master,spi_master_put函数原型如下:                 master:要释放的spi_master。

2,spi_master的注册与注销

        当spi_master初始化完成以后就需要将其注册到linux内核,spi_master注册函数为spi_register_master,原型如下:

         master:要注册的spi_master

        返回值:0代表成功,负值代表失败

        如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:

 SPI设备驱动

        spi设备驱动也和i2c设备驱动也很类似,linux内核使用spi_driver结构体来表示spi设备驱动,我们再编写SPI设备驱动的时候需要实现spi_driver。spi_driver结构体定义在include/linux/spi/spi.h 文件中,结构体内容如下:

         可以看出,spi_driver,i2c_driver和platform_driver基本一样,当SPI设备和驱动匹配成功以后probe函数就会执行。

        同样的,spi_driver初始化完后需要向linux内核注册,spi_driver注册函数为spi_register-driver,函数原型如下:

         sdrv:要注册的spi_driver。

        返回值:0代表注册成功,负值代表注册失败

        注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销,函数原型如下:     

         spi_driver注册实例程序如下:

SPI设备和驱动匹配过程     

        SPI设备和驱动的匹配过程是由SPi总线来完成的,这点和platform,I2C等驱动一样,SPI总线为spi_bus_type定义如下:

         可以看出spi_match_device就是SPI设备和驱动的匹配函数。

         spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。

SPI设备驱动编写流程

        1,IO的pinctrl子节点创建与修改

        首先肯定是根据所使用的IO来创建或修改pinctrl子节点,唯独要注意的是我们需要注意检查相应的IO有没有被其他设备使用

        2,spi设备节点的创建与修改

        采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:

         309 行,设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备。

        第 310 行,设置“cs-gpios”属性,也就是片选信号为 GPIO4_IO09

         311 行,设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。

        312 行,设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。

        313 行,将 ecspi1 节点的“status”属性改为“okay”。

        315~320 行,ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述其设备信息。第 315 行的“m25p80@0”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上。这个要根据自己的具体硬件来设置。

        318 行,SPI 设备的 compatible 属性值,用于匹配设备驱动。

        第 319 行,“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz

        320 行,reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道,和“m25p80@0”后面的“0”一样。

        3,SPi设备数据收发处理流程

        SPI设备驱动的核心是spi_master。我们想内核注册成功spi_driver以后就可以使用SPI核心层提供的API函数对设备进行读写操作了。首先是spi_transfer结构体,此结构体用于描述SPI传输信息,结构体内容如下:

         609 行,tx_buf 保存着要发送的数据。

        610 行,rx_buf 用于保存接收到的数据。

        第 611 行,len 是要进行传输的数据长度,SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。

        spi_transfer需要组织成soi_massage,spi_massage也是一个结构体,内容如下:

        在使用spi_massage之前需要对其进行初始化,spi_massage初始化函数为spi_massage_init。函数原型如下:

         m:要初始化的spi_massage

        spi_message初始化完成以后需要将spi_transfer添加到spi_message队列中,这里我们需用到spi_message-add_tail,函数原型如下:

        t:要添加到队列中的api_transfer        

        m:spi_transfer要加入的spi_massage 

        spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

        spi:要进行数据传输的spi_device        

        message:要传输的spi_message 

  •  申请并初始化 spi_transfer,设置 spi_transfer tx_buf 成员变量,tx_buf 为要发送的数据。然后设置rx_buf成员变量,rx_buf保存着接收到的数据,最后设置len成员变量,也就是要进行数据通信的长度
  • 使用spi_message_init函数初始化spi_message
  • 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到api_message队列中
  • 使用spi_sync函数完成SPI数据同步传输
/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{

	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	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);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}

	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
	
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

out2:
	kfree(rxdata);					/* 释放内存 */
out1:	
	kfree(t);						/* 释放内存 */
	
	return ret;
}

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	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);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}
	
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器数据拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值