LED子系統(tǒng)——硬件驅動層 我們來分析驅動框架中每層的實現(xiàn)以及作用。
image-20230417084033734 在LED
子系統(tǒng)中,,硬件驅動層相關文件在包括:kernel/drivers/leds/
目錄下,,其主要的函數(shù)有:led-gpio.c
、led-xxx.c
,,其中led-gpio.c
為通用的平臺驅動程序,,led-xxx.c
為不同廠家提供的平臺驅動程序。
我們在這里主要分析led-gpio.c
1,、gpio_led_probe分析 打開該文件,,直接找到加載驅動的入口函數(shù)gpio_led_probe
1.1 相關數(shù)據(jù)結構 1.1.1 gpio_led_platform_data 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
的平臺數(shù)據(jù),,用于對LED
硬件設備的統(tǒng)一管理
這個結構體用于父節(jié)點向子節(jié)點傳遞的數(shù)據(jù)時使用
1.1.2 gpio_leds_priv struct gpio_leds_priv { int num_leds; struct gpio_led_data leds []; };
結構體名稱 :gpio_leds_priv
文件位置 :drivers/leds/leds-gpio.c
主要作用 :LED
驅動的私有數(shù)據(jù)類型,管理全部的LED
設備,。
這里的num_leds
通過解析設備樹的子節(jié)點的個數(shù)來獲取
leds[]
根據(jù)獲取的num_leds
個數(shù),,分配對應的空間,來初始化相關數(shù)據(jù)
1.2 實現(xiàn)流程 static int gpio_led_probe (struct platform_device *pdev) { struct gpio_led_platform_data *pdata = dev_get_platdata (&pdev ->dev ); // 檢索設備的平臺數(shù)據(jù) struct gpio_leds_priv *priv ; int i, ret = 0 ; if (pdata && pdata->num_leds) { // 判斷平臺數(shù)據(jù)LED數(shù)量 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 < priv->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); // 創(chuàng)建LED設備 if (IS_ERR(priv)) return PTR_ERR(priv); } platform_set_drvdata(pdev, priv); return 0 ; }
函數(shù)介紹 :gpio_led_probe
是LED
驅動的入口函數(shù),,也是LED
子系統(tǒng)中,,硬件設備和驅動程序匹配后,第一個執(zhí)行的函數(shù),。
實現(xiàn)思路 :
通過dev_get_platdata
檢索設備的平臺數(shù)據(jù),,如果平臺數(shù)據(jù)中的LED
數(shù)量大于零,則使用devm_kzalloc
為其分配內存空間,,并且使用create_gpio_led
進行初始化 如果平臺數(shù)據(jù)不存在或LED
的數(shù)量為零,,則使用gpio_leds_create
創(chuàng)建LED。 最后,,設置驅動程序數(shù)據(jù),,并返回0,表示操作成功,。 數(shù)據(jù)結構 :該函數(shù)主要包括了兩個數(shù)據(jù)結構gpio_led_platform_data
和gpio_leds_priv
2,、gpio_leds_create分析 2.1 相關數(shù)據(jù)結構 2.1.1 gpio_led /* For the leds-gpio driver */ struct gpio_led { const char *name; // LED名稱 const char *default_trigger; // 默認觸發(fā)類型 unsigned gpio; // GPIO編號 unsigned active_low : 1 ; // 低電平有效 unsigned retain_state_suspended : 1 ; unsigned panic_indicator : 1 ; unsigned default_state : 2 ; // 默認狀態(tài) 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
等
2.1.2 gpio_led_data 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
相關數(shù)據(jù)信息,,主要在于led_classdev
,,用于注冊設備節(jié)點信息
由設備樹解析出來的gpio_led
,然后將部分屬性賦值到gpio_led_data
中,,并且初始化led_classdev
相關屬性,,并且實現(xiàn)led_classdev
結構體中的部分函數(shù),。
2.2 實現(xiàn)流程 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); // 獲取子節(jié)點數(shù)量 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結構體關聯(lián) 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關聯(lián)起來 if (ret < 0 ) { fwnode_handle_put(child); return ERR_PTR(ret); } led_dat->cdev.dev->of_node = np; priv->num_leds++; } return priv; }
函數(shù)介紹 :gpio_leds_create
主要用于創(chuàng)建LED
設備,。
實現(xiàn)思路 :
通過device_get_child_node_count
獲取設備樹中LED
子節(jié)點的數(shù)量,,根據(jù)獲取到的子節(jié)點數(shù)量,分配LED
設備對應的內存空間 通過device_for_each_child_node
遍歷每個子節(jié)點,,并為每個子節(jié)點創(chuàng)建對應的LED
設備 對于每個子節(jié)點,,使用fwnode_property_read_string
接口,,讀取設備樹中相關的屬性信息,如:label
,、linux,default-trigger
等,,將這些信息賦值給gpio_led
結構體中 最后將遍歷的每個LED
,調用create_gpio_led
進行設備的創(chuàng)建 3,、create_gpio_led分析 3.1 相關數(shù)據(jù)結構 3.1.1 led_classdev 該數(shù)據(jù)結構屬于核心層,,在硬件驅動層需要與其進行關聯(lián),遂在此介紹,。
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
:創(chuàng)建sysfs
文件節(jié)點,向上提供用戶訪問接口由上面可知,,在創(chuàng)建gpio_led_data
時,,順便初始化 led_classdev
結構體,賦值相關屬性以及部分回調函數(shù),,最終將led_classdev
注冊進入LED
子系統(tǒng)框架中,,在sysfs
中創(chuàng)建對應的文件節(jié)點。
3.2 實現(xiàn)流程 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設備注冊到子系統(tǒng)中 }
函數(shù)介紹 :create_gpio_led
創(chuàng)建LED
設備的核心函數(shù)
實現(xiàn)思路 :
先通過gpio_is_valid
接口,,判斷GPIO
是否合法 將上層從設備樹解析出來的信息,,填充到gpio_led_data
字段中,并且初始化部分字段,,如:led_classdev
,、gpio_desc
等 填充回調函數(shù),實現(xiàn)相應的動作,,如:gpio_led_set
,、gpio_led_set_blocking
、gpio_blink_set
等 最后調用devm_of_led_classdev_register
接口,,將LED
設備注冊到LED
框架之中,。 4、回調函數(shù)分析 硬件驅動層,,肯定包括最終操作硬件的部分,,也就是上面提到的一些回調函數(shù),屬于我們驅動工程師開發(fā)的內容,。
4.1 gpio_blink_set 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); }
函數(shù)介紹 :gpio_blink_set
主要用于設置閃爍的時延
4.2 gpio_led_set 和gpio_led_set_blocking 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 ; }
函數(shù)介紹 :gpio_led_set
和gpio_led_set_blocking
主要用于設置亮度,,區(qū)別在于gpio_led_set
是不可睡眠的,gpio_led_set_blocking
是可休眠的,。
5,、總結 上面我們了解了硬件驅動層的實現(xiàn)流程以及相關數(shù)據(jù)結構,,總結來看:
5.1 數(shù)據(jù)結構之間的關系如下 LED子系統(tǒng)-LED數(shù)據(jù)結構.drawio 5.2 函數(shù)實現(xiàn)流程如下 gpio_led_probe(drivers/leds/leds-gpio.c) |--> gpio_leds_create |--> create_gpio_led // 創(chuàng)建LED設備 |--> devm_of_led_classdev_register
5.3 主要作用如下 從設備樹獲取LED
相關屬性信息,賦值給gpio_led
結構體 將gpio_led
,、gpio_leds_priv
,、led_classdev
等數(shù)據(jù)結構關聯(lián)起來 單片機接口類資料大匯總 50冊
十天學會單片機非常完整版
單片機外圍電路設計
單片機編程技巧100例