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 :clean4:测试代码
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子系统的实现;