android_kernel_sony_sdm845/kernel/power/wakeup_irq_debug.c
exer 7ee5becf00 kernel/power: Import Sony Wakelock
*52.1.A.3.74

Signed-off-by: Sorayukii <sorayukii69@gmail.com>
2026-05-15 17:48:10 +07:00

357 lines
7.3 KiB
C

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/suspend.h>
#include <linux/platform_device.h>
#include <linux/kernel_stat.h>
#include <linux/slab.h>
#include <linux/wakeup_reason.h>
#include <linux/irq.h>
#define MODULE_NAME "wakeup_debug"
struct irq_info {
struct list_head list;
unsigned int wakeup_count;
unsigned int irq_count;
char irq_name[128];
int irq;
};
struct wakeup_irq {
struct list_head head;
bool enabled;
};
static int update_irq_table(struct wakeup_irq *p, int irq)
{
struct irq_info *i;
list_for_each_entry(i, &p->head, list) {
if (i->irq == irq) {
i->irq_count = kstat_irqs_cpu(i->irq, 0);
return 1;
}
}
return 0;
}
static void add_irq_to_table(struct wakeup_irq *p, int irq)
{
struct irq_info *info;
struct irq_desc *desc;
/*
* TODO: add here a filter for root IRQs
*/
info = kzalloc(sizeof(*info), GFP_KERNEL);
info->irq_count = kstat_irqs_cpu(irq, 0);
info->irq = irq;
desc = irq_to_desc(irq);
/*
* IRQ can be disabled and freed, thus
* we save its name because of history
*/
if (desc && desc->action && desc->action->name)
(void) scnprintf(
info->irq_name, sizeof(info->irq_name),
"%s", desc->action->name);
list_add_tail(&info->list, &p->head);
}
static int wakeup_irq_suspend(struct device *dev)
{
struct irq_desc *desc;
struct wakeup_irq *p;
int irq, rv;
/* get private data */
p = dev_get_drvdata(dev);
if (!p->enabled)
goto leave;
/*
* we want to add IRQs to list by reverse way,
* therefore all potential triggered IRQ (nested)
* will be in the beginning of the list
*/
for_each_irq_desc_reverse(irq, desc) {
raw_spin_lock_irq(&desc->lock);
rv = irqd_is_wakeup_set(&desc->irq_data);
raw_spin_unlock_irq(&desc->lock);
if (rv == 0)
continue;
rv = update_irq_table(p, irq);
if (rv)
continue;
add_irq_to_table(p, irq);
dev_dbg(dev, "IRQ %d was added to the list\n", irq);
}
leave:
return 0;
}
static int wakeup_irq_resume(struct device *dev)
{
struct irq_info *i;
struct wakeup_irq *p;
struct irq_desc *desc;
unsigned long flags;
int diff;
/* get private data */
p = dev_get_drvdata(dev);
if (!p->enabled)
goto leave;
/*
* go through all registered IRQs in our list
*/
list_for_each_entry(i, &p->head, list) {
desc = irq_to_desc(i->irq);
if (desc) {
raw_spin_lock_irqsave(&desc->lock, flags);
diff = kstat_irqs_cpu(i->irq, 0) - i->irq_count;
raw_spin_unlock_irqrestore(&desc->lock, flags);
if (diff > 0) {
i->wakeup_count++;
dev_dbg(dev, "%d, wake-up count: %d, active: %d (%s)\n",
i->irq, i->wakeup_count,
irqd_is_wakeup_set(&desc->irq_data),
desc->action && desc->action->name ?
desc->action->name : "");
log_wakeup_reason(i->irq);
update_irq_table(p, i->irq);
}
}
}
leave:
return 0;
}
static ssize_t irq_stat_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wakeup_irq *p;
struct irq_info *i;
int len = 0;
p = dev_get_drvdata(dev);
/* build header of the output */
len += scnprintf(buf + len, PAGE_SIZE - len,
"IRQ\tCount\tName\n");
list_for_each_entry(i, &p->head, list) {
if (i->wakeup_count) {
len += scnprintf(buf + len, PAGE_SIZE - len,
"%d\t%d\t%s\n",
i->irq, i->wakeup_count, i->irq_name);
/*
* Ensure we do not write more than PAGE_SIZE
* bytes of data including null terminator and
* add '\n' symbol to the buffer if it's full
*/
if (len >= PAGE_SIZE - 1) {
buf[PAGE_SIZE - 2] = '\n';
dev_err(dev, "%s:%d: buffer is full\n",
__func__, __LINE__);
break;
}
}
}
return len;
}
static ssize_t enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wakeup_irq *p;
p = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", p->enabled);
}
static ssize_t enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wakeup_irq *p;
int value, ret;
p = dev_get_drvdata(dev);
ret = kstrtoint(buf, 10, &value);
if (ret) {
dev_err(dev, "kstrtoint failed: %d\n", ret);
goto error;
}
if (value == 1)
p->enabled = true;
else if (value == 0)
p->enabled = false;
else
goto error;
return count;
error:
return -EINVAL;
}
static const struct dev_pm_ops wakeup_irq_pm_ops = {
.suspend_noirq = wakeup_irq_suspend,
.resume_early = wakeup_irq_resume,
};
static struct platform_device wakeup_irq_device = {
.name = MODULE_NAME,
.id = 0,
};
static struct platform_driver wakeup_irq_driver = {
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
.pm = &wakeup_irq_pm_ops,
},
};
static struct device_attribute wakeup_irq_attrs[] = {
__ATTR(wakeup_irq_stat,
S_IRUSR | S_IRGRP | S_IROTH, irq_stat_show, NULL),
__ATTR(enable,
S_IRUSR | S_IWUSR, enable_show, enable_store),
};
static int create_sysfs_entries(struct device *dev)
{
int i, rc;
for (i = 0; i < ARRAY_SIZE(wakeup_irq_attrs); i++) {
rc = device_create_file(dev, &wakeup_irq_attrs[i]);
if (rc < 0)
goto revert;
}
return 0;
revert:
for (; --i >= 0;)
device_remove_file(dev, &wakeup_irq_attrs[i]);
return rc;
}
static int wakeup_irq_debug_init(void)
{
struct wakeup_irq *p;
int rv = -1;
p = kzalloc(sizeof(struct wakeup_irq), GFP_KERNEL);
if (p == NULL)
goto out;
INIT_LIST_HEAD(&p->head);
/*
* we need it for PM works correctly
*/
rv = platform_device_register(&wakeup_irq_device);
if (rv) {
pr_err("%s: wakeup_irq_device register failed %d\n",
__func__, rv);
goto free_mem;
}
rv = platform_driver_register(&wakeup_irq_driver);
if (rv) {
pr_err("%s: wakeup_irq_driver register failed %d\n",
__func__, rv);
goto unregister_device;
}
rv = create_sysfs_entries(&wakeup_irq_device.dev);
if (rv < 0) {
pr_err("%s: create_sysfs_entries failed %d\n",
__func__, rv);
goto unregister_driver;
}
/*
* save private data
*/
platform_set_drvdata(&wakeup_irq_device, p);
/*
* by default it's disabled
*/
p->enabled = false;
return 0;
unregister_driver:
platform_driver_unregister(&wakeup_irq_driver);
unregister_device:
platform_device_unregister(&wakeup_irq_device);
free_mem:
kzfree(p);
out:
return rv;
}
static void wakeup_irq_debug_exit(void)
{
struct irq_info *info, *tmp_info;
struct wakeup_irq *p;
int i;
p = dev_get_drvdata(&wakeup_irq_device.dev);
for (i = 0; i < ARRAY_SIZE(wakeup_irq_attrs); i++)
device_remove_file(&wakeup_irq_device.dev,
&wakeup_irq_attrs[i]);
list_for_each_entry_safe(info, tmp_info, &p->head, list) {
list_del(&info->list);
kzfree(info);
}
platform_driver_unregister(&wakeup_irq_driver);
platform_device_unregister(&wakeup_irq_device);
kzfree(p);
}
module_init(wakeup_irq_debug_init);
module_exit(wakeup_irq_debug_exit);
MODULE_DESCRIPTION("Wakeup IRQ Debug Module");
MODULE_AUTHOR("Uladzislau Rezki");
MODULE_LICENSE("GPL v2");