上篇文章我們了解了子系統的框架,下面我們來分析驅動框架中每層的實現以及作用。

在LED子系統中,硬件驅動層相關文件在包括:kernel/drivers/leds/ 目錄下,其主要的函數有:led-gpio.c、led-xxx.c,其中led-gpio.c為通用的平臺驅動程序,led-xxx.c為不同廠家提供的平臺驅動程序。
我們在這里主要分析
led-gpio.c
打開該文件,直接找到加載驅動的入口函數gpio_led_probe
struct?gpio_led_platform_data?{
????int???num_leds;
????const?struct?gpio_led?*leds;
#define?GPIO_LED_NO_BLINK_LOW?0?/*?No?blink?GPIO?state?low?*/
#define?GPIO_LED_NO_BLINK_HIGH?1?/*?No?blink?GPIO?state?high?*/
#define?GPIO_LED_BLINK??2?/*?Please,?blink?*/
????gpio_blink_set_t?gpio_blink_set;
};
結構體名稱:gpio_led_platform_data
文件位置:include/linux/leds.h
主要作用:LED的平臺數據,用于對LED硬件設備的統一管理
這個結構體用于父節點向子節點傳遞的數據時使用
struct?gpio_leds_priv?{
????int?num_leds;
????struct?gpio_led_data?leds[];
};
結構體名稱:gpio_leds_priv
文件位置:drivers/leds/leds-gpio.c
主要作用:LED驅動的私有數據類型,管理全部的LED設備。
這里的
num_leds通過解析設備樹的子節點的個數來獲取
leds[]根據獲取的num_leds個數,分配對應的空間,來初始化相關數據
static?int?gpio_led_probe(struct?platform_device?*pdev)
{
????struct?gpio_led_platform_data?*pdata?=?dev_get_platdata(&pdev->dev);??//?檢索設備的平臺數據
????struct?gpio_leds_priv?*priv;
????int?i,?ret?=?0;
????if?(pdata?&&?pdata->num_leds)?{????????????//?判斷平臺數據LED數量
????????priv?=?devm_kzalloc(&pdev->dev,
????????????????sizeof_gpio_leds_priv(pdata->num_leds),
????????????????????GFP_KERNEL);
????????if?(!priv)
????????????return?-ENOMEM;
????????priv->num_leds?=?pdata->num_leds;
????????for?(i?=?0;?i?num_leds;?i++)?{
????????????ret?=?create_gpio_led(&pdata->leds[i],?&priv->leds[i],
??????????????????????????&pdev->dev,?NULL,
??????????????????????????pdata->gpio_blink_set);
????????????if?(ret?0)
????????????????return?ret;
????????}
????}?else?{
????????priv?=?gpio_leds_create(pdev);???????????//?創建LED設備?
????????if?(IS_ERR(priv))
????????????return?PTR_ERR(priv);
????}
????platform_set_drvdata(pdev,?priv);
????return?0;
}
函數介紹:gpio_led_probe是LED驅動的入口函數,也是LED子系統中,硬件設備和驅動程序匹配后,第一個執行的函數。
實現思路:
dev_get_platdata檢索設備的平臺數據,如果平臺數據中的LED數量大于零,則使用devm_kzalloc為其分配內存空間,并且使用create_gpio_led進行初始化LED的數量為零,則使用gpio_leds_create創建LED。數據結構:該函數主要包括了兩個數據結構gpio_led_platform_data和gpio_leds_priv
/*?For?the?leds-gpio?driver?*/
struct?gpio_led?{
????const?char?*name;?????//?LED名稱
????const?char?*default_trigger;??//?默認觸發類型?
????unsigned??gpio;?????//?GPIO編號
????unsigned?active_low?:?1;???//?低電平有效
????unsigned?retain_state_suspended?:?1;
????unsigned?panic_indicator?:?1;
????unsigned?default_state?:?2;??//?默認狀態
????unsigned?retain_state_shutdown?:?1;
????/*?default_state?should?be?one?of?LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP)?*/
????struct?gpio_desc?*gpiod;???//?GPIO?Group
};
結構體名稱:gpio_led
文件位置:include/linux/leds.h
主要作用:LED的硬件描述結構,包括名稱,GPIO編號,有效電平等等信息。
該結構體的信息大多由解析設備樹獲得,將設備樹中
label解析為name,gpios解析為gpiod,linux,default-trigger解析為default_trigger等
struct?gpio_led_data?{
????struct?led_classdev?cdev;??//?LED?Class
????struct?gpio_desc?*gpiod;??//?GPIO?description
????u8?can_sleep;?????
????u8?blinking;?????//?閃爍
????gpio_blink_set_t?platform_gpio_blink_set;?//?閃爍設置
};
結構體名稱:gpio_led_data
文件位置:drivers/leds/leds-gpio.c
主要作用:LED相關數據信息,主要在于led_classdev,用于注冊設備節點信息
由設備樹解析出來的
gpio_led,然后將部分屬性賦值到gpio_led_data中,并且初始化led_classdev相關屬性,并且實現led_classdev結構體中的部分函數。
static?struct?gpio_leds_priv?*gpio_leds_create(struct?platform_device?*pdev)
{
????struct?device?*dev?=?&pdev->dev;
????struct?fwnode_handle?*child;
????struct?gpio_leds_priv?*priv;
????int?count,?ret;
????count?=?device_get_child_node_count(dev);??//?獲取子節點數量
????if?(!count)
????????return?ERR_PTR(-ENODEV);
????priv?=?devm_kzalloc(dev,?sizeof_gpio_leds_priv(count),?GFP_KERNEL);
????if?(!priv)
????????return?ERR_PTR(-ENOMEM);
????device_for_each_child_node(dev,?child)?{
????????struct?gpio_led_data?*led_dat?=?&priv->leds[priv->num_leds];?//?與gpio_leds_priv結構體關聯
????????struct?gpio_led?led?=?{};
????????const?char?*state?=?NULL;
????????struct?device_node?*np?=?to_of_node(child);
????????ret?=?fwnode_property_read_string(child,?"label",?&led.name);?//?讀設備樹屬性,賦值gpio_led結構體
????????if?(ret?&&?IS_ENABLED(CONFIG_OF)?&&?np)
????????????led.name?=?np->name;
????????if?(!led.name)?{
????????????fwnode_handle_put(child);
????????????return?ERR_PTR(-EINVAL);
????????}
????????led.gpiod?=?devm_fwnode_get_gpiod_from_child(dev,?NULL,?child,
?????????????????????????????????GPIOD_ASIS,
?????????????????????????????????led.name);
????????if?(IS_ERR(led.gpiod))?{
????????????fwnode_handle_put(child);
????????????return?ERR_CAST(led.gpiod);
????????}
????????fwnode_property_read_string(child,?"linux,default-trigger",
????????????????????????&led.default_trigger);
????????if?(!fwnode_property_read_string(child,?"default-state",
?????????????????????????&state))?{
????????????if?(!strcmp(state,?"keep"))
????????????????led.default_state?=?LEDS_GPIO_DEFSTATE_KEEP;
????????????else?if?(!strcmp(state,?"on"))
????????????????led.default_state?=?LEDS_GPIO_DEFSTATE_ON;
????????????else
????????????????led.default_state?=?LEDS_GPIO_DEFSTATE_OFF;
????????}
????????if?(fwnode_property_present(child,?"retain-state-suspended"))
????????????led.retain_state_suspended?=?1;
????????if?(fwnode_property_present(child,?"retain-state-shutdown"))
????????????led.retain_state_shutdown?=?1;
????????if?(fwnode_property_present(child,?"panic-indicator"))
????????????led.panic_indicator?=?1;
????????ret?=?create_gpio_led(&led,?led_dat,?dev,?np,?NULL);?//?將gpio_led結構體、gpio_led_data關聯起來
????????if?(ret?0)?{
????????????fwnode_handle_put(child);
????????????return?ERR_PTR(ret);
????????}
????????led_dat->cdev.dev->of_node?=?np;
????????priv->num_leds++;
????}
????return?priv;
}
函數介紹:gpio_leds_create主要用于創建LED設備。
實現思路:
device_get_child_node_count獲取設備樹中LED子節點的數量,根據獲取到的子節點數量,分配LED設備對應的內存空間device_for_each_child_node遍歷每個子節點,并為每個子節點創建對應的LED設備fwnode_property_read_string接口,讀取設備樹中相關的屬性信息,如:label、linux,default-trigger等,將這些信息賦值給gpio_led結構體中LED,調用create_gpio_led進行設備的創建該數據結構屬于核心層,在硬件驅動層需要與其進行關聯,遂在此介紹。
struct?led_classdev?{
????const?char??*name;
????enum?led_brightness??brightness;
????enum?led_brightness??max_brightness;
????int????flags;
????/*?Lower?16?bits?reflect?status?*/
#define?LED_SUSPENDED??BIT(0)
#define?LED_UNREGISTERING?BIT(1)
????/*?Upper?16?bits?reflect?control?information?*/
#define?LED_CORE_SUSPENDRESUME?BIT(16)
#define?LED_SYSFS_DISABLE?BIT(17)
#define?LED_DEV_CAP_FLASH?BIT(18)
#define?LED_HW_PLUGGABLE?BIT(19)
#define?LED_PANIC_INDICATOR?BIT(20)
#define?LED_BRIGHT_HW_CHANGED?BIT(21)
#define?LED_RETAIN_AT_SHUTDOWN?BIT(22)
????/*?set_brightness_work?/?blink_timer?flags,?atomic,?private.?*/
????unsigned?long??work_flags;
#define?LED_BLINK_SW???0
#define?LED_BLINK_ONESHOT??1
#define?LED_BLINK_ONESHOT_STOP??2
#define?LED_BLINK_INVERT??3
#define?LED_BLINK_BRIGHTNESS_CHANGE??4
#define?LED_BLINK_DISABLE??5
????/*?Set?LED?brightness?level
?????*?Must?not?sleep.?Use?brightness_set_blocking?for?drivers
?????*?that?can?sleep?while?setting?brightness.
?????*/
????void??(*brightness_set)(struct?led_classdev?*led_cdev,
??????????????????????enum?led_brightness?brightness);
????/*
?????*?Set?LED?brightness?level?immediately?-?it?can?block?the?caller?for
?????*?the?time?required?for?accessing?a?LED?device?register.
?????*/
????int?(*brightness_set_blocking)(struct?led_classdev?*led_cdev,
???????????????????????enum?led_brightness?brightness);
????/*?Get?LED?brightness?level?*/
????enum?led_brightness?(*brightness_get)(struct?led_classdev?*led_cdev);
????/*
?????*?Activate?hardware?accelerated?blink,?delays?are?in?milliseconds
?????*?and?if?both?are?zero?then?a?sensible?default?should?be?chosen.
?????*?The?call?should?adjust?the?timings?in?that?case?and?if?it?can't
?????*?match?the?values?specified?exactly.
?????*?Deactivate?blinking?again?when?the?brightness?is?set?to?LED_OFF
?????*?via?the?brightness_set()?callback.
?????*/
????int??(*blink_set)(struct?led_classdev?*led_cdev,
?????????????????????unsigned?long?*delay_on,
?????????????????????unsigned?long?*delay_off);
????struct?device??*dev;
????const?struct?attribute_group?**groups;
????struct?list_head??node;???/*?LED?Device?list?*/
????const?char??*default_trigger;?/*?Trigger?to?use?*/
????unsigned?long???blink_delay_on,?blink_delay_off;
????struct?timer_list??blink_timer;
????int????blink_brightness;
????int????new_blink_brightness;
????void???(*flash_resume)(struct?led_classdev?*led_cdev);
????struct?work_struct?set_brightness_work;
????int???delayed_set_value;
#ifdef?CONFIG_LEDS_TRIGGERS
????/*?Protects?the?trigger?data?below?*/
????struct?rw_semaphore??trigger_lock;
????struct?led_trigger?*trigger;
????struct?list_head??trig_list;
????void???*trigger_data;
????/*?true?if?activated?-?deactivate?routine?uses?it?to?do?cleanup?*/
????bool???activated;
#endif
#ifdef?CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
????int????brightness_hw_changed;
????struct?kernfs_node?*brightness_hw_changed_kn;
#endif
????/*?Ensures?consistent?access?to?the?LED?Flash?Class?device?*/
????struct?mutex??led_access;
};
結構體名稱:led_classdev
文件位置:include/linux/leds.h
主要作用:該結構體所包括的內容較多,主要有以下幾個功能
brightness當前亮度值,max_brightness最大亮度LED閃爍功能控制:blink_timer、blink_brightness、new_blink_brightness等attribute_group:創建sysfs文件節點,向上提供用戶訪問接口由上面可知,在創建
gpio_led_data時,順便初始化led_classdev結構體,賦值相關屬性以及部分回調函數,最終將led_classdev注冊進入LED子系統框架中,在sysfs中創建對應的文件節點。
static?int?create_gpio_led(const?struct?gpio_led?*template,
????struct?gpio_led_data?*led_dat,?struct?device?*parent,
????struct?device_node?*np,?gpio_blink_set_t?blink_set)
{
????int?ret,?state;
????led_dat->gpiod?=?template->gpiod;
????if?(!led_dat->gpiod)?{
????????/*
?????????*?This?is?the?legacy?code?path?for?platform?code?that
?????????*?still?uses?GPIO?numbers.?Ultimately?we?would?like?to?get
?????????*?rid?of?this?block?completely.
?????????*/
????????unsigned?long?flags?=?GPIOF_OUT_INIT_LOW;
????????/*?skip?leds?that?aren't?available?*/
????????if?(!gpio_is_valid(template->gpio))?{????????//?判斷是否gpio合法
????????????dev_info(parent,?"Skipping?unavailable?LED?gpio?%d?(%s)\n",
????????????????????template->gpio,?template->name);
????????????return?0;
????????}
????????if?(template->active_low)
????????????flags?|=?GPIOF_ACTIVE_LOW;
????????ret?=?devm_gpio_request_one(parent,?template->gpio,?flags,
????????????????????????template->name);
????????if?(ret?0)
????????????return?ret;
????????led_dat->gpiod?=?gpio_to_desc(template->gpio);??????//?獲取gpio組
????????if?(!led_dat->gpiod)
????????????return?-EINVAL;
????}
????led_dat->cdev.name?=?template->name;?????????//?賦值一些屬性信息
????led_dat->cdev.default_trigger?=?template->default_trigger;
????led_dat->can_sleep?=?gpiod_cansleep(led_dat->gpiod);
????if?(!led_dat->can_sleep)
????????led_dat->cdev.brightness_set?=?gpio_led_set;??????//?設置LED
????else
????????led_dat->cdev.brightness_set_blocking?=?gpio_led_set_blocking;
????led_dat->blinking?=?0;
????if?(blink_set)?{
????????led_dat->platform_gpio_blink_set?=?blink_set;
????????led_dat->cdev.blink_set?=?gpio_blink_set;
????}
????if?(template->default_state?==?LEDS_GPIO_DEFSTATE_KEEP)?{
????????state?=?gpiod_get_value_cansleep(led_dat->gpiod);
????????if?(state?0)
????????????return?state;
????}?else?{
????????state?=?(template->default_state?==?LEDS_GPIO_DEFSTATE_ON);
????}
????led_dat->cdev.brightness?=?state???LED_FULL?:?LED_OFF;
????if?(!template->retain_state_suspended)
????????led_dat->cdev.flags?|=?LED_CORE_SUSPENDRESUME;
????if?(template->panic_indicator)
????????led_dat->cdev.flags?|=?LED_PANIC_INDICATOR;
????if?(template->retain_state_shutdown)
????????led_dat->cdev.flags?|=?LED_RETAIN_AT_SHUTDOWN;
????ret?=?gpiod_direction_output(led_dat->gpiod,?state);
????if?(ret?0)
????????return?ret;
????return?devm_of_led_classdev_register(parent,?np,?&led_dat->cdev);??//?將LED設備注冊到子系統中
}
函數介紹:create_gpio_led創建LED設備的核心函數
實現思路:
gpio_is_valid接口,判斷GPIO是否合法gpio_led_data字段中,并且初始化部分字段,如:led_classdev、gpio_desc等gpio_led_set、gpio_led_set_blocking、gpio_blink_set等devm_of_led_classdev_register接口,將LED設備注冊到LED框架之中。硬件驅動層,肯定包括最終操作硬件的部分,也就是上面提到的一些回調函數,屬于我們驅動工程師開發的內容。
static?int?gpio_blink_set(struct?led_classdev?*led_cdev,
????unsigned?long?*delay_on,?unsigned?long?*delay_off)
{
????struct?gpio_led_data?*led_dat?=?cdev_to_gpio_led_data(led_cdev);
????led_dat->blinking?=?1;
????return?led_dat->platform_gpio_blink_set(led_dat->gpiod,?GPIO_LED_BLINK,
????????????????????????delay_on,?delay_off);
}
函數介紹:gpio_blink_set主要用于設置閃爍的時延
static?inline?struct?gpio_led_data?*
????????????cdev_to_gpio_led_data(struct?led_classdev?*led_cdev)
{
????return?container_of(led_cdev,?struct?gpio_led_data,?cdev);
}
static?void?gpio_led_set(struct?led_classdev?*led_cdev,
????enum?led_brightness?value)
{
????struct?gpio_led_data?*led_dat?=?cdev_to_gpio_led_data(led_cdev);
????int?level;
????if?(value?==?LED_OFF)
????????level?=?0;
????else
????????level?=?1;
????if?(led_dat->blinking)?{
????????led_dat->platform_gpio_blink_set(led_dat->gpiod,?level,
?????????????????????????NULL,?NULL);
????????led_dat->blinking?=?0;
????}?else?{
????????if?(led_dat->can_sleep)
????????????gpiod_set_value_cansleep(led_dat->gpiod,?level);
????????else
????????????gpiod_set_value(led_dat->gpiod,?level);
????}
}
static?int?gpio_led_set_blocking(struct?led_classdev?*led_cdev,
????enum?led_brightness?value)
{
????gpio_led_set(led_cdev,?value);
????return?0;
}
函數介紹:gpio_led_set 和gpio_led_set_blocking主要用于設置亮度,區別在于gpio_led_set 是不可睡眠的,gpio_led_set_blocking是可休眠的。
上面我們了解了硬件驅動層的實現流程以及相關數據結構,總結來看:

gpio_led_probe(drivers/leds/leds-gpio.c)
????|-->?gpio_leds_create
????????|-->?create_gpio_led????????????//??創建LED設備
????????????|-->?devm_of_led_classdev_register??????
LED相關屬性信息,賦值給gpio_led結構體gpio_led、gpio_leds_priv、led_classdev等數據結構關聯起來LED設備注冊進入LED子系統中
