Linux SPI驱动(Linux驱动开发篇)

1.linux SPI驱动

  • 由于裸机的驱动迎合驱动的分离和分层的思想,分为SPI主机驱动(接口驱动)和SPI设备驱动.
    这种思想的好处,请看我写的另外一篇文章Linux驱动的分离和分层
    其中上面说的裸机驱动请看这篇文章裸机SPI驱动

  • 但是裸机的驱动是没有加入操作系统的

  • 本文也是遵循Linux驱动的分离和分层的思想,因此linux内核把SPI驱动分为两个部分SPI总线驱动SPI设备驱动

  • 总线驱动:SOC的SPI控制器驱动,也叫做SPI适配器驱动

  • 设备驱动:SPI设备驱动就是针对具体的SPI设备而编写的驱动

1.1 SPI总线驱动

  • 首先来看一下SPI 总线,在讲 platform 的时候就说过,platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。想了解platform的知识,请看我这篇文章platform平台模型简述以及这篇文章platform总线
  • SPI总线驱动重点是 SPI 适配器(也就是 SOC 的SPI 接口控制器)驱动
  • SPI总线驱动,或者说 SPI适配器驱动的主要核心:
  1. 申请spi_master,并且初始化它。和i2c中的i2c_adapter一样。
  2. 然后设置transfer函数。和 i2c_algorithm 中的 master_xfer 函数一样控制器数据传输函数
  3. 完成以后通过 spi_register_master函数向Linux内核注册设置好的spi_master
/*
@	定义在include/linux/spi/spi.h文件中
@	 spi_master 结构体 
*/
 struct spi_master { 
     struct device   dev; 
     struct list_head list; 
... 
     s16         bus_num; 
     u16         num_chipselect; 
     u16         dma_alignment; 
     u16         mode_bits; 
     u32         bits_per_word_mask; 
... 
     /* limits on transfer speed */ 
     u32         min_speed_hz; 
     u32         max_speed_hz; 
  
     /* other constraints relevant to this driver */ 
     u16         flags; 
... 
     /* lock and mutex for SPI bus locking */ 
     spinlock_t      bus_lock_spinlock; 
     struct mutex        bus_lock_mutex; 
  
   /* flag indicating that the SPI bus is locked for exclusive use */
     bool            bus_lock_flag; 
... 
     int         (*setup)(struct spi_device *spi); 
  
... 
     int         (*transfer)(struct spi_device *spi, 
                         struct spi_message *mesg); 
... 
   	 int (*transfer_one_message)(struct spi_master *master, 
                 struct spi_message *mesg); 
... 
 }; 

/*
@	申请 spi_master
@   dev:设备,一般是platform_device中的dev 成员变量
@	size:私有数据大小,可以通过 spi_master_get_devdata函数获取到这些私有数据
@	返回值:申请到的 spi_master
*/
struct spi_master *spi_alloc_master(struct device *dev,unsigned  size) 
/*
@	当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master
@   master:要释放的 spi_master。 
@	返回值:无。
*/
void spi_master_put(struct spi_master *master) 

/*
@	当 spi_master 初始化完成以后就需要将其注册到 Linux 内核
@   master:要注册的 spi_master
@	返回值:0,成功;负值,失败
*/
int spi_register_master(struct spi_master *master) 
/*
@	注销spi_master
@	master:要注销的 spi_master。 
@	返回值:无
*/
void spi_unregister_master(struct spi_master *master) 
  • 一般SOC的SPI总线驱动都是由半导体厂商编写的。因此SPI总线驱动对我们这些SOC 使用者来说是被屏蔽掉的,我们只要专注于 SPI设备驱动即可

1.2 SPI设备驱动

  • 上面已经说了spi_master了,还剩下还剩下设备和驱动,spi_client就是描述设备信息的,spi_driver描述驱动内容,类似于 platform_driver。

spi_client结构体

/*
@ 	描述设备信息
@	spi_driver 结构体 
*/
struct i2c_client { 
    unsigned short flags;          /* 标志             */ 
    unsigned short addr;           /* 芯片地址,7 位,存在低 7 位  */ 
......
    char name[I2C_NAME_SIZE];       /* 名字           */ 
    struct i2c_adapter *adapter;    /* 对应的 I2C 适配器       */ 
    struct device dev;            /* 设备结构体         */ 
    int irq;                      /* 中断             */ 
    struct list_head detected; 
.. 
}; 

spi_driver结构体

/*
@ 	定义在include/linux/spi/spi.h文件中
@   spi_driver 结构体 
*/
 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; 
 }; 
  • 有了上面的结构体,我们的目的就是1.构建spi_driver2.向Linux内核注册这个spi_driver
/*
@	spi_driver注册函数为spi_register_driver
@	sdrv:要注册的 spi_driver
@	返回值:0,成功;负值,失败
*/
int spi_register_driver(struct spi_driver *sdrv) 
/*
@	注销SPI设备驱动以后也需要注销掉前面注册的 spi_driver
@	sdrv:要注销的 spi_driver。
@	返回值:无
*/
void spi_unregister_driver(struct spi_driver *sdrv) 
  • 具体注册spi_driver的注册流程:
    spi_driver 结构体,需要 SPI 设备驱动人员编写,包括匹配表、probe 函数等
 /* probe 函数 */ 
 static int xxx_probe(struct spi_device *spi) 
 { 
    /* 具体函数内容 */ 
    return 0; 
 } 
  
 /* remove 函数 */ 
 static int xxx_remove(struct spi_device *spi) 
 { 
    /* 具体函数内容 */ 
      return 0; 
 } 
 /* 传统匹配方式 ID 列表 */ 
 static const struct spi_device_id xxx_id[] = { 
    {"xxx", 0},   
    {} 
 }; 
  
 /* 设备树匹配列表 */ 
 static const struct of_device_id xxx_of_match[] = { 
    { .compatible = "xxx" }, 
    { /* Sentinel */ } 
 }; 
  
 /* SPI 驱动结构体 */  
 static struct spi_driver xxx_driver = { 
    .probe = xxx_probe, 
    .remove = xxx_remove, 
    .driver = { 
            .owner = THIS_MODULE, 
            .name = "xxx", 
            .of_match_table = xxx_of_match,  
          }, 
    .id_table = xxx_id, 
 }; 
          
 /* 驱动入口函数 */ 
 static int __init xxx_init(void) 
 { 
    return spi_register_driver(&xxx_driver); 
 } 
  
 /* 驱动出口函数 */ 
 static void __exit xxx_exit(void) 
 { 
    spi_unregister_driver(&xxx_driver); 
 } 
  
 module_init(xxx_init); 
 module_exit(xxx_exit); 

  • SPI设备和驱动的匹配过程是由 SPI总线来完成的,这点和platform、I2C等驱动一样
/*
@ 	定义在 drivers/spi/spi.c文件中
@	spi_bus_type 结构体
*/
 struct bus_type spi_bus_type = { 
     .name       = "spi", 
     .dev_groups = spi_dev_groups, 
     .match      = spi_match_device, 
     .uevent     = spi_uevent, 
 }; 
 /*
 @	可以看出,SPI设备和驱动的匹配函数为 spi_match_device
 @  四种匹配方式
 */
  static int spi_match_device(struct device *dev,  
struct device_driver *drv) 
 { 
     const struct spi_device *spi = to_spi_device(dev); 
     const struct spi_driver *sdrv = to_spi_driver(drv); 
  
     /* Attempt an OF style match */ 
     if (of_driver_match_device(dev, drv)) 
         return 1; 
  
     /* Then try ACPI */ 
     if (acpi_driver_match_device(dev, drv)) 
         return 1; 
  
     if (sdrv->id_table) 
 static int spi_match_device(struct device *dev, struct device_driver *drv) 
 { 
     const struct spi_device *spi = to_spi_device(dev); 
     const struct spi_driver *sdrv = to_spi_driver(drv); 
  
     /* Attempt an OF style match */ 
     if (of_driver_match_device(dev, drv)) 
         return 1; 
  
     /* Then try ACPI */ 
     if (acpi_driver_match_device(dev, drv)) 
         return 1; 
  
     if (sdrv->id_table) 
         return !!spi_match_id(sdrv->id_table, spi); 
         
     return strcmp(spi->modalias, drv->name) == 0; 
 } 

2. 具体应用

  • 上面bb了那么多,就是要说相比裸机驱动,由于Linux内核的介入,提供了很多供我们使用后的API,大大提高了效率和稳定性。
  • 我们实际中,使用platfom驱动框架开发SPI驱动。涉及到驱动、总线、设备模型。但是经过上面巴拉巴拉。我们只需要编写platform_driver.
  • 这里要注意:设备信息从设备驱动中剥离开来。还是那句话”驱动的分离和分层思想“,这样涉及到是否使用到设备树(设备树给我们提供了方便,当然要用啦!)

2.1 设备信息描述

使用设备树的时候

1.IO的pinctrl子节点创建与修改

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

  • 使用设备树的时候SPI设备信息通过创建相应的节点就行了
  • 在imx6ul.dtsi查询ecspi1控制器,看到下面已经挂了一个节点了
  &ecspi1 { 
     fsl,spi-num-chipselects = <1>; 
     cs-gpios = <&gpio4 9 0>; 
     pinctrl-names = "default"; 
     pinctrl-0 = <&pinctrl_ecspi1>; 
     status = "okay"; 
  
     flash: m25p80@0 { 
         #address-cells = <1>; 
         #size-cells = <1>; 
         compatible = "st,m25p32"; 
         spi-max-frequency = <20000000>; 
         reg = <0>; 
     }; 
 }; 
  • 打开 imx6ull-14x14-evk.dts 这个设备树文件,在里面追加。。。。。

2.2 实例

  • 此例子我们呢使用带有设备树描述设备信息的方式来编写platform_driver

修改设备树

  • 1.修改 IO,ICM20608 用到了ecspi3 接口,打开imx6ull-alientek-emmc.dts文件
/*
@	pinctrl_ecspi3子节点 ===用到了pinctrl系统
@	在 iomuxc节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚
*/
 pinctrl_ecspi3: icm20608 { 
          fsl,pins = <  
              MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20    0x10b0 /* CS */ 
              MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK      0x10b1   /* SCLK */ 
              MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO       0x10b1   /* MISO */ 
              MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI       0x10b1   /* MOSI */ 
          >; 
      }; 
  • 2.ICM20608 是连接到ECSPI3上的,因此需要在ecspi3节点下添加icm20608设备子节点,在imx6ull-alientek-emmc.dts文件中找到ecspi3节点
/*
@	向 ecspi3 节点加入 icm20608 信息 
*/
 &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>; 
     }; 
 }; 
  • 3.设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的dtb启动Linux内核。

icm2068寄存器.h

  • 本来可以放在驱动里面的但是太多了,看起来复杂,就分开写。

#ifndef ICM20608_H
#define ICM20608_H

#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 */

/* ICM20608寄存器 
 *复位后所有寄存器地址都为0,除了
 *Register 107(0X6B) Power Management 1 	= 0x40
 *Register 117(0X75) WHO_AM_I 				= 0xAF或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

ICM20608驱动编写

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/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"/*提供icm20608里面寄存器的地址*/

#define ICM20608_CNT	1
#define ICM20608_NAME	"icm20608"

struct icm20608_dev {
	dev_t devid;				/* 设备号 	 */
	struct cdev cdev;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	struct device_node	*nd; 	/* 设备节点 */
	int major;					/* 主设备号 */
	void *private_data;			/* 私有数据 		*/
	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;

/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
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;
	}

	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
	
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

out2:
	kfree(rxdata);					/* 释放内存 */
out1:	
	kfree(t);						/* 释放内存 */
	
	return ret;
}

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	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) {
		goto out1;
	}
	
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 data = 0;
	icm20608_read_regs(dev, reg, &data, 1);
	return data;
}

/*
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */	

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] = { 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]);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &icm20608dev; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static 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;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* icm20608操作函数 ===直接与应用层联系起来*/
static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};

/*
 * ICM20608内部寄存器初始化函数==IO初始化
 * @param  	: 无
 * @return 	: 无
 */
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						*/
}

 /*
  * @description     : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * 
  */	
static int icm20608_probe(struct spi_device *spi)
{
	/* 1、构建设备号 */
	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);
	}

	/* 2、注册设备 */
	cdev_init(&icm20608dev.cdev, &icm20608_ops);
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);

	/* 3、创建类 */
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev.class)) {
		return PTR_ERR(icm20608dev.class);
	}

	/* 4、创建设备 */
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev.device)) {
		return PTR_ERR(icm20608dev.device);
	}

	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit();		
	return 0;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int 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);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

/* SPI驱动结构体 */	
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,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

测试app编写

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	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;

	int ret = 0;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
		printf("can't open file %s\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); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}


  • 9
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值