#include <linux/init.h>            // __init   __exit
#include <linux/module.h> // module_init module_exit
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> #include <asm/io.h> //writel #include <linux/ioport.h> //request_mem_region

#include <asm/string.h> #include <linux/leds.h> #define GPJ0_REGBASE 0xE0200240
typedef struct GPJ0REG
{
volatile unsigned int gpj0Con;
volatile unsigned int gpj0Dat; }gpj0_reg_t;
gpj0_reg_t *pgpj0_reg =NULL; struct led_classdev cdev1;
struct led_classdev cdev2;
struct led_classdev cdev3; void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness);
void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness);
void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness); void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
printk(KERN_INFO "s5pv210_led1_set successful %d\n",brightness);
if(brightness == LED_OFF)
{
writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<)),&pgpj0_reg->gpj0Dat);
}
else
{
writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<))),&pgpj0_reg->gpj0Dat); }
}
void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
printk(KERN_INFO "s5pv210_led2_set successful %d\n",brightness);
if(brightness == LED_OFF)
{
writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<)),&pgpj0_reg->gpj0Dat);
}
else
{
writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<))),&pgpj0_reg->gpj0Dat); }
}
void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
printk(KERN_INFO "s5pv210_led3_set successful %d\n",brightness);
if(brightness == LED_OFF)
{
writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<)),&pgpj0_reg->gpj0Dat);
}
else
{
writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con);
writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<))),&pgpj0_reg->gpj0Dat); } }
static int __init s5pv210_led_init(void)
{
int ret = -;
printk(KERN_INFO "s5pv210_led_init successful \n");
cdev1.brightness_set = s5pv210_led1_set;
cdev1.name = "led1";
ret = led_classdev_register(NULL, &cdev1);
if (ret < )
{
printk(KERN_WARNING "led_classdev_register fail \n");
goto reg_err1;
} cdev2.brightness_set = s5pv210_led2_set;
cdev2.name = "led2";
ret = led_classdev_register(NULL, &cdev2);
if (ret < )
{
printk(KERN_WARNING "led_classdev_register fail \n");
goto reg_err2;
} cdev3.brightness_set = s5pv210_led3_set;
cdev3.name = "led3";
ret = led_classdev_register(NULL, &cdev3);
if (ret < )
{
printk(KERN_WARNING "led_classdev_register fail \n");
goto reg_err3;
} if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "gpj0_reg"))
{
ret = -EBUSY;
goto mem_err;
}
pgpj0_reg = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t)); return ; mem_err:
led_classdev_unregister(&cdev3); reg_err3:
led_classdev_unregister(&cdev2); reg_err2:
led_classdev_unregister(&cdev1); reg_err1:
return ret;
} static void __exit s5pv210_led_exit(void)
{
printk(KERN_INFO "s5pv210_led_exit successful \n");
iounmap(pgpj0_reg);
release_mem_region(GPJ0_REGBASE,sizeof(gpj0_reg_t)); led_classdev_unregister(&cdev1);
led_classdev_unregister(&cdev2);
led_classdev_unregister(&cdev3); } module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("musk"); // 描述模块的作者
MODULE_DESCRIPTION("x210 LED driver"); // 描述模块的介绍信息
MODULE_ALIAS("led_driver"); // 描述模块的别名信息

一. 内核LED框架介绍:

1.1. 在内核中相关文件

1.1.1. 驱动框架规定的LED这种硬件的驱动框架在:drivers/leds目录下

1.1.2. led-class.c和led-core.c,这两个文件是内核提供的,他们统一描述了内核中所有厂家的不同LED硬件的相同部分的逻辑。

1.1.3. leds-xxxx.c,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用内核提供的接口来和驱动框架进行交互,最终实现驱动的功能。

1.2. 使用LED框架和之前使用写的LED驱动区别(register_chrdev)

1.2.1. LED框架中相关最终去创建一个属于/sys/class/leds这个类的一个设备。如何在这个类下有brightness      max_brightness   power           subsystem       uevent等文件来操作硬件

1.2.2. 之前写的LED驱动通过file_operations结构体绑定相关函数来操作硬件

1.2.3. 这两中方式是并列的。驱动开发者可以选择其中任意一种方式来开发驱动。

二. 分析led-class.c文件

2.1. subsys_initcall & module_init函数

2.1.1. subsys_initcall是一个宏,定义在linux/init.h中。经过对这个宏进行展开,发现这个宏的功能是:将其声明的函数放到一个特定的段:.initcall4.init。

subsys_initcall

__define_initcall("4",fn,4)

2.1.2. 分析module_init宏,可以看出它将函数放到了.initcall6.init段中。

module_init

__initcall

device_initcall

__define_initcall("6",fn,6)

2.1.3. 内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化操作。内核的解决方案就是给内核启动时要调用的所有函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init。n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。

2.2. 经过分析,可以看出,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。

2.3. led_class_attrs数组

2.3.1. 什么是attribute,对应将来/sys/class/leds/目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)

2.3.2. attribute有什么用,作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。

2.3.3. attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。

2.4. leds_init函数

2.4.1. 函数内部调用

leds_init

class_create

2.4.1. 此函数在/sys/class目录下创建leds类文件

static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
return ;
}

2.5. led_classdev结构体

2.5.1. LED驱动框架中最终是通过该结构体与硬件关联起来

struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int flags; /* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16) /* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* Activate hardware accelerated blink, delays are in
* miliseconds and if none is provided then a sensible default
* should be chosen. The call can adjust the timings if it can't
* match the values specified exactly. */
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off); struct device *dev;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */ #ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock; struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
#endif
};

三.相关代码分析led_classdev_register

3.1. 函数内部关系

led_classdev_register

device_create

3.2. led_classdev_register函数分析

3.2.1. led_classdev_register这个函数其实就是去创建一个属于leds这个类的一个设备。其实就是去注册一个设备。所以这个函数其实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。

3.2.2. 当我们使用led驱动框架去编写驱动的时候,这个led_classdev_register函数的作用类似于我们之前使用file_operations方式去注册字符设备驱动时的register_chrdev函数

/**
* led_classdev_register - register a new object of led_classdev class.
* @parent: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, , led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev); #ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock); if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif printk(KERN_DEBUG "Registered led device: %s\n",
led_cdev->name); return ;
}

3.3. led_classdev_unregister函数分析

/**
* led_classdev_unregister - unregisters a object of led_properties class.
* @led_cdev: the led device to unregister
*
* Unregisters a previously registered via led_classdev_register object.
*/
void led_classdev_unregister(struct led_classdev *led_cdev)
{
#ifdef CONFIG_LEDS_TRIGGERS
down_write(&led_cdev->trigger_lock);
if (led_cdev->trigger)
led_trigger_set(led_cdev, NULL);
up_write(&led_cdev->trigger_lock);
#endif device_unregister(led_cdev->dev); down_write(&leds_list_lock);
list_del(&led_cdev->node);
up_write(&leds_list_lock);
}

四. 实验效果

[root@musk210 driver_test]# cd /sys/class/leds/led1/
[root@musk210 led1]# ls
brightness max_brightness power subsystem uevent
[root@musk210 led1]# echo > brightness
[ 3193.332848] s5pv210_led1_set successful
[root@musk210 led1]# ls
brightness max_brightness power subsystem uevent
[root@musk210 led1]# echo > brightness
[ 6081.310443] s5pv210_led1_set successful
[root@musk210 led1]# cd ../
[root@musk210 leds]# ls
led1 led2 led3 mmc0:: mmc1:: mmc2:: mmc3::
[root@musk210 leds]#

4.1. 我们写的驱动确实工作了,被加载了,/sys/class/leds/目录下确实多出来了一个表示设备的文件夹。文件夹里面有相应的操控led硬件的2个属性brightness和max_brightness

4.2. led-class.c中brightness方法有一个show方法和store方法,这两个方法对应用户在/sys/class/leds/led1/brightness目录下直接去读写这个文件时实际执行的代码。

4.2.1. 当我们show brightness时,实际就会执行led_brightness_show函数

4.2.1. 当我们echo 1 > brightness时,实际就会执行led_brightness_store函数

4.3. show方法实际要做的就是读取LED硬件信息,然后把硬件信息返回给我们即可。所以show方法和store方法必要要会去操控硬件。但是led-class.c文件又属于驱动框架中的文件,它本身无法直接读取具体硬件,因此在show和store方法中使用函数指针的方式调用了struct led_classdev结构体中的相应的读取/写入硬件信息的方法。

参考《朱老师.课件_5.4.驱动框架入门之LED》

使用内核LED框架搭建驱动 ——led_classdev_register的更多相关文章

  1. 使用EasySYS搭建驱动开发基本框架

    提供EasySYS的下载地址:http://bbs.pediy.com/showthread.php?p=956643,看雪上有提供下载,自行百度. EasySYS你能够帮我们快速的搭建驱动的开发框架 ...

  2. Linux 驱动框架---platform驱动框架

    Linux系统的驱动框架主要就是三个主要部分组成,驱动.总线.设备.现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC ...

  3. ssh框架搭建的基本步骤(以及各部分作用)

    ssh框架搭建的基本步骤(以及各部分作用)     一.首先,明确spring,struts,hibernate在环境中各自的作用.   struts: 用来响应用户的action,对应到相应的类进行 ...

  4. Spring MVC + jpa框架搭建,及全面分析

    一,hibernate与jpa的关系 首先明确一点jpa是什么?以前我就搞不清楚jpa和hibernate的关系. 1,JPA(Java Persistence API)是Sun官方提出的Java持久 ...

  5. ssh2框架搭建

    原文:ssh2框架搭建 struts2+spring4.0+hibernate4.0 4.x版本与3.x版本有较大区别,要配置方法须要注意,用到的jar包如下 文件结构 src/application ...

  6. SSM框架搭建web服务器实现登录功能(Spring+SpringMVC+Mybatis)

    初学java EE,虽然知道使用框架会使开发更加便捷高效,但是对于初学者来说,感到使用框架比较迷惑,尤其是各种jar包的引用.各种框架的配置.注解的使用等等. 最好的学习方法就是实践,于是下载了一个现 ...

  7. 【转】 linux内核移植和网卡驱动(二)

    原文网址:http://blog.chinaunix.net/uid-29589379-id-4708911.html 一,内核移植步骤: 1, 修改顶层目录下的Makefile ARCH       ...

  8. SSH框架搭建 详细图文教程

    转载请标明原文地址 一.什么是SSH? SSH是JavaEE中三种框架(Struts+Spring+Hibernate)的集成框架,是目前比较流行的一种Java Web开源框架. SSH主要用于Jav ...

  9. Struts2+Spring+Hibernate+Jbpm技术实现Oa(Office Automation)办公系统第一天框架搭建

    =============编码规范,所有文健,所有页面,所有数据库的数据表都采用UTF-8编码格式,避免乱码:===========开发环境:jdk1.7+tomcat8.0+mysql5.7+ecl ...

随机推荐

  1. bzoj4810 [Ynoi2017]由乃的玉米田 莫队+bitset(+数论)

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4810 题解 看数据范围和题目名字应该是根号算法. 因为询问除了区间外,还有第 \(3\) 个参 ...

  2. springcloud 配置actuator

    pom.xml <!--Spring Boot Actuator,感应服务端变化--> <dependency> <groupId>org.springframew ...

  3. C# Asp.NET实现上传大文件(断点续传)

    以ASP.NET Core WebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API ,包括文件的上传和下载. 准备文件上传的API #region 文件上传  ...

  4. asp.net (web)选择文件夹 上传文件

    1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...

  5. 题解 P1433 【吃奶酪】

    这道题是一道著名的NP问题. 正解应该是DP,但我在这里讲一种近似算法--爬山. 希望某些dalao注意一下爬山与模拟退火的区别. 爬山是直往低处往高处爬,每次取大的,也就是一种贪心思想. 而模拟退火 ...

  6. HDU 6287 Just h-index

    Time limit 3000 ms Memory limit 132768 kB OS Windows Source CCPC2018-湖南全国邀请赛-重现赛(感谢湘潭大学) 中文题意 一个序列,每 ...

  7. SQL JOIN INNER LEFT RIGHT FULL

    1.引用2个表(效果同INNER  JOIN) SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons, Ord ...

  8. ionic slide组件使用

    ionic学习使用笔记 slide 组件的使用   开始做的时候,遇到了个要用ionic实现 有一系列的序列需要展示,但是当前页面上只能展示一小部分,剩余的在没有出现时是隐藏的,还得能滑动出现,但是又 ...

  9. 大数据笔记(十八)——Pig的自定义函数

    Pig的自定义函数有三种: 1.自定义过滤函数:相当于where条件 2.自定义运算函数: 3.自定义加载函数:使用load语句加载数据,生成一个bag 默认:一行解析成一个Tuple 需要MR的ja ...

  10. 邻近双线性插值图像缩放的Python实现

    最近在查找有关图像缩放之类的算法,因工作中需要用到诸如此类的图像处理算法就在网上了解了一下相关算法,以及其原理,并用Python实现,且亲自验证过,在次与大家分享. 声明:本文代码示例针对的是plan ...