/* * SYSFS interface for enabling/disabling keys and switches: * * There are 4 attributes under /sys/devices/platform/gpio-keys/ * keys [ro] - bitmap of keys (EV_KEY) which can be * disabled * switches [ro] - bitmap of switches (EV_SW) which can be * disabled * disabled_keys [rw] - bitmap of keys currently disabled * disabled_switches [rw] - bitmap of switches currently disabled * * Userland can change these values and hence disable event generation * for each key (or switch). Disabling a key means its interrupt line * is disabled. * * For example, if we have following switches set up as gpio-keys: * SW_DOCK = 5 * SW_CAMERA_LENS_COVER = 9 * SW_KEYPAD_SLIDE = 10 * SW_FRONT_PROXIMITY = 11 * This is read from switches: * 11-9,5 * Next we want to disable proximity (11) and dock (5), we write: * 11,5 * to file disabled_switches. Now proximity and dock IRQs are disabled. * This can be verified by reading the file disabled_switches: * 11,5 * If we now want to enable proximity (11) switch we write: * 5 * to disabled_switches. * * We can disable only those keys which don't allow sharing the irq.
*/
/** * get_n_events_by_type() - returns maximum number of events per @type * @type: type of button (%EV_KEY, %EV_SW) * * Return value of this function can be used to allocate bitmap * large enough to hold all bits for given type.
*/ staticint get_n_events_by_type(int type)
{
BUG_ON(type != EV_SW && type != EV_KEY);
return (type == EV_KEY) ? KEY_CNT : SW_CNT;
}
/** * get_bm_events_by_type() - returns bitmap of supported events per @type * @dev: input device from which bitmap is retrieved * @type: type of button (%EV_KEY, %EV_SW) * * Return value of this function can be used to allocate bitmap * large enough to hold all bits for given type.
*/ staticconstunsignedlong *get_bm_events_by_type(struct input_dev *dev, int type)
{
BUG_ON(type != EV_SW && type != EV_KEY);
if (!bdata->gpiod)
hrtimer_cancel(&bdata->release_timer); elseif (bdata->debounce_use_hrtimer)
hrtimer_cancel(&bdata->debounce_timer); else
cancel_delayed_work_sync(&bdata->work);
}
/** * gpio_keys_disable_button() - disables given GPIO button * @bdata: button data for button to be disabled * * Disables button pointed by @bdata. This is done by masking * IRQ line. After this function is called, button won't generate * input events anymore. Note that one can only disable buttons * that don't share IRQs. * * Make sure that @bdata->disable_lock is locked when entering * this function to avoid races when concurrent threads are * disabling buttons at the same time.
*/ staticvoid gpio_keys_disable_button(struct gpio_button_data *bdata)
{ if (!bdata->disabled) { /* * Disable IRQ and associated timer/work structure.
*/
disable_irq(bdata->irq);
gpio_keys_quiesce_key(bdata);
bdata->disabled = true;
}
}
/** * gpio_keys_enable_button() - enables given GPIO button * @bdata: button data for button to be disabled * * Enables given button pointed by @bdata. * * Make sure that @bdata->disable_lock is locked when entering * this function to avoid races with concurrent threads trying * to enable the same button at the same time.
*/ staticvoid gpio_keys_enable_button(struct gpio_button_data *bdata)
{ if (bdata->disabled) {
enable_irq(bdata->irq);
bdata->disabled = false;
}
}
/** * gpio_keys_attr_show_helper() - fill in stringified bitmap of buttons * @ddata: pointer to drvdata * @buf: buffer where stringified bitmap is written * @type: button type (%EV_KEY, %EV_SW) * @only_disabled: does caller want only those buttons that are * currently disabled or all buttons that can be * disabled * * This function writes buttons that can be disabled to @buf. If * @only_disabled is true, then @buf contains only those buttons * that are currently disabled. Returns 0 on success or negative * errno on failure.
*/ static ssize_t gpio_keys_attr_show_helper(struct gpio_keys_drvdata *ddata, char *buf, unsignedint type, bool only_disabled)
{ int n_events = get_n_events_by_type(type); unsignedlong *bits;
ssize_t ret; int i;
bits = bitmap_zalloc(n_events, GFP_KERNEL); if (!bits) return -ENOMEM;
for (i = 0; i < ddata->pdata->nbuttons; i++) { struct gpio_button_data *bdata = &ddata->data[i];
/** * gpio_keys_attr_store_helper() - enable/disable buttons based on given bitmap * @ddata: pointer to drvdata * @buf: buffer from userspace that contains stringified bitmap * @type: button type (%EV_KEY, %EV_SW) * * This function parses stringified bitmap from @buf and disables/enables * GPIO buttons accordingly. Returns 0 on success and negative error * on failure.
*/ static ssize_t gpio_keys_attr_store_helper(struct gpio_keys_drvdata *ddata, constchar *buf, unsignedint type)
{ int n_events = get_n_events_by_type(type); constunsignedlong *bitmap = get_bm_events_by_type(ddata->input, type);
ssize_t error; int i;
unsignedlong *bits __free(bitmap) = bitmap_alloc(n_events, GFP_KERNEL); if (!bits) return -ENOMEM;
error = bitmap_parselist(buf, bits, n_events); if (error) return error;
/* First validate */ if (!bitmap_subset(bits, bitmap, n_events)) return -EINVAL;
for (i = 0; i < ddata->pdata->nbuttons; i++) { struct gpio_button_data *bdata = &ddata->data[i];
if (bdata->button->type != type) continue;
if (test_bit(*bdata->code, bits) &&
!bdata->button->can_disable) { return -EINVAL;
}
}
guard(mutex)(&ddata->disable_lock);
for (i = 0; i < ddata->pdata->nbuttons; i++) { struct gpio_button_data *bdata = &ddata->data[i];
if (bdata->button->type != type) continue;
if (test_bit(*bdata->code, bits))
gpio_keys_disable_button(bdata); else
gpio_keys_enable_button(bdata);
}
if (bdata->button->wakeup) { conststruct gpio_keys_button *button = bdata->button;
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);
}
}
if (child) {
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) return dev_err_probe(dev, error, "failed to get gpio\n");
/* * GPIO is optional, we may be dealing with * purely interrupt-driven setup.
*/
bdata->gpiod = NULL;
}
} elseif (gpio_is_valid(button->gpio)) { /* * Legacy GPIO number, so request the GPIO here and * convert it to descriptor.
*/
error = devm_gpio_request_one(dev, button->gpio, GPIOF_IN, desc); if (error < 0) {
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error); return error;
}
bdata->gpiod = gpio_to_desc(button->gpio); if (!bdata->gpiod) return -EINVAL;
if (button->active_low ^ gpiod_is_active_low(bdata->gpiod))
gpiod_toggle_active_low(bdata->gpiod);
}
if (bdata->gpiod) { 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)
bdata->software_debounce =
button->debounce_interval;
/* * If reading the GPIO won't sleep, we can use a * hrtimer instead of a standard timer for the software * debounce, to reduce the latency as much as possible.
*/
bdata->debounce_use_hrtimer =
!gpiod_cansleep(bdata->gpiod);
}
/* * If an interrupt was specified, use it instead of the gpio * interrupt and use the gpio for reading the state. A separate * interrupt may be used as the main button interrupt for * runtime PM to detect events also in deeper idle states. If a * dedicated wakeirq is used for system suspend only, see below * for bdata->wakeirq setup.
*/ if (button->irq) {
bdata->irq = button->irq;
} else {
irq = gpiod_to_irq(bdata->gpiod); if (irq < 0) {
error = irq;
dev_err_probe(dev, error, "Unable to get irq number for GPIO %d\n",
button->gpio); return error;
}
bdata->irq = irq;
}
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;
}
/* * Install custom action to cancel release timer and * workqueue item.
*/
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;
/* Use :wakeup suffix like drivers/base/power/wakeirq.c does */
wakedesc = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", desc); if (!wakedesc) return -ENOMEM;
/* * Wakeirq shares the handler with the main interrupt, it's only * active during system suspend. See gpio_keys_button_enable_wakeup() * and gpio_keys_button_disable_wakeup().
*/
error = devm_request_any_context_irq(dev, bdata->wakeirq, isr,
irqflags, wakedesc, bdata); if (error < 0) {
dev_err(dev, "Unable to claim wakeirq %d; error %d\n",
bdata->irq, error); return error;
}
/* * Disable wakeirq until suspend. IRQF_NO_AUTOEN won't work if * IRQF_SHARED was set based on !button->can_disable.
*/
disable_irq(bdata->wakeirq);
for (i = 0; i < ddata->pdata->nbuttons; i++) { struct gpio_button_data *bdata = &ddata->data[i]; if (bdata->gpiod)
gpio_keys_gpio_report_event(bdata);
}
input_sync(input);
}
/* Enable auto repeat feature of Linux input subsystem */ if (pdata->rep)
__set_bit(EV_REP, input->evbit);
for (i = 0; i < pdata->nbuttons; i++) { conststruct gpio_keys_button *button = &pdata->buttons[i];
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;
}
staticint __maybe_unused
gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata)
{ int error;
error = enable_irq_wake(bdata->irq); if (error) {
dev_err(bdata->input->dev.parent, "failed to configure IRQ %d as wakeup source: %d\n",
bdata->irq, error); return error;
}
if (bdata->wakeup_trigger_type) {
error = irq_set_irq_type(bdata->irq,
bdata->wakeup_trigger_type); if (error) {
dev_err(bdata->input->dev.parent, "failed to set wakeup trigger %08x for IRQ %d: %d\n",
bdata->wakeup_trigger_type, bdata->irq, error);
disable_irq_wake(bdata->irq); return error;
}
}
if (bdata->wakeirq) {
enable_irq(bdata->wakeirq);
disable_irq(bdata->irq);
}
return 0;
}
staticvoid __maybe_unused
gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata)
{ int error;
if (bdata->wakeirq) {
enable_irq(bdata->irq);
disable_irq(bdata->wakeirq);
}
/* * The trigger type is always both edges for gpio-based keys and we do * not support changing wakeup trigger for interrupt-based keys.
*/ if (bdata->wakeup_trigger_type) {
error = irq_set_irq_type(bdata->irq, IRQ_TYPE_EDGE_BOTH); if (error)
dev_warn(bdata->input->dev.parent, "failed to restore interrupt trigger for IRQ %d: %d\n",
bdata->irq, error);
}
error = disable_irq_wake(bdata->irq); if (error)
dev_warn(bdata->input->dev.parent, "failed to disable IRQ %d as wake source: %d\n",
bdata->irq, error);
}
staticint __maybe_unused
gpio_keys_enable_wakeup(struct gpio_keys_drvdata *ddata)
{ struct gpio_button_data *bdata; int error; int i;
for (i = 0; i < ddata->pdata->nbuttons; i++) {
bdata = &ddata->data[i]; if (bdata->button->wakeup) {
error = gpio_keys_button_enable_wakeup(bdata); if (error) goto err_out;
}
bdata->suspended = true;
}
return 0;
err_out: while (i--) {
bdata = &ddata->data[i]; if (bdata->button->wakeup)
gpio_keys_button_disable_wakeup(bdata);
bdata->suspended = false;
}
for (i = 0; i < ddata->pdata->nbuttons; i++) {
bdata = &ddata->data[i];
bdata->suspended = false; if (irqd_is_wakeup_set(irq_get_irq_data(bdata->irq)))
gpio_keys_button_disable_wakeup(bdata);
}
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.16Bemerkung:
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.