Regmap子系统之六轴传感器驱动-编写icm20607.c驱动
(一)在驱动中要操作很多芯片相关的寄存器,所以需要先新建一个icm20607.h的头文件,用来定义相关寄存器值。
#ifndef ICM20607_H #define ICM20607_H /*************************************************************** 文件名 : icm20607.h 描述 : ICM20607寄存器地址描述头文件 ***************************************************************/ #define ICM20608G_ID 0XAF /* ID值 */ #define ICM20608D_ID 0XAE /* ID值 */ #define ICM20607_ID 0X05 /* ICM20607寄存器 *复位后所有寄存器地址都为0,除了 *Register 107(0x41) Power Management 1 *Register 117(0x05) WHO_AM_I *Register 26(0x80) CONFIG */ /* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */ /* ICM20607 SELF TEST GYRO Modify 0x ->5x */ #define ICM20_SELF_TEST_X_GYRO 0x50 #define ICM20_SELF_TEST_Y_GYRO 0x51 #define ICM20_SELF_TEST_Z_GYRO 0x52 #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 |
(二)icm20607.c文件编写
(1)头文件引用
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/uaccess.h> // 包含用户空间数据访问函数的头文件 #include <linux/cdev.h> //包含字符设备头文件 #include <linux/device.h> #include <linux/delay.h> #include <linux/spi/spi.h> #include <linux/regmap.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include "icm20607.h" |
(2)创建相关宏定义和变量
#define ICM20607_REG_WHOAMI 0x75 #define ICM20607_WHOAMI_VALUE 0xAF #define DEVICE_NAME "icm20607" // 设备名称 static dev_t dev_num; //分配的设备号 int major; //主设备号 int minor; //次设备号 struct icm20607_dev { struct spi_device *spi_dev; /* spi设备 */ dev_t dev_num; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ struct device_node *nd; /* 设备节点 */ int cs_gpio; /* 片选所使用的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 regmap *spi_regmap; /* regmap */ struct regmap_config regmap_config; }; |
(3)驱动模块的入口和出口
module_init(icm20607_init); module_exit(icm20607_exit); |
(4)icm20607_init和icm20607_exit实现
static int __init icm20607_init(void) { int ret; ret = spi_register_driver(&icm20607_driver); if (ret < 0) { pr_err("Failed to register ICM20607 driver: %d\n", ret); return ret; } pr_info("ICM20607 SPI device driver loaded\n"); return 0; } static void __exit icm20607_exit(void) { spi_unregister_driver(&icm20607_driver); pr_info("ICM20607 SPI device driver unloaded\n"); } |
在入口函数中调用了spi_register_driver函数,来注册SPI总线驱动程序。在出口函数中调用了spi_unregister_driver函数,来注销驱动程序。
spi_register_driver函数原型如下:
int spi_register_driver(struct spi_driver *drv); |
该函数接受一个指向struct spi_driver结构体的指针作为参数,并返回一个整数值,表示注册是否成功。struct spi_driver结构体定义了SPI总线驱动程序的属性和回调函数。
以下是struct spi_driver结构体的常见成员:
driver:struct device_driver类型的成员,描述了驱动程序的基本信息,如名称、总线类型等。
probe:指向驱动程序的探测函数的指针。探测函数在与设备匹配时被调用,用于初始化设备并注册相关资源。
remove:指向驱动程序的移除函数的指针。移除函数在设备被卸载时被调用,用于清理和释放相关资源。
id_table:指向struct spi_device_id数组的指针,用于匹配驱动程序和设备之间的关联关系。
probe_new:指向新版的探测函数的指针。新版探测函数支持更多功能,并可以替代旧版的probe函数。
remove_new:指向新版的移除函数的指针。新版移除函数支持更多功能,并可以替代旧版的remove函数。
通过调用spi_register_driver函数并传入正确配置的struct spi_driver结构体,可以将SPI总线驱动程序注册到Linux内核,使其能够接收和处理SPI设备的相关操作。
(5)spi_driver类型结构体定义
static struct spi_driver icm20607_driver = { .driver = { .name = "icm20607", .owner = THIS_MODULE, .of_match_table = icm20607_of__match, }, .probe = icm20607_probe, .remove = icm20607_remove, }; |
(6)icm20607_of__match实现,用来与设备树中的compatible匹配
static const struct of_device_id icm20608_of_match[] = { { .compatible = "icm20607" }, { /* Sentinel */ } }; |
(7)remove函数实现,执行icm20607设备的清理操作
static int icm20607_remove(struct spi_device *spi) { struct icm20607_dev *icm20607dev = spi_get_drvdata(spi); // 在此处执行 ICM20607 设备的清理操作 //删除cdev cdev_del(&icm20607dev->cdev); //注销设备号 unregister_chrdev_region(icm20607dev->dev_num, 1); //注销设备 device_destroy(icm20607dev->class, icm20608dev->dev_num); //注销类 class_destroy(icm20607dev->class); //删除regmap regmap_exit(icm20607dev->spi_regmap); pr_info("ICM20607 SPI device removed successfully\n"); return 0; } |
(8)probe函数实现,此处简略描述regmap注册的过程:
static int icm20607_probe(struct spi_device *spi) { int ret; unsigned int whoami; struct icm20607_dev *icm20607dev; //分配icm20607dev对象的空间 icm20607dev = devm_kzalloc(&spi->dev, sizeof(*icm20607dev), GFP_KERNEL); if(!icm20607dev) return -ENOMEM; // 创建 ICM20607 设备的 regmap icm20608dev->spi_regmap = regmap_init_spi(spi, &spi_regmap_config); if (IS_ERR(icm20607dev->spi_regmap)) { dev_err(&spi->dev, "Failed to initialize regmap: %ld\n", PTR_ERR(icm20607dev->spi_regmap)); return PTR_ERR(icm20607dev->spi_regmap); } ...... /*初始化spi_device */ icm20607dev->spi_dev = spi; spi->mode = SPI_MODE_0; spi_setup(spi); /* 初始化ICM20607内部寄存器 */ icm20607_reginit(icm20607dev); /* 保存icm20607dev结构体 */ spi_set_drvdata(spi, icm20607dev); pr_info("ICM20607 SPI device probed successfully\n"); return 0; } |
probe函数中首先使用devm_kzalloc函数分配了icm20607dev的结构体空间,然后使用regmap_init_spi函数创建regmap实例,再进行spi控制器的初始化和配置,最后对ICM20607的内部寄存器进行配置。
其中regmap_init_spi函数中传入了“&spi_regmap_config”参数,前边有提到这个是用来配置regmap对象的,下边我们看这个参数是如何定义的。
(9)spi_regmap_config的定义
static const struct regmap_config spi_regmap_config = { .reg_bits = 8, .val_bits = 8, .read_flag_mask = 0x80, .reg_read = icm20607_spi_read, .reg_write = icm20607_spi_write, .max_register = ICM20607_REG_WHOAMI, }; |
可以看到这其中规定了寄存器地址的位数,存储寄存器的位数,读寄存器掩码,读寄存器函数,写寄存器函数,最大寄存器地址。
(10)读写寄存器函数实现
static int icm20607_spi_read(struct icm20608_dev *dev, unsigned int reg, unsigned int *val) { return regmap_read(dev->spi_regmap, reg, val); } static int icm20607_spi_write(struct icm20608_dev *dev, unsigned int reg, unsigned int val) { return regmap_write(dev->spi_regmap, reg, val); } |
可以看出读写函数非常简单明了,直接使用regmap_read和regmap_write函数即可。
①regmap_read函数原型如下:
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val); |
该函数用于从给定的寄存器地址(reg)读取数据,并将读取的值存储在val指向的变量中。map参数是一个指向struct regmap的指针,表示寄存器映射对象。返回值为0表示读取成功,否则表示读取失败。
②regmap_write函数原型如下:
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val); |
该函数用于向给定的寄存器地址(reg)写入数据(val)。map参数是一个指向struct regmap的指针,表示寄存器映射对象。返回值为0表示写入成功,否则表示写入失败。