| // SPDX-License-Identifier: GPL-2.0 | |
| // Copyright (C) STMicroelectronics 2018 | |
| // Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics. | |
| #include <linux/input.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/mfd/stpmic1.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/property.h> | |
| #include <linux/regmap.h> | |
| /** | |
| * struct stpmic1_onkey - OnKey data | |
| * @input_dev: pointer to input device | |
| * @irq_falling: irq that we are hooked on to | |
| * @irq_rising: irq that we are hooked on to | |
| */ | |
| struct stpmic1_onkey { | |
| struct input_dev *input_dev; | |
| int irq_falling; | |
| int irq_rising; | |
| }; | |
| static irqreturn_t onkey_falling_irq(int irq, void *ponkey) | |
| { | |
| struct stpmic1_onkey *onkey = ponkey; | |
| struct input_dev *input_dev = onkey->input_dev; | |
| input_report_key(input_dev, KEY_POWER, 1); | |
| pm_wakeup_event(input_dev->dev.parent, 0); | |
| input_sync(input_dev); | |
| return IRQ_HANDLED; | |
| } | |
| static irqreturn_t onkey_rising_irq(int irq, void *ponkey) | |
| { | |
| struct stpmic1_onkey *onkey = ponkey; | |
| struct input_dev *input_dev = onkey->input_dev; | |
| input_report_key(input_dev, KEY_POWER, 0); | |
| pm_wakeup_event(input_dev->dev.parent, 0); | |
| input_sync(input_dev); | |
| return IRQ_HANDLED; | |
| } | |
| static int stpmic1_onkey_probe(struct platform_device *pdev) | |
| { | |
| struct stpmic1 *pmic = dev_get_drvdata(pdev->dev.parent); | |
| struct device *dev = &pdev->dev; | |
| struct input_dev *input_dev; | |
| struct stpmic1_onkey *onkey; | |
| unsigned int val, reg = 0; | |
| int error; | |
| onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL); | |
| if (!onkey) | |
| return -ENOMEM; | |
| onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling"); | |
| if (onkey->irq_falling < 0) | |
| return onkey->irq_falling; | |
| onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising"); | |
| if (onkey->irq_rising < 0) | |
| return onkey->irq_rising; | |
| if (!device_property_read_u32(dev, "power-off-time-sec", &val)) { | |
| if (val > 0 && val <= 16) { | |
| dev_dbg(dev, "power-off-time=%d seconds\n", val); | |
| reg |= PONKEY_PWR_OFF; | |
| reg |= ((16 - val) & PONKEY_TURNOFF_TIMER_MASK); | |
| } else { | |
| dev_err(dev, "power-off-time-sec out of range\n"); | |
| return -EINVAL; | |
| } | |
| } | |
| if (device_property_present(dev, "st,onkey-clear-cc-flag")) | |
| reg |= PONKEY_CC_FLAG_CLEAR; | |
| error = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR, | |
| PONKEY_TURNOFF_MASK, reg); | |
| if (error) { | |
| dev_err(dev, "PKEY_TURNOFF_CR write failed: %d\n", error); | |
| return error; | |
| } | |
| if (device_property_present(dev, "st,onkey-pu-inactive")) { | |
| error = regmap_update_bits(pmic->regmap, PADS_PULL_CR, | |
| PONKEY_PU_INACTIVE, | |
| PONKEY_PU_INACTIVE); | |
| if (error) { | |
| dev_err(dev, "ONKEY Pads configuration failed: %d\n", | |
| error); | |
| return error; | |
| } | |
| } | |
| input_dev = devm_input_allocate_device(dev); | |
| if (!input_dev) { | |
| dev_err(dev, "Can't allocate Pwr Onkey Input Device\n"); | |
| return -ENOMEM; | |
| } | |
| input_dev->name = "pmic_onkey"; | |
| input_dev->phys = "pmic_onkey/input0"; | |
| input_set_capability(input_dev, EV_KEY, KEY_POWER); | |
| onkey->input_dev = input_dev; | |
| /* interrupt is nested in a thread */ | |
| error = devm_request_threaded_irq(dev, onkey->irq_falling, NULL, | |
| onkey_falling_irq, IRQF_ONESHOT, | |
| dev_name(dev), onkey); | |
| if (error) { | |
| dev_err(dev, "Can't get IRQ Onkey Falling: %d\n", error); | |
| return error; | |
| } | |
| error = devm_request_threaded_irq(dev, onkey->irq_rising, NULL, | |
| onkey_rising_irq, IRQF_ONESHOT, | |
| dev_name(dev), onkey); | |
| if (error) { | |
| dev_err(dev, "Can't get IRQ Onkey Rising: %d\n", error); | |
| return error; | |
| } | |
| error = input_register_device(input_dev); | |
| if (error) { | |
| dev_err(dev, "Can't register power button: %d\n", error); | |
| return error; | |
| } | |
| platform_set_drvdata(pdev, onkey); | |
| device_init_wakeup(dev, true); | |
| return 0; | |
| } | |
| static int stpmic1_onkey_suspend(struct device *dev) | |
| { | |
| struct platform_device *pdev = to_platform_device(dev); | |
| struct stpmic1_onkey *onkey = platform_get_drvdata(pdev); | |
| if (device_may_wakeup(dev)) { | |
| enable_irq_wake(onkey->irq_falling); | |
| enable_irq_wake(onkey->irq_rising); | |
| } | |
| return 0; | |
| } | |
| static int stpmic1_onkey_resume(struct device *dev) | |
| { | |
| struct platform_device *pdev = to_platform_device(dev); | |
| struct stpmic1_onkey *onkey = platform_get_drvdata(pdev); | |
| if (device_may_wakeup(dev)) { | |
| disable_irq_wake(onkey->irq_falling); | |
| disable_irq_wake(onkey->irq_rising); | |
| } | |
| return 0; | |
| } | |
| static DEFINE_SIMPLE_DEV_PM_OPS(stpmic1_onkey_pm, | |
| stpmic1_onkey_suspend, | |
| stpmic1_onkey_resume); | |
| static const struct of_device_id of_stpmic1_onkey_match[] = { | |
| { .compatible = "st,stpmic1-onkey" }, | |
| { }, | |
| }; | |
| MODULE_DEVICE_TABLE(of, of_stpmic1_onkey_match); | |
| static struct platform_driver stpmic1_onkey_driver = { | |
| .probe = stpmic1_onkey_probe, | |
| .driver = { | |
| .name = "stpmic1_onkey", | |
| .of_match_table = of_match_ptr(of_stpmic1_onkey_match), | |
| .pm = pm_sleep_ptr(&stpmic1_onkey_pm), | |
| }, | |
| }; | |
| module_platform_driver(stpmic1_onkey_driver); | |
| MODULE_DESCRIPTION("Onkey driver for STPMIC1"); | |
| MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>"); | |
| MODULE_LICENSE("GPL v2"); |