diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 37dc40d1fcfbbe89cf5c4ea136e2a8a7c5188f3e..6836d20278bf7892c09419b0797259aee61edac5 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -74,6 +74,12 @@ config DEVFREQ_GOV_PASSIVE through sysfs entries. The passive governor recommends that devfreq device uses the OPP table to get the frequency/voltage. +config DEVFREQ_GOV_UTIL + tristate "Util" + help + This governor Adjust the frequency based on the load utilization + rate. + comment "DEVFREQ Drivers" config ARM_EXYNOS_BUS_DEVFREQ @@ -143,6 +149,12 @@ config ARM_RK3399_DMC_DEVFREQ It sets the frequency for the memory controller and reads the usage counts from hardware. +config ARM_HISI_UNCORE_DEVFREQ + tristate "HiSilicon uncore DEVFREQ Driver" + depends on ARM64 && ((PCC && ACPI && ACPI_PPTT) || COMPILE_TEST) + help + HiSilicon uncore DEVFREQ Driver + source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 3ca1ad0ecb97f21486f04e34445f1f6ca14bb202..385c0560764d459eb79fd86afcb2791beb318f02 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o +obj-$(CONFIG_DEVFREQ_GOV_UTIL) += governor_util.o # DEVFREQ Drivers obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o @@ -14,6 +15,7 @@ obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ) += imx8m-ddrc.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o +obj-$(CONFIG_ARM_HISI_UNCORE_DEVFREQ) += hisi_uncore_freq.o # DEVFREQ Event Drivers obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ diff --git a/drivers/devfreq/devfreq-event.c b/drivers/devfreq/devfreq-event.c index 6765c03334bc89dcef783d231f951a0f20071a86..2625378e53cbc66b6199c1673f67e891f87e898d 100644 --- a/drivers/devfreq/devfreq-event.c +++ b/drivers/devfreq/devfreq-event.c @@ -256,6 +256,37 @@ struct devfreq_event_dev *devfreq_event_get_edev_by_phandle(struct device *dev, } EXPORT_SYMBOL_GPL(devfreq_event_get_edev_by_phandle); +/** + * devfreq_event_get_edev_by_dev() - Get the devfreq-event dev from + * specified device. + * @dev : the pointer to the given device + * + * Note that this function return the pointer of devfreq-event device. + */ +struct devfreq_event_dev *devfreq_event_get_edev_by_dev(struct device *dev) +{ + struct devfreq_event_dev *edev; + + if (!dev) + return ERR_PTR(-EINVAL); + + mutex_lock(&devfreq_event_list_lock); + list_for_each_entry(edev, &devfreq_event_list, node) { + if (edev->dev.parent == dev) + goto out; + } + + edev = NULL; +out: + mutex_unlock(&devfreq_event_list_lock); + + if (!edev) + return ERR_PTR(-ENODEV); + + return edev; +} +EXPORT_SYMBOL_GPL(devfreq_event_get_edev_by_dev); + /** * devfreq_event_get_edev_count() - Get the count of devfreq-event dev * @dev : the pointer to the given device @@ -328,7 +359,8 @@ struct devfreq_event_dev *devfreq_event_add_edev(struct device *dev, edev->dev.class = devfreq_event_class; edev->dev.release = devfreq_event_release_edev; - dev_set_name(&edev->dev, "event%d", atomic_inc_return(&event_no)); + dev_set_name(&edev->dev, "event-%s-%d", + desc->name, atomic_inc_return(&event_no)); ret = device_register(&edev->dev); if (ret < 0) { put_device(&edev->dev); diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig index 878825372f6f7d7560e443fd794293450ea4ff3d..b8fbd02a600f75a7a73476cff604b71b3c1865f4 100644 --- a/drivers/devfreq/event/Kconfig +++ b/drivers/devfreq/event/Kconfig @@ -39,4 +39,11 @@ config DEVFREQ_EVENT_ROCKCHIP_DFI This add the devfreq-event driver for Rockchip SoC. It provides DFI (DDR Monitor Module) driver to count ddr load. +config DEVFREQ_EVENT_HISI_UNCORE + tristate "HISI UNCORE DEVFREQ event Driver" + depends on HISI_PMU + help + This add the devfreq-event driver for HISI uncore. It provides UMM + (UNCORE Monitor Module) driver to count uncore load. + endif # PM_DEVFREQ_EVENT diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile index 3c847e5d5a358270ad7a889cf4e388c18eecc624..8b905a614433f031a3cb578cc0b11039dc385e85 100644 --- a/drivers/devfreq/event/Makefile +++ b/drivers/devfreq/event/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o +obj-$(CONFIG_DEVFREQ_EVENT_HISI_UNCORE) += hisi-uncore.o \ + hisi-uncore-l3c.o hisi-uncore-ddrc.o diff --git a/drivers/devfreq/event/hisi-uncore-ddrc.c b/drivers/devfreq/event/hisi-uncore-ddrc.c new file mode 100644 index 0000000000000000000000000000000000000000..4fae68d64f94d3f094febc3d83fe91804dfc4fad --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore-ddrc.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hisi-uncore-ddrc.c - Hisi uncore PMU (Platform Performance Monitoring Unit) support + * + * Copyright (c) 2024 Hisi Electronics Co., Ltd. + * Author : Xiangwei Li + * + * This driver is based on drivers/devfreq/hisi_uncore/hisi-uncore-pmu.c + */ + +#include +#include +#include +#include +#include + +#include "hisi-uncore.h" + +#define CORRECT_PERIOD (10) +#define CALC_PRECSION (1000) + +static HISI_UNCORE_EVENT_TYPE_ATTR; +static HISI_UNCORE_EVENT_CONFIG_ATTR; + +static int ddrc_get_events(struct devfreq_event_dev *edev, struct devfreq_event_data *edata) +{ + u64 load; + static int period; + static u64 last_load; + int f0, f1, p0, p1, per_step; + struct hisi_uncore_event_info *info; + + info = devfreq_event_get_drvdata(edev); + load = get_pmu_monitor_status(info); + per_step = info->freq_domain->per_step; + + if (info->is_reset) { + info->is_reset = false; + info->max_load = 0; + period = 0; + return 0; + } + + period++; + if (period == CORRECT_PERIOD) { + edata->load_count = info->max_load; + edata->total_count = info->max_load; + last_load = load; + return 0; + } + + if (period > CORRECT_PERIOD) { + period = 0; + p0 = (CALC_PRECSION * load) / last_load; + p1 = (CALC_PRECSION * last_load) / info->max_load; + f0 = (CALC_PRECSION * load) / (info->max_load * per_step); + f1 = (CALC_PRECSION * last_load) / (info->max_load * per_step); + + if (p1 < per_step || p0 > (f0 * (CALC_PRECSION * info->freq_domain->freq_max)) / + (f1 * (info->freq_domain->freq_max - info->freq_domain->freq_step))) + info->max_load = load; + } + + + info->max_load = max(info->max_load, load); + edata->load_count = load; + edata->total_count = info->max_load; + + return 0; +} + +static int ddrc_set_events(struct devfreq_event_dev *edev) +{ + return 0; +} + +static const struct devfreq_event_ops ddrc_event_ops = { + .set_event = ddrc_set_events, + .get_event = ddrc_get_events, +}; + +static int hisi_ddrc_event_probe(struct platform_device *pdev) +{ + int ret; + struct hisi_uncore_event_info *data; + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, sizeof(struct hisi_uncore_event_info), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + HISI_UNCORE_EVENT_NAME(data->name, "ddrc", dev->id); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + desc->ops = &ddrc_event_ops; + desc->driver_data = data; + desc->name = data->name; + data->desc = desc; + data->freq_domain = pdev->dev.platform_data; + data->freq_domain->per_step = + (CALC_PRECSION * data->freq_domain->freq_step) / data->freq_domain->freq_max; + + edev = devm_devfreq_event_add_edev(dev, desc); + if (IS_ERR(edev)) { + dev_err(dev, + "failed to add devfreq-event device\n"); + ret = PTR_ERR(edev); + return ret; + } + + data->edev = edev; + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_types); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_configs); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + platform_set_drvdata(pdev, data); + + mutex_init(&data->lock); + + return 0; +} + +static int hisi_ddrc_event_remove(struct platform_device *pdev) +{ + struct hisi_uncore_event_info *data = platform_get_drvdata(pdev); + + release_pmu_monitor(data); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_types); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_configs); + + return 0; +} + +static const struct platform_device_id hisi_ddrc_pmu_plat_match[] = { + { .name = "EVT-UNCORE-DDRC", }, + {} +}; +MODULE_DEVICE_TABLE(platform, hisi_ddrc_pmu_plat_match); + +struct platform_driver hisi_ddrc_event_driver = { + .probe = hisi_ddrc_event_probe, + .remove = hisi_ddrc_event_remove, + .driver = { + .name = "EVT-UNCORE-DDRC", + }, + .id_table = hisi_ddrc_pmu_plat_match, +}; + +module_platform_driver(hisi_ddrc_event_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li "); +MODULE_DESCRIPTION("Hisi uncore ddrc pmu events driver"); diff --git a/drivers/devfreq/event/hisi-uncore-l3c.c b/drivers/devfreq/event/hisi-uncore-l3c.c new file mode 100644 index 0000000000000000000000000000000000000000..a7104649fac7a3a48a1a4a2663dee852bec4cb64 --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore-l3c.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hisi-uncore-l3c.c - Hisi uncore PMU (Platform Performance Monitoring Unit) support + * + * Copyright (c) 2024 Hisi Electronics Co., Ltd. + * Author : Xiangwei Li + * + * This driver is based on drivers/devfreq/hisi_uncore/hisi-uncore-pmu.c + */ + +#include +#include +#include +#include +#include + +#include "hisi-uncore.h" + +#define CORRECT_PERIOD (10) +#define CALC_PRECSION (1000) + +static HISI_UNCORE_EVENT_TYPE_ATTR; +static HISI_UNCORE_EVENT_CONFIG_ATTR; + +static int l3c_get_events(struct devfreq_event_dev *edev, struct devfreq_event_data *edata) +{ + u64 load; + static int period; + static u64 last_load; + int f0, f1, p0, p1, per_step; + struct hisi_uncore_event_info *info; + + info = devfreq_event_get_drvdata(edev); + load = get_pmu_monitor_status(info); + per_step = info->freq_domain->per_step; + + if (info->is_reset) { + info->is_reset = false; + info->max_load = 0; + period = 0; + return 0; + } + + period++; + if (period == CORRECT_PERIOD) { + edata->load_count = info->max_load; + edata->total_count = info->max_load; + last_load = load; + return 0; + } + + if (period > CORRECT_PERIOD) { + period = 0; + p0 = (CALC_PRECSION * load) / last_load; + p1 = (CALC_PRECSION * last_load) / info->max_load; + f0 = (CALC_PRECSION * load) / (info->max_load * per_step); + f1 = (CALC_PRECSION * last_load) / (info->max_load * per_step); + + if (p1 < per_step || p0 > (f0 * (CALC_PRECSION * info->freq_domain->freq_max)) / + (f1 * (info->freq_domain->freq_max - info->freq_domain->freq_step))) + info->max_load = load; + } + + info->max_load = max(info->max_load, load); + edata->load_count = load; + edata->total_count = info->max_load; + + return 0; +} + +static int l3c_set_events(struct devfreq_event_dev *edev) +{ + return 0; +} + +static const struct devfreq_event_ops l3c_event_ops = { + .set_event = l3c_set_events, + .get_event = l3c_get_events, +}; + +static int hisi_l3c_event_probe(struct platform_device *pdev) +{ + int ret; + struct hisi_uncore_event_info *data; + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, sizeof(struct hisi_uncore_event_info), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + HISI_UNCORE_EVENT_NAME(data->name, "l3c", dev->id); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + desc->ops = &l3c_event_ops; + desc->driver_data = data; + desc->name = data->name; + data->desc = desc; + data->freq_domain = pdev->dev.platform_data; + data->freq_domain->per_step = + CALC_PRECSION / (data->freq_domain->freq_max / data->freq_domain->freq_step); + + edev = devm_devfreq_event_add_edev(dev, desc); + if (IS_ERR(edev)) { + dev_err(dev, + "failed to add devfreq-event device\n"); + ret = PTR_ERR(edev); + return ret; + } + + data->edev = edev; + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_types); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_configs); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + platform_set_drvdata(pdev, data); + + mutex_init(&data->lock); + + return 0; +} + +static int hisi_l3c_event_remove(struct platform_device *pdev) +{ + struct hisi_uncore_event_info *data = platform_get_drvdata(pdev); + + release_pmu_monitor(data); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_types); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_configs); + + return 0; +} + +static const struct platform_device_id hisi_l3c_pmu_plat_match[] = { + { .name = "EVT-UNCORE-L3C", }, + {} +}; +MODULE_DEVICE_TABLE(platform, hisi_l3c_pmu_plat_match); + +struct platform_driver hisi_l3c_event_driver = { + .probe = hisi_l3c_event_probe, + .remove = hisi_l3c_event_remove, + .driver = { + .name = "EVT-UNCORE-L3C", + }, + .id_table = hisi_l3c_pmu_plat_match, +}; + +module_platform_driver(hisi_l3c_event_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li "); +MODULE_DESCRIPTION("Hisi uncore l3c pmu events driver"); diff --git a/drivers/devfreq/event/hisi-uncore.c b/drivers/devfreq/event/hisi-uncore.c new file mode 100644 index 0000000000000000000000000000000000000000..90dba4ecdb5c62c05a42d6731be123ca8e543297 --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HiSilicon uncore devfreq event support + * + * Copyright (C) 2024 Hisilicon Limited + * Author: Xiangwei Li + * + * This code is based on the uncore PMUs event. + */ +#include +#include +#include +#include + +#include "hisi-uncore.h" + +void release_pmu_monitor(struct hisi_uncore_event_info *info) +{ + int type_id, evt_id; + struct pmu_info *pmu_info; + + mutex_lock(&info->lock); + for (type_id = 0; type_id < info->related_pmu_cnt; ++type_id) { + pmu_info = &info->related_pmus[type_id]; + for (evt_id = 0; evt_id < pmu_info->event_cnt; ++evt_id) { + if (!pmu_info->event[evt_id]) + continue; + perf_event_release_kernel(pmu_info->event[evt_id]); + pmu_info->event[evt_id] = NULL; + } + pmu_info->event_cnt = 0; + } + mutex_unlock(&info->lock); + if (devfreq_event_is_enabled(info->edev)) + devfreq_event_disable_edev(info->edev); +} +EXPORT_SYMBOL_GPL(release_pmu_monitor); + +static int reset_pmu_monitor(struct hisi_uncore_event_info *info) +{ + int err; + struct pmu_info *pmu_info; + int type_id, cfg_id; + struct perf_event_attr attr = { + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 0, + }; + + info->is_reset = true; + + if (info->config_cnt == 0 || info->related_pmu_cnt == 0) + return 0; + + mutex_lock(&info->lock); + for (type_id = 0; type_id < info->related_pmu_cnt; ++type_id) { + pmu_info = &info->related_pmus[type_id]; + attr.type = pmu_info->type; + for (cfg_id = 0; cfg_id < info->config_cnt; ++cfg_id) { + attr.config = info->configs[cfg_id]; + pmu_info->event[cfg_id] = perf_event_create_kernel_counter(&attr, + smp_processor_id(), NULL, NULL, NULL); + if (IS_ERR(pmu_info->event[cfg_id])) { + err = PTR_ERR(pmu_info->event[cfg_id]); + pmu_info->event[cfg_id] = NULL; + release_pmu_monitor(info); + info->related_pmu_cnt = 0; + return err; + } + pmu_info->event_cnt++; + } + } + mutex_unlock(&info->lock); + + if (!devfreq_event_is_enabled(info->edev)) + devfreq_event_enable_edev(info->edev); + + return 0; +} + +u64 get_pmu_monitor_status(struct hisi_uncore_event_info *info) +{ + int t_id, c_id; + u64 value, max_load; + u64 enabled, running; + struct pmu_info *pmu_info; + + max_load = 0; + + mutex_lock(&info->lock); + for (t_id = 0; t_id < info->related_pmu_cnt; ++t_id) { + pmu_info = &info->related_pmus[t_id]; + value = 0; + for (c_id = 0; c_id < info->config_cnt; ++c_id) { + if (!pmu_info->event[c_id]) { + value = 0; + break; + } + value += perf_event_read_value(pmu_info->event[c_id], + &enabled, &running); + } + + max_load = max(max_load, value - pmu_info->load); + pmu_info->load = value; + } + + mutex_unlock(&info->lock); + return max_load; +} +EXPORT_SYMBOL_GPL(get_pmu_monitor_status); + +ssize_t hisi_uncore_event_configs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + for (i = 0; i < info->config_cnt; ++i) + sprintf(buf, "%s %lld\n", buf, info->configs[i]); + + return sprintf(buf, "%s\n", buf); +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_configs_show); + +ssize_t hisi_uncore_event_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail, cfg_cnt; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + if (!buf) + return 0; + + release_pmu_monitor(info); + + head = 0; + cfg_cnt = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (cfg_cnt < EVENT_CONFIG_MAX_CNT) { + while (head < count && isspace(buf[head])) + head++; + + if (!isalnum(buf[head])) + break; + + tail = head + 1; + while (tail < count && isalnum(buf[tail])) + tail++; + + strncpy(item, buf + head, tail - head); + item[tail - head] = '\0'; + head = tail; + + err = kstrtou64(item, 10, &info->configs[cfg_cnt]); + if (err) { + info->config_cnt = 0; + return err; + } + + cfg_cnt++; + } + + info->config_cnt = cfg_cnt; + kfree(item); + + err = reset_pmu_monitor(info); + if (err) + return err; + + return count; +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_configs_store); + +ssize_t hisi_uncore_event_types_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + for (i = 0; i < info->related_pmu_cnt; ++i) + sprintf(buf, "%s %d\n", buf, info->related_pmus[i].type); + + return sprintf(buf, "%s\n", buf); +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_types_show); + +ssize_t hisi_uncore_event_types_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail, type_cnt; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + if (!buf) + return 0; + + release_pmu_monitor(info); + + head = 0; + type_cnt = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (type_cnt < EVENT_TYPE_MAX_CNT) { + while (head < count && isspace(buf[head])) + head++; + + if (!isalnum(buf[head])) + break; + + tail = head + 1; + while (tail < count && isalnum(buf[tail])) + tail++; + + strncpy(item, buf + head, tail - head); + item[tail - head] = '\0'; + head = tail; + + err = kstrtou32(item, 10, &info->related_pmus[type_cnt].type); + if (err) { + info->related_pmu_cnt = 0; + return err; + } + + type_cnt++; + } + + info->related_pmu_cnt = type_cnt; + kfree(item); + + err = reset_pmu_monitor(info); + if (err) + return err; + + return count; +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_types_store); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li "); diff --git a/drivers/devfreq/event/hisi-uncore.h b/drivers/devfreq/event/hisi-uncore.h new file mode 100644 index 0000000000000000000000000000000000000000..7a4a41e36a874a9978d7c67bae51a7ab987e3fd9 --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HiSilicon uncore devfreq event support + * + * Copyright (C) 2024 Hisilicon Limited + * Author: Xiangwei Li + * + * This code is based on the uncore PMUs event. + */ +#ifndef __HISI_UNCORE_H__ +#define __HISI_UNCORE_H__ + +#include +#include +#include +#include + +#define HISI_UNCORE_EVENT_NAME(name, type_name, package_id) ({ \ + int len; \ + len = sprintf(name, "uncore-%s-%d", type_name, package_id); \ + len; }) + +#define HISI_UNCORE_EVENT_TYPE_ATTR \ + DEVICE_ATTR_RW(hisi_uncore_event_types) + +#define HISI_UNCORE_EVENT_CONFIG_ATTR \ + DEVICE_ATTR_RW(hisi_uncore_event_configs) + +#define EVENT_TYPE_MAX_CNT (20) +#define EVENT_TYPE_INVALID_VAL (0xffff) +#define EVENT_CONFIG_MAX_CNT (2) +#define EVENT_CONFIG_INVALID_VAL (0xffff) + +/* + * tunable domain + */ +struct freq_domain { + int per_step; + unsigned long freq_min; + unsigned long freq_max; + unsigned long freq_step; +}; + +/* + * The signle uncore pmu info. + */ +struct pmu_info { + __u32 type; + u64 load; + int event_cnt; + struct perf_event *event[EVENT_CONFIG_MAX_CNT]; +}; + +/* + * The uncore pmu controller can monitor device load by read PMU. + */ +struct hisi_uncore_event_info { + char name[0x10]; + bool is_reset; + int config_cnt; + __u64 configs[EVENT_CONFIG_MAX_CNT]; + u64 max_load; + struct freq_domain *freq_domain; + struct device *dev; + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct devfreq_perf_event *event; + int related_pmu_cnt; + struct pmu_info related_pmus[EVENT_TYPE_MAX_CNT]; + struct mutex lock; +}; + +ssize_t hisi_uncore_event_configs_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_uncore_event_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +ssize_t hisi_uncore_event_types_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_uncore_event_types_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +void release_pmu_monitor(struct hisi_uncore_event_info *info); +u64 get_pmu_monitor_status(struct hisi_uncore_event_info *info); + +#endif /* __HISI_UNCORE_PMU_H__ */ diff --git a/drivers/devfreq/governor_util.c b/drivers/devfreq/governor_util.c new file mode 100644 index 0000000000000000000000000000000000000000..bdd33a712f2a05024577d7f3ef9dbd0491b61fe3 --- /dev/null +++ b/drivers/devfreq/governor_util.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Frequency modulation based on util + * + * Copyright (C) 2024 HISI UNCORE + * Xiangwei Li + */ + +#include +#include +#include +#include +#include "governor.h" + +/* Default constants for DevFreq-Util (DFUL) */ +#define BW_UTIL_DEFAULT (50) + +static int devfreq_util_func(struct devfreq *df, + unsigned long *freq) +{ + int err; + struct devfreq_dev_status *stat; + unsigned long cur_bw, max_bw; + unsigned long cur_freq, step_freq; + unsigned long min_freq, max_freq; + unsigned int util, dful_val = BW_UTIL_DEFAULT; + struct devfreq_util_data *data = df->data; + + err = devfreq_update_stats(df); + if (err) + return err; + + stat = &df->last_status; + + if (data) + dful_val = data->dful_val; + + if (dful_val > 100) + return -EINVAL; + + /* Assume MAX if it is going to be divided by zero */ + if (stat->total_time == 0) { + *freq = df->scaling_max_freq; + return 0; + } + + /* Prevent overflow */ + if (stat->busy_time >= (1 << 24) || stat->total_time >= (1 << 24)) { + stat->busy_time >>= 7; + stat->total_time >>= 7; + } + + min_freq = df->scaling_min_freq; + max_freq = df->scaling_max_freq; + cur_freq = df->previous_freq; + cur_bw = stat->busy_time; + max_bw = stat->total_time; + + /* Set the desired frequency based on the load */ + util = div_u64(cur_bw * 100, + max_bw * div_u64(cur_freq * 100, max_freq) / 100); + *freq = cur_freq * div_u64(util * 100, dful_val) / 100; + + step_freq = div_u64(max_freq - min_freq, + df->profile->max_state - 1); + *freq = div_u64(*freq, step_freq) * step_freq; + *freq = clamp(*freq, min_freq, max_freq); + + return 0; +} + +static int devfreq_util_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + switch (event) { + case DEVFREQ_GOV_START: + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + break; + + case DEVFREQ_GOV_UPDATE_INTERVAL: + devfreq_update_interval(devfreq, (unsigned int *)data); + break; + + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_util = { + .name = DEVFREQ_GOV_UTIL, + .get_target_freq = devfreq_util_func, + .event_handler = devfreq_util_handler, +}; + +static int __init devfreq_util_init(void) +{ + return devfreq_add_governor(&devfreq_util); +} +subsys_initcall(devfreq_util_init); + +static void __exit devfreq_util_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_util); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); +} +module_exit(devfreq_util_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c new file mode 100644 index 0000000000000000000000000000000000000000..511e17d70cd6e021ccad71cce3a497346b1f05fc --- /dev/null +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -0,0 +1,681 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HiSilicon uncore frequency scaling driver + * + * Copyright (c) 2024 HiSilicon Co., Ltd + * Author : Jie Zhan + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "event/hisi-uncore.h" + +#include + +#define HZ_PER_MHZ 1000000 + +/* Don't care OPP votlage, take 1V as default */ +#define DEF_OPP_VOLT_UV 1000000 + +#define RELATED_EVENT_MAX_CNT 4 +#define RELATED_EVENT_NAME_LEN 10 + +struct related_event { + char name[RELATED_EVENT_NAME_LEN]; + struct platform_device *pdev; + struct devfreq_event_dev *edev; +}; + +struct hisi_uncore_freq { + struct device *dev; + struct mbox_client cl; + struct pcc_mbox_chan *pchan; + void __iomem *pcc_shmem_addr; + int chan_id; + struct freq_domain freq_domain; + struct devfreq *devfreq; + int related_package; + struct cpumask related_cpus; + int related_event_cnt; + struct related_event related_events[RELATED_EVENT_MAX_CNT]; +}; + +struct hisi_uncore_pcc_data { + u16 status; + u16 resv; + u32 data; +}; + +struct hisi_uncore_pcc_shmem { + struct acpi_pcct_shared_memory head; + struct hisi_uncore_pcc_data pcc_data; +}; + +enum hisi_uncore_pcc_cmd_type { + HUCF_PCC_CMD_GET_CAP = 0, + HUCF_PCC_CMD_GET_FREQ, + HUCF_PCC_CMD_SET_FREQ, + HUCF_PCC_CMD_GET_MODE, + HUCF_PCC_CMD_SET_MODE, + HUCF_PCC_CMD_GET_PLAT_FREQ_MIN, + HUCF_PCC_CMD_GET_PLAT_FREQ_MAX, + HUCF_PCC_CMD_GET_PLAT_FREQ_STEP, + HUCF_PCC_CMD_MAX = 256, +}; + +enum hisi_uncore_freq_mode { + HUCF_MODE_PLATFORM = 0, + HUCF_MODE_OS, +}; + +/* Timeout = PCC nominal latency * NUM */ +#define HUCF_PCC_POLL_TIMEOUT_NUM 1000 +#define HUCF_PCC_POLL_INTERVAL_US 5 + +static int hisi_uncore_request_pcc_chan(struct hisi_uncore_freq *uncore) +{ + struct pcc_mbox_chan *pcc_chan; + int rc; + + uncore->cl = (struct mbox_client) { + .dev = uncore->dev, + .tx_block = false, + .knows_txdone = true, + }; + + pcc_chan = pcc_mbox_request_channel(&uncore->cl, uncore->chan_id); + if (IS_ERR(pcc_chan)) { + dev_err(uncore->dev, "Failed to request PCC channel %u\n", + uncore->chan_id); + return -ENODEV; + } + + uncore->pchan = pcc_chan; + if (!pcc_chan->shmem_base_addr) { + dev_err(uncore->dev, "Invalid PCC shared memory address\n"); + rc = -EINVAL; + goto err_pcc_chan_free; + } + + if (pcc_chan->shmem_size < sizeof(struct hisi_uncore_pcc_shmem)) { + dev_err(uncore->dev, "Invalid PCC shared memory size (%lluB)\n", + pcc_chan->shmem_size); + rc = -EINVAL; + goto err_pcc_chan_free; + } + + uncore->pcc_shmem_addr = ioremap(pcc_chan->shmem_base_addr, + pcc_chan->shmem_size); + if (!uncore->pcc_shmem_addr) { + rc = -ENOMEM; + goto err_pcc_chan_free; + } + + return 0; + +err_pcc_chan_free: + pcc_mbox_free_channel(uncore->pchan); + return rc; +} + +static void hisi_uncore_free_pcc_chan(struct hisi_uncore_freq *uncore) +{ + if (uncore->pchan) + pcc_mbox_free_channel(uncore->pchan); +} + +static acpi_status hisi_uncore_pcc_reg_scan(struct acpi_resource *res, + void *ctx) +{ + struct acpi_resource_generic_register *reg; + struct hisi_uncore_freq *uncore; + + if (!res || res->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + return AE_OK; + + reg = &res->data.generic_reg; + if (reg->space_id != ACPI_ADR_SPACE_PLATFORM_COMM) + return AE_OK; + + /* PCC subspace ID stored in Access Size */ + uncore = ctx; + uncore->chan_id = reg->access_size; + return AE_CTRL_TERMINATE; +} + +static int hisi_uncore_init_pcc_chan(struct hisi_uncore_freq *uncore) +{ + acpi_handle handle = ACPI_HANDLE(uncore->dev); + acpi_status status; + + uncore->chan_id = -1; + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + hisi_uncore_pcc_reg_scan, uncore); + if (ACPI_FAILURE(status) || uncore->chan_id < 0) { + dev_err(uncore->dev, "Failed to get a PCC channel\n"); + return -ENODEV; + } + + return hisi_uncore_request_pcc_chan(uncore); +} + +static int hisi_uncore_cmd_send(struct hisi_uncore_freq *uncore, + u8 cmd, u32 *data) +{ + struct hisi_uncore_pcc_shmem __iomem *addr = uncore->pcc_shmem_addr; + struct pcc_mbox_chan *pchan = uncore->pchan; + struct hisi_uncore_pcc_shmem shmem; + u16 status; + int rc; + + /* Copy data */ + shmem.head = (struct acpi_pcct_shared_memory) { + .signature = PCC_SIGNATURE | uncore->chan_id, + .command = cmd, + .status = 0, + }; + shmem.pcc_data.data = *data; + memcpy_toio(addr, &shmem, sizeof(shmem)); + + /* Ring doorbell */ + rc = mbox_send_message(pchan->mchan, &cmd); + if (rc < 0) { + dev_err(uncore->dev, "Failed to send mbox message, %d\n", rc); + return rc; + } + + /* Wait status */ + rc = readw_poll_timeout(&addr->head.status, status, + status & (PCC_STATUS_CMD_COMPLETE || + PCC_STATUS_ERROR), + HUCF_PCC_POLL_INTERVAL_US, + pchan->latency * HUCF_PCC_POLL_TIMEOUT_NUM); + if (rc) { + dev_err(uncore->dev, "PCC channel response timeout\n"); + return -ETIME; + } + + if (status & PCC_STATUS_ERROR) { + dev_err(uncore->dev, "PCC cmd error\n"); + return -EIO; + } + + /* Success, copy data back */ + memcpy_fromio(data, &addr->pcc_data.data, sizeof(*data)); + + mbox_client_txdone(pchan->mchan, 0); + return rc; +} + +static int hisi_uncore_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + u32 data = *freq / HZ_PER_MHZ; + + if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) + data = roundup(data, uncore->freq_domain.freq_step); + else + data = rounddown(data, uncore->freq_domain.freq_step); + + data = clamp((unsigned long)data, + uncore->freq_domain.freq_min, uncore->freq_domain.freq_max); + + return hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_FREQ, &data); +} + +static int hisi_uncore_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + int rc, i, ratio; + struct related_event *event; + struct devfreq_event_data edata; + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + + ratio = 0; + for (i = 0; i < uncore->related_event_cnt; ++i) { + event = &uncore->related_events[i]; + event->edev = devfreq_event_get_edev_by_dev(&event->pdev->dev); + if (!event->edev) + continue; + rc = devfreq_event_get_event(event->edev, &edata); + if (rc) + return rc; + + if (edata.load_count == edata.total_count) { + stat->busy_time = edata.load_count; + stat->total_time = edata.total_count; + return 0; + } + + if (ratio <= edata.load_count * 100 / edata.total_count) { + stat->busy_time = edata.load_count; + stat->total_time = edata.total_count; + ratio = edata.load_count * 100 / edata.total_count; + } + } + + return 0; +} + +static int hisi_uncore_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + u32 data; + int rc; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_FREQ, &data); + *freq = data * HZ_PER_MHZ; + + return rc; +} + +static int hisi_uncore_add_opp(struct hisi_uncore_freq *uncore) +{ + unsigned long freq_mhz; + u32 data; + int rc; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_MIN, &data); + if (rc) + return rc; + uncore->freq_domain.freq_min = data; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_MAX, &data); + if (rc) + return rc; + uncore->freq_domain.freq_max = data; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_STEP, &data); + if (rc) + return rc; + uncore->freq_domain.freq_step = data; + + for (freq_mhz = uncore->freq_domain.freq_min; freq_mhz <= uncore->freq_domain.freq_max; + freq_mhz += uncore->freq_domain.freq_step) { + rc = dev_pm_opp_add(uncore->dev, freq_mhz * HZ_PER_MHZ, DEF_OPP_VOLT_UV); + if (rc) { + unsigned long freq_curr = freq_mhz; + + dev_err(uncore->dev, "Add OPP %lu failed (%d)\n", freq_mhz, rc); + + for (freq_mhz = uncore->freq_domain.freq_min; freq_mhz < freq_curr; + freq_mhz += uncore->freq_domain.freq_step) + dev_pm_opp_remove(uncore->dev, + freq_mhz * HZ_PER_MHZ); + break; + } + } + + return rc; +} + +static void hisi_uncore_remove_opp(struct hisi_uncore_freq *uncore) +{ + unsigned long freq_mhz; + + for (freq_mhz = uncore->freq_domain.freq_min; freq_mhz <= uncore->freq_domain.freq_max; + freq_mhz += uncore->freq_domain.freq_step) + dev_pm_opp_remove(uncore->dev, freq_mhz * HZ_PER_MHZ); +} + +static int hisi_uncore_devfreq_register(struct hisi_uncore_freq *uncore) +{ + struct devfreq_dev_profile *profile; + u32 data; + + data = HUCF_MODE_OS; + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data); + msleep(200); + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_FREQ, &data); + dev_info(uncore->dev, "init freq %u\n", data); + + profile = devm_kzalloc(uncore->dev, sizeof(*profile), GFP_KERNEL); + if (!profile) + return -ENOMEM; + + profile->initial_freq = (unsigned long)data * HZ_PER_MHZ; + profile->polling_ms = 1000; + profile->timer = DEVFREQ_TIMER_DELAYED; + profile->target = hisi_uncore_target; + profile->get_dev_status = hisi_uncore_get_dev_status; + profile->get_cur_freq = hisi_uncore_get_cur_freq; + + uncore->devfreq = devm_devfreq_add_device(uncore->dev, profile, + DEVFREQ_GOV_USERSPACE, NULL); + if (IS_ERR(uncore->devfreq)) { + dev_err(uncore->dev, "Failed to add devfreq device\n"); + return PTR_ERR(uncore->devfreq); + } + + return 0; +} + +static int hisi_uncore_mark_related_cpus(struct hisi_uncore_freq *uncore, + char *property, + int (get_topo_id)(int cpu), + struct cpumask *(get_cpumask)(int cpu)) +{ + unsigned int i, cpu; + size_t len; + u32 *num; + int rc; + + rc = device_property_count_u32(uncore->dev, property); + if (rc < 0) + return rc; + + len = rc; + num = kcalloc(len, sizeof(*num), GFP_KERNEL); + if (!num) + return -ENOMEM; + + rc = device_property_read_u32_array(uncore->dev, property, num, len); + if (rc) + goto out; + + for (i = 0; i < len; i++) { + for_each_possible_cpu(cpu) { + if (get_topo_id(cpu) == num[i]) { + cpumask_or(&uncore->related_cpus, + &uncore->related_cpus, + get_cpumask(cpu)); + break; + } + } + } + +out: + kfree(num); + return rc; +} + +static int get_package_id(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static struct cpumask *get_package_cpumask(int cpu) +{ + return topology_core_cpumask(cpu); +} + +static int get_cluster_id(int cpu) +{ + return topology_cluster_id(cpu); +} + +static struct cpumask *get_cluster_cpumask(int cpu) +{ + return topology_cluster_cpumask(cpu); +} + +static int hisi_uncore_mark_related_cpus_wrap(struct hisi_uncore_freq *uncore) +{ + int rc; + + cpumask_clear(&uncore->related_cpus); + + rc = hisi_uncore_mark_related_cpus(uncore, "related-package", + get_package_id, + get_package_cpumask); + if (rc == 0) + return rc; + + rc = hisi_uncore_mark_related_cpus(uncore, "related-cluster", + get_cluster_id, + get_cluster_cpumask); + return rc; +} + +static ssize_t related_cpus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + return cpumap_print_to_pagebuf(true, buf, &uncore->related_cpus); +} +DEVICE_ATTR_RO(related_cpus); + +static int get_related_package(struct hisi_uncore_freq *uncore) +{ + int rc; + + rc = device_property_read_u32(uncore->dev, "related-package", + &uncore->related_package); + if (rc) { + dev_err(uncore->dev, "failed to read related-package property\n"); + return rc; + } + + return 0; +} + +static ssize_t related_package_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + return sprintf(buf, "%u\n", uncore->related_package); +} +DEVICE_ATTR_RO(related_package); + +static int creat_related_event(struct hisi_uncore_freq *uncore, char *name) +{ + int evt_id; + struct related_event *event; + char dev_name[RELATED_EVENT_NAME_LEN + 10]; + + evt_id = uncore->related_event_cnt; + event = &uncore->related_events[evt_id]; + + sprintf(dev_name, "%s-%s", "EVT-UNCORE", name); + event->pdev = platform_device_register_data( + uncore->dev, + dev_name, + uncore->related_package, + &(uncore->freq_domain), + sizeof(uncore->freq_domain)); + if (IS_ERR(event->pdev)) + return PTR_ERR(event->pdev); + + strncpy(event->name, name, strlen(name)); + + return 0; +} + +static void remove_related_event(struct hisi_uncore_freq *uncore) +{ + int evt_id; + struct related_event *event; + + devfreq_suspend_device(uncore->devfreq); + + for (evt_id = 0; evt_id < uncore->related_event_cnt; ++evt_id) { + event = &uncore->related_events[evt_id]; + event->edev = NULL; + memset(event->name, 0, RELATED_EVENT_NAME_LEN); + platform_device_unregister(event->pdev); + } + + uncore->related_event_cnt = 0; +} + +static ssize_t related_events_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail; + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + if (!buf) + return 0; + + remove_related_event(uncore); + + head = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (uncore->related_event_cnt < RELATED_EVENT_MAX_CNT) { + while (head < count && isspace(buf[head])) + head++; + + if (!isalnum(buf[head])) + break; + + tail = head + 1; + while (tail < count && isalnum(buf[tail])) + tail++; + + strncpy(item, buf + head, tail - head); + item[tail - head] = '\0'; + head = tail; + + err = creat_related_event(uncore, item); + if (err) { + kfree(item); + return err; + } + uncore->related_event_cnt++; + } + + devfreq_resume_device(uncore->devfreq); + + kfree(item); + return count; +} + +static ssize_t related_events_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int evt_id; + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + for (evt_id = 0; evt_id < uncore->related_event_cnt; ++evt_id) + sprintf(buf, "%s %s", buf, uncore->related_events[evt_id].name); + + return sprintf(buf, "%s\n", buf); +} +DEVICE_ATTR_RW(related_events); + +static int hisi_uncore_probe(struct platform_device *pdev) +{ + struct hisi_uncore_freq *uncore; + int rc; + + uncore = devm_kzalloc(&pdev->dev, sizeof(*uncore), GFP_KERNEL); + if (!uncore) + return -ENOMEM; + + uncore->dev = &pdev->dev; + platform_set_drvdata(pdev, uncore); + + rc = hisi_uncore_init_pcc_chan(uncore); + if (rc) { + dev_err(&pdev->dev, "PCC channel init failed %d", rc); + return rc; + } + + rc = hisi_uncore_add_opp(uncore); + if (rc) { + dev_err(&pdev->dev, "Register freq failed (%d)\n", rc); + goto err_free_pcc; + } + + rc = hisi_uncore_devfreq_register(uncore); + if (rc) { + dev_err(&pdev->dev, "Failed to register devfreq (%d)\n", rc); + goto err_free_opp; + } + + rc = get_related_package(uncore); + if (rc) + goto err_free_opp; + + hisi_uncore_mark_related_cpus_wrap(uncore); + + rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_cpus); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + + rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_package); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + + rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_events); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + + return 0; + +err_free_opp: + hisi_uncore_remove_opp(uncore); +err_free_pcc: + hisi_uncore_free_pcc_chan(uncore); + + return rc; +} + +static int hisi_uncore_remove(struct platform_device *pdev) +{ + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + u32 data = HUCF_MODE_PLATFORM; + + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data); + + hisi_uncore_remove_opp(uncore); + hisi_uncore_free_pcc_chan(uncore); + remove_related_event(uncore); + device_remove_file(&uncore->devfreq->dev, &dev_attr_related_cpus); + device_remove_file(&uncore->devfreq->dev, &dev_attr_related_package); + + return 0; +} + +static const struct acpi_device_id hisi_uncore_acpi_match[] = { + { "HISI04F1", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, hisi_uncore_acpi_match); + +static struct platform_driver hisi_uncore_platdrv = { + .probe = hisi_uncore_probe, + .remove = hisi_uncore_remove, + .driver = { + .name = "hisi_uncore_freq", + .acpi_match_table = hisi_uncore_acpi_match, + }, +}; +module_platform_driver(hisi_uncore_platdrv); + +MODULE_DESCRIPTION("HiSilicon uncore frequency scaling driver"); +MODULE_AUTHOR("Jie Zhan "); +MODULE_AUTHOR("Xiangwei Li "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/devfreq-event.h b/include/linux/devfreq-event.h index 4a50a5c71a5ff037265d47c6123f05eb67af911a..1c7f64f09126bc8d04fb4f65532b1ad1a1a7e3ec 100644 --- a/include/linux/devfreq-event.h +++ b/include/linux/devfreq-event.h @@ -109,6 +109,8 @@ extern struct devfreq_event_dev *devfreq_event_get_edev_by_phandle( struct device *dev, const char *phandle_name, int index); +extern struct devfreq_event_dev *devfreq_event_get_edev_by_dev( + struct device *dev); extern int devfreq_event_get_edev_count(struct device *dev, const char *phandle_name); extern struct devfreq_event_dev *devfreq_event_add_edev(struct device *dev, @@ -162,6 +164,12 @@ static inline struct devfreq_event_dev *devfreq_event_get_edev_by_phandle( return ERR_PTR(-EINVAL); } +static inline struct devfreq_event_dev *devfreq_event_get_edev_by_dev( + struct device *dev) +{ + return ERR_PTR(-EINVAL); +} + static inline int devfreq_event_get_edev_count(struct device *dev, const char *phandle_name) { diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index fc4c7ad49cee3150ccff33a76c2eb764d7100dc0..a37fbe405529e3704e2134621aed71b8ffca7929 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -23,6 +23,7 @@ #define DEVFREQ_GOV_POWERSAVE "powersave" #define DEVFREQ_GOV_USERSPACE "userspace" #define DEVFREQ_GOV_PASSIVE "passive" +#define DEVFREQ_GOV_UTIL "util" /* DEVFREQ notifier interface */ #define DEVFREQ_TRANSITION_NOTIFIER (0) @@ -314,6 +315,20 @@ struct devfreq_passive_data { }; #endif +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_UTIL) +/** + * struct devfreq_util_data - ``void *data`` fed to struct devfreq + * and devfreq_add_device + * @dful_val: Resource utilization baseline. + * + * If the fed devfreq_util_data pointer is NULL to the governor, + * the governor uses the default values. + */ +struct devfreq_util_data { + unsigned int dful_val; +}; +#endif + #else /* !CONFIG_PM_DEVFREQ */ static inline struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile,