【Linux驱动】platform 设备驱动分离(三)—— LED驱动(设备树)

本文详细介绍了如何在Linux平台中,利用设备树和platform驱动机制驱动LED,包括添加设备树节点、设置compatible属性匹配、注册platform驱动以及处理中断。作者还提供了完整的驱动代码示例和测试脚本,展示了整个过程。
摘要由CSDN通过智能技术生成

在使用设备树的情况下,无需自己手动注册 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)

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值