Linux下SPI驱动简介
??SPI驱动框架和I2C驱动框架是十分相似的,不同的是因为SPI是通过片选引脚来选择从机设备的,因此SPI不再需要像I2C那样先进行寻址操作(查询从机地址)后再进行对应寄存器的数据交互,并且SPI是全双工通信,通信速率要远高于I2C。
??但是SPI显然占用的硬件资源也比I2C要多,并且SPI没有了像I2C那样指定的流控制(例如开始、停止信号)和没有了像I2C应当机制(导致无法确认数据是否接收到了)。
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适配器(控制器)
struct spi_master {
struct device dev;
struct list_head list;
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg);
int *cs_gpios;
......
??transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。 ??transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。
SPI 主机驱动(或设配器驱动)的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。 spi_master 申请与释放: struct spi_master *spi_alloc_master(struct device *dev, unsigned size) void spi_master_put(struct spi_master *master) spi_master 的注册与注销: int spi_register_master(struct spi_master *master) void spi_unregister_master(struct spi_master *master)
SPI设备驱动
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
??可以看出,spi_driver 和 i2c_driver、 platform_driver 基本一样,当 SPI 设备和驱动匹配成功以后 probe 函数就会执行。
spi_driver注册示例
示例代码 62.1.1.3 spi_driver 注册示例程序
1
2 static int xxx_probe(struct spi_device *spi)
3 {
4
5 return 0;
6 }
7 8
9 static int xxx_remove(struct spi_device *spi)
10 {
11
12 return 0;
13 }
14
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 { }
24 };
25
26
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);
SPI 设备和驱动匹配过程
??SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、 I2C 等驱动一样, SPI总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:
131 struct bus_type spi_bus_type = {
132 .name = "spi",
133 .dev_groups = spi_dev_groups,
134 .match = spi_match_device,
135 .uevent = spi_uevent,
136 };
??可以看出, SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
99 static int spi_match_device(struct device *dev, struct device_driver *drv)
100 {
101 const struct spi_device *spi = to_spi_device(dev);
102 const struct spi_driver *sdrv = to_spi_driver(drv);
103
104
105 if (of_driver_match_device(dev, drv))
106 return 1;
107
108
109 if (acpi_driver_match_device(dev, drv))
110 return 1;
111
112 if (sdrv->id_table)
113 return !!spi_match_id(sdrv->id_table, spi);
114
115 return strcmp(spi->modalias, drv->name) == 0;
116 }
??spi_match_device 函数和 i2c_match_device 函数对于设备和驱动的匹配过程基本一样。 ??第 105 行, of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。 ??第 109 行, acpi_driver_match_device 函数用于 ACPI 形式的匹配。 ??第 113 行, spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。 ??第 115 行,比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。
编写imc20608六轴传感器SPI驱动
编写可以参考NXP官方spi-imx.c驱动程序和\Documentation\devicetree\bindings\spi\目录下的fsl-imx-cspi.txt、 spi-bus.txt绑定文档
硬件原理图: 具体引脚复用和什么配置参数不懂的可以查看I2C驱动那篇。
设备树编写操作
第一步:在pinctrl_ecspi3子节点下添加属性(寄存器地址及配置参数),如下。
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10B0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10B1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10B1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10B1
>;
};
第二步:在ecspi3节点上追加属性,如下。
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
spidev0: icm20608@0 {
reg = <0>;
compatible = "alientek,icm20608";
spi-max-frequency = <8000000>;
};
};
重新加载设备树文件,在/sys/bus/spi/devices目录下查看,如下。
具体的imc20608驱动程序
icm20608.c
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.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_gpio.h>
#include <linux/gpio.h>
#include <linux/atomic.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/atomic.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include "icm20608.h"
struct icm20608_dev {
dev_t devid;
int major;
int minor;
int count;
char* name;
struct cdev cdev;
struct class *class;
struct device *device;
void *private_data;
struct device_node *nd;
int cs_gpio;
signed int gyro_x_adc;
signed int gyro_y_adc;
signed int gyro_z_adc;
signed int accel_x_adc;
signed int accel_y_adc;
signed int accel_z_adc;
signed int temp_adc;
};
static struct icm20608_dev icm20608dev;
void icm20608_readdata(struct icm20608_dev *dev);
static int icm20608_open(struct inode *inde,struct file *filp) {
filp->private_data = &icm20608dev;
return 0;
}
static int icm20608_release(struct inode *inde,struct file *filp) {
return 0;
}
static ssize_t icm20608_read(struct file *filp,char __user *buf,
size_t count,loff_t *ppos) {
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 ssize_t icm20608_write(struct file *filp,const char __user *buf,
size_t count,loff_t *ppos) {
struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
return 0;
}
#if 0
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;
}
txdata[0] = reg | 0x80;
t->tx_buf = txdata;
t->rx_buf = rxdata;
t->len = len+1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
if(ret) {
goto out2;
}
memcpy(buf, rxdata+1, len);
out2:
kfree(rxdata);
out1:
kfree(t);
return ret;
}
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len) {
int ret = 0;
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) {
kfree(txdata);
}
txdata[0] = reg & 0XEF;
memcpy(&txdata[1], buf, len);
t->tx_buf = txdata;
t->len = 1 + len;
spi_message_init(&m);
spi_message_add_tail(t,&m);
ret = spi_sync(spi,&m);
if(ret < 0) {
printk("spi_sync error!\r\n");
}
kfree(txdata);
kfree(t);
return ret;
}
#endif
#if 1
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) {
struct spi_device *spi = (struct spi_device *)dev->private_data;
u8 reg_addr = 0;
reg_addr = reg | 0X80;
spi_write_then_read(spi, ®_addr, 1, buf, len);
return 0;
}
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len) {
struct spi_device *spi = (struct spi_device *)dev->private_data;
u8 *txdata;
txdata = kzalloc(len +1, GFP_KERNEL);
txdata[0] = reg & 0XEF;
memcpy(&txdata[1], buf, len);
spi_write(spi, txdata, len+1);
return 0;
}
#endif
static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {
u8 data = 0;
icm20608_read_regs(dev,reg, &data, 1);
return data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {
icm20608_write_regs(dev, reg, &value, 1);
}
void icm20608_readdata(struct icm20608_dev *dev) {
unsigned char data[14] = { 0 };
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]);
}
void icm20608_reg_init(struct icm20608_dev *dev) {
u8 value = 0;
icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0X80);
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 = 0X%X\r\n",value);
value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);
printk("ICM20_PWR_MGMT_1 = 0X%X\r\n",value);
icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
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);
}
static const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.release = icm20608_release,
.read = icm20608_read,
.write = icm20608_write,
};
static int icm20608_probe(struct spi_device *spi) {
int ret = 0;
printk("icm20608_probe successful!\r\n");
icm20608dev.name = "icm20608";
icm20608dev.major = 0;
icm20608dev.count = 1;
if(icm20608dev.major) {
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
ret = register_chrdev_region(icm20608dev.devid,icm20608dev.count,icm20608dev.name);
} else {
alloc_chrdev_region(&icm20608dev.devid,0,icm20608dev.count,icm20608dev.name);
icm20608dev.major = MAJOR(icm20608dev.devid);
icm20608dev.minor = MINOR(icm20608dev.devid);
}
if (ret < 0) {
printk("icm20608 chrdev region failed!\r\n");
goto fail_devid;
}
printk("icm20608 major = %d, minor = %d \r\n",icm20608dev.major,icm20608dev.minor);
cdev_init(&icm20608dev.cdev, &icm20608_ops);
ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, icm20608dev.count);
if(ret < 0) {
printk("icm20608 add char device failed!\r\n");
goto fail_cdev;
}
icm20608dev.class = class_create(THIS_MODULE,icm20608dev.name);
if(IS_ERR(icm20608dev.class)) {
ret = PTR_ERR(icm20608dev.class);
printk("icm20608 class_create failed!\r\n");
goto fail_class;
}
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, icm20608dev.name);
if(IS_ERR(icm20608dev.device)) {
ret = PTR_ERR(icm20608dev.device);
printk("icm20608 device_create failed!\r\n");
goto fail_device;
}
icm20608dev.private_data = spi;
spi->mode = SPI_MODE_0;
icm20608_reg_init(&icm20608dev);
return 0;
fail_setouput:
gpio_free(icm20608dev.cs_gpio);
fail_requestgpio:
fail_getgpio:
device_destroy(icm20608dev.class,icm20608dev.devid);
fail_device:
class_destroy(icm20608dev.class);
fail_class:
cdev_del(&icm20608dev.cdev);
fail_cdev:
unregister_chrdev_region(icm20608dev.devid,icm20608dev.count);
fail_devid:
return ret;
}
static int icm20608_remove(struct spi_device *spi) {
printk("icm20608_remove finish\r\n");
gpio_free(icm20608dev.cs_gpio);
device_destroy(icm20608dev.class,icm20608dev.devid);
class_destroy(icm20608dev.class);
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid,icm20608dev.count);
return 0;
}
static const struct spi_device_id icm20608_id[] = {
{"alientek,icm20608", 0},
{}
};
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{}
};
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 = icm20608_id,
};
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);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");
??这里解释一下上图:红色框框是使用软件片选cs并属性名称为“cs-gpio”不带s时,内核就不能自动给我们调用相应API控制片选cs,那么此时我们就要自己手动控制cs信号了。 蓝色框框是在我们使用“cs-gpios”带s时,不能直接这样使用,因为在每次调用API后片选cs都会被内核置高(无效状态),那么读寄存器内容通信就被间断了(发送完寄存器地址后读寄存器内容期间片选cs是不能置为无效状态的)。
icm20608.h
#ifndef _BSP_ICM20608_H
#define _BSP_ICM20608_H
#define ICM20608G_ID 0XAF
#define ICM20608D_ID 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
icm20608APP.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd, ret;
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;
if(argc != 2) {
printf("ERROR USAGE!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0) {
printf("file %s open failed!\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);
}
close(fd);
return 0;
}
操作及现象
|