結合以前對Linux內核的platform總線 ,以及對Linux內核的LED設備的驅動框架的分析,本文將編寫基於platform總線與/sys接口的LED設備的實例代碼並對其進行分析。html
platform總線分析,詳見Linux platform驅動模型。linux
字符設備的cdev接口分析,詳見Linux字符設備驅動框架(一):Linux內核的LED設備驅動框架。數據結構
硬件接口:框架
CPU:s5pv210;函數
LED的GPIO:GPIO_J0_3 ~ GPIO_J0_5;post
LED的工做方式:低電平亮,高電平滅。學習
使用Linux內核的LED驅動框架以前,需確保Linux內核支持LED驅動框架。進入Linux內核的配置界面menuconfig進行設置,具體配置以下:測試
Device Drivers --->spa
[*] LED Support ---> 指針
本文將以兩種形式編寫LED驅動代碼:
(1)將led_device和led_driver分別寫成兩個單獨的文件模塊;(這種作法適合學習使用)
(2)將led_device集成至Linux內核的/arch/arm/mach-s5pv210/mach-x210.c中,再將led_driver編寫爲獨立模塊。(這種作法更接近於實戰的驅動程序,更好的使用設備樹)
本文將設備信息寫成一個模塊的形式,須要時加載該模塊便可。
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/platform_device.h> //定義並初始化LED設備的相關資源 static struct resource led_resource = { .start = 0xE0200240, .end = 0xE0200240 + 8 - 1, .flags = IORESOURCE_MEM, }; //定義並初始化LED設備信息 static struct platform_device led_dev = { .name = "led", //設備名稱 .id = -1, //設備數量,-1表示只有一個設備 .num_resources = 1, //資源數量 .resource = &led_resource, //資源指針 .dev = { .release = led_release, }, }; //註冊LED設備 static int __init led_device_init(void) { return platform_device_register(&led_dev); } //註銷LED設備 static void __exit led_device_exit(void) { platform_device_unregister(&led_dev); } module_init(led_device_init); module_exit(led_device_exit); MODULE_AUTHOR("Lin"); MODULE_DESCRIPTION("led device for x210"); MODULE_LICENSE("GPL");
led_driver_init():模塊加載函數
platform_driver_register()將驅動對象模塊註冊到平臺總線
led_probe()探測函數,提取相應的信息
platform_get_resource()獲取設備資源
request_mem_region()、ioremap()虛擬內存映射
readl()、write()初始化硬件設備
kzalloc()申請led_classdev內存
led_classdev_register()註冊LED設備
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <asm/io.h> #include <linux/leds.h> //定義GPIO_J0寄存器變量 typedef struct GPJ0REG { volatile unsigned int gpj0con; volatile unsigned int gpj0dat; }gpj0_reg_t; static gpj0_reg_t *pGPIOREG = NULL; static struct led_classdev *led_cdev = NULL; //LED設備實際的硬件操做函數 void led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { int reg_value = 0; //向LED設備屬性文件brightness寫入0,LED設備滅 if (brightness == LED_OFF) { reg_value = readl(&(pGPIOREG->gpj0dat)); reg_value |= (1 << 3) | (1 << 4) | (1 << 5); writel(reg_value, &(pGPIOREG->gpj0dat)); } //不然,LED設備亮 else { reg_value = readl(&(pGPIOREG->gpj0dat)); reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5)); writel(reg_value, &(pGPIOREG->gpj0dat)); } } static int led_probe(struct platform_device *pdev) { struct resource *res_led = NULL; int ret = -1; int reg_value = 0; /********************************申請資源*********************************/ //獲取資源 res_led = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_led) { return -ENOMEM; } //動態內存映射 if (!request_mem_region(res_led->start, resource_size(res_led), "GPIOJ0")) { return -EBUSY; } pGPIOREG = ioremap(res_led->start, resource_size(res_led)); if (pGPIOREG == NULL) { ret = -ENOENT; goto ERR_STEP; } /********************************設備初始化********************************/ //初始化資源,設置GPIO爲輸出模式 reg_value = readl(&(pGPIOREG->gpj0dat)); reg_value |= (1 << (3*4)) | (1 << (4*4)) | (1 << (5*4)); writel(reg_value, &(pGPIOREG->gpj0dat)); /********************************建立接口**********************************/ //申請led_classdev內存 led_cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); if (led_cdev == NULL) { ret = -ENOMEM; goto ERR_STEP1; } led_cdev->name = pdev->name; led_cdev->brightness_set = led_brightness_set; //註冊LED設備 ret = led_classdev_register(NULL, led_cdev); if (ret) { goto ERR_STEP2; } return 0; /******************************倒映式錯誤處理********************************/ ERR_STEP2: kfree(led_cdev); ERR_STEP1: iounmap(pGPIOREG); ERR_STEP: release_mem_region(res_led->start, resource_size(res_led)); return ret; } static int led_remove(struct platform_device *pdev) { led_classdev_unregister(led_cdev); //註銷LED設備 iounmap(pGPIOREG); //釋放內存 kfree(led_cdev); //釋放內存 return 0; } //定義並初始化LED驅動信息 static struct platform_driver led_drv = { .driver = { .name = "led", .owner = THIS_MODULE, }, .probe = led_probe, .remove = led_remove, }; static int __init led_driver_init(void) { return platform_driver_register(&led_drv); } static void __exit led_driver_exit(void) { platform_driver_unregister(&led_drv); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_AUTHOR("Lin"); MODULE_DESCRIPTION("led driver for x210"); MODULE_LICENSE("GPL");
在/kernel/arch/arm/mach-s5pv210/include/mach目錄下,創建一個leds_gpio.h文件,並填充以下內容。
#ifndef __ASM_ARCH_LEDSGPIO_H #define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h" #define S3C24XX_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */ #define S3C24XX_LEDF_TRISTATE (1<<1) /* tristate to turn off */ //定義一個LED設備的數據結構 struct s3c24xx_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; }; #endif
在/kernel/arch/arm/mach-s5pv210/mach-x210.c下,添加以下內容,並添加對leds_gpio.h的包含。
/* LEDS */ static struct s5pv210_led_platdata s5pv210_led1_pdata = { .name = "xled1", .gpio = S5PV210_GPJ0(3), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "heartbeat", }; static struct s5pv210_led_platdata s5pv210_led2_pdata = { .name = "xled2", .gpio = S5PV210_GPJ0(4), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "heartbeat", }; static struct s5pv210_led_platdata s5pv210_led3_pdata = { .name = "xled3", .gpio = S5PV210_GPJ0(5), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "heartbeat", }; static struct platform_device s5pv210_led1 = { .name = "s5pv210_led", .id = 1, .dev = { .platform_data = &s5pv210_led1_pdata, }, }; static struct platform_device s5pv210_led2 = { .name = "s5pv210_led", .id = 2, .dev = { .platform_data = &s5pv210_led2_pdata, }, }; static struct platform_device s5pv210_led3 = { .name = "s5pv210_led", .id = 3, .dev = { .platform_data = &s5pv210_led3_pdata, }, };
將LED設備信息集成至smdkc110_devices,內核初始化時smdkc110_devices中的設備將被註冊進內核。
static struct platform_device *smdkc110_devices[] __initdata = { ...... &s5pv210_led1, &s5pv210_led2, &s5pv210_led3, };
#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 <mach/hardware.h> #include <mach/regs-gpio.h> #include <mach/leds-gpio.h> #define X210_LED_OFF 1 // X210中LED是正極接電源,負極節GPIO #define X210_LED_ON 0 // 因此1是滅,0是亮 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); } //LED設備硬件操做函數 static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value) { struct s5pv210_gpio_led *led = to_gpio(led_cdev); struct s5pv210_led_platdata *pd = led->pdata; if (value == LED_OFF) { gpio_set_value(pd->gpio, X210_LED_OFF); } else { gpio_set_value(pd->gpio, X210_LED_ON); } } static int s5pv210_led_remove(struct platform_device *dev) { struct s5pv210_gpio_led *led = pdev_to_gpio(dev); led_classdev_unregister(&led->cdev);//註銷LED設備 kfree(led); //釋放內存 gpio_free(led->pdata->gpio); //釋放GPIO return 0; } static int s5pv210_led_probe(struct platform_device *dev) { struct s5pv210_led_platdata *pdata = dev->dev.platform_data; struct s5pv210_gpio_led *led; int ret; /*****************************申請GPIO資源******************************/ ret = gpio_request(pdata->gpio, pdata->name); if (ret) { printk(KERN_ERR "gpio_request failed, ret = %d.\n", ret); return -EIO; } /************************申請,並初始化GPIO資源*************************/ // 設置爲輸出模式,而且默認輸出1讓LED燈滅 gpio_direction_output(pdata->gpio, 1); /********************************建立接口********************************/ 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);//將led存入dev->p->driver_data led->cdev.brightness_set = s5pv210_led_set; led->cdev.name = pdata->name; led->pdata = pdata; //註冊LED設備 ret = led_classdev_register(&dev->dev, &led->cdev); if (ret < 0) { dev_err(&dev->dev, "led_classdev_register failed\n"); kfree(led); return ret; } 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_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); MODULE_DESCRIPTION("S5PV210 LED driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:s5pv210_led");
裝載上述兩個模塊(device、driver)以後,進入/sys/class/leds目錄下,會發現多了一個led文件夾。進入led文件夾,會看到LED設備的屬性文件。
(1)向屬性文件brightness寫入0," echo 0 > brightness ",LED設備滅;
(2)向屬性文件brightness寫入1," echo 1 > brightness ",LED設備亮;
(3)讀取屬性文件max_brightness," cat max_brightness ",獲取LED設備的最大亮度值。