18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
38c2ecf20Sopenharmony_ci */
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include <linux/kernel.h>
68c2ecf20Sopenharmony_ci#include <linux/init.h>
78c2ecf20Sopenharmony_ci#include <linux/module.h>
88c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
98c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/of.h>
128c2ecf20Sopenharmony_ci#include <linux/of_gpio.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
158c2ecf20Sopenharmony_ci#include <linux/pm_qos.h>
168c2ecf20Sopenharmony_ci#include <linux/irq.h>
178c2ecf20Sopenharmony_ci#include <media/rc-core.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define GPIO_IR_DEVICE_NAME	"gpio_ir_recv"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct gpio_rc_dev {
228c2ecf20Sopenharmony_ci	struct rc_dev *rcdev;
238c2ecf20Sopenharmony_ci	struct gpio_desc *gpiod;
248c2ecf20Sopenharmony_ci	int irq;
258c2ecf20Sopenharmony_ci	struct device *pmdev;
268c2ecf20Sopenharmony_ci	struct pm_qos_request qos;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	int val;
328c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = dev_id;
338c2ecf20Sopenharmony_ci	struct device *pmdev = gpio_dev->pmdev;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	/*
368c2ecf20Sopenharmony_ci	 * For some cpuidle systems, not all:
378c2ecf20Sopenharmony_ci	 * Respond to interrupt taking more latency when cpu in idle.
388c2ecf20Sopenharmony_ci	 * Invoke asynchronous pm runtime get from interrupt context,
398c2ecf20Sopenharmony_ci	 * this may introduce a millisecond delay to call resume callback,
408c2ecf20Sopenharmony_ci	 * where to disable cpuilde.
418c2ecf20Sopenharmony_ci	 *
428c2ecf20Sopenharmony_ci	 * Two issues lead to fail to decode first frame, one is latency to
438c2ecf20Sopenharmony_ci	 * respond to interrupt, another is delay introduced by async api.
448c2ecf20Sopenharmony_ci	 */
458c2ecf20Sopenharmony_ci	if (pmdev)
468c2ecf20Sopenharmony_ci		pm_runtime_get(pmdev);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	val = gpiod_get_value(gpio_dev->gpiod);
498c2ecf20Sopenharmony_ci	if (val >= 0)
508c2ecf20Sopenharmony_ci		ir_raw_event_store_edge(gpio_dev->rcdev, val == 1);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	if (pmdev) {
538c2ecf20Sopenharmony_ci		pm_runtime_mark_last_busy(pmdev);
548c2ecf20Sopenharmony_ci		pm_runtime_put_autosuspend(pmdev);
558c2ecf20Sopenharmony_ci	}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic int gpio_ir_recv_probe(struct platform_device *pdev)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
638c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
648c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev;
658c2ecf20Sopenharmony_ci	struct rc_dev *rcdev;
668c2ecf20Sopenharmony_ci	u32 period = 0;
678c2ecf20Sopenharmony_ci	int rc;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (!np)
708c2ecf20Sopenharmony_ci		return -ENODEV;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL);
738c2ecf20Sopenharmony_ci	if (!gpio_dev)
748c2ecf20Sopenharmony_ci		return -ENOMEM;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	gpio_dev->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN);
778c2ecf20Sopenharmony_ci	if (IS_ERR(gpio_dev->gpiod)) {
788c2ecf20Sopenharmony_ci		rc = PTR_ERR(gpio_dev->gpiod);
798c2ecf20Sopenharmony_ci		/* Just try again if this happens */
808c2ecf20Sopenharmony_ci		if (rc != -EPROBE_DEFER)
818c2ecf20Sopenharmony_ci			dev_err(dev, "error getting gpio (%d)\n", rc);
828c2ecf20Sopenharmony_ci		return rc;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci	gpio_dev->irq = gpiod_to_irq(gpio_dev->gpiod);
858c2ecf20Sopenharmony_ci	if (gpio_dev->irq < 0)
868c2ecf20Sopenharmony_ci		return gpio_dev->irq;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	rcdev = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW);
898c2ecf20Sopenharmony_ci	if (!rcdev)
908c2ecf20Sopenharmony_ci		return -ENOMEM;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	rcdev->priv = gpio_dev;
938c2ecf20Sopenharmony_ci	rcdev->device_name = GPIO_IR_DEVICE_NAME;
948c2ecf20Sopenharmony_ci	rcdev->input_phys = GPIO_IR_DEVICE_NAME "/input0";
958c2ecf20Sopenharmony_ci	rcdev->input_id.bustype = BUS_HOST;
968c2ecf20Sopenharmony_ci	rcdev->input_id.vendor = 0x0001;
978c2ecf20Sopenharmony_ci	rcdev->input_id.product = 0x0001;
988c2ecf20Sopenharmony_ci	rcdev->input_id.version = 0x0100;
998c2ecf20Sopenharmony_ci	rcdev->dev.parent = dev;
1008c2ecf20Sopenharmony_ci	rcdev->driver_name = KBUILD_MODNAME;
1018c2ecf20Sopenharmony_ci	rcdev->min_timeout = 1;
1028c2ecf20Sopenharmony_ci	rcdev->timeout = IR_DEFAULT_TIMEOUT;
1038c2ecf20Sopenharmony_ci	rcdev->max_timeout = 10 * IR_DEFAULT_TIMEOUT;
1048c2ecf20Sopenharmony_ci	rcdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
1058c2ecf20Sopenharmony_ci	rcdev->map_name = of_get_property(np, "linux,rc-map-name", NULL);
1068c2ecf20Sopenharmony_ci	if (!rcdev->map_name)
1078c2ecf20Sopenharmony_ci		rcdev->map_name = RC_MAP_EMPTY;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	gpio_dev->rcdev = rcdev;
1108c2ecf20Sopenharmony_ci	if (of_property_read_bool(np, "wakeup-source"))
1118c2ecf20Sopenharmony_ci		device_init_wakeup(dev, true);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	rc = devm_rc_register_device(dev, rcdev);
1148c2ecf20Sopenharmony_ci	if (rc < 0) {
1158c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register rc device (%d)\n", rc);
1168c2ecf20Sopenharmony_ci		return rc;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	of_property_read_u32(np, "linux,autosuspend-period", &period);
1208c2ecf20Sopenharmony_ci	if (period) {
1218c2ecf20Sopenharmony_ci		gpio_dev->pmdev = dev;
1228c2ecf20Sopenharmony_ci		pm_runtime_set_autosuspend_delay(dev, period);
1238c2ecf20Sopenharmony_ci		pm_runtime_use_autosuspend(dev);
1248c2ecf20Sopenharmony_ci		pm_runtime_set_suspended(dev);
1258c2ecf20Sopenharmony_ci		pm_runtime_enable(dev);
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, gpio_dev);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	return devm_request_irq(dev, gpio_dev->irq, gpio_ir_recv_irq,
1318c2ecf20Sopenharmony_ci				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
1328c2ecf20Sopenharmony_ci				"gpio-ir-recv-irq", gpio_dev);
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic int gpio_ir_recv_remove(struct platform_device *pdev)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev);
1388c2ecf20Sopenharmony_ci	struct device *pmdev = gpio_dev->pmdev;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	if (pmdev) {
1418c2ecf20Sopenharmony_ci		pm_runtime_get_sync(pmdev);
1428c2ecf20Sopenharmony_ci		cpu_latency_qos_remove_request(&gpio_dev->qos);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		pm_runtime_disable(pmdev);
1458c2ecf20Sopenharmony_ci		pm_runtime_put_noidle(pmdev);
1468c2ecf20Sopenharmony_ci		pm_runtime_set_suspended(pmdev);
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	return 0;
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
1538c2ecf20Sopenharmony_cistatic int gpio_ir_recv_suspend(struct device *dev)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev))
1588c2ecf20Sopenharmony_ci		enable_irq_wake(gpio_dev->irq);
1598c2ecf20Sopenharmony_ci	else
1608c2ecf20Sopenharmony_ci		disable_irq(gpio_dev->irq);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	return 0;
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic int gpio_ir_recv_resume(struct device *dev)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev))
1708c2ecf20Sopenharmony_ci		disable_irq_wake(gpio_dev->irq);
1718c2ecf20Sopenharmony_ci	else
1728c2ecf20Sopenharmony_ci		enable_irq(gpio_dev->irq);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	return 0;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic int gpio_ir_recv_runtime_suspend(struct device *dev)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	cpu_latency_qos_remove_request(&gpio_dev->qos);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	return 0;
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic int gpio_ir_recv_runtime_resume(struct device *dev)
1878c2ecf20Sopenharmony_ci{
1888c2ecf20Sopenharmony_ci	struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	cpu_latency_qos_add_request(&gpio_dev->qos, 0);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic const struct dev_pm_ops gpio_ir_recv_pm_ops = {
1968c2ecf20Sopenharmony_ci	.suspend        = gpio_ir_recv_suspend,
1978c2ecf20Sopenharmony_ci	.resume         = gpio_ir_recv_resume,
1988c2ecf20Sopenharmony_ci	.runtime_suspend = gpio_ir_recv_runtime_suspend,
1998c2ecf20Sopenharmony_ci	.runtime_resume  = gpio_ir_recv_runtime_resume,
2008c2ecf20Sopenharmony_ci};
2018c2ecf20Sopenharmony_ci#endif
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic const struct of_device_id gpio_ir_recv_of_match[] = {
2048c2ecf20Sopenharmony_ci	{ .compatible = "gpio-ir-receiver", },
2058c2ecf20Sopenharmony_ci	{ },
2068c2ecf20Sopenharmony_ci};
2078c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, gpio_ir_recv_of_match);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cistatic struct platform_driver gpio_ir_recv_driver = {
2108c2ecf20Sopenharmony_ci	.probe  = gpio_ir_recv_probe,
2118c2ecf20Sopenharmony_ci	.remove = gpio_ir_recv_remove,
2128c2ecf20Sopenharmony_ci	.driver = {
2138c2ecf20Sopenharmony_ci		.name   = KBUILD_MODNAME,
2148c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(gpio_ir_recv_of_match),
2158c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
2168c2ecf20Sopenharmony_ci		.pm	= &gpio_ir_recv_pm_ops,
2178c2ecf20Sopenharmony_ci#endif
2188c2ecf20Sopenharmony_ci	},
2198c2ecf20Sopenharmony_ci};
2208c2ecf20Sopenharmony_cimodule_platform_driver(gpio_ir_recv_driver);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO IR Receiver driver");
2238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
224