1.gpio_key.c介紹
1.1 功能介紹
Linux核心下的 drivers/input/keyboard/gpio_keys.c實作了一個體系結構無關的GPIO按鍵驅動,使用此按鍵驅動,隻需在裝置樹gpio-key節點添加需要的按鍵子節點即可。驅動的實作非常簡單,但是較适合于實作獨立式按鍵驅動。
1.2 架構介紹
gpio-keys是基于input架構實作的一個通用GPIO按鍵驅動。該驅動基于platform_driver架構,實作了驅動和裝置分離,符合Linux裝置驅動模型的思想。本文以自己的了解介紹gpio_keys.c驅動實作原理及代碼技巧。
2.裝置樹
裝置樹節點轉化成device_node.gpio_keys.
c與其他驅動一樣采用platform總線架構,在裝置樹擷取硬體相關屬性。
Show me the code:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2PnVGcq5ycup3d0tGcy4mNvwFN5IDM5UzNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.jpeg)
從上述代碼可看到,gpio_keys.c節點内定義兩個按鍵節點: “key_power”、”key_headset”。每個按鍵節點包括一個gpio所用到的所有硬體屬性。拿第一個裝置節點解析:
key_power {
label = "Power Key"; //按鍵描述性名稱
linux,code =<116>; //鍵值,即中斷觸發上報的鍵值與核心定義的保持一緻。
gpios = <&pmic_eic 1="">; //按鍵gpio &pmic_eic: gpio組由dtsi定義 1: gpio号 1:有效電平
debounce-interval =<5>; //去抖間隔 機關ms
gpio-key,wakeup; //可喚醒系統
gpio-key,level-trigger; //中斷觸發方式level-trigger: 條件觸發 edge-trigger: 邊緣觸發
};
複制
3.資料結構
優秀的代碼必然離不開優秀的資料結構,gpio_key.c之是以能夠做到完全脫離闆級晶片,實作通用與其資料結構的建立不無關系。以下列舉使用到的資料結構:
device_node //裝置樹節點結構體
device //裝置類型
platform_device //platform總線裝置
platform_data
input_dev
gpio_keys_platform_data
注:其中device_node、device 、platform_device 、三個結構體,是在驅動程式設計中經常被用到較為重要的資料結構。
以我自己的了解來解釋這三種結構體的關系:
device_node : 用于采集一個裝置樹節點資訊的結構體。是以需要具備裝置樹屬性的成員,例如:硬體裝置名(const char *name)、屬性指針(struct property *property)、父節點(struct device_node *parent)、子節點(struct device_node *child )等。
device : 指相同類型裝置的一種集合結構體。包含了這種類型的所有資訊,例如:裝置類型(const struct device_type *type;)、該裝置基于的總線類型(struct bus_type *bus)、具體的裝置樹節點資訊(struct device_node *of_node)等。
platform_device: 基于platform總線的裝置結構體。即挂在虛拟platform總線架構的裝置,其包含的成員:裝置結構體(struct device dev)、虛拟platform裝置名(const char *name)等。
列舉以下這三種最常見的結構體,之間的關系:
device_node = device->of_node
device = platform_device->dev
platform_device = of_find_device_by_node(device_node)
複制
由上述關系可知道,隻要有一個參數已知,其餘兩個參數都可以擷取到。列舉出他們建的關系:
platform_device --> device --> device_node --> platform_device
4.驅動實作
4.1擷取裝置樹屬性
按道理,能成功進入probe,就說明裝置樹platform_device和驅動platform_driver比對成功,傳進來的platform_device *pdev就包含裝置樹硬體資訊。
依然是熟悉的配方!裝置節點拿屬性。
先從傳來的參數裡拿裝置:
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
複制
然後判斷有沒有資料, pdata若沒資料,我就繼續拿:
error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
pdata = &alt_pdata;
複制
看一下gpio_keys_get_devtree_pdata裡是怎麼拿的:
注:容易進入誤區的一點,驅動裝置樹of _xx API并不都是直接從裝置樹裡直接拿到屬性的,而是先将裝置樹節點屬性值轉化為device_node資料結構體成員值,然後驅動通過of_xx API從device_node結構體中擷取到相應屬性的值。是以device_node的轉化就非常重要了!
通過上述代碼,發現還是從傳參struct device dev裡拿struct device_node node。
(1) 先拿裝置節點: node = dev->of_node;(也可以通過of_find_node_by_path(); 或of_find_compatible_node() 拿裝置節點。)
然後拿父節點屬性值 “input-name”,對應裝置樹
(2) 再通過of_xx API 拿node對應的屬性值。
這裡,源碼通過周遊檢測node下存在的子節點,實作對每一個按鍵屬性初始化。貼出周遊循環宏。
(3) 在周遊裡拿每個gpio預設電平。
即裝置樹中gpios = <&pmic_eic 1 1> 第三個參數。此屬性還有另一種寫法例如gpios = <&pmic 1 GPIO_ACTIVE_HIGH>;意義是一樣的。
of_get_gpio_flags(pp, 0, &flags); 傳回gpios項裡的第三個有效電平參數。
(4) 在周遊裡擷取中斷号
(5) 然後通過of_property_read_u32 可在device_node拿到指定屬性名的屬性值。
注:其實在節點屬性值擷取時,根據屬性值的格式都可以選擇of_property_xx函數擷取,代碼裡所謂的特定屬性擷取API大都是對of_property_xx的一層封裝,例如
附上驅動周遊擷取裝置樹節點屬性值代碼:
4.2使用input架構
(1) 申請input裝置
input = input_allocate_device();
複制
(2) 填充input結構體成員
(3) 設定GPIO按鍵
主要負責申請GPIO管腳,設定狀态,輸出方向,中斷申請等
API: gpio_keys_setup_key(pdev, input, bdata, button);
複制
(4) 注冊input裝置
input_register_device(input);
複制
4.3上報按鍵事件
按鍵狀态發生變化時,會觸發中斷,在中斷子服務函數中,先通過消抖參數值判斷是否消抖,如果消抖就啟用定時器上報,若無需消抖就就直接上報按鍵事件。
中斷子服務函數: