代码汇总
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"); // 描述模块的别名信息
细讲:(详细步骤)
第一步:
在drivers\leds中新建leds-s5pv210.c文件,此文件为driver文件
仿照leds-s3c24xx.c文件去写:(卸载和装载函数后面写)
static int s5pv210_led_remove(struct platform_device *dev)//卸载函数
{
xxxxx
return 0;
}
static int s5pv210_led_probe(struct platform_device *dev)//装载函数
{
xxxxx
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(s3c24xx_led_exit);
第二步:
现在写device
1.检查.\arch\arm\mach-s5pv210中的mach-x210.c中是否有 led 相关的 platform_device(发现没有)
包括在smdkc110_devices[]数组中也找不到 led ,所以我们需要编写 led 相关的 platform_device 并放在这个数组中。
2、参考mach-mini2440.c中添加 led 的 platform_device ,定义在mach-x210.c文件中
在.\arch\arm\mach-s5pv210\include\mach目录下新建文件:leds-gpio.h 并编写 s5pv210_led_platdate 结构体
#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 */
make完后生成了新的zimage,我们把它tftpboot中后,重启开发板查看: 找到了我们写的device
(开发板会自动装载)
第三步:
前两步我写好了driver的模板与device。现在,让两个“相遇”
现在我们还差装载driver(通过ismode)
1.根据我们的推测:driver只要一装载,就会注册
2.注册了driver后,因为我们有device 了,所以device和driver的name会进行匹配,匹配上了后就会进行 probe 函数
所以我们现在开始写probe 函数:(见上文的汇总代码),接下来写的是遇到的bug与解决
BUG
gpio request failed,led1可以正常驱动,但是led2和3就不行
分析:
注册了三个led的device,这三个led都是独立的设备,这三个设备都会试图去匹配同一个driver,因为这三个led的设备platform_device的name都是s5pv210_led。每次匹配都会调用一次probe函数。
因为之前的驱动代码是根据直接注册指定的io口,他固定死了,所以导致了重复注册并报错。
(下图为之前代码)。
思路:我们想办法:
把device的platform_date提取出来,pdata
被赋值为 dev->dev.platform_data
,即设备的配置数据
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
gpio_request(pdata->gpio, pdata->name)
BUG2
我们如果用三个方法操作led,会变得很麻烦(需要编写三个方法)。所以我们使用一个方法,在方法的内部对各个led进行区分。
遇到的困难:led_classdev中,不能传递gpio号。
解决:
自己定义一个结构体:里面包含led的led_classdev和s5pv210_led_platdata(s5pv210_led_platdata里面有gpio号,我们就可以通过下面的函数进行指针的传递)
struct s5pv210_gpio_led {
struct led_classdev cdev;
struct s5pv210_led_platdata *pdata;
};
有两个函数:
platform_get_drvdata(dev);:可以通过platform_device反推s5pv210_gpio_led的指针。通过 platform_device
结构体获取到与之关联的 struct s3c24xx_gpio_led
结构体指针。
container_of(led_cdev, struct s5pv210_gpio_led, cdev);:将一个 struct led_classdev
结构体指针转换为一个指向 struct s3c24xx_gpio_led
结构体的指针。从 struct led_classdev
结构体获取到与之关联的 struct s3c24xx_gpio_led
结构体指针。
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);
}