树莓派linux led字符设备驱动(pinctrl 和 gpio 子系统)

一、pinctrl 子系统

   Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO
的配置推出了 gpio 子系统。pinctrl 子系统就是为了解决配置寄存器比较繁琐且易出错而引入的。要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息。

二、gpio 子系统

   pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,而 gpio 子系统就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了使用 GPIO。

三、编写驱动程序

3.1、修改设备树文件

ledtest{
    
    
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "ledtest";
	pinctrl-names = "default";
	linux,default-trigger = "input";
	gpios = <&gpio 26 0>;
	status = "okay";
};

3.2、编写驱动

#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/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"ledtest"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 */

/* gpioled设备结构体 */
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;			/* led所使用的GPIO编号		*/
};

struct gpioled_dev gpioled;	/* led设备 */


static int led_open(struct inode *inode, struct file *filp)
{
    
    
	filp->private_data = &gpioled; /* 设置私有数据 */
	return 0;
}


static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    
    
	return 0;
}


static ssize_t led_write(struct file *filp, const 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);	/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
    
    
		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
	}
	return 0;
}

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,
};


static int __init led_init(void)
{
    
    
	int ret = 0;

	/* 设置LED所使用的GPIO */
	/* 1、获取设备节点:gpioled */
	gpioled.nd = of_find_node_by_path("/ledtest");
	if(gpioled.nd == NULL) {
    
    
		printk("ledtest node not find!\r\n");
		return -EINVAL;
	} else {
    
    
		printk("ledtest node find!\r\n");
	}

	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios ", 0);
	if(gpioled.led_gpio < 0) {
    
    
		printk("can't get gpios");
		return -EINVAL;
	}
	printk("gpios num = %d\r\n", gpioled.led_gpio);
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0) {
    
    
		printk("can't set gpio!\r\n");
	}

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (gpioled.major) {
    
    		/*  定义了设备号 */
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	} else {
    
    						/* 没有定义设备号 */
		alloc_chrdev_region(&gpioled.devid, 0, 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);	
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

	/* 4、创建类 */
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(gpioled.class)) {
    
    
		return PTR_ERR(gpioled.class);
	}

	/* 5、创建设备 */
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
	if (IS_ERR(gpioled.device)) {
    
    
		return PTR_ERR(gpioled.device);
	}
	return 0;
}


static void __exit led_exit(void)
{
    
    
	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev);/*  删除cdev */
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

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

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zsx");

四、运行测试

  将编译出来的 led.ko文件拷贝到/lib/modules/5.10.17-v7l+/ 目录中,输入如下命令加载 led.ko 驱动模块:

sudo depmod //第一次加载驱动的时候需要运行此命令
sudo modprobe ledtest//加载驱动
dmesg //查看模块打印信息

  驱动加载成功以后会自动在/dev 目录下创建设备节点文件/dev/ledtest

猜你喜欢

转载自blog.csdn.net/xxxx123041/article/details/120356217