1, Create project
1. Add device tree node
1) Add device node
Create a device node under the root node:
gpioled { compatible = "gpioled_test"; status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&gpioled>; gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; }; key_input { compatible = "key_test"; status = "okay"; pinctrl-names = "default"; pinctrl = <&keyinput>; gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; };
2) Add pinctrl node
Add the pinctrl node imx6ul-evk to the child node under the iomuxc node:
gpioled: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0 >; }; keyinput: keygrp { fsl,pins = < MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xf080 >; };
2. Add device structure
/* Equipment structure */ struct key_dev { dev_t devid; //Equipment number int major; //Main equipment No int minor; //Secondary equipment No struct class *class; //class struct device *device; //equipment struct cdev cdev; //Character device struct device_node *led_nd; //led device tree node struct device_node *key_nd; //key device tree node int led_gpio; //led gpio number int key_gpio; //key gpio No int led_state; atomic_t key_val; //Key value }; struct key_dev key;
3. Write load and unload registration functions
The loading and unloading registration functions are as follows:
module_init(key_init); module_exit(key_exit);
Entry function:
/* Entry function */ static int __init key_init(void) { int ret = 0; /* Equipment number processing */ key.major = 0; if(key.major){ //The master device number is set key.devid = MKDEV(key.major, key.minor); ret = register_chrdev_region(key.devid, DEVICE_CNT, DEVICE_NAME); if(ret < 0){ //Failed to register device number printk("fail to register devid\r\n"); return -EINVAL; } }else{ //The master device number is not set ret = alloc_chrdev_region(&key.devid, 0, DEVICE_CNT, DEVICE_NAME); if(ret < 0){ //Failed to apply for equipment number printk("fail to alloc devid\r\n"); return -EINVAL; } key.major = MAJOR(key.devid); key.minor = MINOR(key.devid); printk("major is %d\r\nminor is %d\r\n",key.major, key.minor); } /* Register character device */ key.cdev.owner = THIS_MODULE; cdev_init(&key.cdev, &key_fops); ret = cdev_add(&key.cdev, key.devid, DEVICE_CNT); if(ret < 0){ //Failed to add character device ret = -EINVAL; printk("fail to add cdev\r\n"); goto fail_add_cdev; } /* Automatically create device nodes */ key.class = NULL; key.device = NULL; key.class = class_create(THIS_MODULE, DEVICE_NAME); if(key.class == NULL){ //Failed to create class ret = -EINVAL; printk("fail to create class\r\n"); goto fail_cclass; } key.device = device_create(key.class, NULL, key.devid, NULL, DEVICE_NAME); if(key.device == NULL){ //Failed to create device ret = -EINVAL; printk("fail to create device\r\n"); goto fail_cdevice; } /* Get led device tree node */ key.led_nd = of_find_node_by_name(NULL, "gpioled"); if(key.led_nd == NULL){ printk("fail to find led_nd\r\n"); ret = -EINVAL; goto fail_find_gpio_nd; } /* Get key device tree node */ key.key_nd = of_find_node_by_name(NULL, "key_input"); if(key.key_nd == NULL){ printk("fail to find led_nd\r\n"); ret = -EINVAL; goto fail_find_gpio_nd; } /* Get led_gpio number */ key.led_gpio = of_get_named_gpio(key.led_nd, "gpios", 0); if(key.led_gpio < 0){ //Get led_gpio number failed printk("fail to find led_nd\r\n"); ret = -EINVAL; goto fail_find_gpio_num; } printk("led_gpio num: %d\r\n", key.led_gpio); /* Get key_gpio number */ key.key_gpio = of_get_named_gpio(key.key_nd, "gpios", 0); if(key.key_gpio < 0){ //Get led_gpio number failed printk("fail to find key_nd\r\n"); ret = -EINVAL; goto fail_find_gpio_num; } printk("key_gpio num: %d\r\n", key.key_gpio); /* Register gpio */ gpio_request(key.led_gpio, "gpioled"); gpio_request(key.key_gpio, "gpiokey"); /* Set gpio I / O */ gpio_direction_output(key.led_gpio, 1); gpio_direction_input(key.key_gpio); printk("key init\r\n"); return 0; fail_add_cdev: unregister_chrdev_region(key.devid, DEVICE_CNT); fail_cclass: cdev_del(&key.cdev); unregister_chrdev_region(key.devid, DEVICE_CNT); fail_cdevice: class_destroy(key.class); cdev_del(&key.cdev); unregister_chrdev_region(key.devid, DEVICE_CNT); fail_find_gpio_nd: device_destroy(key.class, key.devid); class_destroy(key.class); cdev_del(&key.cdev); unregister_chrdev_region(key.devid, DEVICE_CNT); fail_find_gpio_num: device_destroy(key.class, key.devid); class_destroy(key.class); cdev_del(&key.cdev); unregister_chrdev_region(key.devid, DEVICE_CNT); return ret; }
Exit function:
/* Exit function */ static void __exit key_exit(void) { led_switch(LED_OFF); gpio_free(key.led_gpio); gpio_free(key.key_gpio); device_destroy(key.class, key.devid); class_destroy(key.class); cdev_del(&key.cdev); unregister_chrdev_region(key.devid, DEVICE_CNT); }
3. Write the specific operation function of the device
/* open function */ static int key_open(struct inode *inode, struct file *filp) { filp->private_data = &key; key.led_state = 1; return 0; } /* read function */ static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int value; int ret = 0; struct key_dev *dev = filp->private_data; if(gpio_get_value(dev->key_gpio) == 0){ //key press while(!gpio_get_value(dev->key_gpio)) //Wait for the key to release atomic_set(&dev->key_val, KEY_VAL); led_switch(dev->led_state = !(dev->led_state)); }else{ atomic_set(&dev->key_val, INVALKEY); } value = atomic_read(&dev->key_val); ret = copy_to_user(buf, &value, sizeof(value)); return 0; } /* Set of operation functions */ static const struct file_operations key_fops = { .owner = THIS_MODULE, .open = key_open, .read = key_read, };
4. Add header file and create virtual address pointer
When referring to the driver code of linux kernel, find the header file that may be used and add it to the project. When calling a system call function or library function, use the man command on the terminal to view which header files need to be included in the called function.
man command number meaning: 1: standard command 2: system call 3: library function
Add the following header files:
#include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <asm/io.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #define DEVICE_NAME "key_input" #define DEVICE_CNT 1 #define LED_ON 1 #define LED_OFF 0 #define KEY_VAL 1 #define INVALKEY 0
5. Add License and author information
The driver License is required. If it is missing, an error will be reported. Add the following code at the end of the file:
MODULE_LICENSE("GPL"); MODULE_AUTHOR("lzk");
6. Write led state switching function
/* LED State switching function */ void led_switch(int led_state) { if(led_state == LED_ON){ gpio_set_value(gpioled.led_gpio, 0); //Using API functions of gpio subsystem }else{ gpio_set_value(gpioled.led_gpio, 1); } }
2, Writing test applications
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #define KEY_VAL 1 #define INVALKEY 0 int main(int argc, char *argv[]) { int fd = 0; int ret = 0; char *filename; int keyvalue; if(argc != 3){ printf("missing parameter!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0){ printf("open file %s failed\r\n", filename); return -1; } while(1){ read(fd, &keyvalue, sizeof(keyvalue)); if(keyvalue == KEY_VAL){ printf("KEY0 Press\r\n");/* Press */ } } ret = close(fd); if(ret < 0) //The return value is less than 0. Closing the file failed { printf("close file %s failed\r\n", filename); return -1; } return 0; }
3, Compilation and testing
1. Write Makefile and compile the driver
The driver source code needs to be compiled into ko module, create Makefile:
KERNELDIR := /home/liuzhikai/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga CURRENT_PATH := $(shell pwd) obj-m := key.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
-
In line 1, KERNELDIR indicates the Linux kernel source code directory used by the development board, using the absolute path.
-
Line 2, CURRENT_PATH indicates the current path.
-
In line 3, obj-m indicates that the key C this file is compiled into modules.
-
Line 5, the default target is kernel_modules.
-
Line 8, the specific compilation command. The following modules represent the compilation module, and - C represents switching the current working directory to the specified directory. M represents the module source code directory. After adding M=dir to the "make modules" command, the program will automatically read the module source code in the specified dir directory and compile it ko file.
After the compilation is successful, a key will be generated Ko = = file, which is the driver module.
2. Compile driver
The test APP needs to run on the ARM development board, so it is compiled using the cross compiler:
arm-linux-gnueabihf-gcc keyAPP.c -o ledAPP
Compile the executable program that generates the keyAPP.
3. Run test
Select network startup for linux system, and mount the root file system with nfs. The U-Boot settings are as follows:
Value of bootcmd:
tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
Value of bootargs:
console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.138:/home/liuzhikai/linux/nfs/rootfs ip=192.168.1.123:192.168.1.138:192.168.1.2:255.255.255.0::eth0:off
Set dtsled KO and ledAPP are copied to the following directory (non-existent directory creation):
sudo cp dtsled.ko keyApp /home/liuzhikai/linux/nfs/rootfs/lib/modules/4.1.15/ -f
Load the driver module with the following command:
depmod modprobe dtsled.ko //Load driver module ./keyAPP /dev/dtsled 1 //Execute the application and turn on the led ./keyAPP /dev/dtsled 0 //Execute the application and turn off the led rmmod dtsled.ko //Unloading the driver module