| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Driver for the PF1550 ONKEY | |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. | |
| * | |
| * Portions Copyright (c) 2025 Savoir-faire Linux Inc. | |
| * Samuel Kayode <samuel.kayode@savoirfairelinux.com> | |
| */ | |
| #include <linux/err.h> | |
| #include <linux/input.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/mfd/pf1550.h> | |
| #include <linux/platform_device.h> | |
| #define PF1550_ONKEY_IRQ_NR 6 | |
| struct onkey_drv_data { | |
| struct device *dev; | |
| const struct pf1550_ddata *pf1550; | |
| bool wakeup; | |
| struct input_dev *input; | |
| }; | |
| static irqreturn_t pf1550_onkey_irq_handler(int irq, void *data) | |
| { | |
| struct onkey_drv_data *onkey = data; | |
| struct platform_device *pdev = to_platform_device(onkey->dev); | |
| int i, state, irq_type = -1; | |
| for (i = 0; i < PF1550_ONKEY_IRQ_NR; i++) | |
| if (irq == platform_get_irq(pdev, i)) | |
| irq_type = i; | |
| switch (irq_type) { | |
| case PF1550_ONKEY_IRQ_PUSHI: | |
| state = 0; | |
| break; | |
| case PF1550_ONKEY_IRQ_1SI: | |
| case PF1550_ONKEY_IRQ_2SI: | |
| case PF1550_ONKEY_IRQ_3SI: | |
| case PF1550_ONKEY_IRQ_4SI: | |
| case PF1550_ONKEY_IRQ_8SI: | |
| state = 1; | |
| break; | |
| default: | |
| dev_err(onkey->dev, "onkey interrupt: irq %d occurred\n", | |
| irq_type); | |
| return IRQ_HANDLED; | |
| } | |
| input_event(onkey->input, EV_KEY, KEY_POWER, state); | |
| input_sync(onkey->input); | |
| return IRQ_HANDLED; | |
| } | |
| static int pf1550_onkey_probe(struct platform_device *pdev) | |
| { | |
| struct onkey_drv_data *onkey; | |
| struct input_dev *input; | |
| bool key_power = false; | |
| int i, irq, error; | |
| onkey = devm_kzalloc(&pdev->dev, sizeof(*onkey), GFP_KERNEL); | |
| if (!onkey) | |
| return -ENOMEM; | |
| onkey->dev = &pdev->dev; | |
| onkey->pf1550 = dev_get_drvdata(pdev->dev.parent); | |
| if (!onkey->pf1550->regmap) | |
| return dev_err_probe(&pdev->dev, -ENODEV, | |
| "failed to get regmap\n"); | |
| onkey->wakeup = device_property_read_bool(pdev->dev.parent, | |
| "wakeup-source"); | |
| if (device_property_read_bool(pdev->dev.parent, | |
| "nxp,disable-key-power")) { | |
| error = regmap_clear_bits(onkey->pf1550->regmap, | |
| PF1550_PMIC_REG_PWRCTRL1, | |
| PF1550_ONKEY_RST_EN); | |
| if (error) | |
| return dev_err_probe(&pdev->dev, error, | |
| "failed: disable turn system off"); | |
| } else { | |
| key_power = true; | |
| } | |
| input = devm_input_allocate_device(&pdev->dev); | |
| if (!input) | |
| return dev_err_probe(&pdev->dev, -ENOMEM, | |
| "failed to allocate the input device\n"); | |
| input->name = pdev->name; | |
| input->phys = "pf1550-onkey/input0"; | |
| input->id.bustype = BUS_HOST; | |
| if (key_power) | |
| input_set_capability(input, EV_KEY, KEY_POWER); | |
| onkey->input = input; | |
| platform_set_drvdata(pdev, onkey); | |
| for (i = 0; i < PF1550_ONKEY_IRQ_NR; i++) { | |
| irq = platform_get_irq(pdev, i); | |
| if (irq < 0) | |
| return irq; | |
| error = devm_request_threaded_irq(&pdev->dev, irq, NULL, | |
| pf1550_onkey_irq_handler, | |
| IRQF_NO_SUSPEND, | |
| "pf1550-onkey", onkey); | |
| if (error) | |
| return dev_err_probe(&pdev->dev, error, | |
| "failed: irq request (IRQ: %d)\n", | |
| i); | |
| } | |
| error = input_register_device(input); | |
| if (error) | |
| return dev_err_probe(&pdev->dev, error, | |
| "failed to register input device\n"); | |
| device_init_wakeup(&pdev->dev, onkey->wakeup); | |
| return 0; | |
| } | |
| static int pf1550_onkey_suspend(struct device *dev) | |
| { | |
| struct platform_device *pdev = to_platform_device(dev); | |
| struct onkey_drv_data *onkey = platform_get_drvdata(pdev); | |
| int i, irq; | |
| if (!device_may_wakeup(&pdev->dev)) | |
| regmap_write(onkey->pf1550->regmap, | |
| PF1550_PMIC_REG_ONKEY_INT_MASK0, | |
| ONKEY_IRQ_PUSHI | ONKEY_IRQ_1SI | ONKEY_IRQ_2SI | | |
| ONKEY_IRQ_3SI | ONKEY_IRQ_4SI | ONKEY_IRQ_8SI); | |
| else | |
| for (i = 0; i < PF1550_ONKEY_IRQ_NR; i++) { | |
| irq = platform_get_irq(pdev, i); | |
| if (irq > 0) | |
| enable_irq_wake(irq); | |
| } | |
| return 0; | |
| } | |
| static int pf1550_onkey_resume(struct device *dev) | |
| { | |
| struct platform_device *pdev = to_platform_device(dev); | |
| struct onkey_drv_data *onkey = platform_get_drvdata(pdev); | |
| int i, irq; | |
| if (!device_may_wakeup(&pdev->dev)) | |
| regmap_write(onkey->pf1550->regmap, | |
| PF1550_PMIC_REG_ONKEY_INT_MASK0, | |
| ~((u8)(ONKEY_IRQ_PUSHI | ONKEY_IRQ_1SI | | |
| ONKEY_IRQ_2SI | ONKEY_IRQ_3SI | ONKEY_IRQ_4SI | | |
| ONKEY_IRQ_8SI))); | |
| else | |
| for (i = 0; i < PF1550_ONKEY_IRQ_NR; i++) { | |
| irq = platform_get_irq(pdev, i); | |
| if (irq > 0) | |
| disable_irq_wake(irq); | |
| } | |
| return 0; | |
| } | |
| static DEFINE_SIMPLE_DEV_PM_OPS(pf1550_onkey_pm_ops, pf1550_onkey_suspend, | |
| pf1550_onkey_resume); | |
| static const struct platform_device_id pf1550_onkey_id[] = { | |
| { "pf1550-onkey", }, | |
| { /* sentinel */ } | |
| }; | |
| MODULE_DEVICE_TABLE(platform, pf1550_onkey_id); | |
| static struct platform_driver pf1550_onkey_driver = { | |
| .driver = { | |
| .name = "pf1550-onkey", | |
| .pm = pm_sleep_ptr(&pf1550_onkey_pm_ops), | |
| }, | |
| .probe = pf1550_onkey_probe, | |
| .id_table = pf1550_onkey_id, | |
| }; | |
| module_platform_driver(pf1550_onkey_driver); | |
| MODULE_AUTHOR("Freescale Semiconductor"); | |
| MODULE_DESCRIPTION("PF1550 onkey Driver"); | |
| MODULE_LICENSE("GPL"); |