1:概述
2:交叉编译环境搭建
3:编写驱动
4:测试驱动
5:总结
1:概述
1.1:代码基于exynos 4412平台,android 4.4系统,为了提高代码的移植性,将led驱动挂在platform虚拟总线下,自动建立设备节点,创建节点属性,并且gpio操作全部采用标准的gpio操作;
1.2:代码参考led子系统,使用了led子系统的一些数据结构;
2:交叉编译环境搭建
2.1:安装adb工具,adb shell进入板子文件系统,使用 cat /proc/version查看板子的内核版本,然后下载版本相同的内核源码;
2.2:安装交叉编译工具,我使用的是arm-2009q3编译工具,在将其放在/usr/local/arm/目录下,并在.vimrc中将编译工具加入到系统环境变量中;
2.3:内核目录下,修改Makefile中的ARCH和CROSS_COMPILE,生成.config文件(具体依据硬件平台);
2.4:执行make,相当于执行make zImage 和make modules,等待编译完成;
3:贴出代码和Makefile,具体参考注释
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/err.h>
#include <mach/gpio-exynos4.h>
#define USE_IMMEDIATE
static struct class *leds_class;
//用来描述单个的led ddevice
static struct led_dev
{
struct device *dev;
struct gpio_led led;
};
//描述设备中所有led
static struct leds_driver_data
{
unsigned char leds_num;
struct led_dev leds[];
};
//节点属性读方法
static ssize_t led_val_show(struct device *dev,struct device_attribute *attr, char *buf)
{
int val = 0;
struct led_dev *led_dev;
led_dev = dev_get_drvdata(dev);
val = gpio_get_value(led_dev->led.gpio);
if(val == 1)
return sprintf(buf,"%s is on\n",led_dev->led.name);
else
return sprintf(buf,"%s is off\n",led_dev->led.name);
}
//节点属性写方法
static ssize_t led_val_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t size)
{
struct led_dev *led_dev;
led_dev = dev_get_drvdata(dev);
if(memcmp(buf,"ON",2) == 0)
{
gpio_set_value(led_dev->led.gpio,1);
}
else
if(memcmp(buf,"OFF",3) == 0)
{
gpio_set_value(led_dev->led.gpio,0);
}
return size; /*必须返回size*/
}
//设备属性定义
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR,led_val_show, led_val_store);
//探测函数
static int s3c4410_led_probe(struct platform_device *pdev)
{
int result = 0;
unsigned char i = 0;
struct gpio_led_platform_data *pdata = pdev->dev.platform_data; //从platform_device中获取led的平台数据
struct leds_driver_data *leds_data; //定义led驱动的数据结构体指针,这个结构描述设备中所有的led
leds_class = class_create(THIS_MODULE,"leds"); //在/sys/class/下创建leds目录
if(IS_ERR(leds_class))
{
result = PTR_ERR(leds_class);
printk(KERN_ALERT "Failed create leds class\n");
}
//为led驱动数据结构体动态申请内存
leds_data = kmalloc((sizeof(struct leds_driver_data) + sizeof(struct led_dev)*pdata->num_leds),GFP_KERNEL);
if(leds_data == NULL)
{
result = -ENOMEM;
printk(KERN_ALERT "Failed alloc template\n");
goto class_destroy;
}
//获得设备中led的个数
leds_data->leds_num = pdata->num_leds;
//遍历设备中的led
for(i=0;i<leds_data->leds_num;i++)
{
struct led_dev *led_dev;
led_dev = &leds_data->leds[i];
led_dev->led.name = pdata->leds[i].name; //取出名字
led_dev->led.gpio = pdata->leds[i].gpio; //取出gpio号
//在/sys/class/leds/下创建目录,每个led对应一个目录
led_dev->dev = device_create(leds_class,&pdev->dev,0,leds_data,"%s",led_dev->led.name);
if(IS_ERR(led_dev->dev))
{
result = PTR_ERR(led_dev->dev);
printk(KERN_ALERT "Failed create device\n");
goto free;
}
//创建设备节点,比如/sys/class/leds/led1/val
result = device_create_file(led_dev->dev,&dev_attr_val);
if(result < 0)
{
printk(KERN_ALERT "Failed to create arrribute val.\n");
goto device_destroy;
}
//申请gpio
gpio_request(led_dev->led.gpio,led_dev->led.name);
//将device设备模型中的私有指针指向led_dev,方便在节点属性方法中使用led_dev结构
dev_set_drvdata(led_dev->dev,led_dev);
}
//将平台设备数据私有指针指向leds_data,这个函数会调用dev_set_drvdata()函数,方便在remove函数中使用leds_data结构
platform_set_drvdata(pdev,leds_data);
return 0;
device_destroy:
device_destroy(leds_class,0); //销毁设备
free:
kfree(leds_data); //释放内存
class_destroy:
class_destroy(leds_class); //销毁leds_class类
return result;;
}
static int s3c4410_led_remove(struct platform_device *pdev)
{
char i = 0;
struct leds_driver_data *leds_data = platform_get_drvdata(pdev); //得到led驱动私有数据结构
for(i=0;i<leds_data->leds_num;i++) //遍历释放内存
{
struct led_dev *led_dev;
led_dev = &leds_data->leds[i];
gpio_free(led_dev->led.gpio); //释放gpio
device_remove_file(led_dev->dev,&dev_attr_val); //删除属性节点
}
device_destroy(leds_class,0); //销毁device设备,删除leds下的目录
kfree(leds_data); //释放内存
class_destroy(leds_class); //销毁leds_class类,删除leds目录
leds_class = NULL;
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver platform_led_driver = {
.probe = s3c4410_led_probe,
.remove = s3c4410_led_remove,
.driver = {
.name = "gpio-leds",
.owner = THIS_MODULE,
},
};
static struct gpio_led gpio_leds[] =
{
{
.name = "led1",
.gpio = EXYNOS4_GPK1(1),
},
{
.name = "led2",
.gpio = EXYNOS4_GPL2(0),
},
};
static struct gpio_led_platform_data gpio_leds_data =
{
.leds = gpio_leds,
.num_leds = ARRAY_SIZE(gpio_leds),
};
static struct platform_device platform_led_device =
{
.name = "gpio-leds", //必须和platform_driver中的name相同
.id = -1,
.dev = {
.platform_data = &gpio_leds_data, //平台设被中的led私有数据
},
};
static int __init led_init(void)
{
platform_device_register(&platform_led_device);
return platform_driver_register(&platform_led_driver);
}
static void __exit led_exit(void)
{
platform_driver_unregister(&platform_led_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Golf/fxb,<1029930509@qq.com>");
MODULE_DESCRIPTION("Led platform device driver");
Makefile
#Kbuild Makefile
#Kbuild的生成规则,编译对象hello_word依赖文件Hello_Word.c文件
#将编译对象hello_word编译成模块
obj-m := led.o
led-objs := leds.o
#ubuntu使用的内核源代码,build是链接文件
KERNEL_DIR :=/usr/src/golf/android4.4/iTop4412_Kernel_3.0
#当前目录
PWD := $(shell pwd)
#首先执行内核源代码中的TOP Makefile文件,设置内核编译环境,再执行用户定义的Kbuile Makefile文件
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
#make clean命令
clean:
rm *.o *.ko *.mod.c
#执行"make clean"会无视"clean"文件存在与否。
.PHONY :clean
4:测试代码
4.1:使用adb push 将模块放在板子中,insmod模块
4.2:在/sys/clas/leds/下会有led1和led2两个目录,目录中有val属性,可以使用cat读取当前led等状态,使用echo "OFF" > val 或者 echo "ON" > val,控制led开关;
5:总结
5.1:代码把移植性放在首位,参考led子系统进行驱动设计,并且使用platform总线,使用标准的gpio操作,以后要使用。只需要修改platform_device即可;
5.2:led的驱动,按照led子系统来移植是最可取的方法,因为内核中已经提供了很全面的机制来控制设备,后面博文分享led子系统的实现;