1. 程式人生 > >(3.8)一個按鍵所能涉及的:核心按鍵標準驅動gpio-keys

(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,
};

演示

以前沒試過,正好實現演示一下