在使用设备树的情况下,无需自己手动注册 platform 设备,在开发板上电加载Linux 内核和设备树的时候,就已经将设备树信息转换成 platform_device 类型了。因此,在使用设备树的情况下,只需要编写 platform 驱动。
为了防止自己新增的节点和设备树原本的发生冲突,建议注释原本的节点。
一、添加设备树节点
这里使用自己创建的节点只是为了熟悉匹配流程,当然也可以使用原本的节点,只需要保证在注册 platform 驱动时 compatible 变量的值和设备树中 compatible 属性的值保持一致即可。(因为platform 驱动与设备树匹配时,依赖 compatible 属性)
再次强调,这里新增LED节点的 compatible 属性值为 alientek-led。(后续要用到)
gpio-led {
compatible = "alientek-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <3 IRQ_TYPE_EDGE_RISING>;
status = "okay";
};
二、platform 驱动注册
整体逻辑与不使用设备树时的一样,需额外新增 compatible 属性,上面也提到了 platform 驱动与设备树匹配时,依赖 compatible 属性。
第一步,在 platform_driver 的实例中添加 of_match_table 变量的定义。
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ull-led",
.of_match_table = led_of_match, // 匹配表
},
.probe = led_probe,
.remove = led_remove,
};
第二步,定义 led_of_match 变量。compatible 属性在这里添加,这里的 compatible 变量的值需要与上面设备树的 compatible 属性值保持一致。
struct of_device_id led_of_match[] = {
{ .compatible = "alientek-led" },
};
三、完整驱动
剩下的就和之前使用 pinctrl 子系统和 gpio 子系统驱动LED的逻辑基本一致了。在 probe 函数中执行获取设备树节点、初始化 gpio、注册设备号、驱动节点等操作。
这么看下来,与之前写的 pinctrl 子系统和gpio子系统来驱动LED时,不同之处在于,新增了驱动与设备的匹配。
#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/uaccess.h>
#include <linux/cdev.h> // cdev_init
#include <linux/device.h> // device_create
#include <linux/err.h> // IS_ERR
#include <asm/io.h> // ioremap、iounmap
#include <linux/platform_device.h>
#include <linux/of.h> // 获取设备树属性 API
#include <linux/of_gpio.h> // of_get_named_gpio
#include <asm/gpio.h> // gpio_set_value
#define CHRDEVBASE_NAME "platled" /* 设备名 */
enum LED_STAT {
LED_ON,
LED_OFF
};
struct chrdev_led_t{
struct class* class; /* 设备节点所属类 */
struct device* device; /* 设备节点 */
struct cdev dev; /* 字符设备 */
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node* gpioNode; /* 设备树节点 */
int gpioNum; /* gpio 引脚编号 */
};
static struct chrdev_led_t chrdev_led;
/*
* @description : 打开设备
* @param – pinode : 传递给驱动的 inode
* @param - pfile : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int chrdevbase_open(struct inode *pinode, struct file *pfile)
{
/* 用户实现具体功能 */
printk("open platled driver\n");
pfile->private_data = &chrdev_led;
return 0;
}
/*
* @description : 向设备写数据
* @param - pfile : 要打开的设备文件(文件描述符)
* @param - buf : 要给设备写入的数据(用户缓冲区)
* @param - cnt : 要写入的数据长度
* @param - offset : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrdevbase_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offset)
{
// 获取模块数据
struct chrdev_led_t* pdev = pfile->private_data;
// printk("write chrdevbase\n");
uint8_t databuf[1];
uint8_t ledstat;
uint32_t ret = 0;
// 将数据从用户缓冲区拷贝到内核缓冲区
ret = copy_from_user(databuf, buf, cnt);
if(ret != 0)
return 0;
ledstat = buf[0] - '0';
// printk("led state: %d\n", ledstat);
if (ledstat == LED_ON)
{
gpio_set_value(pdev->gpioNum, 0);
}
else if(ledstat == LED_OFF)
{
gpio_set_value(pdev->gpioNum, 1);
}
return cnt;
}
/*
* @description : 关闭/释放设备
* @param - pfile : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int chrdevbase_release (struct inode *pinode, struct file * pfile)
{
/* 用户实现具体功能 */
printk("close chrdevbase\n");
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
static int led_probe(struct platform_device *dev)
{
printk("match device successfully!\n");
uint32_t ret = 0;
/* 通过设备树获取到寄存器地址 */
// 获取节点
chrdev_led.gpioNode = of_find_node_by_path("/gpio-led");
if(chrdev_led.gpioNode == NULL)
{
printk("node cannot be found!\n");
return -1;
}
// 读取gpio编号
chrdev_led.gpioNum = of_get_named_gpio(chrdev_led.gpioNode, "led-gpio", 0);
if (chrdev_led.gpioNum < 0)
{
printk("gpio property fetch failed!\n");
return -1;
}
// 配置 GPIO1_IO03 为输出且高电平,默认关闭LED
ret = gpio_direction_output(chrdev_led.gpioNum, 1);
if (ret < 0)
{
printk("gpio set failed!\n");
return -1;
}
/* 1. 注册设备号 */
if (chrdev_led.major)
{
chrdev_led.devid = MKDEV(chrdev_led.major, 0);
ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
}
else
{
ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
chrdev_led.major = MAJOR(chrdev_led.devid);
chrdev_led.minor = MINOR(chrdev_led.devid);
}
/* 2. 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;
cdev_init(&chrdev_led.dev, &chrdevbase_fops); // 初始化字符设备
/* 3. 将字符设备添加到内核 */
cdev_add(&chrdev_led.dev, chrdev_led.devid, 1); // 将字符设备添加到内核
/* 自动创建设备节点 */
// 设备节点所属类
chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.class))
{
return PTR_ERR(chrdev_led.class);
}
// 创建设备节点
chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.device))
{
return PTR_ERR(chrdev_led.device);
}
return 0;
}
static int led_remove(struct platform_device *dev)
{
/* 注销字符设备 */
unregister_chrdev_region(chrdev_led.devid, 1); // 注销设备号
cdev_del(&chrdev_led.dev); // 卸载字符设备
device_destroy(chrdev_led.class, chrdev_led.devid); // 删除节点
class_destroy(chrdev_led.class); // 删除类
return 0;
}
struct of_device_id led_of_match[] = {
{ .compatible = "alientek-led" },
};
/* 驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ull-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
printk("platled driver init!\n");
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
printk("platled driver removed!\n");
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");
四、main.c测试脚本
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void printHelp()
{
printf("usage: ./chrdevbaseApp <driver_path> <state>\n");
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
printHelp();
return -1;
}
char* driver_path = argv[1]; // 位置0 保存的是 ./chrdevbaseApp
char* led_stat = argv[2];
int fd = open(driver_path, O_WRONLY);
if (fd < 0)
{
perror("open file failed");
return -2;
}
write(fd, led_stat, 1);
close(fd);
return 0;
}
五、Makefile脚本
ARCH := arm
CROSS_COMPILE := arm-linux-gnueabihf-
CC := $(CROSS_COMPILE)gcc
KERNEL_DIR := /home/gzx/IMX6ULL/kernel/4.1.15/linux-imx-4.1.15-source-compiled
ROOTFS_MODULE_DIR := /usr/local/nfsd/rootfs/lib/modules/4.1.15/
CURRENR_DIR := $(shell pwd)
MODULE_NAME := led-driver
obj-m += $(foreach subitem,${MODULE_NAME},$(subitem).o)
kofile = $(patsubst %.o,%.ko,${obj-m})
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENR_DIR) modules
$(CC) -o testApp main.c
.PHONY:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENR_DIR) clean
@rm testApp
.PHONY:copy
copy:
sudo cp -f testApp ${kofile} $(ROOTFS_MODULE_DIR)