(3.8)一個按鍵所能涉及的:核心按鍵標準驅動gpio-keys
/* AUTHOR: Pinus
* Creat on : 2018-10-30
* KERNEL : linux-4.4.145
* BOARD : JZ2440(arm9 s3c2440)
* REFS : 核心 gpio-keys.c
*/
分析
從入口函式開始
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver); // 註冊平臺裝置
}
顯然其採用了platform bus的方法設計驅動,由此也可以猜想出,我們若想要實現該驅動,理想情況下不用更改gpio-keys.c中的任何一個字,只需要編寫其需要的平臺裝置資源。
static struct platform_driver gpio_keys_device_driver = { .probe = gpio_keys_probe, // 驅動和裝置匹配後呼叫該函式 .remove = gpio_keys_remove, .driver = { .name = "gpio-keys", /*platform driver和platform device通過name來匹配 */ .pm = &gpio_keys_pm_ops, // 寫了但並未具體賦值,無意義 .of_match_table = of_match_ptr(gpio_keys_of_match), // 匹配列表 } };
platform_driver 是用於平臺驅動註冊的結構體,其中各項含義皆有註釋。前文中也有分析。
當驅動和裝置的.name匹配,必然會去呼叫gpio_keys_probe,為方便閱讀只保留重要部分,完整程式碼可再核心原始碼中獲取。
// probe主要完成初始化引腳,註冊輸入裝置input_dev,在sys目錄下產生相應的檔案
static int gpio_keys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); //獲取具體的裝置platform_data資源;可得知我們寫的平臺裝置的platform_data成員應當提供gpio_keys_platform_data型別資料 struct gpio_keys_drvdata *ddata; //定義一個結構體,用來整合關聯各類資訊 struct input_dev *input; // 宣告輸入子系統結構 ... input = devm_input_allocate_device(dev); // 例項化結構體 ... ddata->pdata = pdata; // 將平臺資源整合在,ddate中 ddata->input = input; // 將輸入子系統裝置整合在,ddate中 mutex_init(&ddata->disable_lock); // 初始化ddate中的互斥鎖 platform_set_drvdata(pdev, ddata); // 把ddate儲存在平臺裝置pdev中 pdev->dev->driver_data = ddata input_set_drvdata(input, ddata); // 同理, input->dev->driver_data = ddata /*設定input相關屬性*/ input->name = pdata->name ? : pdev->name; // 優先使用在裝置資源裡定義的name input->phys = "gpio-keys/input0"; input->dev.parent = &pdev->dev; input->open = gpio_keys_open; // input開啟操作 input->close = gpio_keys_close; // input關閉操作 input->id.bustype = BUS_HOST; input->id.vendor = 0x0001; input->id.product = 0x0001; input->id.version = 0x0100; /* Enable auto repeat feature of Linux input subsystem 使能輸入子系統 自動重複上報的特點*/ if (pdata->rep) __set_bit(EV_REP, input->evbit); for (i = 0; i < pdata->nbuttons; i++) { // 根據按鍵數目迴圈設定所有按鍵 const struct gpio_keys_button *button = &pdata->buttons[i]; struct gpio_button_data *bdata = &ddata->data[i]; error = gpio_keys_setup_key(pdev, input, bdata, button); // 初始化按鍵,將按鍵資訊也儲存在ddate中 ... } error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group); ... error = input_register_device(input); // 註冊input裝置 ... device_init_wakeup(&pdev->dev, wakeup); ... }
程式碼一開始就定義了兩個很重要的結構體,弄清楚這兩個結構體的組成,對理解程式碼有河大幫助。
其一:
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); //獲取具體的裝置platform_data資源;可得知我們寫的平臺裝置的platform_data成員應當提供
由註釋也可以看出這個的作用了,他就是我們在平臺裝置側應該給出的資料
/**
* struct gpio_keys_platform_data - platform data for gpio_keys driver
* @buttons: pointer to array of &gpio_keys_button structures
* describing buttons attached to the device
* @nbuttons: number of elements in @buttons array
* @poll_interval: polling interval in msecs - for polling driver only
* @rep: enable input subsystem auto repeat
* @enable: platform hook for enabling the device
* @disable: platform hook for disabling the device
* @name: input device name
*/
struct gpio_keys_platform_data {
struct gpio_keys_button *buttons; // 按鍵設定資訊
int nbuttons; // 按鍵數目
unsigned int poll_interval;
unsigned int rep:1;
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
const char *name;
};
這其中又有一個結構體gpio_keys_button
/**
* struct gpio_keys_button - configuration parameters
* @code: input event code (KEY_*, SW_*)
* @gpio: %-1 if this key does not support gpio
* @active_low: %true indicates that button is considered // 1 低電平有效,低電平表示按下
* depressed when gpio is low
* @desc: label that will be attached to button's gpio // 按鍵的描述 名字
* @type: input event type 輸入事件型別(%EV_KEY, %EV_SW, %EV_ABS)
* @wakeup: configure the button as a wake-up source 配置一個按鍵為喚醒源
* @debounce_interval: debounce ticks interval in msecs
* @can_disable: %true indicates that userspace is allowed to
* disable button via sysfs 1 表示可以在使用者空間內disable
* @value: axis value for %EV_ABS 座標值,EV_ABS專用
* @irq: Irq number in case of interrupt keys 中斷號
* @gpiod: GPIO descriptor
*/
struct gpio_keys_button {
unsigned int code; //此按鍵對應的鍵碼
int gpio; //此按鍵對應的一個io口
int active_low; // 1 低電平有效,低電平表示按下
const char *desc; //就是申請io口,申請中斷時使用的名字
unsigned int type; //輸入裝置的事件型別,按鍵用EV_KEY
int wakeup; //表示按鍵按下時是否喚醒系統, 這個需要io口硬體上有這功能
int debounce_interval; // 去抖動間隔 防抖動用,間隔多久時間
bool can_disable;
int value;
unsigned int irq;
struct gpio_desc *gpiod;
};
看其各項含義,不由讓我聯想到我們在學習用input子系統實現按鍵時所進行的設定,顯然這些都應該是裝置資源的一部分。
其二:struct gpio_keys_drvdata *ddata; //定義一個結構體,用來整合關聯各類資訊
struct gpio_keys_drvdata {
const struct gpio_keys_platform_data *pdata; // 平臺裝置資源
struct input_dev *input; // 指向定義的輸入子系統裝置
struct mutex disable_lock; // 定義一個互斥鎖
struct gpio_button_data data[0]; // 按鍵設定資訊
};
它就是驅動定義的結構體,用來關聯各裝置相關項,其內也有一個結構體gpio_button_data
struct gpio_button_data {
const struct gpio_keys_button *button; // 按鍵的設定
struct input_dev *input;
struct timer_list release_timer; // 防抖動定時器
unsigned int release_delay; /* in msecs, for IRQ-only buttons ms, 按下的延遲,超過表示一直按 */
struct delayed_work work;
unsigned int software_debounce; /* 去抖動 in msecs, for GPIO-driven buttons */
unsigned int irq;
spinlock_t lock;
bool disabled;
bool key_pressed;
};
其中有關於定時的設定,顯然是用來防抖動的。
大致介紹這兩個結構體,繼續向下看,
error = gpio_keys_setup_key(pdev, input, bdata, button); // 初始化按鍵,將按鍵資訊也儲存在ddate中
static int gpio_keys_setup_key(struct platform_device *pdev, struct input_dev *input, struct gpio_button_data *bdata, const struct gpio_keys_button *button)
{
const char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
irq_handler_t isr;
unsigned long irqflags;
int irq;
int error;
bdata->input = input; // 關聯進ddate
bdata->button = button;
spin_lock_init(&bdata->lock);
if (gpio_is_valid(button->gpio)) { // 如果設定的io口有效根據io腳設定,否則根據IRQ設定,都沒有返回錯誤
error = devm_gpio_request_one(&pdev->dev, button->gpio, GPIOF_IN, desc); // 申請io引腳
...
if (button->debounce_interval) {
error = gpio_set_debounce(button->gpio, button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->software_debounce = button->debounce_interval;
}
if (button->irq) {
bdata->irq = button->irq; // 如果設定了中斷號就用沒有就自動申請
} else {
irq = gpio_to_irq(button->gpio);
...
bdata->irq = irq;
}
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);// 定時器的超時處理函式,用於按鍵後資訊上報
isr = gpio_keys_gpio_isr; // 按鍵中斷handler
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
...
input_set_capability(input, button->type ?: EV_KEY, button->code); // 設定按鍵產生哪一類事件下的哪一些事件 L,S,ENTER,LEFTSHIFT
...
error = devm_request_any_context_irq(&pdev->dev, bdata->irq, isr, irqflags, desc, bdata); // 註冊按鍵中斷
/* 內部呼叫 rc = request_any_context_irq(irq, handler, irqflags, devname, bdata);
isr = gpio_keys_gpio_isr; // 按鍵中斷handler
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
const char *desc = button->desc ? button->desc : "gpio_keys"
struct gpio_button_data *bdata
==> request_any_context_irq(bdata->irq, gpio_keys_gpio_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, button->desc, gpio_button_data);
* 以前我們這麼做 request_irq(IRQ_EINT0, button_irq_handle, IRQF_TRIGGER_FALLING, "S2", &pins_desc[0]);
*/
...
}
實現
/* realize gpio-keys @pinus 2018-10-30 */
static struct gpio_keys_button jz2440_buttons[] = {
[0] = {
.code = KEY_L,
.gpio = S3C2410_GPF(0),
.desc = "S2",
.active_low = 1,
},
[1] = {
.code = KEY_S,
.gpio = S3C2410_GPF(2),
.desc = "S3",
.active_low = 1,
},
[2] = {
.code = KEY_ENTER,
.gpio = S3C2410_GPG(3),
.desc = "S4",
.active_low = 1,
},
[3] = {
.code = KEY_LEFTSHIFT,
.gpio = S3C2410_GPG(11),
.desc = "S5",
.active_low = 1,
},
};
static struct gpio_keys_platform_data jz2440_keys_pdata = {
.buttons = jz2440_buttons,
.nbuttons = ARRAY_SIZE(jz2440_buttons),
};
static struct platform_device jz2440_buttons_device = {
.name = "gpio-keys",
.id = -1,
.dev = {
.platform_data = &jz2440_keys_pdata,
},
};
static struct platform_device *smdk2440_devices[] __initdata = {
...
/* modify by pinus 2018-10-30 */
&jz2440_buttons_device,
};
演示
以前沒試過,正好實現演示一下