从platform设备驱动开始的linux点灯之路

概要

本文主要介绍基于设备树的platform设备驱动编写流程,以一个简单的led设备为案例展开。其中涉及pinctrl和gpio子系统,以及字符设备驱动,platform设备驱动。本文的内容是基于nxp的imx6ull开发板的资源。

设备资源编辑流程

编辑设备树

本节内容使用了pinctrl和gpio子系统,在设备树中描述所需资源的信息,通过pinctrl的方式表达。在设备树的iomuxc下添加pinctrl_led:gpioled节点

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
    >;
};

然后在设备树的根目录下添加myled设备

myled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "myled";
		status = "okay";
		pinctrl_name = "default";
		pinctrl-0 = <&pinctrl_led>;
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 定义在imx6ul-pinfunc.h文件中,我们可以在该文件中看到其被定义为5个数值

#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03             0x0068 0x02F4 0x0000 0x5 0x0

这5个数值的含义如下:

<mux_reg conf_reg input_reg mux_mode input_val> 前三个为寄存器偏移地址,后两个为寄存器配置数据。

而在MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 后的0x10B0表示引脚的电气特性。

编辑platform设备驱动需要的资源

static const struct of_device_id led_idtable[] = {
    {.compatible = "myled"},
    {}
};
int myled_probe(struct platform_device *pdev);
int myled_remove(struct platform_device *pdev);
static struct platform_driver ledplatform_driver = {
    .probe = myled_probe,
    .remove = myled_remove,
    .driver = {
        .name = "Braju's Led",
        .of_match_table = of_match_ptr(led_idtable),
    },
};

在写代码前先明确自己需要哪些资源,在编写platform设备驱动的过程中,我们首先需要一个platform driver来表述我们的驱动信息。以下是其原型

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

其中probe是当匹配到设备时触发的回调函数,主要用于设备信息的初始化。

remove函数是当驱动被卸载的时候触发,用于释放驱动创建占用的资源

shutdown函数在系统关机时被调用

suspend在系统休眠时调用

resume在系统唤醒时调用

driver,该结构体用来描述驱动可匹配的device以及驱动名称。驱动是依托于设备存在的,所以我们需要在driver中实现一个devicetable,表述形式为struct of_device_id  *of_match_table下面是该结构体的原型

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

在这个结构体中通常使用的属性为data和compatible,data可以作数据将设备信息传递给驱动,可以用来进行热插拔的应用,用以进行设备的识别。而compatible作为与设备树中的device进行匹配的信息。

以上就是我们的platform所需要的资源了。接下来就是介绍实现过程

设备驱动实现流程

明确当前设备类型

在实现一样东西前,需要先确定好目标。这里我的目标是写一个led字符设备驱动,可以通过文件操作控制led的亮灭,所以我们定义这样一个led对象以及文件操作对象。

struct leddev_dev{
    dev_t devid; 
    struct cdev mcdev; 
    struct class *mclass; 
    struct device *mdevice;
    int marjor;
    int led0;
}myleddev;
int myled_open(struct inode *pnode, struct file *pfile);
int myled_release (struct inode *pnode, struct file *pfile);
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset);
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset);
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_release,
    .write = myled_write,
    .read = myled_read,
};

以上就是我们所需要控制的设备。

实现platform设备驱动匹配以及初始化

当所有目标都明确后我们需要实现probe,实现当匹配到设备后自动进行的初始化流程

首先是字符设备驱动的通用5个步骤。

1.申请设备id

 /*1.设置设备号*/
    if(my_leddev.marjor){
        my_leddev.devid = MKDEV(my_leddev.marjor, 0);
        register_chrdev_region(my_leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
    }
    else{
        alloc_chrdev_region(&my_leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
        my_leddev.marjor = MAJOR(my_leddev.devid);
    }

2.注册cdev

/*2.注册设备*/
    cdev_init(&my_leddev.mcdev, &led_fops);
    cdev_add(&my_leddev.mcdev, my_leddev.devid, LEDDEV_CNT);

3.创建class

/*3.创建类*/
    my_leddev.mclass = class_create(THIS_MODULE, LEDDEV_NAME);
    if(IS_ERR(my_leddev.mclass)){
        return PTR_ERR(my_leddev.mclass);
    }

4.创建device

/*4.创建设备*/
    my_leddev.mdevice = device_create(my_leddev.mclass, NULL, my_leddev.devid, NULL, LEDDEV_NAME);
    if(IS_ERR(my_leddev.mdevice)){
        return PTR_ERR(my_leddev.mdevice);
    }

5.初始化硬件资源

通过of匹配的方式来从设备中拉取到pinctrl的信息,然后通过gpio家族函数申请并初始化硬件设备。

int led_init(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    const struct of_device_id *match;
    int ret = 0;
    match =  of_match_device(led_idtable, &pdev->dev);
    if(match){
        my_leddev.led0 = of_get_named_gpio(pdev->dev.of_node, "led-gpio", 0);
        if(my_leddev.led0 < 0){
            pr_err("can't get led gpio!\r\n");
            return -EINVAL;
        }
    }
    else{
        pr_err("can't match device!\r\n");
        return -ENOENT;
    }
    gpio_request(my_leddev.led0, "led0");
    ret = gpio_direction_output(my_leddev.led0, 1);
    if(ret < 0){
        pr_err("can't set gpio!\r\n");
        return -EINVAL;
    }
    return 0;
}

通过以上的5个步骤我们就完成了所有资源的初始化。

LED驱动外部接口实现

然后就是对外部应用的接口实现了,我们的目标是可以通过read读取led的状态,以及通过write来设置led的状态。

int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data = &my_leddev.led0;
    return 0;
}
int myled_release (struct inode *pnode, struct file *pfile)
{
    pfile->private_data = NULL;
    return 0;
}
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset)
{
    int retv;
    int led = *((int *)pfile->private_data);
    retv = gpio_get_value(led);
    if(retv == 0){
        u8 status = 1;
        retv = copy_to_user(pdate, &status, sizeof(status));
        if(retv < 0){
            pr_err("copy date to userspace failed!");
            return EFAULT;
        }
    }
    else if(retv == 1){
        u8 status = 0;
        retv = copy_to_user(pdate, &status, sizeof(status));
        if(retv < 0){
            pr_err("copy date to userspace failed!");
            return -EFAULT;
        }
    }
    else {
        return -ENAVAIL;
    }
    return 1;
}
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset)
{
    int ret;
    u8 ledstatus;
    int led = *((int *)pfile->private_data);
    ret = copy_from_user(&ledstatus, pdate, size);
    if(ret < 0){
        return -EFAULT;
    }
    if(ledstatus == LED_ON){
        gpio_set_value(led, 0);
    }
    else if(ledstatus == LED_OFF){
        gpio_set_value(led,1);
    }
    return 0; 
}

以上就是一个字符设备驱动实现的流程了,只能说easy。

完整代码

所有的一切都从点亮一个灯开始。只要熟练掌握了点灯,其他的都是考验内功(算法)深度。

下面给出完整代码,可以作为其他字符设备platform驱动的一个demo。

#include "linux/init.h"
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/device.h"
#include "linux/cdev.h"
#include "linux/errno.h"
#include "linux/fs.h"
#include "linux/platform_device.h"
#include "linux/types.h"
#include "asm/mach/map.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#define LEDDEV_CNT    1///<设备个数
#define LEDDEV_NAME  "myled"
#define LED_ON        1
#define LED_OFF       0
struct leddev_dev{
    dev_t devid;
    struct cdev mcdev;
    struct class *mclass;
    struct device *mdevice;
    int marjor;
    int led0;
}my_leddev;
int myled_probe(struct platform_device *pdev);
int myled_remove(struct platform_device *pdev);
static const struct of_device_id led_idtable[] = {
    {.compatible = "myled"},
    {}
};
int myled_open(struct inode *pnode, struct file *pfile);
int myled_release (struct inode *pnode, struct file *pfile);
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset);
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset);
static struct platform_driver ledplatform_driver = {
    .probe = myled_probe,
    .remove = myled_remove,
    .driver = {
        .name = "Braju's Led",
        .of_match_table = of_match_ptr(led_idtable),
    },
};
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_release,
    .write = myled_write,
    .read = myled_read,
};
int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data = &my_leddev.led0;
    return 0;
}
int myled_release (struct inode *pnode, struct file *pfile)
{
    pfile->private_data = NULL;
    return 0;
}
ssize_t myled_read (struct file *pfile, char __user *pdate, size_t size, loff_t * poffset)
{
    int retv;
    int led = *((int *)pfile->private_data);
    retv = gpio_get_value(led);
    if(retv == 0){
        u8 status = 1;
        retv = copy_to_user(pdate, &status, sizeof(status));
        if(retv < 0){
            pr_err("copy date to userspace failed!");
            return EFAULT;
        }
    }
    else if(retv == 1){
        u8 status = 0;
        retv = copy_to_user(pdate, &status, sizeof(status));
        if(retv < 0){
            pr_err("copy date to userspace failed!");
            return -EFAULT;
        }
    }
    else {
        return -ENAVAIL;
    }
    return 1;
}
ssize_t myled_write (struct file *pfile, const char __user *pdate, size_t size, loff_t *poffset)
{
    int ret;
    u8 ledstatus;
    int led = *((int *)pfile->private_data);
    ret = copy_from_user(&ledstatus, pdate, size);
    if(ret < 0){
        return -EFAULT;
    }
    if(ledstatus == LED_ON){
        gpio_set_value(led, 0);
    }
    else if(ledstatus == LED_OFF){
        gpio_set_value(led,1);
    }
    return 0; 
}
int led_init(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    const struct of_device_id *match;
    int ret = 0;
    match =  of_match_device(led_idtable, &pdev->dev);
    if(match){
        my_leddev.led0 = of_get_named_gpio(pdev->dev.of_node, "led-gpio", 0);
        if(my_leddev.led0 < 0){
            pr_err("can't get led gpio!\r\n");
            return -EINVAL;
        }
    }
    else{
        pr_err("can't match device!\r\n");
        return -ENOENT;
    }
    gpio_request(my_leddev.led0, "led0");
    ret = gpio_direction_output(my_leddev.led0, 1);
    if(ret < 0){
        pr_err("can't set gpio!\r\n");
        return -EINVAL;
    }
    return 0;
}
int myled_probe(struct platform_device *pdev)
{
    pr_info("led platform device was matched! \r\n");
    /*1.设置设备号*/
    if(my_leddev.marjor){
        my_leddev.devid = MKDEV(my_leddev.marjor, 0);
        register_chrdev_region(my_leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
    }
    else{
        alloc_chrdev_region(&my_leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
        my_leddev.marjor = MAJOR(my_leddev.devid);
    }
    /*2.注册设备*/
    cdev_init(&my_leddev.mcdev, &led_fops);
    cdev_add(&my_leddev.mcdev, my_leddev.devid, LEDDEV_CNT);
    /*3.创建类*/
    my_leddev.mclass = class_create(THIS_MODULE, LEDDEV_NAME);
    if(IS_ERR(my_leddev.mclass)){
        return PTR_ERR(my_leddev.mclass);
    }
    /*4.创建设备*/
    my_leddev.mdevice = device_create(my_leddev.mclass, NULL, my_leddev.devid, NULL, LEDDEV_NAME);
    if(IS_ERR(my_leddev.mdevice)){
        return PTR_ERR(my_leddev.mdevice);
    }
    /*5.设备初始化*/
    return led_init(pdev);
}
int myled_remove(struct platform_device *pdev)
{
    cdev_del(&my_leddev.mcdev);
    unregister_chrdev_region(my_leddev.devid, LEDDEV_CNT);
    device_destroy(my_leddev.mclass, my_leddev.devid);
    class_destroy(my_leddev.mclass);
    return 0;
}
module_platform_driver(ledplatform_driver);
MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值