目录
- 一、linux设备驱动模型简介
- 1、什么是设备驱动模型
- 2、为什么需要设备驱动模型
- 3、驱动开发的2个点
- 二、设备驱动模型的底层架构
- 1、kobject
- 2、kobj_type
- 3、kset
- 三、总线式设备驱动组织方式
- 1、总线
- 2、设备
- 3、驱动
- 4、类
- 5、总结
- 四、platform平台总线工作原理
- 1、何为平台总线
- 2、平台总线下管理的2员大将
- 3、平台总线体系的工作流程
- 4、代码分析:platform本身注册
- 5、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
- 6、platdata怎么玩
- 7、match函数的调用轨迹
- 8、probe函数的功能和意义
- 五、平台总线实践环节
一、linux设备驱动模型简介
1、什么是设备驱动模型
是这是一个比较抽象的概念,并无明确的定义,它是linux内核为了管理硬件上的设备和对应的驱动制定的一套软件体系。
(1)类class、总线bus、设备device、驱动driver(对应4个结构体来描述)
(2)kobject(结构体,类似于一个基类)和对象生命周期 (kobject实现了一种自我管理的机制)
(3)sysfs(使内核空间 和 用户空间建立了一种映射关系)
(4)udev(实现内核空间 和 用户空间信息的同步)
以上(1)、(2)、(3)、(4)等都属于设备驱动模型的范畴。
2、为什么需要设备驱动模型
(1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用
(2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
(3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施
(4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
3、驱动开发的2个点
(1)驱动源码本身编写、调试。重点在于对硬件的了解。
(2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。
二、设备驱动模型的底层架构
https://www.cnblogs.com/deng-tao/p/6033932.html
https://blog.csdn.net/yong199105140/article/details/8240875
1、kobject
(1)定义在linux/kobject.h中
struct kobject {
const char *name;/*名称*/
struct list_head entry;/*用于链入所属的kset的链表,将不同的对象使用双向链表连接起来*/
struct kobject *parent;/*父object*/
struct kset *kset;/*内部提供了对象上锁的功能*/
struct kobj_type *ktype;/*对用户空间的表示*/
struct sysfs_dirent *sd;/*sysfs中的目录项*/
struct kref kref;/*生命周期(引用计数)管理,*/
unsigned int state_initialized:1;/*标记:初始化*/
unsigned int state_in_sysfs:1;/*标记:在sysfs中*/
unsigned int state_add_uevent_sent:1;/*标记:已发出KOBJ_ADD uevent*/
unsigned int state_remove_uevent_sent:1;/*标记:已发出的KOBJ_REMOVE uevent*/
unsigned int uevent_suppress:1;/*标记:禁止发出uevent*/
};
Kobject是Linux 2.6引入的新的设备管理机制,在内核中由struct kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。Kobject是组成设备模型的基本结构。
上层结构例如device,device_driver,bus_type都嵌入了一个kobject,这相当于面向对象程序设计机制中的继承。
当kobject被嵌入到其他结构体中时,该结构体便拥有了kobject提供的标准功能。更重要的一点是,嵌入kobject结构体可以成为对象层次架构中的一部分。比如cdev结构体就可以通过其父进程指针cdev->kobj->parent和链表 cdev->kobj->entry来插入到对象层次结构中。
(2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示(sys)
(3)设备驱动模型中的各种对象其内部都会包含一个kobject,从而可以使用kobject的功能和服务。
(4)地位相当于面向对象体系架构中的总基类
2、kobj_type
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};release:release成员是一个指向对应于该类kobject的release方法。release指针指向在kobject引用计数减至0时要被调用的析构函数。该函数负责释放所有kobject使用的内存和其它相关清理工作,注意必须得提供release函数。每个kobject必须有一个release函数,并且这个kobject必须在release函数被调用前保持不
变(稳定状态 )。这样,每一个 kobject需要有一个关联的 kobj_type结构,指向这个结构的指针能在 2个不同的地方找到:kobject结构自身包含一个成员(ktype)指向kobj_type;如果这个 kobject 是一个 kset的成员, kset会提供kobj_type指针。sysfs_ops和default_attrs控制如何在sysfs中表示这个类型的对象:
sysfs_ops变量指向sysfs_ops结构体。该结构体描述了sysfs文件读写时的特性default_attrs指向一个attribute结构体数组。default_attrs指针是默认属性的列表,
当创建注册这个ktype的kobject的时候,这些默认属性会被自动的添加。这些结构体定义了该kobject相关的默认属性。属性给定了对象的特征,如果该kobject被导
出到sysfs中,那么这些属性都将相应的作为文件而导出。
(1)很多书中简称为ktype,每一个kobject都需要绑定(通过指针)一个ktype来提供相应功能
(2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)
(3)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口
3、kset
struct kset {struct list_head list;spinlock_t list_lock;//自旋锁,有其表示可以上锁struct kobject kobj;const struct kset_uevent_ops *uevent_ops;
};kset是kobject对象的集合体。把它看成一个容器,可将所有相关的kobject对象,比如“
全部的块设备”置于同一位置。kset可把kobject集中到一个集合中,而ktype描述相关类型kobject所共有的特性,它
们之间的重要区别在于:具有相同ktype的kobject可以分到不同的kset中(同一个kset中的
kobject是否具有相同的ktype呢)。kobject的kset指针指向相应的kset集合。
(1)kset的主要作用是做顶层kobject的容器类(装kobject)
(2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
(3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
(4)引用计数:kobject的主要功能之一就是为我们提供了一个统一的引用计数系统。初始化后,kobject的引用计数设置为1。只要引用计数不为0(表示有多个对象在引用该对象),那么该对象就会继续保留在内存中,也可以说是被“钉”住了,任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后则减少它的引用计数。当引用计数减为0时(此时相当于close),对象便可以被销毁,同时相关内存也都被释放。
增加一个引用计数可通过kobject_get()函数完成:
struct kobject * kobject_get(struct kobject *kobj);
该函数正常情况下返回一个指向kobject的指针,如果失败,则返回NULL指针。减少引用计数通过kobject_put()完成:
void kobject_put(struct kobject *kobj);
(5)Kobject的添加:仅仅初始化一个kobject是不能自动将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数kobject_add():
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
kobject在sysfs中的位置取决于kobject在对象层次结构中的位置。如果kobject的父指针被设置,那么在sysfs中kobject将被映射为其父目录下的子目录,如果parent没有设置,那么kobject将被映射到kset->kobj中的子目录。两者都未设置,映射为 sysfs下的根级目录。
如果kobject被关联到一个特殊的kset,在调用kobject_add()之前kobj->kset必须被赋值。
如果一个kset被关联到一个kobject,那么在kobject_add()调用中该kobject的父节点可被设置为NULL(就是kobject_add()的第二个参数设置为NULL),并且,该kobject的父节点就是那个kset本身。
三、总线式设备驱动组织方式
1、总线
(1)物理上的真实总线及其作用(英文bus)
物理上:可指用于通电供电的总线,也可指计算机上的物理总线,例如USB总线分成多个USB接口
(2)驱动框架中的总线式设计:驱动管理方法
总线分为usb/pci/iic等多种总线,其中的某个总线又可划分为设备和驱动两部分,当添加一个USB设备时,会将其划分给USB总线,寻找相应的驱动。
(3)bus_type结构体,关键是match函数和uevent函数
struct bus_type {const char *name;struct bus_attribute *bus_attrs;//bus本身所具有的一些属性struct device_attribute *dev_attrs;struct driver_attribute *drv_attrs;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 (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;//函数指针集,与电源管理相关struct bus_type_private *p;
};
2、设备
(1)struct device是硬件设备在内核驱动框架中的抽象
(2)device_register用于向内核驱动框架注册一个设备
int __must_check device_register(struct device *dev);
(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device
struct device {struct device *parent;struct device_private *p;struct kobject kobj;const char *init_name; /* initial name of the device */struct device_type *type;struct mutex mutex; /* mutex to synchronize calls to* its driver.*/struct bus_type *bus; /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void *platform_data; /* Platform specific data, devicecore doesn't touch it */struct dev_pm_info power;#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */
#endifu64 *dma_mask; /* dma mask (if dma'able device) */u64 coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */struct device_dma_parameters *dma_parms;struct list_head dma_pools; /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride *//* arch specific additions */struct dev_archdata archdata;
#ifdef CONFIG_OFstruct device_node *of_node;
#endifdev_t devt; /* dev_t, creates the sysfs "dev" */spinlock_t devres_lock;struct list_head devres_head;struct klist_node knode_class;struct class *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev);
};
3、驱动
(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2:probe,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理(使用总线框架写驱动才会使用)
struct device_driver {const char *name;struct bus_type *bus;struct module *owner;const char *mod_name; /* used for built-in modules */bool suppress_bind_attrs; /* disables bind/unbind via sysfs */#if defined(CONFIG_OF)const struct of_device_id *of_match_table;
#endifint (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p;
};
4、类
(1)相关结构体:struct class 和 struct class_device
struct class {const char *name;struct module *owner;struct class_attribute *class_attrs;struct device_attribute *dev_attrs;struct kobject *dev_kobj;int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, mode_t *mode);void (*class_release)(struct class *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);const struct dev_pm_ops *pm;struct class_private *p;
};
(2)udev的使用离不开class
(3)class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。
从分类、总线这两个不同的角度都可以找到设备,而且是同一个,其中利用了符号链接,/sys/目录下有很多的符号链接
5、总结
(1)模型思想很重要,其实就是面向对象的思想
(2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高
四、platform平台总线工作原理
1、何为平台总线
(1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的。
(2)CPU与外部通信的2种方式:地址总线式连接(如DM9000网卡连接方式)和专用接口式(USB、IIC等)连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。
CPU的一部分外设内部直连,而且工作方式扩展到CPU的地址空间中(210为4G, 0——2^32-1,寄存器扩展到地址总线空间,每个寄存器都有一个物理地址,而nandflash等与CPU连接通过专用的接口),例如定时器等本身不对应总线,为统一标准,规定所有的设备都有总线,故而原来不是总线的设备归到平台总线(虚拟出来的总线)。
(3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?
2、平台总线下管理的2员大将
(1)platform工作体系都定义在drivers/base/platform.c中
(2)两个结构体:platform_device和platform_driver
(3)两个接口函数:platform_device_register和platform_driver_register
struct platform_device {const char * name; // 平台总线下设备的名字int id;//区分多个设备的id例如led1、led2之类的struct device dev; // 所有设备通用的属性部分u32 num_resources; // 设备使用到的resource的个数,资源可以是地址范围,中断号等等struct resource * resource; // 设备使用到的资源数组的首地址const struct platform_device_id *id_entry; // 设备ID表 ,同一系列不同设备的id/* arch specific additions */struct pdev_archdata archdata; // 自留地,用来提供扩展性的
};
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; // 设备ID表
};
3、平台总线体系的工作流程
(1)第一步:系统启动时在bus系统中注册platform
(2)第二步:内核移植的人负责提供platform_device
(3)第三步:写驱动的人负责提供platform_driver
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
4、代码分析:platform本身注册
(1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的。
(2)platform_match函数就是平台总线的匹配方法。该函数的工作方法是:如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了,如果找完id_table都还没找到就说明没有匹配上;如果没有id_table或者没有匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。
有两个重要的链表挂在bus上,一个是设备device链表,一个是驱动driver链表。
static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* match against the id table first */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
5、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
(1)platform_driver_register
static struct platform_driver s3c24xx_led_driver = {.probe = s3c24xx_led_probe,//检测驱动是否可用,类似安装,初始化驱动.remove = s3c24xx_led_remove,//用来卸载设备.driver = {//所有设备共有的一些属性.name = "s3c24xx_led",.owner = THIS_MODULE,},
};//可以尝试搜索s3c24xx_led找到它对应的device结构体
static int __init s3c24xx_led_init(void)
{return platform_driver_register(&s3c24xx_led_driver);//注册driver
}
static void __exit s3c24xx_led_exit(void)
{platform_driver_unregister(&s3c24xx_led_driver);
}
(2)platform_device_register
/*** platform_add_devices - add a numbers of platform devices* @devs: array of platform devices to add* @num: number of platform devices in array*/
int platform_add_devices(struct platform_device **devs, int num)
{int i, ret = 0;for (i = 0; i < num; i++) {ret = platform_device_register(devs[i]);if (ret) {while (--i >= 0)//只要出错会将之前注册的注销掉platform_device_unregister(devs[i]);break;}}return ret;
}int platform_device_register(struct platform_device *pdev)
{device_initialize(&pdev->dev);return platform_device_add(pdev);
}int platform_device_add(struct platform_device *pdev)
{int i, ret = 0;if (!pdev)return -EINVAL;if (!pdev->dev.parent)pdev->dev.parent = &platform_bus;pdev->dev.bus = &platform_bus_type;if (pdev->id != -1)dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);elsedev_set_name(&pdev->dev, "%s", pdev->name);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)) {printk(KERN_ERR"%s: failed to claim resource %d\n",dev_name(&pdev->dev), 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:while (--i >= 0) {struct resource *r = &pdev->resource[i];unsigned long type = resource_type(r);if (type == IORESOURCE_MEM || type == IORESOURCE_IO)release_resource(r);}return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);
6、platdata怎么玩
struct platform_device {const char * name;int id;struct device dev;u32 num_resources;struct resource * resource;const struct platform_device_id *id_entry;/* arch specific additions */struct pdev_archdata archdata;
};
struct device {struct device *parent;struct device_private *p;struct kobject kobj;const char *init_name; /* initial name of the device */struct device_type *type;struct mutex mutex; /* mutex to synchronize calls to* its driver.*/struct bus_type *bus; /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void *platform_data; /* Platform specific data, devicecore doesn't touch it */struct dev_pm_info power;#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */
#endifu64 *dma_mask; /* dma mask (if dma'able device) */u64 coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */struct device_dma_parameters *dma_parms;struct list_head dma_pools; /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride *//* arch specific additions */struct dev_archdata archdata;
#ifdef CONFIG_OFstruct device_node *of_node;
#endifdev_t devt; /* dev_t, creates the sysfs "dev" */spinlock_t devres_lock;struct list_head devres_head;struct klist_node knode_class;struct class *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev);
};
struct s3c24xx_led_platdata {unsigned int gpio;//使用的GPIO对应的编号unsigned int flags;//属性char *name;//名称char *def_trigger;/与某个硬件的操作相绑定,例如读取硬盘时led闪烁
};
static int s3c24xx_led_probe(struct platform_device *dev)
{struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;...
}
(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)
(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
(3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。
7、match函数的调用轨迹
struct bus_type {const char *name;struct bus_attribute *bus_attrs;struct device_attribute *dev_attrs;struct driver_attribute *drv_attrs;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 (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;struct bus_type_private *p;
};
struct bus_type platform_bus_type = {.name = "platform",.dev_attrs = platform_dev_attrs,.match = platform_match,.uevent = platform_uevent,.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
#define to_platform_device(x) container_of((x), struct platform_device, dev)
#define to_platform_driver(drv) (container_of((drv), struct platform_driver,driver))static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* match against the id table first */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}

8、probe函数的功能和意义
probe函数在设备驱动注册进行最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用platform设备的probe函数完成驱动注册最后工作。资源、中断调用函数以及其他相关工作。
每当我们向一根bus注册一个驱动driver时,套路是这样的:driver_register(struct device_driver * drv) -> bus_add_driver() ->
driver_attach() ->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);bus_for_each_dev遍历该总线上所有的device,执行一次__driver_attach(),看能不能将驱动关联(attach)到某个设备上去。__driver_attach()->driver_probe_device()->drv->bus->match(dev, drv), // 调用bus的match函数,看device和driver匹不匹配。如果匹配上,继续执行really_probe()。->really_probe()->driver->probe()。(如果bus->probe非空,则调用bus->probe)
summary:对于上述所讲的知识如果想深入理解,建议自己去分析一个平台总线驱动的程序,理理思路和代码的流程。
五、平台总线实践环节
1、检查mach-x210.c中是否有led 相关的 platform_device(1726行上下附近都是)
注意:platform_device中的name要与platform_driver驱动中相同,否则无法匹配。可能三个led都会匹配上同一个驱动,使得probe函数被执行三次。这就使得每次申请使用后要及时释放,否则第二个设备就无法使用platform_driver了,会出现错误。
添加:
static struct s5pv210_led_platdata x210_led0_pdata = {.name = "led0",.gpio = S5PV210_GPJ0(3),.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger = "",
};static struct platform_device x210_led0 = {.name = "s5pv210_led",.id = 0,.dev = {.platform_data = &x210_led0_pdata,},
};static struct s5pv210_led_platdata x210_led1_pdata = {.name = "led1",.gpio = S5PV210_GPJ0(4),.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger = "",
};static struct platform_device x210_led1 = {.name = "s5pv210_led",.id = 1,.dev = {.platform_data = &x210_led1_pdata,},
};
2、参考mach-mini2440.c中添加led的platform_device定义在mach-x210.c文件中
所以在kernel\arch\arm\mach-s5pv210\include\mach目录下添加leds-gpio.h文件,否则没有类似struct s3c24xx_led_platdata这样的数据结构的定义
/* arch/arm/mach-s5cpv210/include/mach/leds-gpio.h** Copyright (c) 2006 Simtec Electronics* http://armlinux.simtec.co.uk/* Ben Dooks <ben@simtec.co.uk>** S5PV210 - LEDs GPIO connector** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.
*/#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"#define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */struct s5pv210_led_platdata {unsigned int gpio;unsigned int flags;char *name;char *def_trigger;
};#endif /* __ASM_ARCH_LEDSGPIO_H */
将修改好的文件同步到ubuntu的内核源码目录下,重新编译内核进行烧录
3、驱动程序:led-s5pv210.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>#define GPIO_LED1 S5PV210_GPJ0(3)#define x210_LED_OFF 1 //210开发板中led正极接电源,负极接GPIO
#define x210_LED_ON 0 //所以1是灭,0是亮struct led_classdev led_cdev;//定义结构体变量,用于描述led设备类的一个设备struct s5pv210_gpio_led {struct led_classdev cdev;struct s5pv210_led_platdata *pdata;};static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{return platform_get_drvdata(dev);
}static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}static void s5pv210_led_set(struct led_classdev *led_cdev,enum led_brightness value)
{struct s5pv210_gpio_led *p = to_gpio(led_cdev);printk(KERN_INFO "s5pv210_led_set\n");//在这里根据用户设定的值(value)操作硬件if (value == LED_OFF){gpio_set_value(p->pdata->gpio, x210_LED_OFF);}else{gpio_set_value(p->pdata->gpio, x210_LED_ON);}}static int s5pv210_led_probe(struct platform_device *dev)
{int ret = -1;struct s5pv210_led_platdata *pdata = dev->dev.platform_data;struct s5pv210_gpio_led *led;printk(KERN_INFO "-----s5pv210_led_probe------\n");led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);if (led == NULL) {dev_err(&dev->dev, "No memory for device\n");return -ENOMEM;}platform_set_drvdata(dev, led);//在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源if (gpio_request(pdata->gpio, pdata->name)){printk(KERN_INFO "pdata.gpio_request failed.\n");}else{//设置为输出模式,并且默认输出1让led灯灭gpio_direction_output(pdata->gpio, 1);}//填充描述led类设备的属性的结构体变量led->cdev.name = pdata->name;led->cdev.brightness = 0;led->cdev.brightness_set = s5pv210_led_set;led->pdata = pdata;ret = led_classdev_register(&dev->dev, &led->cdev);//来源于led-class.c,创建属于led类的//一个设备if (ret < 0) { printk(KERN_INFO "led_classdev_register failed\n");return ret;}return 0;}static int s5pv210_led_remove(struct platform_device *dev)
{struct s5pv210_gpio_led *led = pdev_to_gpio(dev);printk(KERN_INFO "-----s5pv210_led_remove------\n");led_classdev_unregister(&led->cdev);kfree(led);gpio_free(led->pdata->gpio);return 0; }static struct platform_driver s5pv210_led_driver = {.probe = s5pv210_led_probe,.remove = s5pv210_led_remove,.driver = {.name = "s5pv210_led",.owner = THIS_MODULE,},
};static int __init s5pv210_led_init(void)
{return platform_driver_register(&s5pv210_led_driver);
}static void __exit s5pv210_led_exit(void)
{platform_driver_unregister(&s5pv210_led_driver);
}module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

















