设备树下的字符设备驱动框架

(以驱动LED为例)

设备树下的字符设备驱动框架_driver

设备树下的字符设备驱动框架如上图所示,下面详细介绍代码的编写

1. 修改设备树文件

1.1 添加pinctrl节点

  • 在iomuxc节点的imx6ul-evk子节点下创建“pinctrl_led”节点,复用GPIO1_IO03
pinctrl_led: ledgrp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10B0
	>;
};

1.2 添加LED设备节点

  • 在根节点下创建LED设备节点,设置PIN对应的pinctrl节点,指定所使用的的GPIO
gpioled {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "alpha-gpioled";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_led>;
	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	status = "okay";
};

1.3 检查PIN是否冲突

  • 检查pinctrl中设置以及设备节点中指定的引脚有没有被别的外设使用
//检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用
pinctrl_tsc: tscgrp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
		MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
		//GPIO_IO03被pinctrl_tsc节点占用,因此需要屏蔽掉
		/* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */ 
		MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
	>;
};
//检查"gpio1 3"有没有被其他设备节点占用
&tsc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_tsc>;
	//"gpio1 3"被tsc设备节点占用,因此需要屏蔽掉
	/* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
	measure-delay-time = <0xffff>;
	pre-charge-time = <0xfff>;
	status = "okay";
};

1.4 编译设备树并启动Linux

  • 使用“make dtbs”命令编译设备树,并使用该设备树启动Linux系统
#在内核根目录下
make dtbs 	#编译设备树
#启动Linux系统后
cd /proc/device-tree	#查看"gpioled"节点是否存在

2. 编写驱动程序

2.1 定义设备结构体及设备

  • 结构体中包含设备号/cdev/类/设备/主次设备号/设备节点/使用的GPIO编号
struct gpioled_dev{
	dev_t devid;			//设备号
	struct cdev cdev;		//cdev字符设备
	struct class *class;	//类
	struct device *device;	//设备
	int major;				//主设备号
	int minor;				//次设备号
	struct device_node *nd;	//设备节点
	int led_gpio;			//所使用的gpio编号
};

struct gpioled_dev gpioled;	//定义led设备

2.2 编写设备操作函数

  • led_open函数:打开设备,并设置私有数据
static int led_open(struct inode *inode, struct file *filp){
	filp->private_data = &gpioled;	//设置私有数据
	return 0;
}
  • led_read函数:从设备读取数据
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	return 0;
}
  • led_write函数:向设备写数据,根据写入的值来操作GPIO
static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt); //将用户空间的数据拷贝到内核空间
	if(retvalue < 0){
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];
	if(ledstat == LEDON){
		gpio_set_value(dev->led_gpio, 0);
	}else if(ledstat == LEDOFF){
		gpio_set_value(dev->led_gpio, 1);
	}
	return 0;
}
  • led_release函数:关闭/释放设备
static int led_release(struct inode *inode, struct file *filp){
	return 0;
}
//设备操作函数
static struct file_operations gpioled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = led_release,
};

2.3 编写驱动入口函数

  • 设置所使用的GPIO

a. 获取设备节点

gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
	printk("gpioled node cant not found!\r\n");
	return -EINVAL;
} else {
	printk("gpioled node has been found!\r\n");
}

b. 获取gpio属性,得到所使用的GPIO编号

gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio",0);
if(gpioled.led_gpio < 0) {
	printk("cant not get led-gpio!\r\n");
	return -EINVAL;
} else {
	printk("led-gpio num = %d !\r\n",gpioled.led_gpio);
}

c. 设置GPIO为输出,并默认高电平

ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0){
	printk("can not set gpio!\r\n");
}
  • 注册字符设备驱动

a. 创建设备号

if(gpioled.major){
	gpioled.devid = MKDEV(gpioled.major, 0);
	register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}else{
	alloc_chrdev_region(&gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	gpioled.major = MAJOR(gpioled.devid);
	gpioled.minor = MINOR(gpioled.devid);
}
printk("gpioled major = %d, minor = %d\r\n",gpioled.major,gpioled.minor);

b. 初始化cdev

gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);

c. 添加cdev

cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

d. 创建类

gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
	return PTR_ERR(gpioled.class);
}

e. 创建设备

gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
	return PTR_ERR(gpioled.device);
}

2.4 编写驱动出口函数

  • 删除cdev,注销设备驱动,删除类和设备
static void __exit led_exit(void){
	cdev_del(&gpioled.cdev); 
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
}

2.5 其他

  • LICENSE信息
MODULE_LICENSE("GPL");

3. 编写测试APP

操作“/dev/led”驱动文件对LED进行控制

  • 使用main传参功能,传递led驱动文件
if(argc != 3){
	printf("Error Usage!\r\n");
	return -1;
}

filename = argv[1];
  • 打开led驱动文件
fd = open(filename, O_RDWR);
if(fd < 0){
	printf("Can't open file %s\r\n", filename);
	return -1;
}
  • 从打开的驱动文件读取数据
if(atoi(argv[2]) == 1)
	retvalue = read(fd, readbuf, 50);
	if(retvalue < 0){
		printf("read file %s failed!\r\n", filename);
	}else{
		printf("read data:%s\r\n",readbuf);
	}
}
  • 向打开的驱动文件中写入数据
if(atoi(argv[2]) == 2){
	memcpy(writebuf, usrdata, sizeof(usrdata));
	retvalue = write(fd, writebuf, 50);
	if(retvalue < 0){
		printf("write file %s failed!\r\n", filename);
	}
}
  • 关闭驱动文件
retvalue = close(fd);
if(retvalue < 0){
	printf("Can't close file %s\r\n", filename);
	return -1;
}

4. 运行测试

4.1 编译驱动程序

  • 修改Makefile编译目标变量
obj-m := gpioled.o
  • 使用“make -j32”编译出驱动模块文件
make -j32

4.2 编译测试APP

  • 使用“arm-linux-gnueabihf-gcc”命令编译测试APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp

4.3 运行测试

  • 将驱动文件和APP可执行文件拷贝至“rootfs/lib/modules/4.1.15”中
  • 第一次加载驱动时,需使用“depmod”命令
depmod
  • 使用“modprobe”命令加载驱动
modprobe gpioled.ko
  • 使用“./ledApp /dev/gpioled 1"命令打开LED
./ledApp /dev/gpioled 1
  • 使用“./ledApp /dev/gpioled 0"命令关闭LED
./ledApp /dev/gpioled 0
  • 使用”rmmod"命令卸载驱动
rmmod gpioled.ko