目前的linux版本的許多驅動都是基於裝置模型,LED也不例外。
簡單地說,裝置模型就是系統認為所有的裝置都是掛接在匯流排上的,而要使裝置工作,就需要相應的驅動。裝置模型會產生一個虛擬檔案系統——sysfs,它給使用者提供了一個從使用者空間去訪問核心裝置的方法,它在linux裡的路徑是/sys。如果要寫程式訪問sysfs,可以像讀寫普通檔案一樣來操作/sys目錄下的檔案。
對於基於s3c2440的開發板來說,linux-3.6.6自動的LED驅動只需改變串連LED的IO連接埠,及高、低電平響應即可。我的開發板的四個LED串連在了B口的5到8引腳上,當輸出低電平時被點亮,與linux內建的LED驅動一致,因此無需做任何改動。
使用menuconfig來配置核心,這裡要加上對LED模組的內容,即:
Device Drivers--->
[*]LED Support--->
<*>LED Class Support
<*>LED Support for Samsung S3C24xx GPIO LEDs
編譯核心,並把編譯好的核心下載到開發板上,運行:
[root@zhaocj /]#ls
bin etc lib proc sys usr
dev home linuxrc sbin temp
[root@zhaocj /]#cd sys
[root@zhaocj /sys]#ls
block class devices fs module
bus dev firmware kernel power
進入sys目錄下,我們看到該目錄下有一些子目錄。
[root@zhaocj /sys]#cd class
[root@zhaocj class]#ls
backlight hidraw leds rtc vc
bdi hwmon mem sound video_output
block i2c-adapter misc spi_master vtconsole
firmware i2c-dev mmc_host spidev watchdog
gpio input mtd tty
graphics lcd net udc
進入class目錄,我們會看到在該目錄下有一些裝置,其中leds就是本次我們要操作的LED。
[root@zhaocj class]#cd leds
[root@zhaocj leds]#ls
backlight led1 led2 led3 led4
在leds目錄下,會看到四個LED的目錄,這就是開發板上的四個LED。另外backlight目錄是關於LCD的背光,與LED無關。
[root@zhaocj leds]#cd led1
[root@zhaocj led1]#ls
brightness max_brightness subsystem
device power uevent
brightness檔案就是LED裝置,對其進行操作就可完成對LED的控制。
[root@zhaocj led1]#cat brightness
0
可以看出led1當前的狀態是關閉。(0表示關閉,1表示開啟)
[root@zhaocj led1]#cat >brightness<<eof
> 1
> eof
#[root@zhaocj led1]#
向brightness寫1,表示開啟LED。這時led1會被點亮。
當然,我們也可以編寫使用者程式來控制開發板上的四個LED
/**********************
****leds.c**************
**********************/
#include<stdint.h>
#include<string.h>#include<fcntl.h>#include<unistd.h>#include<stdio.h>#include<linux/input.h>#include<unistd.h>int main(int argc, char *argv[]){ int fd, no;/*判斷是要控制哪個LED,並開啟相應的檔案*/ no=(int)argv[1][3]-48; switch(no) { case 1: fd = open("/sys/class/leds/led1/brightness", O_RDWR); break; case 2: fd = open("/sys/class/leds/led2/brightness", O_RDWR); break; case 3: fd = open("/sys/class/leds/led3/brightness", O_RDWR); break; case 4: fd = open("/sys/class/leds/led4/brightness", O_RDWR); break; default: return -1;} if(fd<0) { printf("can not open file.\n"); return -1; }/*完成開啟或關閉LED操作*/ if(!strcmp(argv[2],"on")) write(fd, "1", 1); else if(!strcmp(argv[2],"off")) write(fd, "0", 1); close(fd); return 0;}
上面的程式只做簡單測試之用。編譯該檔案:
arm-linux-gcc -o leds leds.c
把leds檔案下載到temp目錄下,運行:
[root@zhaocj /temp]# ./leds led2 on
則點亮led2。
[root@zhaocj /temp]# ./leds led2 off
則關閉led2。
下面我就來簡單分析一下linux內建的LED子系統。
在mach-zhaocj2440.c檔案,建立了LED裝置,如下:
/* LEDS */
static struct s3c24xx_led_platdata zhaocj2440_led1_pdata = {
.name = "led1",
.gpio = S3C2410_GPB(5),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "heartbeat",
};
static struct s3c24xx_led_platdata zhaocj2440_led2_pdata = {
.name = "led2",
.gpio = S3C2410_GPB(6),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "nand-disk",
};
static struct s3c24xx_led_platdata zhaocj2440_led3_pdata = {
.name = "led3",
.gpio = S3C2410_GPB(7),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "mmc0",
};
static struct s3c24xx_led_platdata zhaocj2440_led4_pdata = {
.name = "led4",
.gpio = S3C2410_GPB(8),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "",
};
定義了四個LED資料,名字分別為led1~led4,這就是我們在leds目錄下看到這四個子目錄。它們所串連的引腳分別為B口的5~8,這是由S3C2410_GPB()宏定義完成的。標識S3C24XX_LEDF_ACTLOW表示的是低電平有效,S3C24XX_LEDF_TRISTATE表示的三態無效。另外def_trigger表示的是觸發控制,如我們對nand進行讀寫操作時,led2會不停的閃,在這裡我們沒有用到這個功能,暫時不用理會。
static struct platform_device zhaocj2440_led1= {
.name = "s3c24xx_led",
.id = 1,
.dev = {
.platform_data = &zhaocj2440_led1_pdata,
},
};
static struct platform_device zhaocj2440_led2= {
.name = "s3c24xx_led",
.id = 2,
.dev = {
.platform_data = &zhaocj2440_led2_pdata,
},
};
static struct platform_device zhaocj2440_led3= {
.name = "s3c24xx_led",
.id = 3,
.dev = {
.platform_data = &zhaocj2440_led3_pdata,
},
};
static struct platform_device zhaocj2440_led4= {
.name = "s3c24xx_led",
.id = 4,
.dev = {
.platform_data = &zhaocj2440_led4_pdata,
},
};
上面則建立了匯流排平台裝置,四個LED的裝置名稱都是s3c24xx_led,子裝置id分別從1到4,裝置資料則是上面定義的四個LED資料。然後把這四個LED裝置再添加到開發板的裝置數組中,即:
static struct platform_device *zhaocj2440_devices[]__initdata = {
……
&zhaocj2440_led1,
&zhaocj2440_led2,
&zhaocj2440_led3,
&zhaocj2440_led4,
……
};
最後,在開發板系統初始化過程中,再把裝置數組中的裝置逐一註冊到系統匯流排上,即:
static void __init zhaocj2440_init(void)
{
……
platform_add_devices(zhaocj2440_devices,ARRAY_SIZE(zhaocj2440_devices));
……
}
這樣就完成了LED裝置的建立。
光有裝置還不能工作,任何一個裝置的運行還需要與之相對應的驅動。對於基於s3c24xx的LED來說,它的驅動是在drivers/leds目錄下Leds-s3c24xx.c檔案內建立的,即:
static struct platform_driver s3c24xx_led_driver = {
.probe = s3c24xx_led_probe,
.remove = s3c24xx_led_remove,
.driver = {
.name = "s3c24xx_led",
.owner = THIS_MODULE,
},
};
裝置和驅動是如何匹配的呢?即裝置如何找到它所對應的驅動的呢?靠的就是name。我們會發現platform_device和platform_driver都有元素name,它們的內容如果一致,裝置和驅動就會配對成功。對於LED來說,它們的name都是s3c24xx_led。當裝置和驅動匹配上以後,就要運行probe所指定的函數,簡單地說,它就是完成一些初始化工作。當需要移除裝置時,就需要運行remove所指定的函數,它完成的任務是登出裝置。對於支援熱插拔的裝置來說,尤為重要。
現在就來說一下s3c24xx_led_probe函數:
static int s3c24xx_led_probe(struct platform_device *dev)
{
structs3c24xx_led_platdata*pdata = dev->dev.platform_data;
structs3c24xx_gpio_led *led;
intret;
/*用於給LED分配記憶體空間*/
led =devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
GFP_KERNEL);
if(led == NULL) {
dev_err(&dev->dev,"No memory for device\n");
return-ENOMEM;
}
/*儲存LED裝置結構*/
platform_set_drvdata(dev,led);
/*給LED結構體賦值,其中s3c24xx_led_set就是具體負責操作LED的函數*/
led->cdev.brightness_set= s3c24xx_led_set;
led->cdev.default_trigger= pdata->def_trigger;
led->cdev.name= pdata->name;
led->cdev.flags|= LED_CORE_SUSPENDRESUME;
led->pdata = pdata;
/*為LED分配io引腳*/
ret =devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
if(ret < 0)
returnret;
/*no point in having a pull-up if we are always driving */
/*無需上拉*/
s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
/*設定io引腳為輸入*/
if(pdata->flags & S3C24XX_LEDF_TRISTATE)
gpio_direction_input(pdata->gpio);
else
gpio_direction_output(pdata->gpio,
pdata->flags& S3C24XX_LEDF_ACTLOW ? 1 :0);
/*register our new led device */
/*註冊一個新的LED裝置類對象
該函數是在drivers/leds目錄下的Led-class.c檔案內定義的*/
ret= led_classdev_register(&dev->dev, &led->cdev);
if(ret < 0)
dev_err(&dev->dev,"led_classdev_register failed\n");
return ret;
}
從以上分析可以看出,s3c24xx_led_probe函數主要就是完成LED裝置的一些初始化工作。而負責開、關LED任務的是s3c24xx_led_set函數,在該函數內,gpio_set_value(pd->gpio, state);是具體完成為相應引腳置1或清零的任務。
drivers/leds目錄下的Led-class.c檔案是LED子系統的底層核心檔案,它主要負責建立LED類,以及建立裝置節點,上面提到的led_classdev_register函數就是在這個檔案中定義的。為了更好的理解LED子系統,我們再簡單分析一下該檔案。
在子系統初始化時,會調用leds_init函數,它的第一段代碼:
leds_class = class_create(THIS_MODULE,"leds");
就是建立leds類,也就是我們在sys/class目錄下看到的leds目錄。另外
leds_class->dev_attrs = led_class_attrs;
是賦予該類的屬性。那麼我們再來看看led_class_attrs結構的第一句代碼:
__ATTR(brightness, 0644, led_brightness_show,led_brightness_store)
其中brightness就是我們對LED具體操作的裝置檔案名稱,0644是該檔案的許可權,led_brightness_show是讀檔案所調用的函數,led_brightness_store是寫檔案所調用的函數。讀檔案也就是讀取LED的狀態(是關還是開),寫檔案也就是完成開啟LED或關閉LED操作。
最後再分析一下前面提到的led_classdev_register函數。在該函數內首先利用device_create函數建立裝置節點,也就是在leds目錄下,產生led1~led4這四個目錄。另一項重要的任務就是把裝置節點添加到leds的鏈表中。
對linux內建的LED子系統的分析就到這裡。我想只要理解了該子系統,那麼自己完全可以寫出關於GPIO讀寫操作的任何驅動程式來。