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);
else
kfree(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);