From 7c0d40384b0648030d5202114d90239b8db7d4e0 Mon Sep 17 00:00:00 2001 From: Phil Elwell Date: Wed, 2 Aug 2023 11:30:48 +0100 Subject: [PATCH] Input: Add raspberrypi-button firmware driver Raspberry Pi 5s have a power/suspend button that is only accessible to the firmware. Add a driver to read it and generate key events. Signed-off-by: Phil Elwell --- drivers/input/misc/Kconfig | 10 ++ drivers/input/misc/Makefile | 1 + drivers/input/misc/raspberrypi-button.c | 138 +++++++++++++++++++++ include/soc/bcm2835/raspberrypi-firmware.h | 1 + 4 files changed, 150 insertions(+) create mode 100644 drivers/input/misc/raspberrypi-button.c --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -918,6 +918,16 @@ config INPUT_RT5120_PWRKEY To compile this driver as a module, choose M here. the module will be called rt5120-pwrkey. +config INPUT_RASPBERRYPI_BUTTON + tristate "Raspberry Pi button support" + depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) + help + This enables support for firmware-controlled buttons on Raspberry + Pi devices. + + To compile this driver as a module, choose M here. the module will + be called raspberrypi-button. + config INPUT_STPMIC1_ONKEY tristate "STPMIC1 PMIC Onkey support" depends on MFD_STPMIC1 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o +obj-$(CONFIG_INPUT_RASPBERRYPI_BUTTON) += raspberrypi-button.o obj-$(CONFIG_INPUT_RT5120_PWRKEY) += rt5120-pwrkey.o obj-$(CONFIG_INPUT_AXP20X_PEK) += axp20x-pek.o obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o --- /dev/null +++ b/drivers/input/misc/raspberrypi-button.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Driver for Raspberry Pi power button + * + * Copyright (C) 2023 Raspberry Pi Ltd. + * + * This driver is based on drivers/hwmon/raspberrypi-hwmon.c and + * input/misc/pm8941-pwrkey.c/ - see original files for copyright information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct rpi_button { + struct device *dev; + struct rpi_firmware *fw; + struct input_dev *input; + struct delayed_work poll_work; + unsigned long poll_rate; + const char *name; + u32 id; + u32 code; +}; + +static void button_poll(struct work_struct *work) +{ + struct rpi_button *button; + u32 value; + int err; + + button = container_of(work, struct rpi_button, + poll_work.work); + + value = BIT(button->id); + err = rpi_firmware_property(button->fw, RPI_FIRMWARE_GET_BUTTONS_PRESSED, + &value, sizeof(value)); + if (err) { + dev_err_once(button->dev, "GET_BUTTON_PRESSED not implemented?\n"); + return; + } + + if (value & BIT(button->id)) { + input_event(button->input, EV_KEY, button->code, 1); + input_sync(button->input); + input_event(button->input, EV_KEY, button->code, 0); + input_sync(button->input); + } + + schedule_delayed_work(&button->poll_work, button->poll_rate); +} + +static int rpi_button_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_button *button; + int err; + + button = devm_kzalloc(dev, sizeof(*button), GFP_KERNEL); + if (!button) + return -ENOMEM; + + button->dev = dev; + + /* Get the firmware pointer from our parent */ + button->fw = dev_get_drvdata(dev->parent); + + if (device_property_read_u32(dev, "id", &button->id)) + button->id = RASPBERRYPI_BUTTON_POWER; + + if (device_property_read_string(dev, "label", &button->name)) + button->name = "raspberrypi-button"; + + if (device_property_read_u32(dev, "linux,code", &button->code)) { + dev_err(&pdev->dev, "no linux,code property\n"); + return -EINVAL; + } + + button->input = devm_input_allocate_device(dev); + if (!button->input) { + dev_dbg(&pdev->dev, "unable to allocate input device\n"); + return -ENOMEM; + } + + input_set_capability(button->input, EV_KEY, button->code); + + button->input->name = button->name; + button->input->phys = "raspberrypi-button/input0"; + button->input->dev.parent = dev; + button->poll_rate = HZ; + + err = input_register_device(button->input); + if (err) { + dev_err(&pdev->dev, "failed to register input device: %d\n", + err); + return err; + } + + err = devm_delayed_work_autocancel(dev, &button->poll_work, + button_poll); + if (err) + return err; + + platform_set_drvdata(pdev, button); + schedule_delayed_work(&button->poll_work, button->poll_rate); + + return 0; +} + +static const struct of_device_id rpi_button_match[] = { + { .compatible = "raspberrypi,firmware-button", }, + { } +}; +MODULE_DEVICE_TABLE(of, rpi_button_match); + +static struct platform_driver rpi_button_driver = { + .probe = rpi_button_probe, + .driver = { + .name = "raspberrypi-button", + .of_match_table = of_match_ptr(rpi_button_match), + }, +}; +module_platform_driver(rpi_button_driver); + +MODULE_AUTHOR("Phil Elwell "); +MODULE_DESCRIPTION("Raspberry Pi button driver"); +MODULE_LICENSE("Dual BSD/GPL"); --- a/include/soc/bcm2835/raspberrypi-firmware.h +++ b/include/soc/bcm2835/raspberrypi-firmware.h @@ -98,6 +98,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_REBOOT_FLAGS = 0x00030064, RPI_FIRMWARE_SET_REBOOT_FLAGS = 0x00038064, RPI_FIRMWARE_NOTIFY_DISPLAY_DONE = 0x00030066, + RPI_FIRMWARE_GET_BUTTONS_PRESSED = 0x00030088, /* Dispmanx TAGS */ RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001,