kernel_samsung_a53x/drivers/soc/samsung/exynos-wow.c
2024-06-15 16:02:09 -03:00

559 lines
14 KiB
C
Executable file

/*
* exynos-wow.c - Exynos Workload Watcher Driver
*
* Copyright (C) 2021 Samsung Electronics
* Hanjun Shin <hanjun.shin@samsung.com>
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <soc/samsung/exynos-wow.h>
#include <linux/of_address.h>
static struct exynos_wow_data *exynos_wow;
static char *exynos_wow_event_name[] = { "CCNT", "RW_ACITVE", "RW_DATA", "RW_REQUEST", "R_MO_COUNT" };
static int exynos_wow_set_polling(struct exynos_wow_data *data)
{
schedule_delayed_work(&data->dwork, msecs_to_jiffies(data->polling_delay));
return 0;
}
static int exynos_wow_start_polling(struct exynos_wow_data *data)
{
schedule_delayed_work(&data->dwork, 0);
return 0;
}
static int exynos_wow_stop_polling(struct exynos_wow_data *data)
{
cancel_delayed_work(&data->dwork);
return 0;
}
static int exynos_wow_set_counter(struct exynos_wow_data *data, int mode)
{
int i, ret = 0;
unsigned int value, offset;
switch (mode) {
case WOW_PPC_START:
value = WOW_PPC_PMNC_GLB_CNT_EN | WOW_PPC_PMNC_Q_CH_MODE;
offset = WOW_PPC_PMNC;
break;
case WOW_PPC_STOP:
value = 0;
offset = WOW_PPC_PMNC;
break;
case WOW_PPC_RESET:
value = WOW_PPC_PMNC_RESET_CCNT_PMCNT;
offset = WOW_PPC_PMNC;
break;
default:
return -EINVAL;
}
for (i = 0; i < data->nr_ip; i++) {
struct exynos_wow_ip_data *ip_data = &data->ip_data[i];
int j;
for (j = 0; j < ip_data->nr_base; j++) {
void __iomem *base = ip_data->wow_base[j];
// start counter
writel(value, base + offset);
}
}
return ret;
}
static inline int exynos_wow_start_counter(struct exynos_wow_data *data)
{
return exynos_wow_set_counter(data, WOW_PPC_START);
}
static inline int exynos_wow_stop_counter(struct exynos_wow_data *data)
{
return exynos_wow_set_counter(data, WOW_PPC_STOP);
}
static inline int exynos_wow_reset_counter(struct exynos_wow_data *data)
{
return exynos_wow_set_counter(data, WOW_PPC_RESET);
}
static int exynos_wow_accumulator(struct exynos_wow_data *data)
{
int i;
u64 total_ccnt = 0, total_active = 0;
exynos_wow_stop_counter(data);
for (i = 0; i < data->nr_ip; i++) {
struct exynos_wow_ip_data *ip_data = &data->ip_data[i];
void __iomem *base = ip_data->wow_base[0];
u64 rw_data, rw_request, active, ccnt, mo_count;
// Get counter value
ccnt = readl(base + WOW_PPC_CCNT);
active = readl(base + WOW_PPC_PMCNT0);
rw_data = readl(base + WOW_PPC_PMCNT1) * ip_data->nr_ppc;
rw_request = readl(base + WOW_PPC_PMCNT2);
mo_count = readl(base + WOW_PPC_PMCNT3_HIGH) & 0xff;
mo_count = mo_count << 32 | readl(base + WOW_PPC_PMCNT3);
// update per IP counter
ip_data->wow_counter[CCNT] += ccnt;
ip_data->wow_counter[RW_ACTIVE] += active;
ip_data->wow_counter[RW_DATA] += rw_data;
ip_data->wow_counter[RW_REQUEST] += rw_request;
ip_data->wow_counter[R_MO_COUNT] += mo_count;
// update total counter
if (!ip_data->monitor_only) {
data->profile.transfer_data += (rw_data * ip_data->bus_width);
if (total_active < active) {
total_ccnt = ccnt;
total_active = active;
}
if (ip_data->use_latency) {
data->profile.nr_requests += rw_request;
data->profile.mo_count += mo_count;
}
}
}
data->profile.ccnt += total_ccnt;
data->profile.active += total_active;
exynos_wow_reset_counter(data);
exynos_wow_start_counter(data);
return 0;
}
int exynos_wow_get_data(struct exynos_wow_profile *result)
{
struct exynos_wow_data *data = exynos_wow;
if (data == NULL)
return -ENODEV;
mutex_lock(&data->lock);
exynos_wow_accumulator(data);
memcpy(result, &data->profile, sizeof(struct exynos_wow_profile));
mutex_unlock(&data->lock);
return 0;
}
EXPORT_SYMBOL(exynos_wow_get_data);
static void exynos_wow_work(struct work_struct *work)
{
struct exynos_wow_data *data =
container_of(work, struct exynos_wow_data, dwork.work);
mutex_lock(&data->lock);
exynos_wow_accumulator(data);
if (data->mode == WOW_ENABLED)
exynos_wow_set_polling(data);
mutex_unlock(&data->lock);
}
/* sysfs nodes for amb control */
#define sysfs_printf(...) count += snprintf(buf + count, PAGE_SIZE, __VA_ARGS__)
static ssize_t
exynos_wow_mode_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct exynos_wow_data *data = platform_get_drvdata(pdev);
unsigned int count = 0;
sysfs_printf("%s\n", data->mode ? "enabled" : "disabled");
return count;
}
static ssize_t
exynos_wow_mode_store(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct exynos_wow_data *data = platform_get_drvdata(pdev);
data->mode = true;
return 0;
}
static ssize_t
exynos_wow_raw_counter_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct exynos_wow_data *data = platform_get_drvdata(pdev);
unsigned int i, j, count = 0;
sysfs_printf("IP_NAME ");
for (i = 0; i < NUM_WOW_EVENT; i++)
sysfs_printf("%s ", exynos_wow_event_name[i]);
sysfs_printf("\n");
for (i = 0; i < data->nr_ip; i++) {
struct exynos_wow_ip_data *ip_data = &data->ip_data[i];
sysfs_printf("%s ", ip_data->ppc_name);
for (j = 0; j < NUM_WOW_EVENT; j++)
sysfs_printf("%llu ", ip_data->wow_counter[j]);
sysfs_printf("\n");
}
return count;
}
static ssize_t
exynos_wow_total_counter_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
unsigned int i, count = 0;
struct exynos_wow_profile profile;
exynos_wow_get_data(&profile);
sysfs_printf("CCNT | ACTIVE | TRANSFER_DATA | MO_COUNT | NR_REQUEST\n");
for (i = 0; i < (sizeof(struct exynos_wow_profile) / sizeof(u64)); i++)
sysfs_printf("%llu ", ((u64*)&profile)[i]);
sysfs_printf("\n");
return count;
}
static ssize_t
exynos_wow_start_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
return 0;
}
static ssize_t
exynos_wow_start_store(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct exynos_wow_data *data = exynos_wow;
exynos_wow_start_counter(data);
return 0;
}
static ssize_t
exynos_wow_stop_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
return 0;
}
static ssize_t
exynos_wow_stop_store(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct exynos_wow_data *data = exynos_wow;
exynos_wow_stop_counter(data);
return 0;
}
static DEVICE_ATTR(wow_mode, S_IWUSR | S_IRUGO,
exynos_wow_mode_show, exynos_wow_mode_store);
static DEVICE_ATTR(raw_counter, S_IRUGO,
exynos_wow_raw_counter_show, NULL);
static DEVICE_ATTR(total_counter, S_IRUGO,
exynos_wow_total_counter_show, NULL);
static DEVICE_ATTR(start, S_IWUSR | S_IRUGO,
exynos_wow_start_show, exynos_wow_start_store);
static DEVICE_ATTR(stop, S_IWUSR | S_IRUGO,
exynos_wow_stop_show, exynos_wow_stop_store);
static struct attribute *exynos_wow_attrs[] = {
&dev_attr_wow_mode.attr,
&dev_attr_raw_counter.attr,
&dev_attr_total_counter.attr,
&dev_attr_start.attr,
&dev_attr_stop.attr,
NULL,
};
static const struct attribute_group exynos_wow_attr_group = {
.attrs = exynos_wow_attrs,
};
static int exynos_wow_work_init(struct platform_device *pdev)
{
struct exynos_wow_data *data = platform_get_drvdata(pdev);
int ret = 0;
/*
if (!thermal_irq_wq) {
attr.nice = 0;
attr.no_numa = true;
cpumask_copy(attr.cpumask, cpu_coregroup_mask(0));
thermal_irq_wq = alloc_workqueue("%s", WQ_HIGHPRI | WQ_UNBOUND |\
WQ_MEM_RECLAIM | WQ_FREEZABLE,
0, "thermal_irq");
apply_workqueue_attrs(thermal_irq_wq, &attr);
}
*/
INIT_DELAYED_WORK(&data->dwork, exynos_wow_work);
return ret;
}
static int exynos_wow_ip_init(struct platform_device *pdev)
{
struct exynos_wow_data *data = platform_get_drvdata(pdev);
int i, ret = 0;
unsigned int value;
for (i = 0; i < data->nr_ip; i++) {
struct exynos_wow_ip_data *ip_data = &data->ip_data[i];
int j;
for (j = 0; j < ip_data->nr_base; j++) {
void __iomem *base = ip_data->wow_base[j];
#ifdef USE_PPMU
// Set PPMU event type
writel(PPMU_PPC_EVENT_TYPE_R_ACTIVE, base + PPMU_PPC_EVENT_EV0_TYPE);
writel(PPMU_PPC_EVENT_TYPE_RW_DATA, base + PPMU_PPC_EVENT_EV1_TYPE);
writel(PPMU_PPC_EVENT_TYPE_RW_REQUEST, base + PPMU_PPC_EVENT_EV2_TYPE);
writel(PPMU_PPC_EVENT_TYPE_R_MO_COUNT, base + PPMU_PPC_EVENT_EV3_TYPE);
#endif
// Enable counters
value = 1 << WOW_PPC_CNTENS_CCNT_OFFSET |
1 << WOW_PPC_CNTENS_PMCNT0_OFFSET |
1 << WOW_PPC_CNTENS_PMCNT1_OFFSET |
1 << WOW_PPC_CNTENS_PMCNT2_OFFSET |
1 << WOW_PPC_CNTENS_PMCNT3_OFFSET;
writel(value, base + WOW_PPC_CNTENS);
// Run counters
writel(WOW_PPC_PMNC_GLB_CNT_EN | WOW_PPC_PMNC_Q_CH_MODE, base + WOW_PPC_PMNC);
}
}
return ret;
}
static int exynos_wow_parse_dt(struct platform_device *pdev)
{
struct device_node *np, *wow_ip, *child;
struct exynos_wow_data *data = platform_get_drvdata(pdev);
struct resource res;
int i = 0;
np = pdev->dev.of_node;
if (of_property_read_u32(np, "polling_delay", &data->polling_delay))
data->polling_delay = 60000;
dev_info(&pdev->dev, "polling_delay (%u)\n", data->polling_delay);
/* Get wow-ip info */
wow_ip = of_find_node_by_name(np, "wow-ip");
data->nr_ip = of_get_child_count(wow_ip);
data->ip_data = kzalloc(sizeof(struct exynos_wow_ip_data) *
data->nr_ip, GFP_KERNEL);
for_each_available_child_of_node(wow_ip, child) {
struct exynos_wow_ip_data *ip_data = &data->ip_data[i++];
int index = 0, reg_index[10];
if (of_property_read_u32(child, "bus_width", &ip_data->bus_width))
ip_data->bus_width = 32;
dev_info(&pdev->dev, "bus_width (%u)\n", ip_data->bus_width);
if (of_property_read_u32(child, "nr_ppc", &ip_data->nr_ppc))
ip_data->nr_ppc = 1;
dev_info(&pdev->dev, "nr_ppc (%u)\n", ip_data->nr_ppc);
if (of_property_read_u32(child, "nr_base", &ip_data->nr_base))
ip_data->nr_base = 1;
dev_info(&pdev->dev, "nr_base (%u)\n", ip_data->nr_base);
if (of_property_read_bool(child, "monitor_only"))
ip_data->monitor_only = true;
dev_info(&pdev->dev, "monitor_only (%d)\n", ip_data->monitor_only);
if (of_property_read_bool(child, "use_latency"))
ip_data->use_latency = true;
dev_info(&pdev->dev, "use_latency (%d)\n", ip_data->use_latency);
strncpy(ip_data->ppc_name, child->name, PPC_NAME_LENGTH);
of_property_read_u32_array(child, "reg_index", reg_index, ip_data->nr_base);
ip_data->wow_base = kzalloc(sizeof(void __iomem *) * ip_data->nr_base, GFP_KERNEL);
for (index = 0; index < ip_data->nr_base; index++) {
of_address_to_resource(np, reg_index[index], &res);
ip_data->wow_base[index] = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
dev_info(&pdev->dev, "paddr (0x%llx) vaddr (0x%llx)\n", res.start, ip_data->wow_base[index]);
}
}
return 0;
}
static int exynos_wow_probe(struct platform_device *pdev)
{
int ret = 0;
exynos_wow = kzalloc(sizeof(struct exynos_wow_data), GFP_KERNEL);
platform_set_drvdata(pdev, exynos_wow);
ret = exynos_wow_parse_dt(pdev);
if (ret) {
dev_err(&pdev->dev, "failed to parse dt (%ld)\n", ret);
kfree(exynos_wow);
exynos_wow = NULL;
return ret;
}
mutex_init(&exynos_wow->lock);
exynos_wow_ip_init(pdev);
exynos_wow_work_init(pdev);
// wow->pm_notify.notifier_call = exynos_wow_pm_notify;
// register_pm_notifier(&wow->pm_notify);
ret = sysfs_create_group(&pdev->dev.kobj, &exynos_wow_attr_group);
if (ret)
dev_err(&pdev->dev, "cannot create exynos wow attr group");
exynos_wow->mode = WOW_ENABLED;
exynos_wow_start_polling(exynos_wow);
dev_info(&pdev->dev, "Probe exynos wow successfully\n");
return 0;
}
static int exynos_wow_remove(struct platform_device *pdev)
{
struct exynos_wow_data *data = platform_get_drvdata(pdev);
kfree(data);
return 0;
}
static int exynos_wow_suspend(struct device *dev)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct exynos_wow_data *data = platform_get_drvdata(pdev);
int ret = 0;
// lock
mutex_lock(&data->lock);
// disable wow
data->mode = WOW_DISABLED;
// unlock
mutex_unlock(&data->lock);
exynos_wow_stop_polling(data);
exynos_wow_stop_counter(data);
return ret;
}
static int exynos_wow_resume(struct device *dev)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct exynos_wow_data *data = platform_get_drvdata(pdev);
int ret = 0;
// lock
mutex_lock(&data->lock);
// enable wow
exynos_wow_ip_init(pdev);
data->mode = WOW_ENABLED;
// unlock
mutex_unlock(&data->lock);
exynos_wow_start_polling(data);
return ret;
}
static const struct of_device_id exynos_wow_match[] = {
{ .compatible = "samsung,exynos-wow", },
{ /* sentinel */ },
};
static const struct dev_pm_ops exynos_wow_pm_ops = {
.suspend_late = exynos_wow_suspend,
.resume_early = exynos_wow_resume,
};
static struct platform_driver exynos_wow_driver = {
.driver = {
.name = "exynos-wow",
.of_match_table = exynos_wow_match,
.suppress_bind_attrs = true,
.pm = &exynos_wow_pm_ops,
},
.probe = exynos_wow_probe,
.remove = exynos_wow_remove,
};
module_platform_driver(exynos_wow_driver);
MODULE_DEVICE_TABLE(of, exynos_wow_match);
MODULE_AUTHOR("Hanjun Shin <hanjun.shin@samsung.com>");
MODULE_DESCRIPTION("EXYNOS Workload Watcher Driver");
MODULE_LICENSE("GPL");