18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci// Copyright (C) STMicroelectronics 2018
38c2ecf20Sopenharmony_ci// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include <linux/input.h>
68c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
78c2ecf20Sopenharmony_ci#include <linux/mfd/stpmic1.h>
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/of.h>
108c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
118c2ecf20Sopenharmony_ci#include <linux/property.h>
128c2ecf20Sopenharmony_ci#include <linux/regmap.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci/**
158c2ecf20Sopenharmony_ci * struct stpmic1_onkey - OnKey data
168c2ecf20Sopenharmony_ci * @input_dev:		pointer to input device
178c2ecf20Sopenharmony_ci * @irq_falling:	irq that we are hooked on to
188c2ecf20Sopenharmony_ci * @irq_rising:		irq that we are hooked on to
198c2ecf20Sopenharmony_ci */
208c2ecf20Sopenharmony_cistruct stpmic1_onkey {
218c2ecf20Sopenharmony_ci	struct input_dev *input_dev;
228c2ecf20Sopenharmony_ci	int irq_falling;
238c2ecf20Sopenharmony_ci	int irq_rising;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic irqreturn_t onkey_falling_irq(int irq, void *ponkey)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	struct stpmic1_onkey *onkey = ponkey;
298c2ecf20Sopenharmony_ci	struct input_dev *input_dev = onkey->input_dev;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	input_report_key(input_dev, KEY_POWER, 1);
328c2ecf20Sopenharmony_ci	pm_wakeup_event(input_dev->dev.parent, 0);
338c2ecf20Sopenharmony_ci	input_sync(input_dev);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic irqreturn_t onkey_rising_irq(int irq, void *ponkey)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct stpmic1_onkey *onkey = ponkey;
418c2ecf20Sopenharmony_ci	struct input_dev *input_dev = onkey->input_dev;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	input_report_key(input_dev, KEY_POWER, 0);
448c2ecf20Sopenharmony_ci	pm_wakeup_event(input_dev->dev.parent, 0);
458c2ecf20Sopenharmony_ci	input_sync(input_dev);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic int stpmic1_onkey_probe(struct platform_device *pdev)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	struct stpmic1 *pmic = dev_get_drvdata(pdev->dev.parent);
538c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
548c2ecf20Sopenharmony_ci	struct input_dev *input_dev;
558c2ecf20Sopenharmony_ci	struct stpmic1_onkey *onkey;
568c2ecf20Sopenharmony_ci	unsigned int val, reg = 0;
578c2ecf20Sopenharmony_ci	int error;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
608c2ecf20Sopenharmony_ci	if (!onkey)
618c2ecf20Sopenharmony_ci		return -ENOMEM;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling");
648c2ecf20Sopenharmony_ci	if (onkey->irq_falling < 0)
658c2ecf20Sopenharmony_ci		return onkey->irq_falling;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising");
688c2ecf20Sopenharmony_ci	if (onkey->irq_rising < 0)
698c2ecf20Sopenharmony_ci		return onkey->irq_rising;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (!device_property_read_u32(dev, "power-off-time-sec", &val)) {
728c2ecf20Sopenharmony_ci		if (val > 0 && val <= 16) {
738c2ecf20Sopenharmony_ci			dev_dbg(dev, "power-off-time=%d seconds\n", val);
748c2ecf20Sopenharmony_ci			reg |= PONKEY_PWR_OFF;
758c2ecf20Sopenharmony_ci			reg |= ((16 - val) & PONKEY_TURNOFF_TIMER_MASK);
768c2ecf20Sopenharmony_ci		} else {
778c2ecf20Sopenharmony_ci			dev_err(dev, "power-off-time-sec out of range\n");
788c2ecf20Sopenharmony_ci			return -EINVAL;
798c2ecf20Sopenharmony_ci		}
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	if (device_property_present(dev, "st,onkey-clear-cc-flag"))
838c2ecf20Sopenharmony_ci		reg |= PONKEY_CC_FLAG_CLEAR;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	error = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR,
868c2ecf20Sopenharmony_ci				   PONKEY_TURNOFF_MASK, reg);
878c2ecf20Sopenharmony_ci	if (error) {
888c2ecf20Sopenharmony_ci		dev_err(dev, "PKEY_TURNOFF_CR write failed: %d\n", error);
898c2ecf20Sopenharmony_ci		return error;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	if (device_property_present(dev, "st,onkey-pu-inactive")) {
938c2ecf20Sopenharmony_ci		error = regmap_update_bits(pmic->regmap, PADS_PULL_CR,
948c2ecf20Sopenharmony_ci					   PONKEY_PU_INACTIVE,
958c2ecf20Sopenharmony_ci					   PONKEY_PU_INACTIVE);
968c2ecf20Sopenharmony_ci		if (error) {
978c2ecf20Sopenharmony_ci			dev_err(dev, "ONKEY Pads configuration failed: %d\n",
988c2ecf20Sopenharmony_ci				error);
998c2ecf20Sopenharmony_ci			return error;
1008c2ecf20Sopenharmony_ci		}
1018c2ecf20Sopenharmony_ci	}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	input_dev = devm_input_allocate_device(dev);
1048c2ecf20Sopenharmony_ci	if (!input_dev) {
1058c2ecf20Sopenharmony_ci		dev_err(dev, "Can't allocate Pwr Onkey Input Device\n");
1068c2ecf20Sopenharmony_ci		return -ENOMEM;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	input_dev->name = "pmic_onkey";
1108c2ecf20Sopenharmony_ci	input_dev->phys = "pmic_onkey/input0";
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	input_set_capability(input_dev, EV_KEY, KEY_POWER);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	onkey->input_dev = input_dev;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/* interrupt is nested in a thread */
1178c2ecf20Sopenharmony_ci	error = devm_request_threaded_irq(dev, onkey->irq_falling, NULL,
1188c2ecf20Sopenharmony_ci					  onkey_falling_irq, IRQF_ONESHOT,
1198c2ecf20Sopenharmony_ci					  dev_name(dev), onkey);
1208c2ecf20Sopenharmony_ci	if (error) {
1218c2ecf20Sopenharmony_ci		dev_err(dev, "Can't get IRQ Onkey Falling: %d\n", error);
1228c2ecf20Sopenharmony_ci		return error;
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	error = devm_request_threaded_irq(dev, onkey->irq_rising, NULL,
1268c2ecf20Sopenharmony_ci					  onkey_rising_irq, IRQF_ONESHOT,
1278c2ecf20Sopenharmony_ci					  dev_name(dev), onkey);
1288c2ecf20Sopenharmony_ci	if (error) {
1298c2ecf20Sopenharmony_ci		dev_err(dev, "Can't get IRQ Onkey Rising: %d\n", error);
1308c2ecf20Sopenharmony_ci		return error;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	error = input_register_device(input_dev);
1348c2ecf20Sopenharmony_ci	if (error) {
1358c2ecf20Sopenharmony_ci		dev_err(dev, "Can't register power button: %d\n", error);
1368c2ecf20Sopenharmony_ci		return error;
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, onkey);
1408c2ecf20Sopenharmony_ci	device_init_wakeup(dev, true);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return 0;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int __maybe_unused stpmic1_onkey_suspend(struct device *dev)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
1488c2ecf20Sopenharmony_ci	struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev)) {
1518c2ecf20Sopenharmony_ci		enable_irq_wake(onkey->irq_falling);
1528c2ecf20Sopenharmony_ci		enable_irq_wake(onkey->irq_rising);
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci	return 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic int __maybe_unused stpmic1_onkey_resume(struct device *dev)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
1608c2ecf20Sopenharmony_ci	struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev)) {
1638c2ecf20Sopenharmony_ci		disable_irq_wake(onkey->irq_falling);
1648c2ecf20Sopenharmony_ci		disable_irq_wake(onkey->irq_rising);
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci	return 0;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(stpmic1_onkey_pm,
1708c2ecf20Sopenharmony_ci			 stpmic1_onkey_suspend,
1718c2ecf20Sopenharmony_ci			 stpmic1_onkey_resume);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic const struct of_device_id of_stpmic1_onkey_match[] = {
1748c2ecf20Sopenharmony_ci	{ .compatible = "st,stpmic1-onkey" },
1758c2ecf20Sopenharmony_ci	{ },
1768c2ecf20Sopenharmony_ci};
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_stpmic1_onkey_match);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic struct platform_driver stpmic1_onkey_driver = {
1818c2ecf20Sopenharmony_ci	.probe	= stpmic1_onkey_probe,
1828c2ecf20Sopenharmony_ci	.driver	= {
1838c2ecf20Sopenharmony_ci		.name	= "stpmic1_onkey",
1848c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(of_stpmic1_onkey_match),
1858c2ecf20Sopenharmony_ci		.pm	= &stpmic1_onkey_pm,
1868c2ecf20Sopenharmony_ci	},
1878c2ecf20Sopenharmony_ci};
1888c2ecf20Sopenharmony_cimodule_platform_driver(stpmic1_onkey_driver);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Onkey driver for STPMIC1");
1918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>");
1928c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
193