从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知
文章以MPU6050三轴加速度计为例,描述I2C驱动的编写
示例代码
设备树
参考文档 Documentation/i2c/instantiating-devices 对设备树进行添加,修改文件 arch/arm/boot/dts/am335x-evmsk.dts,添加以下代码。
mpu6050: mpu6050@68 {
compatible = "ti,mpu6050";
reg = <0x68>;
};
驱动 drv.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <uapi/asm-generic/errno-base.h>
#include "mpu6050.h"
#define MPU6050_MAJOR 255
#define MPU6050_MINOR 0
#define MPU6050_NAME "mpu6050_cdev"
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B
struct mpu6050_dev{
struct i2c_client *client;
struct cdev cdev;
};
struct class *cls = NULL;
struct device *dev = NULL;
static int mpu6050_open(struct inode *inode, struct file *filp)
{
struct mpu6050_dev *mpu6050 = container_of(inode->i_cdev, struct mpu6050_dev, cdev);
filp->private_data = mpu6050;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int mpu6050_release(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t mpu6050_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t mpu6050_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static long mpu6050_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
struct mpu6050_dev *mpu6050 = filp->private_data;
struct atg_val val;
if ( _IOC_TYPE(cmd) != MPU6050_MAGIC)
{
return -ENOTTY; /* not a typewriter */
}
switch(cmd)
{
case MPU6050_GET_VAL:
val.accelx = i2c_smbus_read_word_data(mpu6050->client, ACCEL_XOUT_H);
val.accely = i2c_smbus_read_word_data(mpu6050->client, ACCEL_YOUT_H);
val.accelz = i2c_smbus_read_word_data(mpu6050->client, ACCEL_ZOUT_H);
val.temp = i2c_smbus_read_word_data(mpu6050->client, TEMP_OUT_H);
val.gyrox = i2c_smbus_read_word_data(mpu6050->client, GYRO_XOUT_H);
val.gyroy = i2c_smbus_read_word_data(mpu6050->client, GYRO_YOUT_H);
val.gyroz = i2c_smbus_read_word_data(mpu6050->client, GYRO_ZOUT_H);
val.accelx = be16_to_cpu(val.accelx);
val.accely = be16_to_cpu(val.accely);
val.accelz = be16_to_cpu(val.accelz);
val.temp = be16_to_cpu(val.temp);
val.gyrox = be16_to_cpu(val.gyrox);
val.gyroy = be16_to_cpu(val.gyroy);
val.gyroz = be16_to_cpu(val.gyroz);
if (copy_to_user((struct atg_val __user *)args, &val, sizeof(struct atg_val)))
return -EFAULT;
break;
default:
printk("cmd error.\n");
}
return 0;
}
const struct file_operations mpu6050_ops = {
.open = mpu6050_open,
.release = mpu6050_release,
.read = mpu6050_read,
.write = mpu6050_write,
.unlocked_ioctl = mpu6050_ioctl,
};
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
dev_t dev_no;
int ret;
struct mpu6050_dev *mpu6050;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
printk("%p.\n", client);
dev_no = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
ret = register_chrdev_region(dev_no, 1, MPU6050_NAME);
if ( ret )
{
printk("register_chrdev_region failed.\n");
goto reg_err;
}
mpu6050 = kzalloc(sizeof(struct mpu6050_dev), GFP_KERNEL); /* GFP_KERNEL = 0 */
if ( IS_ERR(mpu6050) ) /* 判断指针是否有误 */
{
printk("kzalloc failed.\n");
ret = -PTR_ERR(mpu6050); /* 返回错误码 ,在include/uapi/asm-generic/errno-base.h中定义 */
goto kzalloc_err;
}
i2c_set_clientdata(client, mpu6050); /* 把数据保存到 client->dev.driver_data */
mpu6050->client = client;
cdev_init(&mpu6050->cdev, &mpu6050_ops);
ret = cdev_add(&mpu6050->cdev, dev_no, 1);
if ( ret )
{
printk("cdev_add failed.\n");
goto add_err;
}
cls = class_create(THIS_MODULE, "mpu6050_cls");
if ( IS_ERR(cls) )
{
printk("class_create failed.\n");
ret = PTR_ERR(cls);
goto cls_err;
}
dev = device_create(cls, NULL, dev_no, NULL, "mpu6050dev%d", 0);
if ( IS_ERR(dev) )
{
printk("device_create failed.\n");
ret = PTR_ERR(dev);
goto dev_err;
}
i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x80);
msleep(200);
i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x40);
i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x00);
i2c_smbus_write_byte_data(client, SMPLRT_DIV, 0x7);
i2c_smbus_write_byte_data(client, CONFIG, 0x6);
i2c_smbus_write_byte_data(client, GYRO_CONFIG, 0x3 << 3);
i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x3 << 3);
return 0;
dev_err:
class_destroy(cls);
cls_err:
cdev_del(&mpu6050->cdev);
add_err:
kfree(mpu6050);
kzalloc_err:
unregister_chrdev_region(dev_no, 1);
reg_err:
return ret;
}
static int mpu6050_remove(struct i2c_client *client)
{
dev_t dev;
struct mpu6050_dev *mpu6050;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
printk("%p.\n", client);
dev = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
mpu6050 = i2c_get_clientdata(client); /* client->dev.driver_data */
device_destroy(cls, dev);
class_destroy(cls);
cdev_del(&mpu6050->cdev);
kfree(mpu6050);
unregister_chrdev_region(dev, 1);
return 0;
}
struct i2c_device_id mpu6050_id[] = {
{.name = "mpu6050"},
{}
};
struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.name = "my_i2cdrv",
.owner = THIS_MODULE,
},
.id_table = mpu6050_id,
};
static int __init i2c_init(void)
{
return i2c_add_driver(&mpu6050_driver);
}
static void __exit i2c_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
module_init(i2c_init);
module_exit(i2c_exit);
/*
不加GPL协议会报错
drv: module license 'unspecified' taints kernel. drv:模块许可证“未指定”污染内核。
Disabling lock debugging due to kernel taint 由于内核污染而禁用锁调试。
*/
MODULE_LICENSE("GPL");
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "mpu6050.h"
#define READ_COUNT 2
const char *mpu6050_pathname = "/dev/mpu6050dev0";
int main()
{
int fd, i;
struct atg_val val;
fd = open(mpu6050_pathname, O_RDWR, 0666);
if (fd < 0)
{
printf("open failed\n");
return -1;
}
for (i=0; i<READ_COUNT; i++)
{
ioctl(fd, MPU6050_GET_VAL, &val);
printf("accelx: %.2f\n", val.accelx / 2048.0);
printf("accely: %.2f\n", val.accely / 2048.0);
printf("accelz: %.2f\n", val.accelz / 2048.0);
printf("temp: %.2f\n", val.temp / 340.0 + 36.53);
printf("gyrox: %.2f\n", val.gyrox / 16.4);
printf("gyroy: %.2f\n", val.gyroy / 16.4);
printf("gyroz: %.2f\n", val.gyroz / 16.4);
sleep(3);
}
close(fd);
return 0;
}
自定义头文件 mpu6050.h
#ifndef _MPU6050_H
#define _MPU6050_H
struct atg_val {
short accelx;
short accely;
short accelz;
short temp;
short gyrox;
short gyroy;
short gyroz;
};
#define MPU6050_MAGIC 'm'
#define MPU6050_GET_VAL _IOR(MPU6050_MAGIC, 0, struct atg_val)
#endif /* mpu6050.h */
Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += drv.o
测试结果
root@am335x-evm:~# insmod drv.ko
[ 67.654517] mpu6050_probe -- 131.
[ 67.657897] cf628000.
root@am335x-evm:~# ./app
[ 70.139433] mpu6050_open -- 54.
accelx: -15.00
accely: -15.00
accelz: -15.00
temp: -53.83
gyrox: -1873.23
gyroy: -1873.23
gyroz: -1873.23
accelx: -15.00
accely: -15.00
accelz: -15.00
temp: -53.83
gyrox: -1873.23
gyroy: -1873.23
gyroz: -1873.23
[ 76.147381] mpu6050_release -- 61.
root@am335x-evm:~# rmmod drv.ko
[ 85.926126] mpu6050_remove -- 206.
[ 85.929656] cf628000.