一 Linux 下 SPI 驱动框架

SPI 驱动框架分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口。

1.1 SPI 主机驱动

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,Linux 内核使用 spi_master结构体表示 SPI 主机驱动。

1.2 SPI 设备驱动

Linux 内核使用 spi_driver 结构体来表示 spi 设备 驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver 。
spi_driver 注册和注销函数原型如下
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
spi_driver 注册示例程序
1 /* probe 函数 */
2 static int xxx_probe(struct spi_device *spi)
3 {
4 /* 具体函数内容 */
5 return 0;
6 }
7
8 /* remove 函数 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具体函数内容 */
12 return 0;
13 }
14 /* 传统匹配方式 ID 列表 */
15 static const struct spi_device_id xxx_id[] = {
16 {"xxx", 0},
17 {}
18 };
19
20 /* 设备树匹配列表 */
21 static const struct of_device_id xxx_of_match[] = {
22 { .compatible = "xxx" },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驱动结构体 */
27 static struct spi_driver xxx_driver = {
28 .probe = xxx_probe,
29 .remove = xxx_remove,
30 .driver = {
31 .owner = THIS_MODULE,
32 .name = "xxx",
33 .of_match_table = xxx_of_match,
34 },
35 .id_table = xxx_id,
36 };
37
38 /* 驱动入口函数 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(&xxx_driver);
42 }
43
44 /* 驱动出口函数 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(&xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);

1.3 SPI 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,和 platform、I2C 等驱动一样。
SPI 设备和驱动的匹配函数为 spi_match_device,定义在 drivers/spi/spi.c 文件中。

二、I.MX6U SPI 主机驱动

SPI 主机驱动一般都由 SOC 厂商编写好了。I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c。

三、SPI 设备驱动编写流程

3.1SPI 设备信息描述

1、在设备树中根据所使用的 IO 来创建或修改 pinctrl 子节点

        pinctrl_ecspi3: ecspi3grp{fsl,pins = <MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20         0x10b0 //片选信号MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK    0x10b1 //CLK信号MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI            0x10b1 //MOSI信号MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO            0x10b1 //MISO信号>;};

2、SPI 设备节点的创建与修改

&ecspi3{fsl,spi-num-chipselects = <1>;                  /* 一个片选 */cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;    /* 片选引脚,软件片选 */pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi3>;//设置 IO 要使用的 pinctrl 子节点status = "okay";/* 对应的icm20608子节点 */spidev0: icm20608@0 {/* @后面的0表示icm20608连接到 ECSPI3 的第 0 个通道上*/reg = <0>;//表示icm20608连接到 ECSPI3 的第 0 个通道上compatible = "alientek,icm20608";//兼容属性spi-max-frequency = <8000000>;/* SPI时钟频率8MHZ */};
};

3.2 SPI 设备数据收发处理流程
SPI 设备驱动的核心是 spi_driver,当我们向 Linux 内 核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。

SPI 数据传输步骤如下:

①、申请并初始化 spi_transfer结构体 ,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是 要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message 。
③、使用 spi_message_add_tail 函数将前面设置好的 spi_transfer 添加到 spi_message 队列中。
④、使用 spi_sync 函数完成 SPI 数据同步传输。
 
SPI 数据读写操作示例代码
 

/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len) {int ret;struct spi_message m;struct spi_transfer t = {.tx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
}/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len) {int ret;struct spi_message m;struct spi_transfer t = {.rx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
}

四、驱动示例代码
4.1 设备树修改

在 iomuxc 节点中添加一个新的pinctrl子节点来描述 ICM20608 所使用的 SPI 引脚。(同3.1)
在 ecspi3 节点追加 icm20608 子节点。(同3.1)
 

4.2示例代码
icm20608 spi接口六轴传感器驱动代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include "icm20608reg.h"#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"/* 设备结构体 */
struct icm20608_dev
{int major;int minor;dev_t devid;struct cdev cdev;struct class *class;struct device *device;void *private_data;int cs_gpio;struct device_node *nd;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设备数据收发处理****************************//* SPI读寄存器 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{u8 data = 0;struct spi_device *spi = (struct spi_device *)dev->private_data;//gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */data = reg | 0x80;spi_write_then_read(spi, &data, 1, buf, len);// spi_write(spi, &data, 1); /* 发送要读取的寄存器地址 */// spi_read(spi, buf, len);  /*读取数据*///gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */return 0;
}/* SPI写寄存器 */
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{u8 data = 0;u8 *txdata;struct spi_device *spi = (struct spi_device *)dev->private_data;txdata = kzalloc(len + 1, GFP_KERNEL);//gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */txdata[0] = reg & ~0x80;         /* 要写的寄存器地址 */memcpy(&txdata[1], buf, len);    /* 要发送的数据拷贝到txdata里面 */spi_write(spi, txdata, len + 1); /* 发送要写的寄存器地址 */// spi_write(spi, &data, 1); /* 发送要写的寄存器地址 */// spi_write(spi, buf, len); /* 发送要写的寄存器地址 */kfree(txdata);//gpio_set_value(dev->cs_gpio, 1); /* 拉高片选 */return 0;
}/*ICM20608读取单个寄存器 */
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写一个寄存器 */
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的数据,读取原始数据,包括三轴陀螺仪、*              : 三轴加速度计和内部温度。* @param - dev   : ICM20608设备* @return      : 无。*/
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]);
}/***************************spi设备的初始化****************************/
/* ICM20608初始化 */
void icm20608_reginit(struct icm20608_dev *dev)
{u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80); /* 复位,复位后为0x40,睡眠模式 */mdelay(50);icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠,自动选择时钟 */mdelay(50);value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);printk("ICM20608 ID=%#X\r\n", value);value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);printk("ICM20_PWR_MGMT_1=%#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  */
}/*************************************文件操作集***********************************/static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev; /* 设置私有数据 */return 0;
}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;
}static const struct file_operations icm20608_fops = {.owner = THIS_MODULE,.open = icm20608_open,.read = icm20608_read,.release = icm20608_release,
};/***************************SPI 设备驱动注册注销流程****************************//*
* @description : spi 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
* @param - spi : spi 设备
*/
static int icm20608_probe(struct spi_device *spi)
{int ret = 0;printk("icm20608_probe\r\n");/* 搭建字符设备驱动框架,在/dev/下 *//* 2,注册字符设备 */icm20608dev.major = 0; /* 由系统分配主设备号 */if (icm20608dev.major){ /* 给定主设备号 */icm20608dev.devid = MKDEV(icm20608dev.major, 0);ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);}else{ /* 没有给定主设备号 */ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);icm20608dev.minor = MINOR(icm20608dev.devid);}if (ret < 0){printk("icm20608 chrdev_region err!\r\n");goto fail_devid;}printk("icm20608 major=%d, minor=%d\r\n", icm20608dev.major, icm20608dev.minor);/* 3,注册字符设备 */icm20608dev.cdev.owner = THIS_MODULE;cdev_init(&icm20608dev.cdev, &icm20608_fops);ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);if (ret < 0){goto fail_cdev;}/* 4,自动创建设备节点 */icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);if (IS_ERR(icm20608dev.class)){ret = PTR_ERR(icm20608dev.class);goto fail_class;}icm20608dev.device = device_create(icm20608dev.class, NULL,icm20608dev.devid, NULL, ICM20608_NAME);if (IS_ERR(icm20608dev.device)){ret = PTR_ERR(icm20608dev.device);goto fail_device;}/* 初始化spi_device */spi->mode = SPI_MODE_0;spi_setup(spi);/* 设置icm20608dev的私有数据为spi */icm20608dev.private_data = spi;/* 初始化icm20608 寄存器 */icm20608_reginit(&icm20608dev);return 0;
fail_gpio:fail_device:class_destroy(icm20608dev.class);
fail_class:cdev_del(&icm20608dev.cdev);
fail_cdev:unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:return ret;
}/*
* @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
* @param - spi : spi 设备
*/
static int icm20608_remove(struct spi_device *spi)
{/* 1,删除字符设备 */cdev_del(&icm20608dev.cdev);/* 2,注销设备号 */unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);/* 3,摧毁设备 */device_destroy(icm20608dev.class, icm20608dev.devid);/* 4,摧毁类 */class_destroy(icm20608dev.class);/*5.释放片选IO */gpio_free(icm20608dev.cs_gpio);return 0;
}/* 传统匹配方式 ID 列表 */
struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{.compatible = "alientek,icm20608",},{}};/* SPI 驱动结构体 */
struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.name = "icm20608",.owner = THIS_MODULE,.of_match_table = icm20608_of_match,},.id_table = icm20608_id,};/*驱动入口函数*/
static int __init icm20608_init(void)
{int ret = 0;ret = spi_register_driver(&icm20608_driver);return ret;
}/*驱动出口函数*/
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

驱动代码中主要包含四个部分

1)SPI设备数据收发处理
2)传感器数据的读取,spi设备的初始化
3)  文件操作集
4)  SPI 设备驱动注册注销流程

示例代码中spi数据收发处理使用的函数为spi_write_then_read函数和spi_write函数

int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx)
{static DEFINE_MUTEX(lock);int          status;struct spi_message   message;struct spi_transfer x[2];u8         *local_buf;/* Use preallocated DMA-safe buffer if we can.  We can't avoid* copying here, (as a pure convenience thing), but we can* keep heap costs out of the hot path unless someone else is* using the pre-allocated buffer or the transfer is too large.*/if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx),GFP_KERNEL | GFP_DMA);if (!local_buf)return -ENOMEM;} else {local_buf = buf;}spi_message_init(&message);memset(x, 0, sizeof(x));if (n_tx) {x[0].len = n_tx;spi_message_add_tail(&x[0], &message);}if (n_rx) {x[1].len = n_rx;spi_message_add_tail(&x[1], &message);}memcpy(local_buf, txbuf, n_tx);x[0].tx_buf = local_buf;x[1].rx_buf = local_buf + n_tx;/* do the i/o */status = spi_sync(spi, &message);if (status == 0)memcpy(rxbuf, x[1].rx_buf, n_rx);if (x[0].tx_buf == buf)mutex_unlock(&lock);elsekfree(local_buf);return status;
}
EXPORT_SYMBOL_GPL(spi_write_then_read);static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{struct spi_transfer    t = {.tx_buf       = buf,.len     = len,};struct spi_message m;spi_message_init(&m);spi_message_add_tail(&t, &m);return spi_sync(spi, &m);
}

从上面两个函数看出,这两个函数也是通过调用以下三个函数完成数据的传输

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

Linux SPI 驱动示例相关推荐

  1. linux i2c adapter 增加设备_「正点原子Linux连载」第六十二章Linux SPI驱动实验(一)...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南>关注官方微信号公众号,获取更多资料:正点原子 第六十二章Linux SPI驱动实验 上一 ...

  2. 【正点原子Linux连载】第六十二章 Linux SPI驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. linux spi不使用框架,Linux spi驱动框架之执行流程

    Linux spi驱动架构由三部分构成:SPI核心层.SPI控制器驱动层.和SPI设备驱动程序. 1.SPI核心层: SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义.SPI控制器驱 ...

  4. Linux spi驱动分析(四)----SPI设备驱动(W25Q32BV)

    一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下: 1.1.基本特性 该芯片最大支持10 ...

  5. Linux spi驱动分析----SPI设备驱动(W25Q32BV)

    转载地址:http://blog.chinaunix.net/uid-25445243-id-4026974.html 一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它 ...

  6. Linux spi驱动框架之执行流程-nuc970-att7022

    转载地址:http://blog.csdn.net/chenliang0224/article/details/51236499 Linux spi驱动架构由三部分构成:SPI核心层.SPI控制器驱动 ...

  7. Linux SPI驱动框架(2)——控制器驱动层

    SPI控制器驱动层   上节中,讲了SPI核心层的东西,这一部分,以全志平台SPI控制器驱动为例,对SPI控制器驱动进行说明. SPI控制器驱动,即SPI硬件控制器对应的驱动,核心部分需要实现硬件SP ...

  8. Linux SPI驱动学习——调用SPI读写函数

    Linux SPI驱动学习--调用SPI读写函数 博客说明 开发环境 1. 基于W25Q16型FLASH来看SPI的读写函数 附录 博客说明 撰写日期 2019.10.25 完稿日期 未完稿 最近维护 ...

  9. Linux SPI驱动框架(3)——设备驱动层

    SPI设备驱动层   Linux SPI驱动框架(1)和(2)中分别介绍了SPI框架中核心层,和控制器驱动层.其实实际开发过程中,不是IC原厂工程师比较少会接触控制器驱动层,设备驱动层才是接触比较多的 ...

最新文章

  1. GraphNVP | 用于分子图生成的可逆流模型
  2. sentinel安装
  3. AlertBox 弹出层(信息提示框)效果
  4. C#中往数据库插入/更新时候关于NUll空值的处理
  5. 近期计算机视觉算法竞赛汇总—总奖池超300万人民币
  6. linux 4433端口,linux – 使用相同的openssl端口443绑定不同端口的apache ssl端口
  7. teechart for java_TeeChart java控件
  8. 10015---MySQL--innodb_flush_log_at_trx_commit参数
  9. HTML背景渐变圆圈,6种CSS3平滑过渡的渐变背景颜色
  10. 启动电容与运行电容-电容-嵌入式开发-物联网开发
  11. 2017lol服务器维修时间,lol年3月16日要维护多长时间2017_lol3.16维护时间公告_好特教程...
  12. ZOJ 3380 Patchouli's Spell Cards(概率DP)
  13. 2022SIE中国民办教育展将亮相上海,赋能行业高质量发展!
  14. abel533 / ECharts
  15. 【IM】极光简单的聊天测试
  16. win10VS+QT/OpenCV/灰点相机配置及其使用
  17. android程序qq登陆,手把手带你实现QQ登录
  18. SystemUI---RAT
  19. 盛极而衰:回顾 Netscape Navigator 浏览器的一生
  20. Qt实现360桌面精灵

热门文章

  1. 最详细的阿里云服务器Centos 安装jdk,tomcat,mysql,redis之jdk安装与卸载
  2. 32位浮点数表示方法
  3. 使用C#对Excel文件进行内容筛选
  4. 通过WebBrowser显示Word文档的方法
  5. Python语言是编译型语言还是解释型语言?(Python程序执行过程)
  6. 用迅雷下载Genymotion的virtual device的办法
  7. 亚马逊云科技X创业邦:专访图灵量子创始人金贤敏
  8. mysql 中间件 租户管理_多租户的设计与实现 - 《Gaea - MySQL 中间件》 - 书栈网 · BookStack...
  9. 交换机三种端口模式的特点
  10. JavaScript, mysql 总结