首页 > 编程语言 >LED驱动程序改造-基于总线设备驱动模型

LED驱动程序改造-基于总线设备驱动模型

时间:2024-04-22 16:46:08浏览次数:34  
标签:__ led 驱动程序 driver 总线 platform device LED include

目前我们基于LED驱动学习了两种编写Linux驱动程序的方法,分别是传统的方法和上下分层的基于面向对象的方法。其中基于上下分层的面向对象的驱动编写方法还可以进一步细化,把下层进行左右分离,针对使用同一芯片的不同开发板,可以抽象出一个针对芯片的GPIO引脚操作的文件,针对不同的开发板编写一个资源文件,前者可以根据后者的信息实现对芯片GPIO的操作,如下图所示。

在上一篇文章中,我只基于上下分层编写了led驱动程序,并没有使用左右分离,但是左右分离的思想也是需要掌握的,具体可以参考韦东山老师的相关教程。

基于面向对象的上下分层、左右分离的思想,可以进一步引出基于总线设备的驱动模型。

传统的驱动编写如下图所示,引脚的寄存器操作和代码紧密绑定,不易扩展,框架不够清晰

基于面向对象的左右分离的思想,引入platform_device和platform_driver,将资源和驱动分离。platform_device负责定义、描述寄存器资源,包括地址、配置信息等,platform_driver负责下层驱动的具体操作,为上层的驱动框架提供寄存器控制的函数,从platform_device获取资源后实现驱动的具体化,从而实现对寄存器的操作。可以理解为platform_driver是类的成员函数,platform_device是类的成员属性,分别实现二者的定义后就可以得到一个对象,这个对象对应的就是我们要操作的设备或者功能。

从完整的逻辑上,platform_device和platform_driver二者结合起来才是一个完整的驱动程序,那么当分别定义了具体的platform_device结构体和platform_driver结构体后,它们是怎么建立关联的呢?

在Linux系统中,通过总线(BUS)模型将设备(Dev)和驱动(Drv)链接起来,流程就是:BUS有两个链表,其中一条存放platform_device结构体变量,另一条存放platform_driver结构体。在向Linux系统注册platform_device或platform_driver的时候,会向对应的链表添加节点,并基于指定的规则进行匹配,比如向Linux注册platform_device结构体时,会向BUS的devices链表添加节点并遍历platform_driver链表匹配满足条件的驱动。同理向Linux注册platform_driver时类似。匹配过程如下图。


platform_device结构体内容如下:

点击查看代码
//结构体中包含很多字段,具体含义可以在以后学习到了再深入了解,开始时主要掌握框架
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;
};


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;
};
//其中定义了很多涉及platform_device的函数指针,也说明它确实是从platform_device获取资源然后进行操作

匹配规则如下: (1)首先检查platform_device中是否定义了driver_override,如果定义了,那么将driver_override和platform_driver中的driver结构体中的name比较;
(2)如果platform_device中没有定义driver_override,那么比较platform_device中name和platform_driver中id_table里的每一项的name
(3)如果(2)中没有匹配的项,就比较platform_device中name和platform_driver中dirver中的name字段

下面以一个具体的驱动为例讲解总线设备驱动模型的注册以及匹配流程
platform_device_register的调用过程如下:

点击查看代码
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}
前两行与设备树有关,暂时忽略,看到最后一行调用platform_device_add,顾名思义就是添加platform_device
点击查看代码
int platform_device_add(struct platform_device *pdev)
{
	int i, ret;

	if (!pdev)
		return -EINVAL;

	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;

	pdev->dev.bus = &platform_bus_type;

	switch (pdev->id) {
	default:
		dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
		break;
	case PLATFORM_DEVID_NONE:
		dev_set_name(&pdev->dev, "%s", pdev->name);
		break;
	case PLATFORM_DEVID_AUTO:
		/*
		 * Automatically allocated device ID. We mark it as such so
		 * that we remember it must be freed, and we append a suffix
		 * to avoid namespace collision with explicit IDs.
		 */
		ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
		if (ret < 0)
			goto err_out;
		pdev->id = ret;
		pdev->id_auto = true;
		dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
		break;
	}

	for (i = 0; i < pdev->num_resources; i++) {
		struct resource *p, *r = &pdev->resource[i];

		if (r->name == NULL)
			r->name = dev_name(&pdev->dev);

		p = r->parent;
		if (!p) {
			if (resource_type(r) == IORESOURCE_MEM)
				p = &iomem_resource;
			else if (resource_type(r) == IORESOURCE_IO)
				p = &ioport_resource;
		}

		if (p && insert_resource(p, r)) {
			dev_err(&pdev->dev, "failed to claim resource %d\n", i);
			ret = -EBUSY;
			goto failed;
		}
	}

	pr_debug("Registering platform device '%s'. Parent at %s\n",
		 dev_name(&pdev->dev), dev_name(pdev->dev.parent));

	ret = device_add(&pdev->dev); 
	if (ret == 0)
		return ret;

 failed:
	if (pdev->id_auto) {
		ida_simple_remove(&platform_devid_ida, pdev->id);
		pdev->id = PLATFORM_DEVID_AUTO;
	}

	while (--i >= 0) {
		struct resource *r = &pdev->resource[i];
		if (r->parent)
			release_resource(r);
	}

 err_out:
	return ret;
}

上述代码关键在ret = device_add(&pdev->dev),查看函数定义
其中有两个关键函数是bus_add_device(dev)和bus_probe_device(dev);前者是将设备放入设备链表中,后者是枚举总线中的驱动链表找到匹配的(dev,drv)
进入bus_probe_device,可以找到device_attach(dev);这个函数就是用于为添加的设备匹配驱动。进入device_attach找到bus_for_each_drv(dev->bus, NULL, dev, __device_attach);这个函数就是枚举总线上所有驱动进行匹配,如果匹配成功就可以调用drv的probe
更具体的细节参考视频https://www.bilibili.com/video/BV14f4y1Q7ti?p=20&vd_source=71fc7d920651b891affb9d75b719c8ad

题外话:学到这里的时候还是有点似懂非懂,那么可以先去做实验,亲自动手用一下这个理论、框架,通过实践反馈、加强对理论的理解,如果一味追求先完全弄懂理论,反而可能会带来理论理解上的偏差,因为难以获取反馈。理论和实践本来就是紧密结合的。

下面基于正点原子的imx6ull开发板编写基于总线设备驱动模型的LED驱动程序

总线设备驱动模型就是典型的上下分层、左右分离的面向对象的思想方法。首先实现上层的驱动框架:
(1)实现file_operations结构体。file_operations中open、write等函数会调用下层驱动向上层驱动的操作函数(里面会有对寄存器的操作),从而避免了在框架里直接对寄存器进行操作。
(2)添加上层驱动的入口/出口函数,注册驱动,创建class结构体用于下层驱动创建设备节点。特别注意,在上层驱动中并没有创建设备节点,而是下层的设备文件在注册时调用上层驱动提供的函数接口基于上层的class创建设备文件,可以理解为下层向上层注册设备。因此上层除了完成file_operations结构体、入口/出口函数外,还需要向下层提供设备创建/设备销毁函数。在(1)中会调用下层的函数,这样会存在交叉引用的问题。为了解决这个问题,需要把下层提供的操作函数结构体定义写在头文件中并在上层驱动代码中包含该头文件,这样在编译上层驱动代码时就包含了操作函数结构体的定义,同时下层驱动也包含该头文件负责实现该结构体并向上层注册,因此上层还需要提供一个下层向上层注册led_operations的结构体。
led_opr.h头文件如下,该文件需要包含在上层驱动led_driver.c中

点击查看代码
#ifndef __LED_ORP__H
#define __LED_ORP__H

struct led_operations{
    int (*init)(unsigned int which); //选择哪个LED,使用次设备号进行编号
    int (*ctl)(unsigned int which, char status); //控制led状态
};

#endif

led_driver.c代码如下

点击查看代码
#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/cred.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>

#include "led_opr.h"

/*
led_driver.c 上层驱动框架
实现从下层驱动获取led_operation结构体
为下层提供注册/取消注册接口:下层注册设备时调用上层的函数创建设备文件节点,撤销注册时调用上层函数销毁设备文件节点
*/


// (1)确定主设备号,也可以让内核分配
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;

#define MIN(a, b)   (a < b ? a : b)

/*
    首先为下层驱动提供注册/撤销注册接口
*/

void led_device_register(int minor)
{
    /*
    根据传入的次设备号创建设备节点,设备命名上使用次设备号作为后缀便于理解
    */
    device_create(led_class, NULL, MKDEV(major, minor), NULL, "led_%d", minor);
}

void led_device_unregister(int minor)
{
    /*
        根据次设备号销毁设备
    */
    device_destroy(led_class, MKDEV(major, minor));

}

void register_led_operation(struct led_operations *led_opr)
{
    p_led_opr = led_opr;
}

EXPORT_SYMBOL(led_device_register);
EXPORT_SYMBOL(led_device_unregister);
EXPORT_SYMBOL(register_led_operation);

// (3)实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
//为了不用声明,将函数定义放到前面,但是逻辑顺序应该是在后面
static ssize_t led_drv_write (struct file * file, const char __user * buf, size_t size, loff_t * offset)
{
    char val;
    int ret;
    struct inode *node;
    unsigned int which;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
   /*
   读取从用户空间传入的数据,给DR寄存器写0/1控制GPIO1_IO03输出高/低电平以控制LED亮灭
   */
    ret = copy_from_user(&val, buf, 1);
    node = file_inode(file);
    which = iminor(node);
    p_led_opr->ctl(which, val);
    
    return 1;   //返回1,表示写入1个数据

}


static int led_drv_open (struct inode * node, struct file * file)
{
    unsigned int which;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    which = iminor(node);
    p_led_opr->init(which);   //初始化led,包括地址映射、寄存器配置
    return 0;
}
static int led_drv_close (struct inode * node, struct file *file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    /*
    关闭设备,撤销寄存器地址映射
    */

    return 0;
}

// (2)定义自己的 file_operations
static struct file_operations led_drv = {
    .owner  = THIS_MODULE,
    .open   = led_drv_open,
    .write  = led_drv_write,
    .release    = led_drv_close,
};



// (4)把 file_operations 结构体告诉内核:register_chrdev 注册驱动程序,因此先跳到步骤5


// (5)谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    major = register_chrdev(0, "led_driver", &led_drv); //注册驱动
   

    led_class = class_create(THIS_MODULE, "led_class"); //这个class对象与驱动绑定了吗?为什么需要用它来创建设备?
    err = PTR_ERR(led_class);
    if (IS_ERR(led_class))
    {
        unregister_chrdev(major, "led_driver");
        return -1;
    }

    return 0;

}

// (6)有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    class_destroy(led_class);

    unregister_chrdev(major, "led_driver");
    
}

// (7)其他完善:提供设备信息,自动创建设备节点:class_create,device_create
module_init(led_init);    //凭什么说上面的Init函数就是入口函数?这里这个宏的作用就是告诉内核这是入口函数
module_exit(led_exit);
MODULE_LICENSE("GPL");

接下来实现下层驱动,下层驱动分为两部分,一部分是资源文件,另一个是真正的驱动文件

对于资源文件,主要实现platform_device结构体,里面包含寄存器的地址、配置值、引脚号等信息。对于实现led驱动程序,用到的platform_device结构体中的字段如下:

点击查看代码
struct platform_device led_device = {
    .name = "alientek_led",
    .num_resources = ARRAY_SIZE(resources), 
    .resource = resources,
    .dev = {
        .release = led_device_release,  //若未定义这个字段会报错
    }, 

};

重点在于实现resources数组,如果有多个led可以定义多个resource结构体放到数组中,这里我只实现一个led,但是是可以扩展的。每一个struct resource中我将start设置为一个自定义结构体struct led_resouce的地址,这样就可以通过start找到相关的寄存器信息

点击查看代码
struct resource resources[] = {
    {
        .start = (unsigned int)&GPIO1_IO03, //资源结构体地址
        .flags = IORESOURCE_IRQ,
        .name = "alientek_led_pin",

    },
};
led_resource结构体定义如下,里面几乎包含了配置一个GPIO引脚用到的寄存器,这样我们根据结构体中的变量名就可以直接找到寄存器
点击查看代码
struct led_resource{
    int group_pin;  //[31:16]存放组,[15:0]存放
    //定义变量存放涉及到的寄存器的地址
    unsigned int CCM_CCGR1;
    unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    unsigned int GPIO1_GDIR;
    unsigned int GPIO1_DR; 
};

基于正点原子imx6ull开发板创建的结构体如下

点击查看代码
struct led_resource GPIO1_IO03 = {
    .group_pin = GROUP_PIN(1, 3),
    .CCM_CCGR1 = 0x20C406C,
    .IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x20E0068,
    .GPIO1_GDIR = 0X209C004,
    .GPIO1_DR = 0X209C000
    
};

完整的下层驱动中的资源文件包含led_resoure.h头文件和board_led.c文件,完整代码如下
led_resoure.h

点击查看代码
#ifndef __LED_RESOURCE__H
#define __LED_RESOURCE__H


#include "linux/types.h"
#define GROUP(x)    (x >> 16)   //获取引脚属于哪一组
#define PIN(x)      (x & (0xffff))  //获取引脚编号
#define GROUP_PIN(grp, pin) ((grp << 16) | (pin))   //这里最好给grp << 16和pin加括号,以免宏展开的时候出现问题 

struct led_resource{
    int group_pin;  //[31:16]存放组,[15:0]存放
    //定义变量存放涉及到的寄存器的地址
    unsigned int CCM_CCGR1;
    unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    unsigned int GPIO1_GDIR;
    unsigned int GPIO1_DR; 
};

#endif

board_led.c

点击查看代码
/*
    下层资源文件,为下层驱动提供寄存器、引脚等信息
    1、实现platform_device结构体
    2、实现platform_device向内核注册的init和exit函数
*/


#include "linux/ioport.h"
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_resource.h"
#include "linux/printk.h"

//为了结构清晰,可以将下面的宏定义放入一个头文件,同时头文件中定义一个结构体存放GPIO的信息
// #define GROUP(x)    (x >> 16)   //获取引脚属于哪一组
// #define PIN(x)      (x & (0xffff))  //获取引脚编号
// #define GROUP_PIN(grp, pin) ((grp << 16) | (pin))   //这里最好给grp << 16和pin加括号,以免宏展开的时候出现问题 

struct led_resource GPIO1_IO03 = {
    .group_pin = GROUP_PIN(1, 3),
    .CCM_CCGR1 = 0x20C406C,
    .IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x20E0068,
    .GPIO1_GDIR = 0X209C004,
    .GPIO1_DR = 0X209C000
    
};

struct resource resources[] = {
    {
        .start = (unsigned int)&GPIO1_IO03, //资源结构体地址
        .flags = IORESOURCE_IRQ,
        .name = "alientek_led_pin",

    },
};

static void led_device_release(struct device *dev)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}

struct platform_device led_device = {
    .name = "alientek_led",
    .num_resources = ARRAY_SIZE(resources), 
    .resource = resources,
    .dev = {
        .release = led_device_release,
    }, 

};

static int __init led_device_init(void)
{
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&led_device);
    return 0;
}

static void __exit led_device_exit(void)
{
    platform_device_unregister(&led_device);

}

module_init(led_device_init);
module_exit(led_device_exit);

MODULE_LICENSE("GPL");

接下来实现具体的驱动代码
驱动代码主要实现platform_driver结构体和操作函数结构体led_operations,以及入口/出口函数,同时在入口函数中向上层注册led_operations结构体

platform_driver结构体如下

点击查看代码
struct platform_driver led_driver = {

    .probe = imx6ull_chip_gpio_led_demo_probe,  //注册设备时如果匹配到该驱动就执行这个函数
    .remove = imx6ull_chip_gpio_led_demo_remove,    //卸载驱动时执行
    .driver = {
        .name = "alientek_led",
    },
};

在驱动和设备匹配成功时会执行probe函数,因此主要在probe函数中实现从资源文件结构体platform_device获取资源,读取硬件相关信息
完整的下层驱动操作代码如下:
因为涉及使用上层led_driver.c的注册函数,因此需要包含相关函数的声明,写在头文件led_driver.h中,其内容如下:

点击查看代码
#ifndef __LED_DRIVER__H
#define __LED_DRIVER__H

void led_device_register(int minor);
void led_device_unregister(int minor);
void register_led_operation(struct led_operations *led_opr);
#endif

在下层驱动操作函数board_led.c中涉及到寄存器的映射操作,需要保存映射后地址,因此在led_opr.h头文件中添加相一个结构体定义用于存放led_resource结构体中寄存器的映射地址。完整的led_opr.h代码如下:

点击查看代码
#ifndef __LED_ORP__H
#define __LED_ORP__H

struct led_operations{
    int (*init)(unsigned int which); //选择哪个LED,使用次设备号进行编号
    int (*ctl)(unsigned int which, char status); //控制led状态
};

struct led_operations* get_led_operations(void);

struct registers{   //进行地址映射后寄存器的地址
    unsigned int* CCM_CCGR1;
    unsigned int* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    unsigned int* GPIO1_GDIR;
    unsigned int* GPIO1_DR;
};

#endif

完整的下层驱动代码imx6ull_chip_gpio.c如下:

点击查看代码
/*
下层驱动程序,从下层的资源文件获取寄存器、引脚等信息,实现寄存器配置、操作
1、实现platform_driver结构体并实现入口、出口函数
2、实现led_operation结构体,并给出上层驱动获取该结构体的接口。注意,这里存在交叉引用的问题,因此这个函数最终变为上层提供接口,下层向上层注册该结构体

*/

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "linux/ioport.h"
#include "linux/platform_device.h"
#include "asm/io.h"

#include "led_opr.h"
#include "led_resource.h"
#include "led_driver.h" //包含上层驱动的对外的函数接口声明

static struct led_resource* g_ledpins[10];  //led_resource指针数组,用于接收从资源文件获取的led_resource结构体。g_ledpins是指针数组是因为相应的结构体已经在资源文件创建了,这里只是接收引用
static int g_ledcnt = 0;

struct registers led_registers[10]; //接收寄存器组映射后的地址,这里没有定义成指针,是因为我们需要创建出实体

/*
实现led_operation结构体
*/

static int imx6ull_chip_gpio_led_init(unsigned int which) //选择哪个LED,使用次设备号进行编号
{
    int val;
    if(!led_registers[which].CCM_CCGR1)
    {
        led_registers[which].CCM_CCGR1 = ioremap(g_ledpins[which]->CCM_CCGR1, 4);
        led_registers[which].IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(g_ledpins[which]->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03, 4);
        led_registers[which].GPIO1_GDIR = ioremap(g_ledpins[which]->GPIO1_GDIR, 4);
        led_registers[which].GPIO1_DR = ioremap(g_ledpins[which]->GPIO1_DR, 4);
    }

    /*
    使能GPIO1_IO03时钟
    CCGR1[27:26] = b11
    20C_406Ch
    */
    val = *led_registers[which].CCM_CCGR1;
    val |= (0x3 << 26);
    *(led_registers[which].CCM_CCGR1) = val;

    /*
    配置IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03为GPIO模式
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03[3:0] = b0101
    20E_0068h

    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03暂时不使用,这是配置引脚的模式,比如上升沿,上下拉电阻等
    */
    val = *led_registers[which].IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    val &= ~(0xF);  //低四位清零
    val |= (0x5);   //低四位赋值b0101
    *(led_registers[which].IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03) = val;

    /*
    配置GPIO1_GDIR寄存器使GPIO1_IO03配置为输出
    GPIO1_GDIR[3] = 1
    209_C004h
    */

    *(led_registers[which].GPIO1_DR) |= (0x1 << 3);

    return 1;
}

static int imx6ull_chip_gpio_led_ctl(unsigned int which, char status) //控制led状态
{

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    /*
    根据status修改GPIO1_IO03的输出
    GPIO1_DR
    209_C000h
    */

    if(status)
    {
        *led_registers[which].GPIO1_DR &= ~(1 << 3);

        printk("led on\n");
    }
    else {
        *led_registers[which].GPIO1_DR |= (1 << 3);
        printk("led off\n");
    }
    return 0;
}

struct led_operations led_opr = {
    .init = imx6ull_chip_gpio_led_init,
    .ctl = imx6ull_chip_gpio_led_ctl,
};


static int imx6ull_chip_gpio_led_demo_probe(struct platform_device *dev)
{
    /*  驱动匹配成功后就会执行,因此寄存器资源的获取应该在这个函数中进行
        (1)进行设备创建
        (2)
    */
    int i = 0;
    struct resource *rsc;

    while(1)
    {
        rsc = platform_get_resource(dev, IORESOURCE_IRQ, i++);
		if (!rsc)	//直到遍历到resources的最后一项
			break;

		/* 记录引脚 */
		g_ledpins[g_ledcnt] = (struct led_resource*)rsc->start;		//强制转换类型为resource结构体指针
		
		/* device_create */
		led_device_register(g_ledcnt);  //向上层驱动注册,创建设备节点,次设备号等于数组索引。为了使用该函数,需要包含函数声明,写在led_driver.h中
		g_ledcnt++;
    }

    return 0;
}


static int imx6ull_chip_gpio_led_demo_remove(struct platform_device *dev)
{
    int i;
	/* device_destroy */
	for (i = 0; i < g_ledcnt; i++)
	{
		led_device_register(i);
	}
	g_ledcnt = 0;

	return 0;
    return 0;
}

struct platform_driver led_driver = {

    .probe = imx6ull_chip_gpio_led_demo_probe,  //注册设备时如果匹配到该驱动就执行这个函数
    .remove = imx6ull_chip_gpio_led_demo_remove,    //卸载驱动时执行
    .driver = {
        .name = "alientek_led",
    },
};

static int led_driver_init(void)
{
    int err;
    err = platform_driver_register(&led_driver);
    register_led_operation(&led_opr);    //向上层注册led_operation结构体
    return 0;
}

static void led_driver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");

写的有点混乱,但是主要抓住一个原则:上下分层,左右分离

标签:__,led,驱动程序,driver,总线,platform,device,LED,include
From: https://www.cnblogs.com/starstxg/p/18146213

相关文章

  • 驻极体话筒(MIC)、三极管、led组成的声控led闪光电路分析
    电路:声控LED闪烁灯这里介绍一个通过声音控制LED闪光的简单电路,将它挂在室内音响或电视机的扬声器附近,LED会随喇叭播放的音色声而闪闪发光。电路图如下。 电路工作过程:1、电路上电后,周围环境无声音时,三极管Q1,基极电阻R1,集电极电阻R3,刚好是三极管的一个基极偏置电路,三极管Q1......
  • QT beginner QFileDialog
    QFileQTextStreamQMessageBoxQFileDialog应用示例mainwindow.cpp#include"mainwindow.h"#include"ui_mainwindow.h"#include<QFile>#include<QTextStream>#include<QMessageBox>#include<QFileDialog>MainWindow::MainWi......
  • LED车灯驱动DC-DC降压恒流芯片AP5174高效率线性调光IC摩托车电动车手电筒
    产品描述AP5174是一款效率高,稳定可靠的LED灯恒流驱动控制芯片,内置高精度比较器,固定关断时间控制电路,恒流驱动电路等,特别适合大功率LED恒流驱动。AP5174采用ESOP8封装,散热片内置接SW脚,通过调节外置电流检测的电阻值来设置流过LED灯的电流,支持外加电压线性调光,最大电......
  • LED车灯IC降压恒流驱动AP5103大功率95%高效率深度调光摩托车灯芯片
    产品描述AP5103是一款效率高,稳定可靠的LED灯恒流驱动控制芯片,内置高精度比较器,固定关断时间控制电路,恒流驱动电路等,特别适合大功率LED恒流驱动。AP5103采用ESOP8封装,散热片内置接SW脚,通过调节外置电流检测的电阻值来设置流过LED灯的电流,支持外加电压线性调光,最大电......
  • LED车灯驱动IC高精度电流输出±3%以内降压恒流芯片AP5161
    概述AP5161是一款高精度降压型大功率LED恒流驱动芯片。适用于输入电压100V以内的大功率LED恒流驱动电源。专利的高端电流检测、固定频率、电流模PWM控制方式,具有优异的线性调整率和负载调整率。芯片采用的特有恒流控制方式,使得LED输出电流精度达到±3%以内。芯片内部集......
  • Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled
    内存不足,导致Java 进程被杀掉。 [1534.300650]Kernelpanic-notsyncing:Outofmemory:system-widepanic_on_oomisenabled[1534.301803]CPU:5PID:2930Comm:javaKdump:loadedTainted:GO5.10.0-60.18.0.50.r1083_58.hce2.x86_64#1[153......
  • CAN总线原理_学习
    随着通信技术的发展,现今通信方式和协议五花八门,但CAN通信仍然是车载网络最安全可靠且应用最广的技术之一。过去,汽车通常采用常规的点对点通信方式将电子控制单元及电子装置连接起来,但随着电子设备的不断增加,导线数量也随之增多,采用CAN总线网络结构,可以达到信息共享、......
  • 史上最大、最贵iPad Air即将登场:搭载12.9英寸Mini LED屏
    据研究公司DSCC首席执行官RossYoung最新消息,12.9英寸iPadAir预计将于5月发布。该机最大的亮点就是屏幕,不仅是屏幕扩大为系列史上最高,还采用与当前12.9英寸iPadPro型号一样的Mini-LED显示屏。这块屏幕具备2596个全阵列局部调光区、2732x2048像素分辨率、264ppi、峰值亮度1......
  • led驱动程序进阶-使用面向对象的思想完成led驱动程序
    上一篇文章实现了一个led驱动程序的模板,该模板虽然只是用于led驱动程序的编写,但是对于其它任何设备的驱动程序编写,其面向对象的思想都是可以借鉴和参考的。任何看似高深的技巧,都是从简单出发的,逐步深入。独孤九剑的最高境界就是无剑、无招,所有高深的变化都是从最基本的原理出发!本......
  • PVE下面安装Windows2012R2虚拟机的VirtIO驱动程序
    从官网链接下载不同版本的进行测试安装https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/测试结果如下virtio-win-0.1.215-2/2022-01-1305:42正常安装virtio-win-0.1.217-2/2022-05-3104:41安装失败virtio-win-0.1.221-1/2022......