mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 11:21:33 +00:00
30040818b3
In case runtime PM is enabled, do runtime PM clean up to remove cpu latency qos request, otherwise driver removal may have below kernel dump: [ 19.463299] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000048 [ 19.472161] Mem abort info: [ 19.474985] ESR = 0x0000000096000004 [ 19.478754] EC = 0x25: DABT (current EL), IL = 32 bits [ 19.484081] SET = 0, FnV = 0 [ 19.487149] EA = 0, S1PTW = 0 [ 19.490361] FSC = 0x04: level 0 translation fault [ 19.495256] Data abort info: [ 19.498149] ISV = 0, ISS = 0x00000004 [ 19.501997] CM = 0, WnR = 0 [ 19.504977] user pgtable: 4k pages, 48-bit VAs, pgdp=0000000049f81000 [ 19.511432] [0000000000000048] pgd=0000000000000000, p4d=0000000000000000 [ 19.518245] Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP [ 19.524520] Modules linked in: gpio_ir_recv(+) rc_core [last unloaded: rc_core] [ 19.531845] CPU: 0 PID: 445 Comm: insmod Not tainted 6.2.0-rc1-00028-g2c397a46d47c #72 [ 19.531854] Hardware name: FSL i.MX8MM EVK board (DT) [ 19.531859] pstate: 80000005 (Nzcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 19.551777] pc : cpu_latency_qos_remove_request+0x20/0x110 [ 19.557277] lr : gpio_ir_recv_runtime_suspend+0x18/0x30 [gpio_ir_recv] [ 19.557294] sp : ffff800008ce3740 [ 19.557297] x29: ffff800008ce3740 x28: 0000000000000000 x27: ffff800008ce3d50 [ 19.574270] x26: ffffc7e3e9cea100 x25: 00000000000f4240 x24: ffffc7e3f9ef0e30 [ 19.574284] x23: 0000000000000000 x22: ffff0061803820f4 x21: 0000000000000008 [ 19.574296] x20: ffffc7e3fa75df30 x19: 0000000000000020 x18: ffffffffffffffff [ 19.588570] x17: 0000000000000000 x16: ffffc7e3f9efab70 x15: ffffffffffffffff [ 19.595712] x14: ffff800008ce37b8 x13: ffff800008ce37aa x12: 0000000000000001 [ 19.602853] x11: 0000000000000001 x10: ffffcbe3ec0dff87 x9 : 0000000000000008 [ 19.609991] x8 : 0101010101010101 x7 : 0000000000000000 x6 : 000000000f0bfe9f [ 19.624261] x5 : 00ffffffffffffff x4 : 0025ab8e00000000 x3 : ffff006180382010 [ 19.631405] x2 : ffffc7e3e9ce8030 x1 : ffffc7e3fc3eb810 x0 : 0000000000000020 [ 19.638548] Call trace: [ 19.640995] cpu_latency_qos_remove_request+0x20/0x110 [ 19.646142] gpio_ir_recv_runtime_suspend+0x18/0x30 [gpio_ir_recv] [ 19.652339] pm_generic_runtime_suspend+0x2c/0x44 [ 19.657055] __rpm_callback+0x48/0x1dc [ 19.660807] rpm_callback+0x6c/0x80 [ 19.664301] rpm_suspend+0x10c/0x640 [ 19.667880] rpm_idle+0x250/0x2d0 [ 19.671198] update_autosuspend+0x38/0xe0 [ 19.675213] pm_runtime_set_autosuspend_delay+0x40/0x60 [ 19.680442] gpio_ir_recv_probe+0x1b4/0x21c [gpio_ir_recv] [ 19.685941] platform_probe+0x68/0xc0 [ 19.689610] really_probe+0xc0/0x3dc [ 19.693189] __driver_probe_device+0x7c/0x190 [ 19.697550] driver_probe_device+0x3c/0x110 [ 19.701739] __driver_attach+0xf4/0x200 [ 19.705578] bus_for_each_dev+0x70/0xd0 [ 19.709417] driver_attach+0x24/0x30 [ 19.712998] bus_add_driver+0x17c/0x240 [ 19.716834] driver_register+0x78/0x130 [ 19.720676] __platform_driver_register+0x28/0x34 [ 19.725386] gpio_ir_recv_driver_init+0x20/0x1000 [gpio_ir_recv] [ 19.731404] do_one_initcall+0x44/0x2ac [ 19.735243] do_init_module+0x48/0x1d0 [ 19.739003] load_module+0x19fc/0x2034 [ 19.742759] __do_sys_finit_module+0xac/0x12c [ 19.747124] __arm64_sys_finit_module+0x20/0x30 [ 19.751664] invoke_syscall+0x48/0x114 [ 19.755420] el0_svc_common.constprop.0+0xcc/0xec [ 19.760132] do_el0_svc+0x38/0xb0 [ 19.763456] el0_svc+0x2c/0x84 [ 19.766516] el0t_64_sync_handler+0xf4/0x120 [ 19.770789] el0t_64_sync+0x190/0x194 [ 19.774460] Code: 910003fd a90153f3 aa0003f3 91204021 (f9401400) [ 19.780556] ---[ end trace 0000000000000000 ]--- Signed-off-by: Li Jun <jun.li@nxp.com> Signed-off-by: Sean Young <sean@mess.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
218 lines
5.3 KiB
C
218 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/irq.h>
|
|
#include <media/rc-core.h>
|
|
|
|
#define GPIO_IR_DEVICE_NAME "gpio_ir_recv"
|
|
|
|
struct gpio_rc_dev {
|
|
struct rc_dev *rcdev;
|
|
struct gpio_desc *gpiod;
|
|
int irq;
|
|
struct device *pmdev;
|
|
struct pm_qos_request qos;
|
|
};
|
|
|
|
static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id)
|
|
{
|
|
int val;
|
|
struct gpio_rc_dev *gpio_dev = dev_id;
|
|
struct device *pmdev = gpio_dev->pmdev;
|
|
|
|
/*
|
|
* For some cpuidle systems, not all:
|
|
* Respond to interrupt taking more latency when cpu in idle.
|
|
* Invoke asynchronous pm runtime get from interrupt context,
|
|
* this may introduce a millisecond delay to call resume callback,
|
|
* where to disable cpuilde.
|
|
*
|
|
* Two issues lead to fail to decode first frame, one is latency to
|
|
* respond to interrupt, another is delay introduced by async api.
|
|
*/
|
|
if (pmdev)
|
|
pm_runtime_get(pmdev);
|
|
|
|
val = gpiod_get_value(gpio_dev->gpiod);
|
|
if (val >= 0)
|
|
ir_raw_event_store_edge(gpio_dev->rcdev, val == 1);
|
|
|
|
if (pmdev) {
|
|
pm_runtime_mark_last_busy(pmdev);
|
|
pm_runtime_put_autosuspend(pmdev);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int gpio_ir_recv_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct gpio_rc_dev *gpio_dev;
|
|
struct rc_dev *rcdev;
|
|
u32 period = 0;
|
|
int rc;
|
|
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL);
|
|
if (!gpio_dev)
|
|
return -ENOMEM;
|
|
|
|
gpio_dev->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN);
|
|
if (IS_ERR(gpio_dev->gpiod))
|
|
return dev_err_probe(dev, PTR_ERR(gpio_dev->gpiod),
|
|
"error getting gpio\n");
|
|
gpio_dev->irq = gpiod_to_irq(gpio_dev->gpiod);
|
|
if (gpio_dev->irq < 0)
|
|
return gpio_dev->irq;
|
|
|
|
rcdev = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW);
|
|
if (!rcdev)
|
|
return -ENOMEM;
|
|
|
|
rcdev->priv = gpio_dev;
|
|
rcdev->device_name = GPIO_IR_DEVICE_NAME;
|
|
rcdev->input_phys = GPIO_IR_DEVICE_NAME "/input0";
|
|
rcdev->input_id.bustype = BUS_HOST;
|
|
rcdev->input_id.vendor = 0x0001;
|
|
rcdev->input_id.product = 0x0001;
|
|
rcdev->input_id.version = 0x0100;
|
|
rcdev->dev.parent = dev;
|
|
rcdev->driver_name = KBUILD_MODNAME;
|
|
rcdev->min_timeout = 1;
|
|
rcdev->timeout = IR_DEFAULT_TIMEOUT;
|
|
rcdev->max_timeout = 10 * IR_DEFAULT_TIMEOUT;
|
|
rcdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
|
|
rcdev->map_name = of_get_property(np, "linux,rc-map-name", NULL);
|
|
if (!rcdev->map_name)
|
|
rcdev->map_name = RC_MAP_EMPTY;
|
|
|
|
gpio_dev->rcdev = rcdev;
|
|
|
|
rc = devm_rc_register_device(dev, rcdev);
|
|
if (rc < 0) {
|
|
dev_err(dev, "failed to register rc device (%d)\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
of_property_read_u32(np, "linux,autosuspend-period", &period);
|
|
if (period) {
|
|
gpio_dev->pmdev = dev;
|
|
pm_runtime_set_autosuspend_delay(dev, period);
|
|
pm_runtime_use_autosuspend(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
pm_runtime_enable(dev);
|
|
}
|
|
|
|
platform_set_drvdata(pdev, gpio_dev);
|
|
|
|
return devm_request_irq(dev, gpio_dev->irq, gpio_ir_recv_irq,
|
|
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
|
"gpio-ir-recv-irq", gpio_dev);
|
|
}
|
|
|
|
static int gpio_ir_recv_remove(struct platform_device *pdev)
|
|
{
|
|
struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev);
|
|
struct device *pmdev = gpio_dev->pmdev;
|
|
|
|
if (pmdev) {
|
|
pm_runtime_get_sync(pmdev);
|
|
cpu_latency_qos_remove_request(&gpio_dev->qos);
|
|
|
|
pm_runtime_disable(pmdev);
|
|
pm_runtime_put_noidle(pmdev);
|
|
pm_runtime_set_suspended(pmdev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int gpio_ir_recv_suspend(struct device *dev)
|
|
{
|
|
struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
|
|
if (device_may_wakeup(dev))
|
|
enable_irq_wake(gpio_dev->irq);
|
|
else
|
|
disable_irq(gpio_dev->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_ir_recv_resume(struct device *dev)
|
|
{
|
|
struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
|
|
if (device_may_wakeup(dev))
|
|
disable_irq_wake(gpio_dev->irq);
|
|
else
|
|
enable_irq(gpio_dev->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_ir_recv_runtime_suspend(struct device *dev)
|
|
{
|
|
struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
|
|
cpu_latency_qos_remove_request(&gpio_dev->qos);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_ir_recv_runtime_resume(struct device *dev)
|
|
{
|
|
struct gpio_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
|
|
cpu_latency_qos_add_request(&gpio_dev->qos, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops gpio_ir_recv_pm_ops = {
|
|
.suspend = gpio_ir_recv_suspend,
|
|
.resume = gpio_ir_recv_resume,
|
|
.runtime_suspend = gpio_ir_recv_runtime_suspend,
|
|
.runtime_resume = gpio_ir_recv_runtime_resume,
|
|
};
|
|
#endif
|
|
|
|
static const struct of_device_id gpio_ir_recv_of_match[] = {
|
|
{ .compatible = "gpio-ir-receiver", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, gpio_ir_recv_of_match);
|
|
|
|
static struct platform_driver gpio_ir_recv_driver = {
|
|
.probe = gpio_ir_recv_probe,
|
|
.remove = gpio_ir_recv_remove,
|
|
.driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.of_match_table = of_match_ptr(gpio_ir_recv_of_match),
|
|
#ifdef CONFIG_PM
|
|
.pm = &gpio_ir_recv_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
module_platform_driver(gpio_ir_recv_driver);
|
|
|
|
MODULE_DESCRIPTION("GPIO IR Receiver driver");
|
|
MODULE_LICENSE("GPL v2");
|