| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright(C) 2025 Svyatoslav Ryhel <clamor95@gmail.com> | |
| */ | |
| #include <stdlib.h> | |
| #include <dm.h> | |
| #include <input.h> | |
| #include <keyboard.h> | |
| #include <power/pmic.h> | |
| #include <power/cpcap.h> | |
| #include <linux/delay.h> | |
| #include <linux/err.h> | |
| #include <linux/input.h> | |
| static const unsigned int cpcpap_to_reg[] = { | |
| CPCAP_REG_INT1, | |
| CPCAP_REG_INT2, | |
| CPCAP_REG_INT3, | |
| CPCAP_REG_INT4, | |
| }; | |
| /** | |
| * struct cpcap_pwrbutton_priv | |
| * | |
| * @bank: id of interrupt bank co-responding to an IRQ register | |
| * @id: id of interrupt pin co-responding to the bit in IRQ register | |
| * @keycode: linux key code | |
| * @old_state: holder of last button state | |
| * @skip: holder of keycode skip state. This is required since both pressing | |
| * and releasing generate same event and cause key send duplication. | |
| */ | |
| struct cpcap_pwrbutton_priv { | |
| u32 bank; | |
| u32 id; | |
| u32 keycode; | |
| bool old_state; | |
| bool skip; | |
| }; | |
| static int cpcap_pwrbutton_read_keys(struct input_config *input) | |
| { | |
| struct udevice *dev = input->dev; | |
| struct cpcap_pwrbutton_priv *priv = dev_get_priv(dev); | |
| u32 value, state_changed; | |
| bool state; | |
| value = pmic_reg_read(dev->parent, cpcpap_to_reg[priv->bank]) & | |
| BIT(priv->id); | |
| /* Interrupt bit is cleared by writing it to interrupt reg */ | |
| pmic_reg_write(dev->parent, cpcpap_to_reg[priv->bank], BIT(priv->id)); | |
| state = value >> priv->id; | |
| state_changed = state != priv->old_state; | |
| if (state_changed && !priv->skip) { | |
| priv->old_state = state; | |
| input_add_keycode(input, priv->keycode, state); | |
| } | |
| if (state) | |
| priv->skip = !priv->skip; | |
| return 0; | |
| } | |
| static int cpcap_pwrbutton_of_to_plat(struct udevice *dev) | |
| { | |
| struct cpcap_pwrbutton_priv *priv = dev_get_priv(dev); | |
| ofnode irq_parent; | |
| u32 irq_desc; | |
| int ret; | |
| /* Check interrupt parent, driver supports only CPCAP as parent */ | |
| irq_parent = ofnode_parse_phandle(dev_ofnode(dev), "interrupt-parent", 0); | |
| if (!strstr(ofnode_get_name(irq_parent), "cpcap")) | |
| return -EINVAL; | |
| ret = dev_read_u32(dev, "interrupts", &irq_desc); | |
| if (ret) | |
| return ret; | |
| /* IRQ registers are 16 bit wide */ | |
| priv->bank = irq_desc / 16; | |
| priv->id = irq_desc % 16; | |
| priv->keycode = dev_read_u32_default(dev, "linux,code", KEY_POWER); | |
| priv->old_state = false; | |
| priv->skip = false; | |
| return 0; | |
| } | |
| static int cpcap_pwrbutton_probe(struct udevice *dev) | |
| { | |
| struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev); | |
| struct stdio_dev *sdev = &uc_priv->sdev; | |
| struct input_config *input = &uc_priv->input; | |
| int ret; | |
| input_init(input, false); | |
| input_add_tables(input, false); | |
| /* Register the device */ | |
| input->dev = dev; | |
| input->read_keys = cpcap_pwrbutton_read_keys; | |
| strcpy(sdev->name, "cpcap-pwrbutton"); | |
| ret = input_stdio_register(sdev); | |
| if (ret) { | |
| log_debug("%s: input_stdio_register() failed\n", __func__); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static const struct udevice_id cpcap_pwrbutton_ids[] = { | |
| { .compatible = "motorola,cpcap-pwrbutton" }, | |
| { } | |
| }; | |
| U_BOOT_DRIVER(cpcap_pwrbutton) = { | |
| .name = "cpcap_pwrbutton", | |
| .id = UCLASS_KEYBOARD, | |
| .of_match = cpcap_pwrbutton_ids, | |
| .of_to_plat = cpcap_pwrbutton_of_to_plat, | |
| .probe = cpcap_pwrbutton_probe, | |
| .priv_auto = sizeof(struct cpcap_pwrbutton_priv), | |
| }; |