使用gpio子系统实现按键驱动(二)

一,gpio_keys.c介绍

Linux内核下的drivers/input/keyboard/gpio_keys.c实现了一个体系无关的GPIO按键驱动,使用此按键驱动,只需要在设备树gpio-key节点添加需要的按键子节点即可,适合于实现独立式按键驱动。

gpio-keys是基于input架构实现的一个通用gpio按键驱动,该驱动基于platform_driver架构,实现了驱动和设备分离,符合linux设备驱动模型的思想。

二,主要结构体及其关系

首先大致看下代码实现搞清楚结构体之间的关系,然后根据结构体之前的关系再看代码细节。

1,主要的结构体

struct gpio_keys_drvdata:

struct gpio_keys_drvdata {
    const struct gpio_keys_platform_data *pdata;
    struct input_dev *input;
    struct mutex disable_lock;
    unsigned short *keymap;
    struct gpio_button_data data[];
};

struct gpio_keys_platform_data:

struct gpio_keys_platform_data {
    const 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;
};

struct gpio_button_data:

struct gpio_button_data {
    const struct gpio_keys_button *button;
    struct input_dev *input;
    struct gpio_desc *gpiod;

    unsigned short *code;

    struct timer_list release_timer;
    unsigned int release_delay;    /* in msecs, for IRQ-only buttons */

    struct delayed_work work;
    unsigned int software_debounce;    /* in msecs, for GPIO-driven buttons */

    unsigned int irq;
    unsigned int wakeup_trigger_type;
    spinlock_t lock;
    bool disabled;
    bool key_pressed;
    bool suspended;
};

struct gpio_keys_button:

struct gpio_keys_button {
    unsigned int code;
    int gpio;
    int active_low;
    const char *desc;
    unsigned int type;
    int wakeup;
    int wakeup_event_action;
    int debounce_interval;
    bool can_disable;
    int value;
    unsigned int irq;
};
2,结构体之间的关系

三,关键代码分析

以Android volumn up key为例。

1,设备树配置
gpio_keys {
    compatible = "gpio-keys";
    label = "gpio-keys";


    pinctrl-names = "default";
    pinctrl-0 = <&key_vol_up_default &google_key_default>;


    vol_up {
        label = "volume_up";
        gpios = <&pm7325_gpios 6 GPIO_ACTIVE_LOW>;
        linux,input-type = <1>;
        linux,code = <KEY_VOLUMEUP>;
        gpio-key,wakeup;
        debounce-interval = <15>;
        linux,can-disable;
    };

    google_key {
        label = "google_key";
        gpios = <&tlmm 41 GPIO_ACTIVE_LOW>;
        linux,input-type = <1>;
        linux,code = <KEY_SEARCH>;
        gpio-key,wakeup;
        debounce-interval = <15>;
        linux,can-disable;
    };
    ... ... ...
};
2,

probe函数

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);
    struct fwnode_handle *child = NULL;
    struct gpio_keys_drvdata *ddata;
    struct input_dev *input;
    int i, error;
    int wakeup = 0;

    if (!pdata) {
        //解析设备树配置
        pdata = gpio_keys_get_devtree_pdata(dev);
        if (IS_ERR(pdata))
            return PTR_ERR(pdata);
    }

    //给driver data分配内存,struct_size用来计算gpio_keys_drvdata结构体和nbuttons个gpio_button_data 所占内存大小
    ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons),
                 GFP_KERNEL);
    if (!ddata) {
        dev_err(dev, "failed to allocate state\n");
        return -ENOMEM;
    }
    dev_err(dev, "william debug gpio keys driver\n");

    ddata->keymap = devm_kcalloc(dev,
                     pdata->nbuttons, sizeof(ddata->keymap[0]),
                     GFP_KERNEL);
    if (!ddata->keymap)
        return -ENOMEM;
    
    //分配input设备
    input = devm_input_allocate_device(dev);
    if (!input) {
        dev_err(dev, "failed to allocate input device\n");
        return -ENOMEM;
    }

    ddata->pdata = pdata;
    ddata->input = input;
    mutex_init(&ddata->disable_lock);

    platform_set_drvdata(pdev, ddata);
    input_set_drvdata(input, ddata);

    input->name = pdata->name ? : pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = dev;
    input->open = gpio_keys_open;
    input->close = gpio_keys_close;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    input->keycode = ddata->keymap;
    input->keycodesize = sizeof(ddata->keymap[0]);
    input->keycodemax = pdata->nbuttons;

    /* 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];
        
        //获取每个按键节点child
        if (!dev_get_platdata(dev)) {
            child = device_get_next_child_node(dev, child);
            if (!child) {
                dev_err(dev,
                    "missing child device node for entry %d\n",
                    i);
                return -EINVAL;
            }
        }

        error = gpio_keys_setup_key(pdev, input, ddata,
                        button, i, child);
        if (error) {
            fwnode_handle_put(child);
            return error;
        }

        if (button->wakeup)
            wakeup = 1;
    }

    fwnode_handle_put(child);

    //注册输入设备
    error = input_register_device(input);
    if (error) {
        dev_err(dev, "Unable to register input device, error: %d\n",
            error);
        return error;
    }
    
    device_init_wakeup(dev, wakeup);

    return 0;
}

button设置函数gpio_keys_setup_key

static int gpio_keys_setup_key(struct platform_device *pdev,
                struct input_dev *input,
                struct gpio_keys_drvdata *ddata,
                const struct gpio_keys_button *button,
                int idx,
                struct fwnode_handle *child)
{
    const char *desc = button->desc ? button->desc : "gpio_keys";
    struct device *dev = &pdev->dev;
    //每一个bdata跟button对应,见以上结构体之间的关系
    struct gpio_button_data *bdata = &ddata->data[idx];
    irq_handler_t isr;
    unsigned long irqflags;
    int irq;
    int error;

    bdata->input = input;
    bdata->button = button;
    spin_lock_init(&bdata->lock);

    if (child) {
        //如果child节点不空,使用此函数获取gpio description
        bdata->gpiod = devm_fwnode_gpiod_get(dev, child,
                             NULL, GPIOD_IN, desc);
        if (IS_ERR(bdata->gpiod)) {
            error = PTR_ERR(bdata->gpiod);
            if (error == -ENOENT) {
                /*
                 * GPIO is optional, we may be dealing with
                 * purely interrupt-driven setup.
                 */
                bdata->gpiod = NULL;
            } else {
                if (error != -EPROBE_DEFER)
                    dev_err(dev, "failed to get gpio: %d\n",
                        error);
                return error;
            }
        }
    } else if (gpio_is_valid(button->gpio)) {
        /*
         * Legacy GPIO number, so request the GPIO here and
         * convert it to descriptor.
         */
        unsigned flags = GPIOF_IN;

        if (button->active_low)
            flags |= GPIOF_ACTIVE_LOW;

        error = devm_gpio_request_one(dev, button->gpio, flags, desc);
        if (error < 0) {
            dev_err(dev, "Failed to request GPIO %d, error %d\n",
                button->gpio, error);
            return error;
        }
        
        //将gpio转成gpio description,为了使用新的gpio控制接口,gpiod接口  
        bdata->gpiod = gpio_to_desc(button->gpio);
        if (!bdata->gpiod)
            return -EINVAL;
    }

    if (bdata->gpiod) {
        //GPIO_ACTIVE_LOW表示在低电平时触发某种操作,而GPIO_ACTIVE_HIGH表示在高电平时触发相同的操作,将逻辑电平与物理电平区分开
        bool active_low = gpiod_is_active_low(bdata->gpiod);

        if (button->debounce_interval) {
            error = gpiod_set_debounce(bdata->gpiod,
                    button->debounce_interval * 1000);
            /* use timer if gpiolib doesn't provide debounce */
            if (error < 0)
                //如果对应的gpio chip没有实现debounce的实现,使用software debounce
                bdata->software_debounce =
                        button->debounce_interval;
        }

        if (button->irq) {
            bdata->irq = button->irq;
        } else {
            irq = gpiod_to_irq(bdata->gpiod);
            if (irq < 0) {
                error = irq;
                dev_err(dev,
                    "Unable to get irq number for GPIO %d, error %d\n",
                    button->gpio, error);
                return error;
            }
            bdata->irq = irq;
        }
        
        //初始化一个delayed work,作为终端的下半部
        INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
        
        //中断服务函数,中断上半部
        isr = gpio_keys_gpio_isr;
        //触发中断的电平条件,上升沿或者下降沿触发
        irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

        switch (button->wakeup_event_action) {
        case EV_ACT_ASSERTED:
            bdata->wakeup_trigger_type = active_low ?
                IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
            break;
        case EV_ACT_DEASSERTED:
            bdata->wakeup_trigger_type = active_low ?
                IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING;
            break;
        case EV_ACT_ANY:
        default:
            /*
             * For other cases, we are OK letting suspend/resume
             * not reconfigure the trigger type.
             */
            break;
        }
    } else {
        if (!button->irq) {
            dev_err(dev, "Found button without gpio or irq\n");
            return -EINVAL;
        }

        bdata->irq = button->irq;

        if (button->type && button->type != EV_KEY) {
            dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
            return -EINVAL;
        }

        bdata->release_delay = button->debounce_interval;
        timer_setup(&bdata->release_timer, gpio_keys_irq_timer, 0);

        isr = gpio_keys_irq_isr;
        irqflags = 0;

        /*
         * For IRQ buttons, there is no interrupt for release.
         * So we don't need to reconfigure the trigger type for wakeup.
         */
    }

    bdata->code = &ddata->keymap[idx];
    *bdata->code = button->code;
    //设置该输入设备的能力,支持上报的事件类型
    input_set_capability(input, button->type ?: EV_KEY, *bdata->code);

    /*
     * Install custom action to cancel release timer and
     * workqueue item.
     */
    //用做软件防抖?当中断下半部触发之后,如果在debounce time时间之内,gpio口电平有变化,会执行gpio_keys_quiesce_key把上报键值的work cancel掉
    error = devm_add_action(dev, gpio_keys_quiesce_key, bdata);
    if (error) {
        dev_err(dev, "failed to register quiesce action, error: %d\n",
            error);
        return error;
    }

    /*
     * If platform has specified that the button can be disabled,
     * we don't want it to share the interrupt line.
     */
    if (!button->can_disable)
        irqflags |= IRQF_SHARED;

    //申请中断,中断服务函数isr
    error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
                         desc, bdata);
    if (error < 0) {
        dev_err(dev, "Unable to claim irq %d; error %d\n",
            bdata->irq, error);
        return error;
    }

    return 0;
}

中断服务函数isr

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
    struct gpio_button_data *bdata = dev_id;

    BUG_ON(irq != bdata->irq);

    if (bdata->button->wakeup) {
        const struct gpio_keys_button *button = bdata->button;
        
        //保持系统awake状态
        pm_stay_awake(bdata->input->dev.parent);
        if (bdata->suspended  &&
            (button->type == 0 || button->type == EV_KEY)) {
            /*
             * Simulate wakeup key press in case the key has
             * already released by the time we got interrupt
             * handler to run.
             */
            input_report_key(bdata->input, button->code, 1);
        }
    }
    
    //在防抖时间software_debounce之后,调度执行delayed work,在work中上报input event
    mod_delayed_work(system_wq,
             &bdata->work,
             msecs_to_jiffies(bdata->software_debounce));

    return IRQ_HANDLED;
}

退出delayed work的函数

static void gpio_keys_quiesce_key(void *data)
{
    struct gpio_button_data *bdata = data;

    if (bdata->gpiod)
        cancel_delayed_work_sync(&bdata->work);
    else
        del_timer_sync(&bdata->release_timer);
}

四,按键测试

volumn up:

[     611.497258] /dev/input/event0: EV_KEY       KEY_VOLUMEUP         DOWN

[     611.497258] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 0

[     611.643337] /dev/input/event0: EV_KEY       KEY_VOLUMEUP         UP

[     611.643337] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 6

google key:

[     731.598789] /dev/input/event0: EV_KEY       KEY_SEARCH           DOWN

[     731.598789] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 0

[     731.779700] /dev/input/event0: EV_KEY       KEY_SEARCH           UP

[     731.779700] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 5

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android系统的ADC按键驱动主要涉及到驱动层和应用层两个方面。 驱动层: Android系统的ADC按键驱动主要是由Linux内核的Input子系统实现的。Input子系统是Linux内核中一个用于处理输入设备的子系统,主要功能是接收来自输入设备的数据,并将其传递到应用程序。 在Android系统中,Input子系统主要负责处理键盘、鼠标、触摸屏等输入设备的数据。对于ADC按键,一般都是通过GPIO口连接到处理器上,因此需要通过驱动程序来读取GPIO口的状态,以检测按键的按下和松开。 在Linux内核中,GPIO的读取是通过sysfs文件系统实现的。因此,驱动程序需要将GPIO口的状态映射到sysfs文件系统中的一个文件,以便应用程序能够读取GPIO口的状态。 应用层: 在Android系统中,应用程序一般使用Java语言编写。对于ADC按键,应用程序可以通过Android提供的KeyEvent类来处理按键事件。 当用户按下或松开ADC按键时,Linux内核会将这个事件传递给Android系统的Input子系统。Input子系统会将这个事件转换成一个KeyEvent对象,并将其发送给应用程序。 应用程序可以通过监听KeyEvent对象来处理按键事件。当用户按下或松开ADC按键时,应用程序可以根据不同的按键事件来执行不同的操作,例如打开应用程序、切换菜单、调节音量等。 综上所述,Android系统的ADC按键驱动主要是由Linux内核的Input子系统和应用层来实现的。在驱动层,需要通过驱动程序来读取GPIO口的状态,以检测按键的按下和松开;在应用层,需要通过监听KeyEvent对象来处理按键事件,以执行不同的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值