1520 字
8 分钟
Linux SPI驱动通用框架和编写细节

设备树#

  1. 追加节点: 需要确保UART2节点为disabled,因IO占用
/* Note: to enable ecspi3, uart2 node in imx6ul-14x14-evk.dtsi must be disabled */
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
spidev: icm20608@0 {
compatible = "alientek,icm20608";
spi-max-frequency = <8000000>;
reg = <0>;
};
};

驱动#

  1. 创建of_match_table,匹配设备树
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ /* Sential */ }
};
  1. 创建spi_driver
static struct spi_driver icm20608_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
},
.probe = icm20608_probe,
.remove = icm20608_remove,
};
  1. 填充proberemove
static int icm20608_probe(struct spi_device *spi)
{
printk("SPI driver and DTS match OK\r\n");
if (icm20608dev.major){
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
} else {
alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
}
printk("device reg ok, major = %d\r\n", icm20608dev.major);
icm20608dev.cdev.owner = THIS_MODULE;
cdev_init(&icm20608dev.cdev, &icm20608_fops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
/* SPI初始化 */
spi->mode = SPI_MODE_0; /* MODE0:CPOL = 0, CPHA = 0 */
spi_setup(spi);
icm20608dev.private_data = spi; /* 保存spi_driver至结构体 */
icm20608_reginit();
return 0;
}
static void icm20608_remove(struct spi_device *spi)
{
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
}
  1. 完善接收和发送函数
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret = -1;
struct spi_device *spi = (struct spi_device *)dev->private_data;
/* tx buffer */
unsigned char *txdata = kmalloc(1, GFP_KERNEL);
if (!txdata)
return -ENOMEM;
txdata[0] = reg | 0x80;
/* rx buffer */
/* 待接收数据长度为len,但SPI为全双工,第一次向从机发送准备读取消息时从机返回空数据,所以需要加一 */
unsigned char *rxdata = kmalloc(len + 1, GFP_KERNEL);
struct spi_transfer t = {
.tx_buf = txdata,
.rx_buf = rxdata,
.len = len + 1,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
if (ret){
return ret;
}
memcpy(buf, &rxdata[1], len);
return 0;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
int ret = -1;
struct spi_device *spi = (struct spi_device *)dev->private_data;
unsigned char *txdata = kmalloc(len + 1, GFP_KERNEL);
txdata[0] = reg & 0x7F;
memcpy(&txdata[1], buf, len);
struct spi_transfer t = {
.tx_buf = txdata,
.len = len + 1,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
if (ret){
return ret;
}
return 0;
}

注意:读取寄存器消息时不能将两条消息分开发送,因为ICM20608要求在读取数据时连续发送数据,即发送读取命令后立即接收从机返回的消息,CS片选信号在这期间始终拉低。如果分开两条信息发送,比如:

struct spi_transfer t[2] = {
{
.tx_buf = txdata,
.len = 1,
},
{
.rx_buf = rxdata,
.len = len,
},
};
...
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);

这么做会导致SPI在发送t[0]t[1]时在两条消息中间短暂拉高CS,导致SPI丢失数据。

完整驱动#

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/libata.h> /* 新版kernel不再支持ide.h */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
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; /* 温度原始值 */
};
struct icm20608_dev icm20608dev;
static int icm20608_open(struct inode *inode, struct file *filp);
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt);
static int icm20608_release(struct inode *inode, struct file *filp);
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len);
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len);
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg);
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value);
void icm20608_readdata(struct icm20608_dev *dev);
void icm20608_reginit(void);
static int icm20608_probe(struct spi_device *spi);
static void icm20608_remove(struct spi_device *spi);
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev;
return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
signed int data[7];
long err = 0;
struct icm20608_dev *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 int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret = -1;
struct spi_device *spi = (struct spi_device *)dev->private_data;
/* tx buffer */
unsigned char *txdata = kmalloc(1, GFP_KERNEL);
if (!txdata)
return -ENOMEM;
txdata[0] = reg | 0x80;
/* rx buffer */
/* 待接收数据长度为len,但SPI为全双工,第一次向从机发送准备读取消息时从机返回空数据,所以需要加一 */
unsigned char *rxdata = kmalloc(len + 1, GFP_KERNEL);
struct spi_transfer t = {
.tx_buf = txdata,
.rx_buf = rxdata,
.len = len + 1,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
if (ret){
return ret;
}
memcpy(buf, &rxdata[1], len);
return 0;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
int ret = -1;
struct spi_device *spi = (struct spi_device *)dev->private_data;
unsigned char *txdata = kmalloc(len + 1, GFP_KERNEL);
txdata[0] = reg & 0x7F;
memcpy(&txdata[1], buf, len);
struct spi_transfer t = {
.tx_buf = txdata,
.len = len + 1,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
if (ret){
return ret;
}
return 0;
}
static unsigned char 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)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 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_reginit(void)
{
u8 value = 0;
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#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 const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ /* Sential */ }
};
static struct file_operations icm20608_fops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
static int icm20608_probe(struct spi_device *spi)
{
printk("SPI driver and DTS match OK\r\n");
if (icm20608dev.major){
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
} else {
alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
}
printk("device reg ok, major = %d\r\n", icm20608dev.major);
icm20608dev.cdev.owner = THIS_MODULE;
cdev_init(&icm20608dev.cdev, &icm20608_fops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
spi->mode = SPI_MODE_0; /* MODE0:CPOL = 0, CPHA = 0 */
spi_setup(spi);
icm20608dev.private_data = spi;
icm20608_reginit();
return 0;
}
static void icm20608_remove(struct spi_device *spi)
{
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
}
static struct spi_driver icm20608_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
},
.probe = icm20608_probe,
.remove = icm20608_remove,
};
module_spi_driver(icm20608_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aki");
Linux SPI驱动通用框架和编写细节
https://arkmostima.pages.dev/posts/linuxspi/
作者
Aki
发布于
2025-04-02
许可协议
CC BY-NC-SA 4.0