Linux platform设备驱动

本文章来源于正点原子资料,记录下来,以后参考。
基于正点原子imx6ull开发板

1、platform总线

Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,
bus_type 结构体内容如下:

struct bus_type {
	const char		*name;				/* 总线名字*/
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;	/* 总线属性*/
	const struct attribute_group **dev_groups;	/* 设备属性*/
	const struct attribute_group **drv_groups;	/* 驱动属性*/

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

2、platform驱动

platform_driver 结 构 体 表 示 platform 驱 动 , 此 结 构 体 定 义 在 文 件
include/linux/platform_device.h 中,内容如下:

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

platform驱动框架如下:

#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/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 设备结构体*/
struct led_dev{
    struct cdev cdev;
    /* 设备结构体的其他内容*/
};

struct led_dev leddev;  /* 定义设备结构体变量*/

static int led_open(struct inode *inode, struct file *filp){
    filp->private_data = &leddev;
    return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt , loff_t *offt){
    return 0;
}
const struct file_operations *led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/*
*   platform驱动的probe函数
*   驱动与设备匹配成功以后此函数将会执行
*/
static int led_probe(struct platform_device *dev){
    
    ......
    cdev_init(&leddev.cdev,&led_fops);
    /* 创建字符设备驱动还有未完成的部分*/
    return 0;
}

static int led_remove(struct platform_device *dev){
    ......
    cdev_dev(&leddev.cdev);
    /*  注销字符设备驱动还有要完成的部分*/
    return 0;
}

/* 匹配列表*/
const struct of_device_id led_of_match[] = {
    {.compatible = "led-gpio"},
    {/*必须有一个空的结构体变量*/}
};

/*platform平台驱动结构体*/
static struct platform_driver led_driver = {
	.probe = led_probe,
	.remove = led_remove,
	.shutdown = NULL,
	.driver = {
		.name = "led",   
        .of_match_table = led_of_match,
	},
};

/* 驱动模块加载*/
static int __init leddriver_init(void){
    int ret;
    ret = platform_driver_register(&led_driver);
    return 0;
}

/* 驱动模块卸载*/
static void __exit leddriver_exit(void){
    platform_driver_unregister(&led_driver);
    return ;
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIANG");

代码分析
25-44 行,传统的字符设备驱动,所谓的 platform 驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动。platform 只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。

50-56 行,led_probe函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。比如注册字符设备驱动、添加 cdev、创建类等等。

58-63行, led_remove函数, platform_driver 结构体中的 remove 成员变量,当关闭 platform设备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,销设备号等等。

65-69 行,led_of_match匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。设置了一个匹配项,此匹配项的 compatible 值为“led-gpio”,因此当设备树中设备节点的 compatible 属性值为“led-gpio”的时候此设备就会与此驱动匹配。
第 68行是一个标记,of_device_id 表最后一个匹配项必须是空的。

71-80 行,定义一个 platform_driver 结构体变量 led_driver,表示 platform 驱动,
76-79行设置 paltform_driver 中的 device_driver 成员变量的 name 和 of_match_table 这两个属性。
其中name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。
of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法。
最后73和74 这两行设置 probe 和 remove 这两成员变量。

82-87行,驱动入口函数,调用 platform_driver_register 函数向 Linux 内核注册一个 platform驱动,也就是上面定义的 led_driver结构体变量。
89-93行,驱动出口函数,调用 platform_driver_unregister 函数卸载前面注册的 platform驱动。

3、platform设备

platform 驱动已经准备好了,我们还需要 platform 设备,否则的话单单一个驱动也做不了什么。platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。当然了,你如果一定要用 platform_device 来描述设备信息的话也是可以的。platform_device 结构体定义在文件include/linux/platform_device.h 中,结构体内容如下:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

第1行,name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name字段也要设置为“xxx-gpio”。

第6行,num_resources 表示资源数量,一般为第7行 resource 资源的大小。

第7行,resource 表示资源,也就是设备信息,比如外设寄存器等。Linux 内核使用 resource结构体表示资源,resource 结构体内容如下:

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name 表示资源名字,flags 表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h 里面,如下所示:

29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
30
31 #define IORESOURCE_TYPE_BITS  0x00001f00 /* Resource type */
32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
33 #define IORESOURCE_MEM 0x00000200
34 #define IORESOURCE_REG 0x00000300 /* Register offsets */
35 #define IORESOURCE_IRQ 0x00000400
36 #define IORESOURCE_DMA 0x00000800
37 #define IORESOURCE_BUS 0x00001000
......
104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备,platform_device_unregister 函数原型如下:

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值:无。

platform设备信息框架如下:
led_platformdevice.c

#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/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 
 * 寄存器地址定义
 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)
#define REGISTER_LENGTH				4

/* 释放platform设备时,这个函数将会执行*/
static void	led_release(struct device *dev)
{
	printk("led device released!\r\n");	
}

struct resource led_resources[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = SW_MUX_GPIO1_IO03_BASE,
        .end = SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = SW_PAD_GPIO1_IO03_BASE,
        .end = SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM,
    },
    [3] = {
        .start = GPIO1_DR_BASE,
        .end = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM,
    },
    [4] = {
        .start = GPIO1_GDIR_BASE,
        .end = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM,
    },
};

static struct platform_device led_device = {
    .name	= "led-test",
    .id  = -1,
    .dev = {
        .release = &led_release,
    },
    .num_resources = ARRAY_SIZE(led_resources),
    .resource = led_resources,
};

static int __init leddevice_init(void){
    int ret ;
    ret = platform_device_register(&led_device);
    return ret;
}

static void __exit leddevice_exit(void){
    platform_device_unregister(&led_device);
    return ;
}

module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIANG");

上面的代码是在不支持设备树的 Linux 版本中使用的,当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述,Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式,至于设备树到 platform_device 的具体过程怎么样的,待分析?



实验一:不使用设备树的驱动

led_platformdriver.c

#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/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define CNT 1
#define DEV_NAME "leddemo"
#define LEDON 1
#define LEDOFF 0

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* 设备结构体*/
struct led_dev{
    dev_t devid;        /* 设备号*/
    struct cdev cdev;   /* cdev*/
    /* 设备结构体的其他内容*/
    struct class *class; /* 类*/
    struct device *device; /* 设备*/
    int major; /* 主设备号*/
    int minor; /* 次设备号*/
};

struct led_dev leddev;  /* 定义设备结构体变量*/

static void led_switch(u8 status){
    u32 val = 0;
    if(status == LEDON){
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
        printk("LED ON\n");
    }else if( status == LEDOFF){
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
        printk("LED OFF\n");
    }
}

static int led_open(struct inode *inode, struct file *filp){
    filp->private_data = &leddev;
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt , loff_t *offt){
    int ret;
    unsigned char databuf[1];
    unsigned char ledstate;

    ret = copy_from_user(databuf,buf,cnt);
    if(ret < 0){
        return -EFAULT;
    }
    
    ledstate = databuf[0];
    if( LEDON == ledstate){
        led_switch(LEDON);        
    }else if( LEDOFF == ledstate){
        led_switch(LEDOFF);        
    }
    return 0;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/*
*   platform驱动的probe函数
*   驱动与设备匹配成功以后此函数将会执行
*/
static int led_probe(struct platform_device *dev){
    
    int i = 0;
    int ressize[5];
    u32 val = 0;
    struct resource *resource[5];

    printk("led driver and device has matched!\n");
    /* 1、获取资源*/
    for(i = 0;i < 5;i++){
        resource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
        if(!resource[i]){
            dev_err(&dev->dev,"No MEM resource\n");
            return -ENXIO;
        }
        ressize[i] = resource_size(resource[i]);
        printk("---i---%d--begin=%x--end=%x\n",i,resource[i]->start,resource[i]->end);
    }

    /* 2、初始化LED*/
    /* 寄存器地址映射*/
    IMX6U_CCM_CCGR1 = ioremap(resource[0]->start,ressize[0]);
    SW_MUX_GPIO1_IO03 = ioremap(resource[1]->start,ressize[1]);
    SW_PAD_GPIO1_IO03 = ioremap(resource[2]->start,ressize[2]);
    GPIO1_DR = ioremap(resource[3]->start,ressize[3]);
    GPIO1_GDIR = ioremap(resource[4]->start,ressize[4]);

    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);
    val |= (3 << 26);
    writel(val,IMX6U_CCM_CCGR1);

    /* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03*/
    writel(5,SW_MUX_GPIO1_IO03);
    writel(0x10B0,SW_PAD_GPIO1_IO03);

    /* 设置GPIO1_IO03为输出*/
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3);
    val |= (1 << 3);
    writel(val,GPIO1_GDIR);

    /* 默认关闭LED*/
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val,GPIO1_DR);

    /* 注册字符设备驱动*/
    /* 1、创建设备号*/
    if(leddev.major){
        leddev.devid = MKDEV(leddev.major,0);
        register_chrdev_region(leddev.devid,CNT,DEV_NAME);
    }else{
        alloc_chrdev_region(&leddev.devid,0,CNT,DEV_NAME);
        leddev.major = MAJOR(leddev.devid);
        leddev.minor = MINOR(leddev.devid);
    }

    /* 2、初始化cdev,添加一个cdev*/
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);
    cdev_add(&leddev.cdev,leddev.devid,CNT);
    
    /* 创建字符设备驱动还有未完成的部分*/
    /* 3、创建类*/
    leddev.class = class_create(THIS_MODULE,DEV_NAME);
    if(IS_ERR(leddev.class)){
        return PTR_ERR(leddev.class);
    }

    /* 4、创建设备*/
    leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,DEV_NAME);
    if(IS_ERR(leddev.device)){
        return PTR_ERR(leddev.device);
    }

    return 0;
}

static int led_remove(struct platform_device *dev){
    
    iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

    //......
    cdev_del(&leddev.cdev);
    /*  注销字符设备驱动还有要完成的部分*/
    unregister_chrdev_region(leddev.devid,CNT);
    device_destroy(leddev.class,leddev.devid);
    class_destroy(leddev.class);
    return 0;
}

#if DTB/* 使用传统的设备驱动模式,不使用设备树*/
/* 匹配列表*/
const struct of_device_id led_of_match[] = {
    {.compatible = "led-gpio"},
    {/*必须有一个空的结构体变量*/}
};
#endif

/*platform平台驱动结构体*/
static struct platform_driver led_driver = {
	.probe = led_probe,
	.remove = led_remove,
	.shutdown = NULL,
	.driver = {
		.name = "led-test",      /* 传统的设备驱动模型中,驱动名字,用于和设备匹配*/
        /* 使用传统的设备驱动模式,不使用设备树*/
        //.of_match_table = led_of_match,
	},
};

/* 驱动模块加载*/
static int __init leddriver_init(void){
    int ret;
    ret = platform_driver_register(&led_driver);
    return 0;
}

/* 驱动模块卸载*/
static void __exit leddriver_exit(void){
    platform_driver_unregister(&led_driver);
    return ;
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIANG");

编译led_platformdriver.c和led_platformdevice.c生成led_platformdevice.ko、led_platformdriver.ko

depmod                     //第一次加载驱动的时候需要运行此命令
modprobe led_platformdevice.ko     //加载设备模块
modprobe led_platformdriver.ko      //加载驱动模块
加载驱动后,在根文件系统中/sys/bus/platform/目录下保存着当前板子 platform 总线下的设备和驱动,其中devices 子目录为 platform 设备,drivers 子目录为 plartofm 驱动,如下所示:

/sys/bus/platform/devices # ls -all | grep led-test
lrwxrwxrwx    1 0        0                0 Jan  1 01:25 led-test -> ../../../devices/platform/led-test
/sys/bus/platform/drivers # ls -all | grep led-test
drwxr-xr-x    2 0        0                0 Jan  1 01:14 led-test
/sys/bus/platform/drivers # cd led-test/
/sys/bus/platform/drivers/led-test # ls
bind      led-test  module    uevent    unbind


APP
LedApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDON 1
#define LEDOFF 0

int main(int argc,char *argv[])
{
    int fd;
    int ret;
    unsigned char data;
    char *filename;

    if(argc != 3){
        printf("Usage: ./LedApp /dev/leddemo  1 or\n");
        printf("       ./LedApp /dev/leddemo  0\n");
        return -1;
    }

    //filename = argv[1];
    fd = open(argv[1],O_RDWR);
    if(fd < 0){
        printf("open %s failed\n",argv[1]);
        return -1;
    }

    data = atoi(argv[2]);

    ret = write(fd,&data,sizeof(data));
    if(ret < 0){
        printf("Led control failed\n");
        close(fd);
        return -1;
    }

    ret = close(fd);
    if(ret < 0 ){
        printf("close %s failed\n",argv[1]);
        return -1;
    }

    return 0;
}

实验二:使用设备树的platform驱动

dts修改
在imx6ull-14x14-evk.dts增加

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

	&iomuxc {
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_hog_1>;
		imx6ul-evk {
			pinctrl_led: ledgrp {
				fsl,pins = <
					MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10B0 /* 具体的设置值 */
				>;
			};
			......
		};
	}

platform 驱动
led_dtsplatformdriver.c

#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/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define CNT 1
#define DEV_NAME "leddtsdemo"
#define LEDON 1
#define LEDOFF 0

#define DTS 1

/* 设备结构体*/
struct led_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 led_dev leddev;  /* 定义设备结构体变量*/

static int led_open(struct inode *inode, struct file *filp){
    filp->private_data = &leddev;
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt , loff_t *offt){
    int ret;
    unsigned char databuf[1];
    unsigned char ledstate;
    struct led_dev *dev = filp->private_data;

    ret = copy_from_user(databuf,buf,cnt);
    if(ret < 0){
        return -EFAULT;
    }
    
    ledstate = databuf[0];
    if(ledstate == LEDON){
        gpio_set_value(dev->led_gpio,0);
        printk("LED ON\n");
    }else if(ledstate == LEDOFF){
        gpio_set_value(dev->led_gpio,1);
        printk("LED OFF\n");
    }
    return 0;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/*
*   platform驱动的probe函数
*   驱动与设备匹配成功以后此函数将会执行
*/
static int led_probe(struct platform_device *dev){
    
    int ret;
    char compatible_name[128];
    struct property *proper;

    printk("dts led driver match !\n");

    /* 注册字符设备驱动*/
    /* 1、创建设备号*/
    if(leddev.major){
        leddev.devid = MKDEV(leddev.major,0);
        register_chrdev_region(leddev.devid,CNT,DEV_NAME);
    }else{
        alloc_chrdev_region(&leddev.devid,0,CNT,DEV_NAME);
        leddev.major = MAJOR(leddev.devid);
        leddev.minor = MINOR(leddev.devid);
    }

    /* 2、初始化cdev,添加一个cdev*/
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);
    cdev_add(&leddev.cdev,leddev.devid,CNT);
    
    /* 创建字符设备驱动还有未完成的部分*/
    /* 3、创建类*/
    leddev.class = class_create(THIS_MODULE,DEV_NAME);
    if(IS_ERR(leddev.class)){
        return PTR_ERR(leddev.class);
    }

    /* 4、创建设备*/
    leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,DEV_NAME);
    if(IS_ERR(leddev.device)){
        return PTR_ERR(leddev.device);
    }

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

    /* 2、获取设备树中的gpio属性,获取LED使用的GPIO编号 */
    leddev.led_gpio = of_get_named_gpio(leddev.nd,"led-gpio1",0);
    if(leddev.led_gpio < 0 ){
        printk("can't get led-gpio\n");
        return -EINVAL;
    }

    /**获取设备节点compatible属性值*/
    proper = of_find_property(leddev.nd,"compatible",NULL);
    if(proper == NULL){
        printk("compatible property not found\n");
    }else{
        printk("compatible = %s,%s\n",proper->name,(char*)proper->value);
    }
    
   

    /*3、设置GPIO1_IO3为输出,并且输出高电平,默认关闭LED灯*/
    gpio_request(leddev.led_gpio, "led0");
    ret = gpio_direction_output(leddev.led_gpio,1);
    if(ret < 0){
        printk("can't set gpio1-3\n");
    }

    return 0;
}

static int led_remove(struct platform_device *dev){
    
    gpio_set_value(leddev.led_gpio,1); 
    //......
    cdev_del(&leddev.cdev);
    /*  注销字符设备驱动还有要完成的部分*/
    unregister_chrdev_region(leddev.devid,CNT);
    device_destroy(leddev.class,leddev.devid);
    class_destroy(leddev.class);
    return 0;
}

#if DTS/* 使用传统的设备驱动模式,不使用设备树*/
/* 匹配列表*/
const struct of_device_id led_of_match[] = {
    {.compatible = "atkalpha-gpioled"},
    {/*必须有一个空的结构体变量*/}
};
#endif

/*platform平台驱动结构体*/
static struct platform_driver led_driver = {
	.probe = led_probe,
	.remove = led_remove,
	.shutdown = NULL,
	.driver = {
		.name = "led-test",      /* 传统的设备驱动模型中,驱动名字,用于和设备匹配*/
        /* 使用传统的设备驱动模式,不使用设备树*/
        #if DTS
        .of_match_table = led_of_match,
        #endif 
    },
};

/* 驱动模块加载*/
static int __init leddriver_init(void){
    int ret;
    ret = platform_driver_register(&led_driver);
    return 0;
}

/* 驱动模块卸载*/
static void __exit leddriver_exit(void){
    platform_driver_unregister(&led_driver);
    return ;
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIANG");

编译led_dtsplatformdriver.c生成led_dtsplatformdriver.ko

depmod                     //第一次加载驱动的时候需要运行此命令
modprobe led_dtsplatformdriver.ko      //加载驱动模块

驱动调试方法
1、cat /proc/devices 查看设备
在这里插入图片描述
看图可知,多了一个主设备号为248的字符设备
2、ls -all /dev/* | grep 248
在这里插入图片描述
可以看出在 /dev 目录下多了/dev/leddtsdemo字符设备节点。说明字符设备驱动注册完成。
3、本实验使用的app程序同实验一的相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值