Linux学习笔记(22)——基于ICM20608的SPI设备驱动

  1. 修改设备树文件,增加Iecspi3 节点上增加CM20608子节点
&ecspi3 {
	/* 设置当前片选数量为1, 因为就只接了一个ICM20608 */
	fsl,spi-num-chipselects = <1>;
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_HIGH>;

	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>;
	status = "okay";

	spidev: icm20608@0 {
		compatible = "glen,icm20608";
		spi-max-frequency = <8000000>;
		reg = <0>;
		status = "okay";
	};
};
  1. 编写icm20608设备驱动程序
/* 
 * 文件名   : icm20608.c
 * 作者     : glen  
 * 描述     : 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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608.h"

#define ICM20608_CNT     1
#define ICM20608_NAME    "icm20608"

/**
 * struct icm20608
 * @devid:  设备号
 * @cdev_icm:   cdev
 * @cls:  类
 * @dev_icm: 设备
 * @nd:     设备节点
 * @major:  主设备号
 * @private_data: 私有数据
 * @cs_gpio: 片选所使用的GPIO编号
 */
struct icm20608 {
    dev_t devid;
    struct cdev cdev_icm;
    struct class *cls;
    struct device *dev_icm;
    struct device_node *np;

    int major;
    void *private_data;
    
    int cs_gpio;
};

static struct icm20608 icm20608_dev;

/**
 * 从icm20608寄存器读取多个数据
 * @param   : dev icm20608设备
 * @param   : reg 要读取的寄存器首地址
 * @param   : buf 读到的数据
 * @param   : len 要读取的数据长度
 * @return  : 操作结果
 */
static int icm20608_read_reg(struct icm20608 *dev, u8 reg, void *buf, int len)
{
    int ret;
    unsigned char tx_data[len];
    struct spi_message msg;
    struct spi_transfer *ptrans = NULL;
    struct spi_device *spi = (struct spi_device *)dev->private_data;
    
    ptrans = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    if (ptrans == NULL) {
        ret = -EINVAL;
        goto read_fail_alloc;
    }
    
    gpio_set_value(dev->cs_gpio, 0);

    /* 发送要读取的寄存器地址 */
    tx_data[0] = reg | 0x80;
    ptrans->tx_buf = tx_data;
    ptrans->len = 1;

    spi_message_init(&msg);
    spi_message_add_tail(ptrans, &msg);
    ret = spi_sync(spi, &msg);

    /* 读取数据 */
    tx_data[0] = 0xff;
    ptrans->rx_buf = buf;
    ptrans->len = len;

    spi_message_init(&msg);
    spi_message_add_tail(ptrans, &msg);
    ret = spi_sync(spi, &msg);

    gpio_set_value(dev->cs_gpio, 1);
    kfree(ptrans);

read_fail_alloc:
    return ret;
}

/**
 * 向icm20608寄存器写入多个数据
 * @param   : dev icm20608设备
 * @param   : reg 要读取的寄存器首地址
 * @param   : buf 读到的数据
 * @param   : len 要读取的数据长度
 * @return  : 操作结果
 */
static int icm20608_write_reg(struct icm20608 *dev, u8 reg, u8 *buf, u8 len)
{
    int ret;
    unsigned char tx_data[len];
    struct spi_message msg;
    struct spi_transfer *ptrans = NULL;
    struct spi_device *spi = (struct spi_device *)dev->private_data;

    ptrans = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    if (ptrans == NULL) {
        ret = -EINVAL;
        goto write_fail_alloc;
    }

    gpio_set_value(dev->cs_gpio, 0);

    /* 发送要写入的寄存器地址 */
    tx_data[0] = reg & ~0x80;
    ptrans->tx_buf = tx_data;
    ptrans->len = 1;

    spi_message_init(&msg);
    spi_message_add_tail(ptrans, &msg);
    ret = spi_sync(spi, &msg);

    /* 发送要写入的数据 */
    ptrans->tx_buf = buf;
    ptrans->len = len;

    spi_message_init(&msg);
    spi_message_add_tail(ptrans, &msg);
    ret = spi_sync(spi, &msg);
    
    kfree(ptrans);
    gpio_set_value(dev->cs_gpio, 1);

write_fail_alloc:
    return ret;
}

/**
 * ICM20608内部寄存器初始化函数
 * @param   : none
 * @return  : noreturn
 */
void icm20608_reg_init(void)
{
    int ret = 0;
    u8 value;

    value = 0x80;
    icm20608_write_reg(&icm20608_dev, ICM20_PWR_MGMT_1, &value, 1);
    mdelay(50);

    value = 0x01;
    icm20608_write_reg(&icm20608_dev, ICM20_PWR_MGMT_1, &value, 1);
    mdelay(50);

    ret = icm20608_read_reg(&icm20608_dev, ICM20_WHO_AM_I, &value, 1);
    if (ret == 0) {
        printk("ICM20608 ID = %#X\r\n", value);
    }

    value = 0x00;
    icm20608_write_reg(&icm20608_dev, ICM20_SMPLRT_DIV, &value, 1);
    value = 0x18;
    icm20608_write_reg(&icm20608_dev, ICM20_GYRO_CONFIG, &value, 1);
    icm20608_write_reg(&icm20608_dev, ICM20_ACCEL_CONFIG, &value, 1);
    value = 0x04;
    icm20608_write_reg(&icm20608_dev, ICM20_CONFIG, &value, 1);
    icm20608_write_reg(&icm20608_dev, ICM20_ACCEL_CONFIG2, &value, 1);
    value = 0x00;
    icm20608_write_reg(&icm20608_dev, ICM20_PWR_MGMT_2, &value, 1);
    icm20608_write_reg(&icm20608_dev, ICM20_LP_MODE_CFG, &value, 1);
    icm20608_write_reg(&icm20608_dev, ICM20_FIFO_EN, &value, 1);
}

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

/**
 * 从设备读取数据
 * @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)
{
    int ret;
    signed char data[14];
    struct icm20608 *dev;
    struct icm20608_data icm_data;

    if (filp->private_data == NULL) {
        ret = -EINVAL;
        return ret;
    }
    dev = (struct icm20608 *)filp->private_data;

    icm20608_read_reg(dev, ICM20_ACCEL_XOUT_H, data, 14);
    icm_data.accel_x_adc = (signed short)((data[0] << 8) | (data[1]));
    icm_data.accel_y_adc = (signed short)((data[2] << 8) | (data[3]));
    icm_data.accel_z_adc = (signed short)((data[4] << 8) | (data[5]));
    icm_data.temp_adc    = (signed short)((data[6] << 8) | (data[7]));
    icm_data.gyro_x_adc  = (signed short)((data[8] << 8) | (data[9]));
    icm_data.gyro_y_adc  = (signed short)((data[10] << 8) | (data[11]));
    icm_data.gyro_z_adc  = (signed short)((data[12] << 8) | (data[13]));

    ret = copy_to_user((void *)buf, (const void *)&icm_data, 14);

    return ret;
}

/**
 * 关闭/释放设备
 * @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,
};

/**
 * spi驱动的probe函数, 当驱动与设备匹配会执行此函数
 * @param   client: spi设备
 * @param   id:     spi设备ID
 */
static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;

    /* 构建设备号 */
    if (icm20608_dev.major) {
        icm20608_dev.devid = MKDEV(icm20608_dev.major, 0);
        register_chrdev_region(icm20608_dev.devid, ICM20608_CNT, ICM20608_NAME);
    } else {
        alloc_chrdev_region(&icm20608_dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
        icm20608_dev.major = MAJOR(icm20608_dev.devid);
    }
    printk("icm20608 major is: %d\n", icm20608_dev.devid);

    /* 注册设备 */
    cdev_init(&icm20608_dev.cdev_icm, &icm20608_ops);
    cdev_add(&icm20608_dev.cdev_icm, icm20608_dev.devid, ICM20608_CNT);

    /* 创建类 */
    icm20608_dev.cls = class_create(THIS_MODULE, ICM20608_NAME);
    if (IS_ERR(icm20608_dev.cls)) {
        return PTR_ERR(icm20608_dev.cls);
    }

    /* 创建设备 */
    icm20608_dev.dev_icm = device_create(icm20608_dev.cls, NULL, icm20608_dev.devid, NULL, ICM20608_NAME);
    if (IS_ERR(icm20608_dev.dev_icm)) {
        return PTR_ERR(icm20608_dev.dev_icm);
    }

    /* 获取设备树节点 */
    icm20608_dev.np = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
    if (icm20608_dev.np == NULL) {
        printk("ecspi3 node is not find!\n");
        return -EINVAL;
    }

    /* 获取设备树中的gpio属性, 得到CS片选所使用的GPIO编号 */
    icm20608_dev.cs_gpio = of_get_named_gpio(icm20608_dev.np, "cs-gpio", 0);
    if (icm20608_dev.cs_gpio < 0) {
        printk("Can't get cs-gpio\r\n");
        return -EINVAL;
    }

    /* 设置GPIO1_IO20为输出, 并且输出高电平 */
    ret = gpio_direction_output(icm20608_dev.cs_gpio, 1);
    if (ret < 0) {
        printk("Can't set gpio!\n");
        return -EIO;
    }

    /* 初始化spi_device */
    spi->mode = SPI_MODE_0;
    spi_setup(spi);
    icm20608_dev.private_data = spi;

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

/**
 * spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param   : client spi设备
 * @return  : 0 成功; 负值 失败
 */
static int icm20608_remove(struct spi_device *spi)
{
    /* 删除设备 */
    cdev_del(&icm20608_dev.cdev_icm);
    unregister_chrdev_region(icm20608_dev.devid, ICM20608_CNT);

    /* 注销掉类和设备 */
    device_destroy(icm20608_dev.cls, icm20608_dev.devid);
    class_destroy(icm20608_dev.cls);

    return 0;
}

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

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
    {.compatible = "glen,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,
};


/**
 * \brief   驱动模块加载函数
 * \param   无
 * \retval  无
 */
static int __init icm20608_init(void)
{
    return spi_register_driver(&icm20608_driver);
}

/**
 * \brief   驱动模块缷载函数
 * \param   无
 * \return  无
 */
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

/* 设备注册入口, 展开后
 * static initcall_t \
 *        __initcall_icm20608_init6 \
 *        __used \
 *        __attribute__((__section__(".initcall6.init"))) \
 *        = icm20608_init;
 */
module_init(icm20608_init);

/* 设备注册出口, 展开后
 * static exitcall_t \
 *        __exitcall_icm20608_exit \
 *        __exit_call \
 *        = icm20608_exit;
 */
module_exit(icm20608_exit);

/* 模块的许可证声明, 展开后
 * static const char __UNIQUE_ID_license__COUNTER__[] \
 *   __used __attribute__((section(".modinfo"), unused, aligned(1))) \
 *  = "license=GPL";
 */
MODULE_LICENSE("GPL");

/* 模块的作者声明, 展开后
 * static const char __UNIQUE_ID_author__COUNTER__[]					  \
 * __used __attribute__((section(".modinfo"), unused, aligned(1)))	  \
 * = "author=glen_cao"
 */
MODULE_AUTHOR("glen");
  1. 编写icm20608测试程序
/*
 * 文件名   :  icm20608_test.c
 * 作者     :  glen
 * 描述     :  icm20608测试程序
 */

#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 "linux/ioctl.h"
#include "signal.h"
#include "icm20608.h"

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int fd = 0;  /* 文件描述符 */
    char *filename;
    struct icm20608_data icm_data;

    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, &icm_data, 14);
        if (ret == 0) {
            icm_data.accel_x_act = (float)(icm_data.accel_x_adc) / 2048;
            icm_data.accel_y_act = (float)(icm_data.accel_y_adc) / 2048;
            icm_data.accel_z_act = (float)(icm_data.accel_z_adc) / 2048;
            icm_data.gyro_x_act = (float)(icm_data.gyro_x_adc) / 16.4;
            icm_data.gyro_y_act = (float)(icm_data.gyro_y_adc) / 16.4;
            icm_data.gyro_z_act = (float)(icm_data.gyro_z_adc) / 16.4;
            icm_data.temp_act = ((float)(icm_data.temp_adc) - 25) / 326.8 + 25;

            printf("\n原始值:\n");
            printf("gx = %d, gy = %d, gz = %d\n", icm_data.gyro_x_adc, icm_data.gyro_y_adc, icm_data.gyro_z_adc);
            printf("ax = %d, ay = %d, az = %d\n", icm_data.accel_x_adc, icm_data.accel_y_adc, icm_data.accel_z_adc);
            printf("temp = %d\n", icm_data.temp_adc);

            printf("\n实际值:\n");
            printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\n", icm_data.gyro_x_act, icm_data.gyro_y_act, icm_data.gyro_z_act);
            printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\n", icm_data.accel_x_adc, icm_data.accel_y_adc, icm_data.accel_z_adc);
            printf("temp = %.2f°C\n", icm_data.temp_act);            
        }
        usleep(200000);
    }

    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

拷贝到NFS文件系统目录下

glen@ubuntu:~/linux/imx6ull/linux/driver/20_spi$ sudo cp icm20608.ko icm20608 ~/linux/nfs/rootfs/lib/modules/4.1.15 -f
  1. 测试程序
    4.1. 加载驱动模块
/lib/modules/4.1.15 # insmod icm20608.ko
icm20608 major is: 261095424
ICM20608 ID = 0xae

4.2. 运行测试程序

/lib/modules/4.1.15 # ./random: nonblocking pool is initialized
/lib/modules/4.1.15 # ./icm20608_test /dev/icm20608

原始值:
gx = 37, gy = -25, gz = 10
ax = 4, ay = 7, az = -4
temp = -89

实际值:
act gx = 2.26°/S, act gy = -1.52°/S, act gz = 0.61°/S
act ax = -nang, act ay = -1.52g, act az = 0.61g
temp = 24.65°C

原始值:
gx = 34, gy = -27, gz = 47
ax = 21, ay = -46, az = -11
temp = -87

实际值:
act gx = 2.07°/S, act gy = -1.65°/S, act gz = 2.87°/S
act ax = -nang, act ay = -1.65g, act az = 2.87g
temp = 24.66°C

原始值:
gx = 37, gy = -25, gz = 9
ax = 3, ay = 2, az = -4
temp = -90

实际值:
act gx = 2.26°/S, act gy = -1.52°/S, act gz = 0.55°/S
act ax = -nang, act ay = -1.52g, act az = 0.55g
temp = 24.65°C
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值