1 准备工作
开发板:aio-rk3288j
SDK版本:rk3288_linux_release_20210304
下载工具:Linux_Upgrade_Tool_v2.1
内核版本:4.4.194
文件系统:buildroot
Ubuntu版本:18.04
交叉编译工具:gcc version 6.3.1 20170404
2 硬件原理图
3 设备树文件修改
3.1 添加led设备节点
设备树文件位于内核kernel/arch/arm/boot/dts目录下,我们需要打开rk3288.dtsi、rk3288-linux.dtsi、rk3288-firefly-port.dtsi、rk3288-firefly-aio.dtsi.对于led设备只需要在rk3288-firefly-port.dtsi这文件,找打led设备节点:
leds {
compatible = “gpio-leds”;
work {
label = "firefly:blue:power";
linux,default-trigger = "ir-power-click";
gpios = <&gpio8 1 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&led_power>;
default-state = "on";
};
user {
label = "firefly:yellow:user";
linux,default-trigger = "ir-user-click";
gpios = <&gpio8 2 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&led_user>;
default-state = "off";
};
};
编译内核,输入如下命令
./build.sh kernel
./build.sh updateimg
4 设备树常用 OF 操作函数
rk3288开发板共有两个led,分别对应GPIO8_A1和GPIO8_A2,但是我们在驱动程序中需要通过设备树获取到这两个GPIO的值。所以下面介绍几个常用设备树操作函数,这些 OF 函数原型都定义在 include/linux/of.h 文件中。
4.1 查找节点的 OF 函数
of_find_node_by_name 函数通过节点名字查找指定的节点。
of_find_node_by_type 函数通过 device_type 属性查找指定的节点
of_find_compatible_node 函数根据 device_type 和 compatible 这两个属性查找指定的节点
of_find_matching_node_and_match 函数通过 of_device_id 匹配表来查找指定的节点
of_find_node_by_path 函数通过路径来查找指定的节点
4.2 查找父/子节点的 OF 函数
of_get_parent 函数用于获取指定节点的父节点
of_get_next_child 函数用迭代的方式查找子节点
提取属性值的 OF 函数
of_find_property 函数用于查找指定的属性
of_property_count_elems_of_size 函数用于获取属性中元素的数量
of_property_read_u32_index 函数用于从属性中获取指定标号的 u32 类型数据值
of_property_read_u8/u16/u32/u64_array 这 4 个函数分别是读取属性中 u8、u16、u32 和 u64 类型的数组数据
of_property_read_u8/u16/u32/u64 这四个函数就是用于读取这种只有一个整形值的属性,分别用 于读取 u8、u16、u32 和 u64 类型属性值
of_property_read_string 函数用于读取属性中字符串值
of_n_addr_cells 函数用于获取#address-cells 属性值
of_size_cells 函数用于获取#size-cells 属性值
5 LED驱动程序编写
#include//模块加载卸载函数 #include //内核头文件 #include //数据类型定义 #include //file_operations结构体 #include //class_create等函数 #include #include #include #include #include #include #include #include #define DEVICE_NAME "led" typedef struct{ struct device_node *led_node;//设备树节点 int led_pin;//GPIO 引脚 struct cdev cdev;//定义一个cdev结构体 struct class *class;//创建一个LED类 struct device *device;//创建一个LED设备 该设备是需要挂在LED类下面的 int major;//主设备号 dev_t dev_id; }led_typdef; static led_typdef ledx;//定义一个LED设备 static int led_open(struct inode *inode, struct file *filp) { int ret = -1; retry: ret = gpio_request(ledx.led_pin, "firefly:yellow:user"); if(ret != 0) { printk("gpio %d req failed:%drn",ledx.led_pin,ret); gpio_free(ledx.led_pin); goto retry; } gpio_direction_output(ledx.led_pin,0); return 0; } static int led_release(struct inode* inode ,struct file *filp) { gpio_free(ledx.led_pin); return 0; } static int led_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { int ret; unsigned char pbuf[1]; ret = __copy_from_user(pbuf,buf, 1); if(ret < 0) { printk("__copy_from_user failedrn"); return ret; } gpio_set_value(ledx.led_pin, pbuf[0]); return 0; } static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } static struct file_operations led_fops={ .owner = THIS_MODULE, .open = led_open, .write = led_write, .read = led_read, .release = led_release, }; static int __init led_init(void) { ledx.led_node = of_find_node_by_path("/leds/user"); printk("/leds:%prn",ledx.led_node); if(ledx.led_node == NULL) { printk("find node by path fialed!rn"); return -1; } ledx.led_pin = of_get_named_gpio(ledx.led_node,"gpios",0); printk("led_pin:%drn",ledx.led_pin); if(!gpio_is_valid(ledx.led_pin)) { printk("get_gpio failed:%d rn",ledx.led_pin); return -1; } alloc_chrdev_region(&ledx.dev_id,0,1,DEVICE_NAME); cdev_init(&ledx.cdev,&led_fops); cdev_add(&ledx.cdev,ledx.dev_id,1); ledx.class = class_create(THIS_MODULE, "led_class"); if(ledx.class == NULL) { printk("class_create failedrn"); return -1; } ledx.device = device_create(ledx.class, NULL, ledx.dev_id, NULL, DEVICE_NAME); printk("module init okn"); return 0; } static void led_exit(void) { cdev_del(&ledx.cdev); unregister_chrdev_region(ledx.dev_id, 1); device_destroy(ledx.class, ledx.dev_id); class_destroy(ledx.class); printk("module exit okn"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("lsjml2022");
6 编写测试APP
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!rn");
return -1;
}
filename = argv[1];
//打开驱动
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed!rn", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
//文件写入数据
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0)
{
printf("Control Failed!rn");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0)
{
printf("file %s close failed!rn", argv[1]);
return -1;
}
return 0;
}
}
7 编译驱动程序和测试APP
7.1 编译驱动程序
KDIR:=/rk3288_linux/rk3288_linux_release_20210304/kernel
obj-m:=led_driver.o
PWD:=$(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions
输入如下命令编译出驱动模块文件:
make -j8
编译成功后会生成一个.ko文件拷贝到开发板上并加载
7.2 编译测试app
输入如下命令编译测试 ledApp.c 这个测试程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
编译成功以后就会生成 ledApp 这个应用程序
7.3 运行测试
编译出来的.ko 和 ledApp 这两个文件拷贝到/lib/modules/4.4.194目录中,重启开发板,进入目录/lib/modules/4.4.194中输入加载led.ko驱动模块:
insmod led_driver.ko
驱动加载成功以后就可以使用ledAPP软件来测试驱动是否正常,输入如下命令打开LED灯:
./ledApp /dev/led 1 //打开LED灯
./ledApp /dev/led 0 //关闭 LED 灯
rmmod led.ko //卸载驱动模块



