diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/devfreq | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
24 files changed, 9052 insertions, 0 deletions
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig new file mode 100644 index 000000000..9754d8b31 --- /dev/null +++ b/drivers/devfreq/Kconfig @@ -0,0 +1,156 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig PM_DEVFREQ + bool "Generic Dynamic Voltage and Frequency Scaling (DVFS) support" + select SRCU + select PM_OPP + help + A device may have a list of frequencies and voltages available. + devfreq, a generic DVFS framework can be registered for a device + in order to let the governor provided to devfreq choose an + operating frequency based on the device driver's policy. + + Each device may have its own governor and policy. Devfreq can + reevaluate the device state periodically and/or based on the + notification to "nb", a notifier block, of devfreq. + + Like some CPUs with CPUfreq, a device may have multiple clocks. + However, because the clock frequencies of a single device are + determined by the single device's state, an instance of devfreq + is attached to a single device and returns a "representative" + clock frequency of the device, which is also attached + to a device by 1-to-1. The device registering devfreq takes the + responsibility to "interpret" the representative frequency and + to set its every clock accordingly with the "target" callback + given to devfreq. + + When OPP is used with the devfreq device, it is recommended to + register devfreq's nb to the OPP's notifier head. If OPP is + used with the devfreq device, you may use OPP helper + functions defined in devfreq.h. + +if PM_DEVFREQ + +comment "DEVFREQ Governors" + +config DEVFREQ_GOV_SIMPLE_ONDEMAND + tristate "Simple Ondemand" + help + Chooses frequency based on the recent load on the device. Works + similar as ONDEMAND governor of CPUFREQ does. A device with + Simple-Ondemand should be able to provide busy/total counter + values that imply the usage rate. A device may provide tuned + values to the governor with data field at devfreq_add_device(). + +config DEVFREQ_GOV_PERFORMANCE + tristate "Performance" + help + Sets the frequency at the maximum available frequency. + This governor always returns UINT_MAX as frequency so that + the DEVFREQ framework returns the highest frequency available + at any time. + +config DEVFREQ_GOV_POWERSAVE + tristate "Powersave" + help + Sets the frequency at the minimum available frequency. + This governor always returns 0 as frequency so that + the DEVFREQ framework returns the lowest frequency available + at any time. + +config DEVFREQ_GOV_USERSPACE + tristate "Userspace" + help + Sets the frequency at the user specified one. + This governor returns the user configured frequency if there + has been an input to /sys/devices/.../userspace/set_freq. + Otherwise, the governor does not change the frequency + given at the initialization. + +config DEVFREQ_GOV_PASSIVE + tristate "Passive" + help + Sets the frequency based on the frequency of its parent devfreq + device. This governor does not change the frequency by itself + through sysfs entries. The passive governor recommends that + devfreq device uses the OPP table to get the frequency/voltage. + +comment "DEVFREQ Drivers" + +config ARM_EXYNOS_BUS_DEVFREQ + tristate "ARM Exynos Generic Memory Bus DEVFREQ Driver" + depends on ARCH_EXYNOS || COMPILE_TEST + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select DEVFREQ_GOV_PASSIVE + select DEVFREQ_EVENT_EXYNOS_PPMU + select PM_DEVFREQ_EVENT + help + This adds the common DEVFREQ driver for Exynos Memory bus. Exynos + Memory bus has one more group of memory bus (e.g, MIF and INT block). + Each memory bus group could contain many memoby bus block. It reads + PPMU counters of memory controllers by using DEVFREQ-event device + and adjusts the operating frequencies and voltages with OPP support. + This does not yet operate with optimal voltages. + +config ARM_IMX_BUS_DEVFREQ + tristate "i.MX Generic Bus DEVFREQ Driver" + depends on ARCH_MXC || COMPILE_TEST + select DEVFREQ_GOV_USERSPACE + help + This adds the generic DEVFREQ driver for i.MX interconnects. It + allows adjusting NIC/NOC frequency. + +config ARM_IMX8M_DDRC_DEVFREQ + tristate "i.MX8M DDRC DEVFREQ Driver" + depends on (ARCH_MXC && HAVE_ARM_SMCCC) || \ + (COMPILE_TEST && HAVE_ARM_SMCCC) + select DEVFREQ_GOV_USERSPACE + help + This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows + adjusting DRAM frequency. + +config ARM_TEGRA_DEVFREQ + tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver" + depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \ + ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \ + ARCH_TEGRA_210_SOC || \ + COMPILE_TEST + depends on COMMON_CLK + help + This adds the DEVFREQ driver for the Tegra family of SoCs. + It reads ACTMON counters of memory controllers and adjusts the + operating frequencies and voltages with OPP support. + +config ARM_MEDIATEK_CCI_DEVFREQ + tristate "MEDIATEK CCI DEVFREQ Driver" + depends on ARM_MEDIATEK_CPUFREQ || COMPILE_TEST + select DEVFREQ_GOV_PASSIVE + help + This adds a devfreq driver for MediaTek Cache Coherent Interconnect + which is shared the same regulators with the cpu cluster. It can track + buck voltages and update a proper CCI frequency. Use the notification + to get the regulator status. + +config ARM_RK3399_DMC_DEVFREQ + tristate "ARM RK3399 DMC DEVFREQ Driver" + depends on (ARCH_ROCKCHIP && HAVE_ARM_SMCCC) || \ + (COMPILE_TEST && HAVE_ARM_SMCCC) + select DEVFREQ_EVENT_ROCKCHIP_DFI + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_DEVFREQ_EVENT + help + This adds the DEVFREQ driver for the RK3399 DMC(Dynamic Memory Controller). + It sets the frequency for the memory controller and reads the usage counts + from hardware. + +config ARM_SUN8I_A33_MBUS_DEVFREQ + tristate "sun8i/sun50i MBUS DEVFREQ Driver" + depends on ARCH_SUNXI || COMPILE_TEST + depends on COMMON_CLK + select DEVFREQ_GOV_SIMPLE_ONDEMAND + help + This adds the DEVFREQ driver for the MBUS controller in some + Allwinner sun8i (A33 through H3) and sun50i (A64 and H5) SoCs. + +source "drivers/devfreq/event/Kconfig" + +endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile new file mode 100644 index 000000000..bf40d0492 --- /dev/null +++ b/drivers/devfreq/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PM_DEVFREQ) += devfreq.o +obj-$(CONFIG_PM_DEVFREQ_EVENT) += devfreq-event.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o +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 + +# DEVFREQ Drivers +obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o +obj-$(CONFIG_ARM_IMX_BUS_DEVFREQ) += imx-bus.o +obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ) += imx8m-ddrc.o +obj-$(CONFIG_ARM_MEDIATEK_CCI_DEVFREQ) += mtk-cci-devfreq.o +obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o +obj-$(CONFIG_ARM_SUN8I_A33_MBUS_DEVFREQ) += sun8i-a33-mbus.o +obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o + +# DEVFREQ Event Drivers +obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ diff --git a/drivers/devfreq/devfreq-event.c b/drivers/devfreq/devfreq-event.c new file mode 100644 index 000000000..6765c0333 --- /dev/null +++ b/drivers/devfreq/devfreq-event.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * devfreq-event: a framework to provide raw data and events of devfreq devices + * + * Copyright (C) 2015 Samsung Electronics + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * + * This driver is based on drivers/devfreq/devfreq.c. + */ + +#include <linux/devfreq-event.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/of.h> + +static struct class *devfreq_event_class; + +/* The list of all devfreq event list */ +static LIST_HEAD(devfreq_event_list); +static DEFINE_MUTEX(devfreq_event_list_lock); + +#define to_devfreq_event(DEV) container_of(DEV, struct devfreq_event_dev, dev) + +/** + * devfreq_event_enable_edev() - Enable the devfreq-event dev and increase + * the enable_count of devfreq-event dev. + * @edev : the devfreq-event device + * + * Note that this function increase the enable_count and enable the + * devfreq-event device. The devfreq-event device should be enabled before + * using it by devfreq device. + */ +int devfreq_event_enable_edev(struct devfreq_event_dev *edev) +{ + int ret = 0; + + if (!edev || !edev->desc) + return -EINVAL; + + mutex_lock(&edev->lock); + if (edev->desc->ops && edev->desc->ops->enable + && edev->enable_count == 0) { + ret = edev->desc->ops->enable(edev); + if (ret < 0) + goto err; + } + edev->enable_count++; +err: + mutex_unlock(&edev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(devfreq_event_enable_edev); + +/** + * devfreq_event_disable_edev() - Disable the devfreq-event dev and decrease + * the enable_count of the devfreq-event dev. + * @edev : the devfreq-event device + * + * Note that this function decrease the enable_count and disable the + * devfreq-event device. After the devfreq-event device is disabled, + * devfreq device can't use the devfreq-event device for get/set/reset + * operations. + */ +int devfreq_event_disable_edev(struct devfreq_event_dev *edev) +{ + int ret = 0; + + if (!edev || !edev->desc) + return -EINVAL; + + mutex_lock(&edev->lock); + if (edev->enable_count <= 0) { + dev_warn(&edev->dev, "unbalanced enable_count\n"); + ret = -EIO; + goto err; + } + + if (edev->desc->ops && edev->desc->ops->disable + && edev->enable_count == 1) { + ret = edev->desc->ops->disable(edev); + if (ret < 0) + goto err; + } + edev->enable_count--; +err: + mutex_unlock(&edev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(devfreq_event_disable_edev); + +/** + * devfreq_event_is_enabled() - Check whether devfreq-event dev is enabled or + * not. + * @edev : the devfreq-event device + * + * Note that this function check whether devfreq-event dev is enabled or not. + * If return true, the devfreq-event dev is enabeld. If return false, the + * devfreq-event dev is disabled. + */ +bool devfreq_event_is_enabled(struct devfreq_event_dev *edev) +{ + bool enabled = false; + + if (!edev || !edev->desc) + return enabled; + + mutex_lock(&edev->lock); + + if (edev->enable_count > 0) + enabled = true; + + mutex_unlock(&edev->lock); + + return enabled; +} +EXPORT_SYMBOL_GPL(devfreq_event_is_enabled); + +/** + * devfreq_event_set_event() - Set event to devfreq-event dev to start. + * @edev : the devfreq-event device + * + * Note that this function set the event to the devfreq-event device to start + * for getting the event data which could be various event type. + */ +int devfreq_event_set_event(struct devfreq_event_dev *edev) +{ + int ret; + + if (!edev || !edev->desc) + return -EINVAL; + + if (!edev->desc->ops || !edev->desc->ops->set_event) + return -EINVAL; + + if (!devfreq_event_is_enabled(edev)) + return -EPERM; + + mutex_lock(&edev->lock); + ret = edev->desc->ops->set_event(edev); + mutex_unlock(&edev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(devfreq_event_set_event); + +/** + * devfreq_event_get_event() - Get {load|total}_count from devfreq-event dev. + * @edev : the devfreq-event device + * @edata : the calculated data of devfreq-event device + * + * Note that this function get the calculated event data from devfreq-event dev + * after stoping the progress of whole sequence of devfreq-event dev. + */ +int devfreq_event_get_event(struct devfreq_event_dev *edev, + struct devfreq_event_data *edata) +{ + int ret; + + if (!edev || !edev->desc) + return -EINVAL; + + if (!edev->desc->ops || !edev->desc->ops->get_event) + return -EINVAL; + + if (!devfreq_event_is_enabled(edev)) + return -EINVAL; + + edata->total_count = edata->load_count = 0; + + mutex_lock(&edev->lock); + ret = edev->desc->ops->get_event(edev, edata); + if (ret < 0) + edata->total_count = edata->load_count = 0; + mutex_unlock(&edev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(devfreq_event_get_event); + +/** + * devfreq_event_reset_event() - Reset all opeations of devfreq-event dev. + * @edev : the devfreq-event device + * + * Note that this function stop all operations of devfreq-event dev and reset + * the current event data to make the devfreq-event device into initial state. + */ +int devfreq_event_reset_event(struct devfreq_event_dev *edev) +{ + int ret = 0; + + if (!edev || !edev->desc) + return -EINVAL; + + if (!devfreq_event_is_enabled(edev)) + return -EPERM; + + mutex_lock(&edev->lock); + if (edev->desc->ops && edev->desc->ops->reset) + ret = edev->desc->ops->reset(edev); + mutex_unlock(&edev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(devfreq_event_reset_event); + +/** + * devfreq_event_get_edev_by_phandle() - Get the devfreq-event dev from + * devicetree. + * @dev : the pointer to the given device + * @phandle_name: name of property holding a phandle value + * @index : the index into list of devfreq-event device + * + * Note that this function return the pointer of devfreq-event device. + */ +struct devfreq_event_dev *devfreq_event_get_edev_by_phandle(struct device *dev, + const char *phandle_name, int index) +{ + struct device_node *node; + struct devfreq_event_dev *edev; + + if (!dev->of_node || !phandle_name) + return ERR_PTR(-EINVAL); + + node = of_parse_phandle(dev->of_node, phandle_name, index); + if (!node) + return ERR_PTR(-ENODEV); + + mutex_lock(&devfreq_event_list_lock); + list_for_each_entry(edev, &devfreq_event_list, node) { + if (edev->dev.parent && edev->dev.parent->of_node == node) + goto out; + } + + list_for_each_entry(edev, &devfreq_event_list, node) { + if (of_node_name_eq(node, edev->desc->name)) + goto out; + } + edev = NULL; +out: + mutex_unlock(&devfreq_event_list_lock); + + if (!edev) { + of_node_put(node); + return ERR_PTR(-ENODEV); + } + + of_node_put(node); + + return edev; +} +EXPORT_SYMBOL_GPL(devfreq_event_get_edev_by_phandle); + +/** + * devfreq_event_get_edev_count() - Get the count of devfreq-event dev + * @dev : the pointer to the given device + * @phandle_name: name of property holding a phandle value + * + * Note that this function return the count of devfreq-event devices. + */ +int devfreq_event_get_edev_count(struct device *dev, const char *phandle_name) +{ + int count; + + if (!dev->of_node || !phandle_name) { + dev_err(dev, "device does not have a device node entry\n"); + return -EINVAL; + } + + count = of_property_count_elems_of_size(dev->of_node, phandle_name, + sizeof(u32)); + if (count < 0) { + dev_err(dev, + "failed to get the count of devfreq-event in %pOF node\n", + dev->of_node); + return count; + } + + return count; +} +EXPORT_SYMBOL_GPL(devfreq_event_get_edev_count); + +static void devfreq_event_release_edev(struct device *dev) +{ + struct devfreq_event_dev *edev = to_devfreq_event(dev); + + kfree(edev); +} + +/** + * devfreq_event_add_edev() - Add new devfreq-event device. + * @dev : the device owning the devfreq-event device being created + * @desc : the devfreq-event device's descriptor which include essential + * data for devfreq-event device. + * + * Note that this function add new devfreq-event device to devfreq-event class + * list and register the device of the devfreq-event device. + */ +struct devfreq_event_dev *devfreq_event_add_edev(struct device *dev, + struct devfreq_event_desc *desc) +{ + struct devfreq_event_dev *edev; + static atomic_t event_no = ATOMIC_INIT(-1); + int ret; + + if (!dev || !desc) + return ERR_PTR(-EINVAL); + + if (!desc->name || !desc->ops) + return ERR_PTR(-EINVAL); + + if (!desc->ops->set_event || !desc->ops->get_event) + return ERR_PTR(-EINVAL); + + edev = kzalloc(sizeof(struct devfreq_event_dev), GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + mutex_init(&edev->lock); + edev->desc = desc; + edev->enable_count = 0; + edev->dev.parent = 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)); + ret = device_register(&edev->dev); + if (ret < 0) { + put_device(&edev->dev); + return ERR_PTR(ret); + } + dev_set_drvdata(&edev->dev, edev); + + INIT_LIST_HEAD(&edev->node); + + mutex_lock(&devfreq_event_list_lock); + list_add(&edev->node, &devfreq_event_list); + mutex_unlock(&devfreq_event_list_lock); + + return edev; +} +EXPORT_SYMBOL_GPL(devfreq_event_add_edev); + +/** + * devfreq_event_remove_edev() - Remove the devfreq-event device registered. + * @edev : the devfreq-event device + * + * Note that this function removes the registered devfreq-event device. + */ +int devfreq_event_remove_edev(struct devfreq_event_dev *edev) +{ + if (!edev) + return -EINVAL; + + WARN_ON(edev->enable_count); + + mutex_lock(&devfreq_event_list_lock); + list_del(&edev->node); + mutex_unlock(&devfreq_event_list_lock); + + device_unregister(&edev->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(devfreq_event_remove_edev); + +static int devm_devfreq_event_match(struct device *dev, void *res, void *data) +{ + struct devfreq_event_dev **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_devfreq_event_release(struct device *dev, void *res) +{ + devfreq_event_remove_edev(*(struct devfreq_event_dev **)res); +} + +/** + * devm_devfreq_event_add_edev() - Resource-managed devfreq_event_add_edev() + * @dev : the device owning the devfreq-event device being created + * @desc : the devfreq-event device's descriptor which include essential + * data for devfreq-event device. + * + * Note that this function manages automatically the memory of devfreq-event + * device using device resource management and simplify the free operation + * for memory of devfreq-event device. + */ +struct devfreq_event_dev *devm_devfreq_event_add_edev(struct device *dev, + struct devfreq_event_desc *desc) +{ + struct devfreq_event_dev **ptr, *edev; + + ptr = devres_alloc(devm_devfreq_event_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + edev = devfreq_event_add_edev(dev, desc); + if (IS_ERR(edev)) { + devres_free(ptr); + return ERR_PTR(-ENOMEM); + } + + *ptr = edev; + devres_add(dev, ptr); + + return edev; +} +EXPORT_SYMBOL_GPL(devm_devfreq_event_add_edev); + +/** + * devm_devfreq_event_remove_edev()- Resource-managed devfreq_event_remove_edev() + * @dev : the device owning the devfreq-event device being created + * @edev : the devfreq-event device + * + * Note that this function manages automatically the memory of devfreq-event + * device using device resource management. + */ +void devm_devfreq_event_remove_edev(struct device *dev, + struct devfreq_event_dev *edev) +{ + WARN_ON(devres_release(dev, devm_devfreq_event_release, + devm_devfreq_event_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_devfreq_event_remove_edev); + +/* + * Device attributes for devfreq-event class. + */ +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq_event_dev *edev = to_devfreq_event(dev); + + if (!edev || !edev->desc) + return -EINVAL; + + return sprintf(buf, "%s\n", edev->desc->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t enable_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq_event_dev *edev = to_devfreq_event(dev); + + if (!edev || !edev->desc) + return -EINVAL; + + return sprintf(buf, "%d\n", edev->enable_count); +} +static DEVICE_ATTR_RO(enable_count); + +static struct attribute *devfreq_event_attrs[] = { + &dev_attr_name.attr, + &dev_attr_enable_count.attr, + NULL, +}; +ATTRIBUTE_GROUPS(devfreq_event); + +static int __init devfreq_event_init(void) +{ + devfreq_event_class = class_create(THIS_MODULE, "devfreq-event"); + if (IS_ERR(devfreq_event_class)) { + pr_err("%s: couldn't create class\n", __FILE__); + return PTR_ERR(devfreq_event_class); + } + + devfreq_event_class->dev_groups = devfreq_event_groups; + + return 0; +} +subsys_initcall(devfreq_event_init); diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c new file mode 100644 index 000000000..2951a87cc --- /dev/null +++ b/drivers/devfreq/devfreq.c @@ -0,0 +1,2275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * devfreq: Generic Dynamic Voltage and Frequency Scaling (DVFS) Framework + * for Non-CPU Devices. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/sched.h> +#include <linux/debugfs.h> +#include <linux/devfreq_cooling.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/pm_opp.h> +#include <linux/devfreq.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/printk.h> +#include <linux/hrtimer.h> +#include <linux/of.h> +#include <linux/pm_qos.h> +#include <linux/units.h> +#include "governor.h" + +#define CREATE_TRACE_POINTS +#include <trace/events/devfreq.h> + +#define IS_SUPPORTED_FLAG(f, name) ((f & DEVFREQ_GOV_FLAG_##name) ? true : false) +#define IS_SUPPORTED_ATTR(f, name) ((f & DEVFREQ_GOV_ATTR_##name) ? true : false) + +static struct class *devfreq_class; +static struct dentry *devfreq_debugfs; + +/* + * devfreq core provides delayed work based load monitoring helper + * functions. Governors can use these or can implement their own + * monitoring mechanism. + */ +static struct workqueue_struct *devfreq_wq; + +/* The list of all device-devfreq governors */ +static LIST_HEAD(devfreq_governor_list); +/* The list of all device-devfreq */ +static LIST_HEAD(devfreq_list); +static DEFINE_MUTEX(devfreq_list_lock); + +static const char timer_name[][DEVFREQ_NAME_LEN] = { + [DEVFREQ_TIMER_DEFERRABLE] = { "deferrable" }, + [DEVFREQ_TIMER_DELAYED] = { "delayed" }, +}; + +/** + * find_device_devfreq() - find devfreq struct using device pointer + * @dev: device pointer used to lookup device devfreq. + * + * Search the list of device devfreqs and return the matched device's + * devfreq info. devfreq_list_lock should be held by the caller. + */ +static struct devfreq *find_device_devfreq(struct device *dev) +{ + struct devfreq *tmp_devfreq; + + lockdep_assert_held(&devfreq_list_lock); + + if (IS_ERR_OR_NULL(dev)) { + pr_err("DEVFREQ: %s: Invalid parameters\n", __func__); + return ERR_PTR(-EINVAL); + } + + list_for_each_entry(tmp_devfreq, &devfreq_list, node) { + if (tmp_devfreq->dev.parent == dev) + return tmp_devfreq; + } + + return ERR_PTR(-ENODEV); +} + +static unsigned long find_available_min_freq(struct devfreq *devfreq) +{ + struct dev_pm_opp *opp; + unsigned long min_freq = 0; + + opp = dev_pm_opp_find_freq_ceil(devfreq->dev.parent, &min_freq); + if (IS_ERR(opp)) + min_freq = 0; + else + dev_pm_opp_put(opp); + + return min_freq; +} + +static unsigned long find_available_max_freq(struct devfreq *devfreq) +{ + struct dev_pm_opp *opp; + unsigned long max_freq = ULONG_MAX; + + opp = dev_pm_opp_find_freq_floor(devfreq->dev.parent, &max_freq); + if (IS_ERR(opp)) + max_freq = 0; + else + dev_pm_opp_put(opp); + + return max_freq; +} + +/** + * devfreq_get_freq_range() - Get the current freq range + * @devfreq: the devfreq instance + * @min_freq: the min frequency + * @max_freq: the max frequency + * + * This takes into consideration all constraints. + */ +void devfreq_get_freq_range(struct devfreq *devfreq, + unsigned long *min_freq, + unsigned long *max_freq) +{ + unsigned long *freq_table = devfreq->freq_table; + s32 qos_min_freq, qos_max_freq; + + lockdep_assert_held(&devfreq->lock); + + /* + * Initialize minimum/maximum frequency from freq table. + * The devfreq drivers can initialize this in either ascending or + * descending order and devfreq core supports both. + */ + if (freq_table[0] < freq_table[devfreq->max_state - 1]) { + *min_freq = freq_table[0]; + *max_freq = freq_table[devfreq->max_state - 1]; + } else { + *min_freq = freq_table[devfreq->max_state - 1]; + *max_freq = freq_table[0]; + } + + /* Apply constraints from PM QoS */ + qos_min_freq = dev_pm_qos_read_value(devfreq->dev.parent, + DEV_PM_QOS_MIN_FREQUENCY); + qos_max_freq = dev_pm_qos_read_value(devfreq->dev.parent, + DEV_PM_QOS_MAX_FREQUENCY); + *min_freq = max(*min_freq, (unsigned long)HZ_PER_KHZ * qos_min_freq); + if (qos_max_freq != PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE) + *max_freq = min(*max_freq, + (unsigned long)HZ_PER_KHZ * qos_max_freq); + + /* Apply constraints from OPP interface */ + *min_freq = max(*min_freq, devfreq->scaling_min_freq); + *max_freq = min(*max_freq, devfreq->scaling_max_freq); + + if (*min_freq > *max_freq) + *min_freq = *max_freq; +} +EXPORT_SYMBOL(devfreq_get_freq_range); + +/** + * devfreq_get_freq_level() - Lookup freq_table for the frequency + * @devfreq: the devfreq instance + * @freq: the target frequency + */ +static int devfreq_get_freq_level(struct devfreq *devfreq, unsigned long freq) +{ + int lev; + + for (lev = 0; lev < devfreq->max_state; lev++) + if (freq == devfreq->freq_table[lev]) + return lev; + + return -EINVAL; +} + +static int set_freq_table(struct devfreq *devfreq) +{ + struct dev_pm_opp *opp; + unsigned long freq; + int i, count; + + /* Initialize the freq_table from OPP table */ + count = dev_pm_opp_get_opp_count(devfreq->dev.parent); + if (count <= 0) + return -EINVAL; + + devfreq->max_state = count; + devfreq->freq_table = devm_kcalloc(devfreq->dev.parent, + devfreq->max_state, + sizeof(*devfreq->freq_table), + GFP_KERNEL); + if (!devfreq->freq_table) + return -ENOMEM; + + for (i = 0, freq = 0; i < devfreq->max_state; i++, freq++) { + opp = dev_pm_opp_find_freq_ceil(devfreq->dev.parent, &freq); + if (IS_ERR(opp)) { + devm_kfree(devfreq->dev.parent, devfreq->freq_table); + return PTR_ERR(opp); + } + dev_pm_opp_put(opp); + devfreq->freq_table[i] = freq; + } + + return 0; +} + +/** + * devfreq_update_status() - Update statistics of devfreq behavior + * @devfreq: the devfreq instance + * @freq: the update target frequency + */ +int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) +{ + int lev, prev_lev, ret = 0; + u64 cur_time; + + lockdep_assert_held(&devfreq->lock); + cur_time = get_jiffies_64(); + + /* Immediately exit if previous_freq is not initialized yet. */ + if (!devfreq->previous_freq) + goto out; + + prev_lev = devfreq_get_freq_level(devfreq, devfreq->previous_freq); + if (prev_lev < 0) { + ret = prev_lev; + goto out; + } + + devfreq->stats.time_in_state[prev_lev] += + cur_time - devfreq->stats.last_update; + + lev = devfreq_get_freq_level(devfreq, freq); + if (lev < 0) { + ret = lev; + goto out; + } + + if (lev != prev_lev) { + devfreq->stats.trans_table[ + (prev_lev * devfreq->max_state) + lev]++; + devfreq->stats.total_trans++; + } + +out: + devfreq->stats.last_update = cur_time; + return ret; +} +EXPORT_SYMBOL(devfreq_update_status); + +/** + * find_devfreq_governor() - find devfreq governor from name + * @name: name of the governor + * + * Search the list of devfreq governors and return the matched + * governor's pointer. devfreq_list_lock should be held by the caller. + */ +static struct devfreq_governor *find_devfreq_governor(const char *name) +{ + struct devfreq_governor *tmp_governor; + + lockdep_assert_held(&devfreq_list_lock); + + if (IS_ERR_OR_NULL(name)) { + pr_err("DEVFREQ: %s: Invalid parameters\n", __func__); + return ERR_PTR(-EINVAL); + } + + list_for_each_entry(tmp_governor, &devfreq_governor_list, node) { + if (!strncmp(tmp_governor->name, name, DEVFREQ_NAME_LEN)) + return tmp_governor; + } + + return ERR_PTR(-ENODEV); +} + +/** + * try_then_request_governor() - Try to find the governor and request the + * module if is not found. + * @name: name of the governor + * + * Search the list of devfreq governors and request the module and try again + * if is not found. This can happen when both drivers (the governor driver + * and the driver that call devfreq_add_device) are built as modules. + * devfreq_list_lock should be held by the caller. Returns the matched + * governor's pointer or an error pointer. + */ +static struct devfreq_governor *try_then_request_governor(const char *name) +{ + struct devfreq_governor *governor; + int err = 0; + + lockdep_assert_held(&devfreq_list_lock); + + if (IS_ERR_OR_NULL(name)) { + pr_err("DEVFREQ: %s: Invalid parameters\n", __func__); + return ERR_PTR(-EINVAL); + } + + governor = find_devfreq_governor(name); + if (IS_ERR(governor)) { + mutex_unlock(&devfreq_list_lock); + + if (!strncmp(name, DEVFREQ_GOV_SIMPLE_ONDEMAND, + DEVFREQ_NAME_LEN)) + err = request_module("governor_%s", "simpleondemand"); + else + err = request_module("governor_%s", name); + /* Restore previous state before return */ + mutex_lock(&devfreq_list_lock); + if (err) + return (err < 0) ? ERR_PTR(err) : ERR_PTR(-EINVAL); + + governor = find_devfreq_governor(name); + } + + return governor; +} + +static int devfreq_notify_transition(struct devfreq *devfreq, + struct devfreq_freqs *freqs, unsigned int state) +{ + if (!devfreq) + return -EINVAL; + + switch (state) { + case DEVFREQ_PRECHANGE: + srcu_notifier_call_chain(&devfreq->transition_notifier_list, + DEVFREQ_PRECHANGE, freqs); + break; + + case DEVFREQ_POSTCHANGE: + srcu_notifier_call_chain(&devfreq->transition_notifier_list, + DEVFREQ_POSTCHANGE, freqs); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq, + u32 flags) +{ + struct devfreq_freqs freqs; + unsigned long cur_freq; + int err = 0; + + if (devfreq->profile->get_cur_freq) + devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq); + else + cur_freq = devfreq->previous_freq; + + freqs.old = cur_freq; + freqs.new = new_freq; + devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE); + + err = devfreq->profile->target(devfreq->dev.parent, &new_freq, flags); + if (err) { + freqs.new = cur_freq; + devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE); + return err; + } + + /* + * Print devfreq_frequency trace information between DEVFREQ_PRECHANGE + * and DEVFREQ_POSTCHANGE because for showing the correct frequency + * change order of between devfreq device and passive devfreq device. + */ + if (trace_devfreq_frequency_enabled() && new_freq != cur_freq) + trace_devfreq_frequency(devfreq, new_freq, cur_freq); + + freqs.new = new_freq; + devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE); + + if (devfreq_update_status(devfreq, new_freq)) + dev_warn(&devfreq->dev, + "Couldn't update frequency transition information.\n"); + + devfreq->previous_freq = new_freq; + + if (devfreq->suspend_freq) + devfreq->resume_freq = new_freq; + + return err; +} + +/** + * devfreq_update_target() - Reevaluate the device and configure frequency + * on the final stage. + * @devfreq: the devfreq instance. + * @freq: the new frequency of parent device. This argument + * is only used for devfreq device using passive governor. + * + * Note: Lock devfreq->lock before calling devfreq_update_target. This function + * should be only used by both update_devfreq() and devfreq governors. + */ +int devfreq_update_target(struct devfreq *devfreq, unsigned long freq) +{ + unsigned long min_freq, max_freq; + int err = 0; + u32 flags = 0; + + lockdep_assert_held(&devfreq->lock); + + if (!devfreq->governor) + return -EINVAL; + + /* Reevaluate the proper frequency */ + err = devfreq->governor->get_target_freq(devfreq, &freq); + if (err) + return err; + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); + + if (freq < min_freq) { + freq = min_freq; + flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */ + } + if (freq > max_freq) { + freq = max_freq; + flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */ + } + + return devfreq_set_target(devfreq, freq, flags); +} +EXPORT_SYMBOL(devfreq_update_target); + +/* Load monitoring helper functions for governors use */ + +/** + * update_devfreq() - Reevaluate the device and configure frequency. + * @devfreq: the devfreq instance. + * + * Note: Lock devfreq->lock before calling update_devfreq + * This function is exported for governors. + */ +int update_devfreq(struct devfreq *devfreq) +{ + return devfreq_update_target(devfreq, 0L); +} +EXPORT_SYMBOL(update_devfreq); + +/** + * devfreq_monitor() - Periodically poll devfreq objects. + * @work: the work struct used to run devfreq_monitor periodically. + * + */ +static void devfreq_monitor(struct work_struct *work) +{ + int err; + struct devfreq *devfreq = container_of(work, + struct devfreq, work.work); + + mutex_lock(&devfreq->lock); + err = update_devfreq(devfreq); + if (err) + dev_err(&devfreq->dev, "dvfs failed with (%d) error\n", err); + + queue_delayed_work(devfreq_wq, &devfreq->work, + msecs_to_jiffies(devfreq->profile->polling_ms)); + mutex_unlock(&devfreq->lock); + + trace_devfreq_monitor(devfreq); +} + +/** + * devfreq_monitor_start() - Start load monitoring of devfreq instance + * @devfreq: the devfreq instance. + * + * Helper function for starting devfreq device load monitoring. By + * default delayed work based monitoring is supported. Function + * to be called from governor in response to DEVFREQ_GOV_START + * event when device is added to devfreq framework. + */ +void devfreq_monitor_start(struct devfreq *devfreq) +{ + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN)) + return; + + switch (devfreq->profile->timer) { + case DEVFREQ_TIMER_DEFERRABLE: + INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor); + break; + case DEVFREQ_TIMER_DELAYED: + INIT_DELAYED_WORK(&devfreq->work, devfreq_monitor); + break; + default: + return; + } + + if (devfreq->profile->polling_ms) + queue_delayed_work(devfreq_wq, &devfreq->work, + msecs_to_jiffies(devfreq->profile->polling_ms)); +} +EXPORT_SYMBOL(devfreq_monitor_start); + +/** + * devfreq_monitor_stop() - Stop load monitoring of a devfreq instance + * @devfreq: the devfreq instance. + * + * Helper function to stop devfreq device load monitoring. Function + * to be called from governor in response to DEVFREQ_GOV_STOP + * event when device is removed from devfreq framework. + */ +void devfreq_monitor_stop(struct devfreq *devfreq) +{ + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN)) + return; + + cancel_delayed_work_sync(&devfreq->work); +} +EXPORT_SYMBOL(devfreq_monitor_stop); + +/** + * devfreq_monitor_suspend() - Suspend load monitoring of a devfreq instance + * @devfreq: the devfreq instance. + * + * Helper function to suspend devfreq device load monitoring. Function + * to be called from governor in response to DEVFREQ_GOV_SUSPEND + * event or when polling interval is set to zero. + * + * Note: Though this function is same as devfreq_monitor_stop(), + * intentionally kept separate to provide hooks for collecting + * transition statistics. + */ +void devfreq_monitor_suspend(struct devfreq *devfreq) +{ + mutex_lock(&devfreq->lock); + if (devfreq->stop_polling) { + mutex_unlock(&devfreq->lock); + return; + } + + devfreq_update_status(devfreq, devfreq->previous_freq); + devfreq->stop_polling = true; + mutex_unlock(&devfreq->lock); + + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN)) + return; + + cancel_delayed_work_sync(&devfreq->work); +} +EXPORT_SYMBOL(devfreq_monitor_suspend); + +/** + * devfreq_monitor_resume() - Resume load monitoring of a devfreq instance + * @devfreq: the devfreq instance. + * + * Helper function to resume devfreq device load monitoring. Function + * to be called from governor in response to DEVFREQ_GOV_RESUME + * event or when polling interval is set to non-zero. + */ +void devfreq_monitor_resume(struct devfreq *devfreq) +{ + unsigned long freq; + + mutex_lock(&devfreq->lock); + + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN)) + goto out_update; + + if (!devfreq->stop_polling) + goto out; + + if (!delayed_work_pending(&devfreq->work) && + devfreq->profile->polling_ms) + queue_delayed_work(devfreq_wq, &devfreq->work, + msecs_to_jiffies(devfreq->profile->polling_ms)); + +out_update: + devfreq->stats.last_update = get_jiffies_64(); + devfreq->stop_polling = false; + + if (devfreq->profile->get_cur_freq && + !devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq)) + devfreq->previous_freq = freq; + +out: + mutex_unlock(&devfreq->lock); +} +EXPORT_SYMBOL(devfreq_monitor_resume); + +/** + * devfreq_update_interval() - Update device devfreq monitoring interval + * @devfreq: the devfreq instance. + * @delay: new polling interval to be set. + * + * Helper function to set new load monitoring polling interval. Function + * to be called from governor in response to DEVFREQ_GOV_UPDATE_INTERVAL event. + */ +void devfreq_update_interval(struct devfreq *devfreq, unsigned int *delay) +{ + unsigned int cur_delay = devfreq->profile->polling_ms; + unsigned int new_delay = *delay; + + mutex_lock(&devfreq->lock); + devfreq->profile->polling_ms = new_delay; + + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN)) + goto out; + + if (devfreq->stop_polling) + goto out; + + /* if new delay is zero, stop polling */ + if (!new_delay) { + mutex_unlock(&devfreq->lock); + cancel_delayed_work_sync(&devfreq->work); + return; + } + + /* if current delay is zero, start polling with new delay */ + if (!cur_delay) { + queue_delayed_work(devfreq_wq, &devfreq->work, + msecs_to_jiffies(devfreq->profile->polling_ms)); + goto out; + } + + /* if current delay is greater than new delay, restart polling */ + if (cur_delay > new_delay) { + mutex_unlock(&devfreq->lock); + cancel_delayed_work_sync(&devfreq->work); + mutex_lock(&devfreq->lock); + if (!devfreq->stop_polling) + queue_delayed_work(devfreq_wq, &devfreq->work, + msecs_to_jiffies(devfreq->profile->polling_ms)); + } +out: + mutex_unlock(&devfreq->lock); +} +EXPORT_SYMBOL(devfreq_update_interval); + +/** + * devfreq_notifier_call() - Notify that the device frequency requirements + * has been changed out of devfreq framework. + * @nb: the notifier_block (supposed to be devfreq->nb) + * @type: not used + * @devp: not used + * + * Called by a notifier that uses devfreq->nb. + */ +static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type, + void *devp) +{ + struct devfreq *devfreq = container_of(nb, struct devfreq, nb); + int err = -EINVAL; + + mutex_lock(&devfreq->lock); + + devfreq->scaling_min_freq = find_available_min_freq(devfreq); + if (!devfreq->scaling_min_freq) + goto out; + + devfreq->scaling_max_freq = find_available_max_freq(devfreq); + if (!devfreq->scaling_max_freq) { + devfreq->scaling_max_freq = ULONG_MAX; + goto out; + } + + err = update_devfreq(devfreq); + +out: + mutex_unlock(&devfreq->lock); + if (err) + dev_err(devfreq->dev.parent, + "failed to update frequency from OPP notifier (%d)\n", + err); + + return NOTIFY_OK; +} + +/** + * qos_notifier_call() - Common handler for QoS constraints. + * @devfreq: the devfreq instance. + */ +static int qos_notifier_call(struct devfreq *devfreq) +{ + int err; + + mutex_lock(&devfreq->lock); + err = update_devfreq(devfreq); + mutex_unlock(&devfreq->lock); + if (err) + dev_err(devfreq->dev.parent, + "failed to update frequency from PM QoS (%d)\n", + err); + + return NOTIFY_OK; +} + +/** + * qos_min_notifier_call() - Callback for QoS min_freq changes. + * @nb: Should be devfreq->nb_min + * @val: not used + * @ptr: not used + */ +static int qos_min_notifier_call(struct notifier_block *nb, + unsigned long val, void *ptr) +{ + return qos_notifier_call(container_of(nb, struct devfreq, nb_min)); +} + +/** + * qos_max_notifier_call() - Callback for QoS max_freq changes. + * @nb: Should be devfreq->nb_max + * @val: not used + * @ptr: not used + */ +static int qos_max_notifier_call(struct notifier_block *nb, + unsigned long val, void *ptr) +{ + return qos_notifier_call(container_of(nb, struct devfreq, nb_max)); +} + +/** + * devfreq_dev_release() - Callback for struct device to release the device. + * @dev: the devfreq device + * + * Remove devfreq from the list and release its resources. + */ +static void devfreq_dev_release(struct device *dev) +{ + struct devfreq *devfreq = to_devfreq(dev); + int err; + + mutex_lock(&devfreq_list_lock); + list_del(&devfreq->node); + mutex_unlock(&devfreq_list_lock); + + err = dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_max, + DEV_PM_QOS_MAX_FREQUENCY); + if (err && err != -ENOENT) + dev_warn(dev->parent, + "Failed to remove max_freq notifier: %d\n", err); + err = dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_min, + DEV_PM_QOS_MIN_FREQUENCY); + if (err && err != -ENOENT) + dev_warn(dev->parent, + "Failed to remove min_freq notifier: %d\n", err); + + if (dev_pm_qos_request_active(&devfreq->user_max_freq_req)) { + err = dev_pm_qos_remove_request(&devfreq->user_max_freq_req); + if (err < 0) + dev_warn(dev->parent, + "Failed to remove max_freq request: %d\n", err); + } + if (dev_pm_qos_request_active(&devfreq->user_min_freq_req)) { + err = dev_pm_qos_remove_request(&devfreq->user_min_freq_req); + if (err < 0) + dev_warn(dev->parent, + "Failed to remove min_freq request: %d\n", err); + } + + if (devfreq->profile->exit) + devfreq->profile->exit(devfreq->dev.parent); + + if (devfreq->opp_table) + dev_pm_opp_put_opp_table(devfreq->opp_table); + + mutex_destroy(&devfreq->lock); + srcu_cleanup_notifier_head(&devfreq->transition_notifier_list); + kfree(devfreq); +} + +static void create_sysfs_files(struct devfreq *devfreq, + const struct devfreq_governor *gov); +static void remove_sysfs_files(struct devfreq *devfreq, + const struct devfreq_governor *gov); + +/** + * devfreq_add_device() - Add devfreq feature to the device + * @dev: the device to add devfreq feature. + * @profile: device-specific profile to run devfreq. + * @governor_name: name of the policy to choose frequency. + * @data: devfreq driver pass to governors, governor should not change it. + */ +struct devfreq *devfreq_add_device(struct device *dev, + struct devfreq_dev_profile *profile, + const char *governor_name, + void *data) +{ + struct devfreq *devfreq; + struct devfreq_governor *governor; + unsigned long min_freq, max_freq; + int err = 0; + + if (!dev || !profile || !governor_name) { + dev_err(dev, "%s: Invalid parameters.\n", __func__); + return ERR_PTR(-EINVAL); + } + + mutex_lock(&devfreq_list_lock); + devfreq = find_device_devfreq(dev); + mutex_unlock(&devfreq_list_lock); + if (!IS_ERR(devfreq)) { + dev_err(dev, "%s: devfreq device already exists!\n", + __func__); + err = -EINVAL; + goto err_out; + } + + devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL); + if (!devfreq) { + err = -ENOMEM; + goto err_out; + } + + mutex_init(&devfreq->lock); + mutex_lock(&devfreq->lock); + devfreq->dev.parent = dev; + devfreq->dev.class = devfreq_class; + devfreq->dev.release = devfreq_dev_release; + INIT_LIST_HEAD(&devfreq->node); + devfreq->profile = profile; + devfreq->previous_freq = profile->initial_freq; + devfreq->last_status.current_frequency = profile->initial_freq; + devfreq->data = data; + devfreq->nb.notifier_call = devfreq_notifier_call; + + if (devfreq->profile->timer < 0 + || devfreq->profile->timer >= DEVFREQ_TIMER_NUM) { + mutex_unlock(&devfreq->lock); + err = -EINVAL; + goto err_dev; + } + + if (!devfreq->profile->max_state || !devfreq->profile->freq_table) { + mutex_unlock(&devfreq->lock); + err = set_freq_table(devfreq); + if (err < 0) + goto err_dev; + mutex_lock(&devfreq->lock); + } else { + devfreq->freq_table = devfreq->profile->freq_table; + devfreq->max_state = devfreq->profile->max_state; + } + + devfreq->scaling_min_freq = find_available_min_freq(devfreq); + if (!devfreq->scaling_min_freq) { + mutex_unlock(&devfreq->lock); + err = -EINVAL; + goto err_dev; + } + + devfreq->scaling_max_freq = find_available_max_freq(devfreq); + if (!devfreq->scaling_max_freq) { + mutex_unlock(&devfreq->lock); + err = -EINVAL; + goto err_dev; + } + + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); + + devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev); + devfreq->opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(devfreq->opp_table)) + devfreq->opp_table = NULL; + + atomic_set(&devfreq->suspend_count, 0); + + dev_set_name(&devfreq->dev, "%s", dev_name(dev)); + err = device_register(&devfreq->dev); + if (err) { + mutex_unlock(&devfreq->lock); + put_device(&devfreq->dev); + goto err_out; + } + + devfreq->stats.trans_table = devm_kzalloc(&devfreq->dev, + array3_size(sizeof(unsigned int), + devfreq->max_state, + devfreq->max_state), + GFP_KERNEL); + if (!devfreq->stats.trans_table) { + mutex_unlock(&devfreq->lock); + err = -ENOMEM; + goto err_devfreq; + } + + devfreq->stats.time_in_state = devm_kcalloc(&devfreq->dev, + devfreq->max_state, + sizeof(*devfreq->stats.time_in_state), + GFP_KERNEL); + if (!devfreq->stats.time_in_state) { + mutex_unlock(&devfreq->lock); + err = -ENOMEM; + goto err_devfreq; + } + + devfreq->stats.total_trans = 0; + devfreq->stats.last_update = get_jiffies_64(); + + srcu_init_notifier_head(&devfreq->transition_notifier_list); + + mutex_unlock(&devfreq->lock); + + err = dev_pm_qos_add_request(dev, &devfreq->user_min_freq_req, + DEV_PM_QOS_MIN_FREQUENCY, 0); + if (err < 0) + goto err_devfreq; + err = dev_pm_qos_add_request(dev, &devfreq->user_max_freq_req, + DEV_PM_QOS_MAX_FREQUENCY, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); + if (err < 0) + goto err_devfreq; + + devfreq->nb_min.notifier_call = qos_min_notifier_call; + err = dev_pm_qos_add_notifier(dev, &devfreq->nb_min, + DEV_PM_QOS_MIN_FREQUENCY); + if (err) + goto err_devfreq; + + devfreq->nb_max.notifier_call = qos_max_notifier_call; + err = dev_pm_qos_add_notifier(dev, &devfreq->nb_max, + DEV_PM_QOS_MAX_FREQUENCY); + if (err) + goto err_devfreq; + + mutex_lock(&devfreq_list_lock); + + governor = try_then_request_governor(governor_name); + if (IS_ERR(governor)) { + dev_err(dev, "%s: Unable to find governor for the device\n", + __func__); + err = PTR_ERR(governor); + goto err_init; + } + + devfreq->governor = governor; + err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START, + NULL); + if (err) { + dev_err_probe(dev, err, + "%s: Unable to start governor for the device\n", + __func__); + goto err_init; + } + create_sysfs_files(devfreq, devfreq->governor); + + list_add(&devfreq->node, &devfreq_list); + + mutex_unlock(&devfreq_list_lock); + + if (devfreq->profile->is_cooling_device) { + devfreq->cdev = devfreq_cooling_em_register(devfreq, NULL); + if (IS_ERR(devfreq->cdev)) + devfreq->cdev = NULL; + } + + return devfreq; + +err_init: + mutex_unlock(&devfreq_list_lock); +err_devfreq: + devfreq_remove_device(devfreq); + devfreq = NULL; +err_dev: + kfree(devfreq); +err_out: + return ERR_PTR(err); +} +EXPORT_SYMBOL(devfreq_add_device); + +/** + * devfreq_remove_device() - Remove devfreq feature from a device. + * @devfreq: the devfreq instance to be removed + * + * The opposite of devfreq_add_device(). + */ +int devfreq_remove_device(struct devfreq *devfreq) +{ + if (!devfreq) + return -EINVAL; + + devfreq_cooling_unregister(devfreq->cdev); + + if (devfreq->governor) { + devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_STOP, NULL); + remove_sysfs_files(devfreq, devfreq->governor); + } + + device_unregister(&devfreq->dev); + + return 0; +} +EXPORT_SYMBOL(devfreq_remove_device); + +static int devm_devfreq_dev_match(struct device *dev, void *res, void *data) +{ + struct devfreq **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_devfreq_dev_release(struct device *dev, void *res) +{ + devfreq_remove_device(*(struct devfreq **)res); +} + +/** + * devm_devfreq_add_device() - Resource-managed devfreq_add_device() + * @dev: the device to add devfreq feature. + * @profile: device-specific profile to run devfreq. + * @governor_name: name of the policy to choose frequency. + * @data: devfreq driver pass to governors, governor should not change it. + * + * This function manages automatically the memory of devfreq device using device + * resource management and simplify the free operation for memory of devfreq + * device. + */ +struct devfreq *devm_devfreq_add_device(struct device *dev, + struct devfreq_dev_profile *profile, + const char *governor_name, + void *data) +{ + struct devfreq **ptr, *devfreq; + + ptr = devres_alloc(devm_devfreq_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + devfreq = devfreq_add_device(dev, profile, governor_name, data); + if (IS_ERR(devfreq)) { + devres_free(ptr); + return devfreq; + } + + *ptr = devfreq; + devres_add(dev, ptr); + + return devfreq; +} +EXPORT_SYMBOL(devm_devfreq_add_device); + +#ifdef CONFIG_OF +/* + * devfreq_get_devfreq_by_node - Get the devfreq device from devicetree + * @node - pointer to device_node + * + * return the instance of devfreq device + */ +struct devfreq *devfreq_get_devfreq_by_node(struct device_node *node) +{ + struct devfreq *devfreq; + + if (!node) + return ERR_PTR(-EINVAL); + + mutex_lock(&devfreq_list_lock); + list_for_each_entry(devfreq, &devfreq_list, node) { + if (devfreq->dev.parent + && devfreq->dev.parent->of_node == node) { + mutex_unlock(&devfreq_list_lock); + return devfreq; + } + } + mutex_unlock(&devfreq_list_lock); + + return ERR_PTR(-ENODEV); +} + +/* + * devfreq_get_devfreq_by_phandle - Get the devfreq device from devicetree + * @dev - instance to the given device + * @phandle_name - name of property holding a phandle value + * @index - index into list of devfreq + * + * return the instance of devfreq device + */ +struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, + const char *phandle_name, int index) +{ + struct device_node *node; + struct devfreq *devfreq; + + if (!dev || !phandle_name) + return ERR_PTR(-EINVAL); + + if (!dev->of_node) + return ERR_PTR(-EINVAL); + + node = of_parse_phandle(dev->of_node, phandle_name, index); + if (!node) + return ERR_PTR(-ENODEV); + + devfreq = devfreq_get_devfreq_by_node(node); + of_node_put(node); + + return devfreq; +} + +#else +struct devfreq *devfreq_get_devfreq_by_node(struct device_node *node) +{ + return ERR_PTR(-ENODEV); +} + +struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, + const char *phandle_name, int index) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_OF */ +EXPORT_SYMBOL_GPL(devfreq_get_devfreq_by_node); +EXPORT_SYMBOL_GPL(devfreq_get_devfreq_by_phandle); + +/** + * devm_devfreq_remove_device() - Resource-managed devfreq_remove_device() + * @dev: the device from which to remove devfreq feature. + * @devfreq: the devfreq instance to be removed + */ +void devm_devfreq_remove_device(struct device *dev, struct devfreq *devfreq) +{ + WARN_ON(devres_release(dev, devm_devfreq_dev_release, + devm_devfreq_dev_match, devfreq)); +} +EXPORT_SYMBOL(devm_devfreq_remove_device); + +/** + * devfreq_suspend_device() - Suspend devfreq of a device. + * @devfreq: the devfreq instance to be suspended + * + * This function is intended to be called by the pm callbacks + * (e.g., runtime_suspend, suspend) of the device driver that + * holds the devfreq. + */ +int devfreq_suspend_device(struct devfreq *devfreq) +{ + int ret; + + if (!devfreq) + return -EINVAL; + + if (atomic_inc_return(&devfreq->suspend_count) > 1) + return 0; + + if (devfreq->governor) { + ret = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_SUSPEND, NULL); + if (ret) + return ret; + } + + if (devfreq->suspend_freq) { + mutex_lock(&devfreq->lock); + ret = devfreq_set_target(devfreq, devfreq->suspend_freq, 0); + mutex_unlock(&devfreq->lock); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(devfreq_suspend_device); + +/** + * devfreq_resume_device() - Resume devfreq of a device. + * @devfreq: the devfreq instance to be resumed + * + * This function is intended to be called by the pm callbacks + * (e.g., runtime_resume, resume) of the device driver that + * holds the devfreq. + */ +int devfreq_resume_device(struct devfreq *devfreq) +{ + int ret; + + if (!devfreq) + return -EINVAL; + + if (atomic_dec_return(&devfreq->suspend_count) >= 1) + return 0; + + if (devfreq->resume_freq) { + mutex_lock(&devfreq->lock); + ret = devfreq_set_target(devfreq, devfreq->resume_freq, 0); + mutex_unlock(&devfreq->lock); + if (ret) + return ret; + } + + if (devfreq->governor) { + ret = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_RESUME, NULL); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(devfreq_resume_device); + +/** + * devfreq_suspend() - Suspend devfreq governors and devices + * + * Called during system wide Suspend/Hibernate cycles for suspending governors + * and devices preserving the state for resume. On some platforms the devfreq + * device must have precise state (frequency) after resume in order to provide + * fully operating setup. + */ +void devfreq_suspend(void) +{ + struct devfreq *devfreq; + int ret; + + mutex_lock(&devfreq_list_lock); + list_for_each_entry(devfreq, &devfreq_list, node) { + ret = devfreq_suspend_device(devfreq); + if (ret) + dev_err(&devfreq->dev, + "failed to suspend devfreq device\n"); + } + mutex_unlock(&devfreq_list_lock); +} + +/** + * devfreq_resume() - Resume devfreq governors and devices + * + * Called during system wide Suspend/Hibernate cycle for resuming governors and + * devices that are suspended with devfreq_suspend(). + */ +void devfreq_resume(void) +{ + struct devfreq *devfreq; + int ret; + + mutex_lock(&devfreq_list_lock); + list_for_each_entry(devfreq, &devfreq_list, node) { + ret = devfreq_resume_device(devfreq); + if (ret) + dev_warn(&devfreq->dev, + "failed to resume devfreq device\n"); + } + mutex_unlock(&devfreq_list_lock); +} + +/** + * devfreq_add_governor() - Add devfreq governor + * @governor: the devfreq governor to be added + */ +int devfreq_add_governor(struct devfreq_governor *governor) +{ + struct devfreq_governor *g; + struct devfreq *devfreq; + int err = 0; + + if (!governor) { + pr_err("%s: Invalid parameters.\n", __func__); + return -EINVAL; + } + + mutex_lock(&devfreq_list_lock); + g = find_devfreq_governor(governor->name); + if (!IS_ERR(g)) { + pr_err("%s: governor %s already registered\n", __func__, + g->name); + err = -EINVAL; + goto err_out; + } + + list_add(&governor->node, &devfreq_governor_list); + + list_for_each_entry(devfreq, &devfreq_list, node) { + int ret = 0; + struct device *dev = devfreq->dev.parent; + + if (!strncmp(devfreq->governor->name, governor->name, + DEVFREQ_NAME_LEN)) { + /* The following should never occur */ + if (devfreq->governor) { + dev_warn(dev, + "%s: Governor %s already present\n", + __func__, devfreq->governor->name); + ret = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_STOP, NULL); + if (ret) { + dev_warn(dev, + "%s: Governor %s stop = %d\n", + __func__, + devfreq->governor->name, ret); + } + /* Fall through */ + } + devfreq->governor = governor; + ret = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_START, NULL); + if (ret) { + dev_warn(dev, "%s: Governor %s start=%d\n", + __func__, devfreq->governor->name, + ret); + } + } + } + +err_out: + mutex_unlock(&devfreq_list_lock); + + return err; +} +EXPORT_SYMBOL(devfreq_add_governor); + +static void devm_devfreq_remove_governor(void *governor) +{ + WARN_ON(devfreq_remove_governor(governor)); +} + +/** + * devm_devfreq_add_governor() - Add devfreq governor + * @dev: device which adds devfreq governor + * @governor: the devfreq governor to be added + * + * This is a resource-managed variant of devfreq_add_governor(). + */ +int devm_devfreq_add_governor(struct device *dev, + struct devfreq_governor *governor) +{ + int err; + + err = devfreq_add_governor(governor); + if (err) + return err; + + return devm_add_action_or_reset(dev, devm_devfreq_remove_governor, + governor); +} +EXPORT_SYMBOL(devm_devfreq_add_governor); + +/** + * devfreq_remove_governor() - Remove devfreq feature from a device. + * @governor: the devfreq governor to be removed + */ +int devfreq_remove_governor(struct devfreq_governor *governor) +{ + struct devfreq_governor *g; + struct devfreq *devfreq; + int err = 0; + + if (!governor) { + pr_err("%s: Invalid parameters.\n", __func__); + return -EINVAL; + } + + mutex_lock(&devfreq_list_lock); + g = find_devfreq_governor(governor->name); + if (IS_ERR(g)) { + pr_err("%s: governor %s not registered\n", __func__, + governor->name); + err = PTR_ERR(g); + goto err_out; + } + list_for_each_entry(devfreq, &devfreq_list, node) { + int ret; + struct device *dev = devfreq->dev.parent; + + if (!strncmp(devfreq->governor->name, governor->name, + DEVFREQ_NAME_LEN)) { + /* we should have a devfreq governor! */ + if (!devfreq->governor) { + dev_warn(dev, "%s: Governor %s NOT present\n", + __func__, governor->name); + continue; + /* Fall through */ + } + ret = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_STOP, NULL); + if (ret) { + dev_warn(dev, "%s: Governor %s stop=%d\n", + __func__, devfreq->governor->name, + ret); + } + devfreq->governor = NULL; + } + } + + list_del(&governor->node); +err_out: + mutex_unlock(&devfreq_list_lock); + + return err; +} +EXPORT_SYMBOL(devfreq_remove_governor); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + return sprintf(buf, "%s\n", dev_name(df->dev.parent)); +} +static DEVICE_ATTR_RO(name); + +static ssize_t governor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + + if (!df->governor) + return -EINVAL; + + return sprintf(buf, "%s\n", df->governor->name); +} + +static ssize_t governor_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + int ret; + char str_governor[DEVFREQ_NAME_LEN + 1]; + const struct devfreq_governor *governor, *prev_governor; + + if (!df->governor) + return -EINVAL; + + ret = sscanf(buf, "%" __stringify(DEVFREQ_NAME_LEN) "s", str_governor); + if (ret != 1) + return -EINVAL; + + mutex_lock(&devfreq_list_lock); + governor = try_then_request_governor(str_governor); + if (IS_ERR(governor)) { + ret = PTR_ERR(governor); + goto out; + } + if (df->governor == governor) { + ret = 0; + goto out; + } else if (IS_SUPPORTED_FLAG(df->governor->flags, IMMUTABLE) + || IS_SUPPORTED_FLAG(governor->flags, IMMUTABLE)) { + ret = -EINVAL; + goto out; + } + + /* + * Stop the current governor and remove the specific sysfs files + * which depend on current governor. + */ + ret = df->governor->event_handler(df, DEVFREQ_GOV_STOP, NULL); + if (ret) { + dev_warn(dev, "%s: Governor %s not stopped(%d)\n", + __func__, df->governor->name, ret); + goto out; + } + remove_sysfs_files(df, df->governor); + + /* + * Start the new governor and create the specific sysfs files + * which depend on the new governor. + */ + prev_governor = df->governor; + df->governor = governor; + ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL); + if (ret) { + dev_warn(dev, "%s: Governor %s not started(%d)\n", + __func__, df->governor->name, ret); + + /* Restore previous governor */ + df->governor = prev_governor; + ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL); + if (ret) { + dev_err(dev, + "%s: reverting to Governor %s failed (%d)\n", + __func__, prev_governor->name, ret); + df->governor = NULL; + goto out; + } + } + + /* + * Create the sysfs files for the new governor. But if failed to start + * the new governor, restore the sysfs files of previous governor. + */ + create_sysfs_files(df, df->governor); + +out: + mutex_unlock(&devfreq_list_lock); + + if (!ret) + ret = count; + return ret; +} +static DEVICE_ATTR_RW(governor); + +static ssize_t available_governors_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct devfreq *df = to_devfreq(d); + ssize_t count = 0; + + if (!df->governor) + return -EINVAL; + + mutex_lock(&devfreq_list_lock); + + /* + * The devfreq with immutable governor (e.g., passive) shows + * only own governor. + */ + if (IS_SUPPORTED_FLAG(df->governor->flags, IMMUTABLE)) { + count = scnprintf(&buf[count], DEVFREQ_NAME_LEN, + "%s ", df->governor->name); + /* + * The devfreq device shows the registered governor except for + * immutable governors such as passive governor . + */ + } else { + struct devfreq_governor *governor; + + list_for_each_entry(governor, &devfreq_governor_list, node) { + if (IS_SUPPORTED_FLAG(governor->flags, IMMUTABLE)) + continue; + count += scnprintf(&buf[count], (PAGE_SIZE - count - 2), + "%s ", governor->name); + } + } + + mutex_unlock(&devfreq_list_lock); + + /* Truncate the trailing space */ + if (count) + count--; + + count += sprintf(&buf[count], "\n"); + + return count; +} +static DEVICE_ATTR_RO(available_governors); + +static ssize_t cur_freq_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned long freq; + struct devfreq *df = to_devfreq(dev); + + if (!df->profile) + return -EINVAL; + + if (df->profile->get_cur_freq && + !df->profile->get_cur_freq(df->dev.parent, &freq)) + return sprintf(buf, "%lu\n", freq); + + return sprintf(buf, "%lu\n", df->previous_freq); +} +static DEVICE_ATTR_RO(cur_freq); + +static ssize_t target_freq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + + return sprintf(buf, "%lu\n", df->previous_freq); +} +static DEVICE_ATTR_RO(target_freq); + +static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long value; + int ret; + + /* + * Protect against theoretical sysfs writes between + * device_add and dev_pm_qos_add_request + */ + if (!dev_pm_qos_request_active(&df->user_min_freq_req)) + return -EAGAIN; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + return -EINVAL; + + /* Round down to kHz for PM QoS */ + ret = dev_pm_qos_update_request(&df->user_min_freq_req, + value / HZ_PER_KHZ); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long min_freq, max_freq; + + mutex_lock(&df->lock); + devfreq_get_freq_range(df, &min_freq, &max_freq); + mutex_unlock(&df->lock); + + return sprintf(buf, "%lu\n", min_freq); +} +static DEVICE_ATTR_RW(min_freq); + +static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long value; + int ret; + + /* + * Protect against theoretical sysfs writes between + * device_add and dev_pm_qos_add_request + */ + if (!dev_pm_qos_request_active(&df->user_max_freq_req)) + return -EINVAL; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + return -EINVAL; + + /* + * PM QoS frequencies are in kHz so we need to convert. Convert by + * rounding upwards so that the acceptable interval never shrinks. + * + * For example if the user writes "666666666" to sysfs this value will + * be converted to 666667 kHz and back to 666667000 Hz before an OPP + * lookup, this ensures that an OPP of 666666666Hz is still accepted. + * + * A value of zero means "no limit". + */ + if (value) + value = DIV_ROUND_UP(value, HZ_PER_KHZ); + else + value = PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE; + + ret = dev_pm_qos_update_request(&df->user_max_freq_req, value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long min_freq, max_freq; + + mutex_lock(&df->lock); + devfreq_get_freq_range(df, &min_freq, &max_freq); + mutex_unlock(&df->lock); + + return sprintf(buf, "%lu\n", max_freq); +} +static DEVICE_ATTR_RW(max_freq); + +static ssize_t available_frequencies_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct devfreq *df = to_devfreq(d); + ssize_t count = 0; + int i; + + if (!df->profile) + return -EINVAL; + + mutex_lock(&df->lock); + + for (i = 0; i < df->max_state; i++) + count += scnprintf(&buf[count], (PAGE_SIZE - count - 2), + "%lu ", df->freq_table[i]); + + mutex_unlock(&df->lock); + /* Truncate the trailing space */ + if (count) + count--; + + count += sprintf(&buf[count], "\n"); + + return count; +} +static DEVICE_ATTR_RO(available_frequencies); + +static ssize_t trans_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + ssize_t len = 0; + int i, j; + unsigned int max_state; + + if (!df->profile) + return -EINVAL; + max_state = df->max_state; + + if (max_state == 0) + return scnprintf(buf, PAGE_SIZE, "Not Supported.\n"); + + mutex_lock(&df->lock); + if (!df->stop_polling && + devfreq_update_status(df, df->previous_freq)) { + mutex_unlock(&df->lock); + return 0; + } + mutex_unlock(&df->lock); + + len += scnprintf(buf + len, PAGE_SIZE - len, " From : To\n"); + len += scnprintf(buf + len, PAGE_SIZE - len, " :"); + for (i = 0; i < max_state; i++) { + if (len >= PAGE_SIZE - 1) + break; + len += scnprintf(buf + len, PAGE_SIZE - len, "%10lu", + df->freq_table[i]); + } + if (len >= PAGE_SIZE - 1) + return PAGE_SIZE - 1; + + len += scnprintf(buf + len, PAGE_SIZE - len, " time(ms)\n"); + + for (i = 0; i < max_state; i++) { + if (len >= PAGE_SIZE - 1) + break; + if (df->freq_table[i] == df->previous_freq) + len += scnprintf(buf + len, PAGE_SIZE - len, "*"); + else + len += scnprintf(buf + len, PAGE_SIZE - len, " "); + if (len >= PAGE_SIZE - 1) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%10lu:", + df->freq_table[i]); + for (j = 0; j < max_state; j++) { + if (len >= PAGE_SIZE - 1) + break; + len += scnprintf(buf + len, PAGE_SIZE - len, "%10u", + df->stats.trans_table[(i * max_state) + j]); + } + if (len >= PAGE_SIZE - 1) + break; + len += scnprintf(buf + len, PAGE_SIZE - len, "%10llu\n", (u64) + jiffies64_to_msecs(df->stats.time_in_state[i])); + } + + if (len < PAGE_SIZE - 1) + len += scnprintf(buf + len, PAGE_SIZE - len, "Total transition : %u\n", + df->stats.total_trans); + + if (len >= PAGE_SIZE - 1) { + pr_warn_once("devfreq transition table exceeds PAGE_SIZE. Disabling\n"); + return -EFBIG; + } + + return len; +} + +static ssize_t trans_stat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + int err, value; + + if (!df->profile) + return -EINVAL; + + if (df->max_state == 0) + return count; + + err = kstrtoint(buf, 10, &value); + if (err || value != 0) + return -EINVAL; + + mutex_lock(&df->lock); + memset(df->stats.time_in_state, 0, (df->max_state * + sizeof(*df->stats.time_in_state))); + memset(df->stats.trans_table, 0, array3_size(sizeof(unsigned int), + df->max_state, + df->max_state)); + df->stats.total_trans = 0; + df->stats.last_update = get_jiffies_64(); + mutex_unlock(&df->lock); + + return count; +} +static DEVICE_ATTR_RW(trans_stat); + +static struct attribute *devfreq_attrs[] = { + &dev_attr_name.attr, + &dev_attr_governor.attr, + &dev_attr_available_governors.attr, + &dev_attr_cur_freq.attr, + &dev_attr_available_frequencies.attr, + &dev_attr_target_freq.attr, + &dev_attr_min_freq.attr, + &dev_attr_max_freq.attr, + &dev_attr_trans_stat.attr, + NULL, +}; +ATTRIBUTE_GROUPS(devfreq); + +static ssize_t polling_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + + if (!df->profile) + return -EINVAL; + + return sprintf(buf, "%d\n", df->profile->polling_ms); +} + +static ssize_t polling_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned int value; + int ret; + + if (!df->governor) + return -EINVAL; + + ret = sscanf(buf, "%u", &value); + if (ret != 1) + return -EINVAL; + + df->governor->event_handler(df, DEVFREQ_GOV_UPDATE_INTERVAL, &value); + ret = count; + + return ret; +} +static DEVICE_ATTR_RW(polling_interval); + +static ssize_t timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *df = to_devfreq(dev); + + if (!df->profile) + return -EINVAL; + + return sprintf(buf, "%s\n", timer_name[df->profile->timer]); +} + +static ssize_t timer_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + char str_timer[DEVFREQ_NAME_LEN + 1]; + int timer = -1; + int ret = 0, i; + + if (!df->governor || !df->profile) + return -EINVAL; + + ret = sscanf(buf, "%16s", str_timer); + if (ret != 1) + return -EINVAL; + + for (i = 0; i < DEVFREQ_TIMER_NUM; i++) { + if (!strncmp(timer_name[i], str_timer, DEVFREQ_NAME_LEN)) { + timer = i; + break; + } + } + + if (timer < 0) { + ret = -EINVAL; + goto out; + } + + if (df->profile->timer == timer) { + ret = 0; + goto out; + } + + mutex_lock(&df->lock); + df->profile->timer = timer; + mutex_unlock(&df->lock); + + ret = df->governor->event_handler(df, DEVFREQ_GOV_STOP, NULL); + if (ret) { + dev_warn(dev, "%s: Governor %s not stopped(%d)\n", + __func__, df->governor->name, ret); + goto out; + } + + ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL); + if (ret) + dev_warn(dev, "%s: Governor %s not started(%d)\n", + __func__, df->governor->name, ret); +out: + return ret ? ret : count; +} +static DEVICE_ATTR_RW(timer); + +#define CREATE_SYSFS_FILE(df, name) \ +{ \ + int ret; \ + ret = sysfs_create_file(&df->dev.kobj, &dev_attr_##name.attr); \ + if (ret < 0) { \ + dev_warn(&df->dev, \ + "Unable to create attr(%s)\n", "##name"); \ + } \ +} \ + +/* Create the specific sysfs files which depend on each governor. */ +static void create_sysfs_files(struct devfreq *devfreq, + const struct devfreq_governor *gov) +{ + if (IS_SUPPORTED_ATTR(gov->attrs, POLLING_INTERVAL)) + CREATE_SYSFS_FILE(devfreq, polling_interval); + if (IS_SUPPORTED_ATTR(gov->attrs, TIMER)) + CREATE_SYSFS_FILE(devfreq, timer); +} + +/* Remove the specific sysfs files which depend on each governor. */ +static void remove_sysfs_files(struct devfreq *devfreq, + const struct devfreq_governor *gov) +{ + if (IS_SUPPORTED_ATTR(gov->attrs, POLLING_INTERVAL)) + sysfs_remove_file(&devfreq->dev.kobj, + &dev_attr_polling_interval.attr); + if (IS_SUPPORTED_ATTR(gov->attrs, TIMER)) + sysfs_remove_file(&devfreq->dev.kobj, &dev_attr_timer.attr); +} + +/** + * devfreq_summary_show() - Show the summary of the devfreq devices + * @s: seq_file instance to show the summary of devfreq devices + * @data: not used + * + * Show the summary of the devfreq devices via 'devfreq_summary' debugfs file. + * It helps that user can know the detailed information of the devfreq devices. + * + * Return 0 always because it shows the information without any data change. + */ +static int devfreq_summary_show(struct seq_file *s, void *data) +{ + struct devfreq *devfreq; + struct devfreq *p_devfreq = NULL; + unsigned long cur_freq, min_freq, max_freq; + unsigned int polling_ms; + unsigned int timer; + + seq_printf(s, "%-30s %-30s %-15s %-10s %10s %12s %12s %12s\n", + "dev", + "parent_dev", + "governor", + "timer", + "polling_ms", + "cur_freq_Hz", + "min_freq_Hz", + "max_freq_Hz"); + seq_printf(s, "%30s %30s %15s %10s %10s %12s %12s %12s\n", + "------------------------------", + "------------------------------", + "---------------", + "----------", + "----------", + "------------", + "------------", + "------------"); + + mutex_lock(&devfreq_list_lock); + + list_for_each_entry_reverse(devfreq, &devfreq_list, node) { +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE) + if (!strncmp(devfreq->governor->name, DEVFREQ_GOV_PASSIVE, + DEVFREQ_NAME_LEN)) { + struct devfreq_passive_data *data = devfreq->data; + + if (data) + p_devfreq = data->parent; + } else { + p_devfreq = NULL; + } +#endif + + mutex_lock(&devfreq->lock); + cur_freq = devfreq->previous_freq; + devfreq_get_freq_range(devfreq, &min_freq, &max_freq); + timer = devfreq->profile->timer; + + if (IS_SUPPORTED_ATTR(devfreq->governor->attrs, POLLING_INTERVAL)) + polling_ms = devfreq->profile->polling_ms; + else + polling_ms = 0; + mutex_unlock(&devfreq->lock); + + seq_printf(s, + "%-30s %-30s %-15s %-10s %10d %12ld %12ld %12ld\n", + dev_name(&devfreq->dev), + p_devfreq ? dev_name(&p_devfreq->dev) : "null", + devfreq->governor->name, + polling_ms ? timer_name[timer] : "null", + polling_ms, + cur_freq, + min_freq, + max_freq); + } + + mutex_unlock(&devfreq_list_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(devfreq_summary); + +static int __init devfreq_init(void) +{ + devfreq_class = class_create(THIS_MODULE, "devfreq"); + if (IS_ERR(devfreq_class)) { + pr_err("%s: couldn't create class\n", __FILE__); + return PTR_ERR(devfreq_class); + } + + devfreq_wq = create_freezable_workqueue("devfreq_wq"); + if (!devfreq_wq) { + class_destroy(devfreq_class); + pr_err("%s: couldn't create workqueue\n", __FILE__); + return -ENOMEM; + } + devfreq_class->dev_groups = devfreq_groups; + + devfreq_debugfs = debugfs_create_dir("devfreq", NULL); + debugfs_create_file("devfreq_summary", 0444, + devfreq_debugfs, NULL, + &devfreq_summary_fops); + + return 0; +} +subsys_initcall(devfreq_init); + +/* + * The following are helper functions for devfreq user device drivers with + * OPP framework. + */ + +/** + * devfreq_recommended_opp() - Helper function to get proper OPP for the + * freq value given to target callback. + * @dev: The devfreq user device. (parent of devfreq) + * @freq: The frequency given to target function + * @flags: Flags handed from devfreq framework. + * + * The callers are required to call dev_pm_opp_put() for the returned OPP after + * use. + */ +struct dev_pm_opp *devfreq_recommended_opp(struct device *dev, + unsigned long *freq, + u32 flags) +{ + struct dev_pm_opp *opp; + + if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) { + /* The freq is an upper bound. opp should be lower */ + opp = dev_pm_opp_find_freq_floor(dev, freq); + + /* If not available, use the closest opp */ + if (opp == ERR_PTR(-ERANGE)) + opp = dev_pm_opp_find_freq_ceil(dev, freq); + } else { + /* The freq is an lower bound. opp should be higher */ + opp = dev_pm_opp_find_freq_ceil(dev, freq); + + /* If not available, use the closest opp */ + if (opp == ERR_PTR(-ERANGE)) + opp = dev_pm_opp_find_freq_floor(dev, freq); + } + + return opp; +} +EXPORT_SYMBOL(devfreq_recommended_opp); + +/** + * devfreq_register_opp_notifier() - Helper function to get devfreq notified + * for any changes in the OPP availability + * changes + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + */ +int devfreq_register_opp_notifier(struct device *dev, struct devfreq *devfreq) +{ + return dev_pm_opp_register_notifier(dev, &devfreq->nb); +} +EXPORT_SYMBOL(devfreq_register_opp_notifier); + +/** + * devfreq_unregister_opp_notifier() - Helper function to stop getting devfreq + * notified for any changes in the OPP + * availability changes anymore. + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * + * At exit() callback of devfreq_dev_profile, this must be included if + * devfreq_recommended_opp is used. + */ +int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq) +{ + return dev_pm_opp_unregister_notifier(dev, &devfreq->nb); +} +EXPORT_SYMBOL(devfreq_unregister_opp_notifier); + +static void devm_devfreq_opp_release(struct device *dev, void *res) +{ + devfreq_unregister_opp_notifier(dev, *(struct devfreq **)res); +} + +/** + * devm_devfreq_register_opp_notifier() - Resource-managed + * devfreq_register_opp_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + */ +int devm_devfreq_register_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ + struct devfreq **ptr; + int ret; + + ptr = devres_alloc(devm_devfreq_opp_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = devfreq_register_opp_notifier(dev, devfreq); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = devfreq; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_devfreq_register_opp_notifier); + +/** + * devm_devfreq_unregister_opp_notifier() - Resource-managed + * devfreq_unregister_opp_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + */ +void devm_devfreq_unregister_opp_notifier(struct device *dev, + struct devfreq *devfreq) +{ + WARN_ON(devres_release(dev, devm_devfreq_opp_release, + devm_devfreq_dev_match, devfreq)); +} +EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier); + +/** + * devfreq_register_notifier() - Register a driver with devfreq + * @devfreq: The devfreq object. + * @nb: The notifier block to register. + * @list: DEVFREQ_TRANSITION_NOTIFIER. + */ +int devfreq_register_notifier(struct devfreq *devfreq, + struct notifier_block *nb, + unsigned int list) +{ + int ret = 0; + + if (!devfreq) + return -EINVAL; + + switch (list) { + case DEVFREQ_TRANSITION_NOTIFIER: + ret = srcu_notifier_chain_register( + &devfreq->transition_notifier_list, nb); + break; + default: + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(devfreq_register_notifier); + +/* + * devfreq_unregister_notifier() - Unregister a driver with devfreq + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + * @list: DEVFREQ_TRANSITION_NOTIFIER. + */ +int devfreq_unregister_notifier(struct devfreq *devfreq, + struct notifier_block *nb, + unsigned int list) +{ + int ret = 0; + + if (!devfreq) + return -EINVAL; + + switch (list) { + case DEVFREQ_TRANSITION_NOTIFIER: + ret = srcu_notifier_chain_unregister( + &devfreq->transition_notifier_list, nb); + break; + default: + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(devfreq_unregister_notifier); + +struct devfreq_notifier_devres { + struct devfreq *devfreq; + struct notifier_block *nb; + unsigned int list; +}; + +static void devm_devfreq_notifier_release(struct device *dev, void *res) +{ + struct devfreq_notifier_devres *this = res; + + devfreq_unregister_notifier(this->devfreq, this->nb, this->list); +} + +/** + * devm_devfreq_register_notifier() + * - Resource-managed devfreq_register_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + * @list: DEVFREQ_TRANSITION_NOTIFIER. + */ +int devm_devfreq_register_notifier(struct device *dev, + struct devfreq *devfreq, + struct notifier_block *nb, + unsigned int list) +{ + struct devfreq_notifier_devres *ptr; + int ret; + + ptr = devres_alloc(devm_devfreq_notifier_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = devfreq_register_notifier(devfreq, nb, list); + if (ret) { + devres_free(ptr); + return ret; + } + + ptr->devfreq = devfreq; + ptr->nb = nb; + ptr->list = list; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_devfreq_register_notifier); + +/** + * devm_devfreq_unregister_notifier() + * - Resource-managed devfreq_unregister_notifier() + * @dev: The devfreq user device. (parent of devfreq) + * @devfreq: The devfreq object. + * @nb: The notifier block to be unregistered. + * @list: DEVFREQ_TRANSITION_NOTIFIER. + */ +void devm_devfreq_unregister_notifier(struct device *dev, + struct devfreq *devfreq, + struct notifier_block *nb, + unsigned int list) +{ + WARN_ON(devres_release(dev, devm_devfreq_notifier_release, + devm_devfreq_dev_match, devfreq)); +} +EXPORT_SYMBOL(devm_devfreq_unregister_notifier); diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig new file mode 100644 index 000000000..878825372 --- /dev/null +++ b/drivers/devfreq/event/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig PM_DEVFREQ_EVENT + bool "DEVFREQ-Event device Support" + help + The devfreq-event device provide the raw data and events which + indicate the current state of devfreq-event device. The provided + data from devfreq-event device is used to monitor the state of + device and determine the suitable size of resource to reduce the + wasted resource. + + The devfreq-event device can support the various type of events + (e.g., raw data, utilization, latency, bandwidth). The events + may be used by devfreq governor and other subsystem. + +if PM_DEVFREQ_EVENT + +config DEVFREQ_EVENT_EXYNOS_NOCP + tristate "Exynos NoC (Network On Chip) Probe DEVFREQ event Driver" + depends on ARCH_EXYNOS || COMPILE_TEST + select PM_OPP + select REGMAP_MMIO + help + This add the devfreq-event driver for Exynos SoC. It provides NoC + (Network on Chip) Probe counters to measure the bandwidth of AXI bus. + +config DEVFREQ_EVENT_EXYNOS_PPMU + tristate "Exynos PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver" + depends on ARCH_EXYNOS || COMPILE_TEST + select PM_OPP + help + This add the devfreq-event driver for Exynos SoC. It provides PPMU + (Platform Performance Monitoring Unit) counters to estimate the + utilization of each module. + +config DEVFREQ_EVENT_ROCKCHIP_DFI + tristate "ROCKCHIP DFI DEVFREQ event Driver" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + This add the devfreq-event driver for Rockchip SoC. It provides DFI + (DDR Monitor Module) driver to count ddr load. + +endif # PM_DEVFREQ_EVENT diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile new file mode 100644 index 000000000..3c847e5d5 --- /dev/null +++ b/drivers/devfreq/event/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Exynos DEVFREQ Event Drivers + +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 diff --git a/drivers/devfreq/event/exynos-nocp.c b/drivers/devfreq/event/exynos-nocp.c new file mode 100644 index 000000000..ccc531ee6 --- /dev/null +++ b/drivers/devfreq/event/exynos-nocp.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * exynos-nocp.c - Exynos NoC (Network On Chip) Probe support + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/devfreq-event.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "exynos-nocp.h" + +struct exynos_nocp { + struct devfreq_event_dev *edev; + struct devfreq_event_desc desc; + + struct device *dev; + + struct regmap *regmap; + struct clk *clk; +}; + +/* + * The devfreq-event ops structure for nocp probe. + */ +static int exynos_nocp_set_event(struct devfreq_event_dev *edev) +{ + struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); + int ret; + + /* Disable NoC probe */ + ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, + NOCP_MAIN_CTL_STATEN_MASK, 0); + if (ret < 0) { + dev_err(nocp->dev, "failed to disable the NoC probe device\n"); + return ret; + } + + /* Set a statistics dump period to 0 */ + ret = regmap_write(nocp->regmap, NOCP_STAT_PERIOD, 0x0); + if (ret < 0) + goto out; + + /* Set the IntEvent fields of *_SRC */ + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_SRC, + NOCP_CNT_SRC_INTEVENT_MASK, + NOCP_CNT_SRC_INTEVENT_BYTE_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_SRC, + NOCP_CNT_SRC_INTEVENT_MASK, + NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_SRC, + NOCP_CNT_SRC_INTEVENT_MASK, + NOCP_CNT_SRC_INTEVENT_CYCLE_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_SRC, + NOCP_CNT_SRC_INTEVENT_MASK, + NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); + if (ret < 0) + goto out; + + + /* Set an alarm with a max/min value of 0 to generate StatALARM */ + ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MIN, 0x0); + if (ret < 0) + goto out; + + ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MAX, 0x0); + if (ret < 0) + goto out; + + /* Set AlarmMode */ + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_ALARM_MODE, + NOCP_CNT_ALARM_MODE_MASK, + NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_ALARM_MODE, + NOCP_CNT_ALARM_MODE_MASK, + NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_ALARM_MODE, + NOCP_CNT_ALARM_MODE_MASK, + NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); + if (ret < 0) + goto out; + + ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_ALARM_MODE, + NOCP_CNT_ALARM_MODE_MASK, + NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); + if (ret < 0) + goto out; + + /* Enable the measurements by setting AlarmEn and StatEn */ + ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, + NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK, + NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK); + if (ret < 0) + goto out; + + /* Set GlobalEN */ + ret = regmap_update_bits(nocp->regmap, NOCP_CFG_CTL, + NOCP_CFG_CTL_GLOBALEN_MASK, + NOCP_CFG_CTL_GLOBALEN_MASK); + if (ret < 0) + goto out; + + /* Enable NoC probe */ + ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, + NOCP_MAIN_CTL_STATEN_MASK, + NOCP_MAIN_CTL_STATEN_MASK); + if (ret < 0) + goto out; + + return 0; + +out: + /* Reset NoC probe */ + if (regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, + NOCP_MAIN_CTL_STATEN_MASK, 0)) { + dev_err(nocp->dev, "Failed to reset NoC probe device\n"); + } + + return ret; +} + +static int exynos_nocp_get_event(struct devfreq_event_dev *edev, + struct devfreq_event_data *edata) +{ + struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); + unsigned int counter[4]; + int ret; + + /* Read cycle count */ + ret = regmap_read(nocp->regmap, NOCP_COUNTERS_0_VAL, &counter[0]); + if (ret < 0) + goto out; + + ret = regmap_read(nocp->regmap, NOCP_COUNTERS_1_VAL, &counter[1]); + if (ret < 0) + goto out; + + ret = regmap_read(nocp->regmap, NOCP_COUNTERS_2_VAL, &counter[2]); + if (ret < 0) + goto out; + + ret = regmap_read(nocp->regmap, NOCP_COUNTERS_3_VAL, &counter[3]); + if (ret < 0) + goto out; + + edata->load_count = ((counter[1] << 16) | counter[0]); + edata->total_count = ((counter[3] << 16) | counter[2]); + + dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name, + edata->load_count, edata->total_count); + + return 0; + +out: + dev_err(nocp->dev, "Failed to read the counter of NoC probe device\n"); + + return ret; +} + +static const struct devfreq_event_ops exynos_nocp_ops = { + .set_event = exynos_nocp_set_event, + .get_event = exynos_nocp_get_event, +}; + +static const struct of_device_id exynos_nocp_id_match[] = { + { .compatible = "samsung,exynos5420-nocp", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, exynos_nocp_id_match); + +static struct regmap_config exynos_nocp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = NOCP_COUNTERS_3_VAL, +}; + +static int exynos_nocp_parse_dt(struct platform_device *pdev, + struct exynos_nocp *nocp) +{ + struct device *dev = nocp->dev; + struct device_node *np = dev->of_node; + struct resource *res; + void __iomem *base; + + if (!np) { + dev_err(dev, "failed to find devicetree node\n"); + return -EINVAL; + } + + nocp->clk = devm_clk_get(dev, "nocp"); + if (IS_ERR(nocp->clk)) + nocp->clk = NULL; + + /* Maps the memory mapped IO to control nocp register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + exynos_nocp_regmap_config.max_register = resource_size(res) - 4; + + nocp->regmap = devm_regmap_init_mmio(dev, base, + &exynos_nocp_regmap_config); + if (IS_ERR(nocp->regmap)) { + dev_err(dev, "failed to initialize regmap\n"); + return PTR_ERR(nocp->regmap); + } + + return 0; +} + +static int exynos_nocp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct exynos_nocp *nocp; + int ret; + + nocp = devm_kzalloc(&pdev->dev, sizeof(*nocp), GFP_KERNEL); + if (!nocp) + return -ENOMEM; + + nocp->dev = &pdev->dev; + + /* Parse dt data to get resource */ + ret = exynos_nocp_parse_dt(pdev, nocp); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to parse devicetree for resource\n"); + return ret; + } + + /* Add devfreq-event device to measure the bandwidth of NoC */ + nocp->desc.ops = &exynos_nocp_ops; + nocp->desc.driver_data = nocp; + nocp->desc.name = np->full_name; + nocp->edev = devm_devfreq_event_add_edev(&pdev->dev, &nocp->desc); + if (IS_ERR(nocp->edev)) { + dev_err(&pdev->dev, + "failed to add devfreq-event device\n"); + return PTR_ERR(nocp->edev); + } + platform_set_drvdata(pdev, nocp); + + ret = clk_prepare_enable(nocp->clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare ppmu clock\n"); + return ret; + } + + pr_info("exynos-nocp: new NoC Probe device registered: %s\n", + dev_name(dev)); + + return 0; +} + +static int exynos_nocp_remove(struct platform_device *pdev) +{ + struct exynos_nocp *nocp = platform_get_drvdata(pdev); + + clk_disable_unprepare(nocp->clk); + + return 0; +} + +static struct platform_driver exynos_nocp_driver = { + .probe = exynos_nocp_probe, + .remove = exynos_nocp_remove, + .driver = { + .name = "exynos-nocp", + .of_match_table = exynos_nocp_id_match, + }, +}; +module_platform_driver(exynos_nocp_driver); + +MODULE_DESCRIPTION("Exynos NoC (Network on Chip) Probe driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/event/exynos-nocp.h b/drivers/devfreq/event/exynos-nocp.h new file mode 100644 index 000000000..2d6f08cfd --- /dev/null +++ b/drivers/devfreq/event/exynos-nocp.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * exynos-nocp.h - Exynos NoC (Network on Chip) Probe header file + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + */ + +#ifndef __EXYNOS_NOCP_H__ +#define __EXYNOS_NOCP_H__ + +enum nocp_reg { + NOCP_ID_REVISION_ID = 0x04, + NOCP_MAIN_CTL = 0x08, + NOCP_CFG_CTL = 0x0C, + + NOCP_STAT_PERIOD = 0x24, + NOCP_STAT_GO = 0x28, + NOCP_STAT_ALARM_MIN = 0x2C, + NOCP_STAT_ALARM_MAX = 0x30, + NOCP_STAT_ALARM_STATUS = 0x34, + NOCP_STAT_ALARM_CLR = 0x38, + + NOCP_COUNTERS_0_SRC = 0x138, + NOCP_COUNTERS_0_ALARM_MODE = 0x13C, + NOCP_COUNTERS_0_VAL = 0x140, + + NOCP_COUNTERS_1_SRC = 0x14C, + NOCP_COUNTERS_1_ALARM_MODE = 0x150, + NOCP_COUNTERS_1_VAL = 0x154, + + NOCP_COUNTERS_2_SRC = 0x160, + NOCP_COUNTERS_2_ALARM_MODE = 0x164, + NOCP_COUNTERS_2_VAL = 0x168, + + NOCP_COUNTERS_3_SRC = 0x174, + NOCP_COUNTERS_3_ALARM_MODE = 0x178, + NOCP_COUNTERS_3_VAL = 0x17C, +}; + +/* NOCP_MAIN_CTL register */ +#define NOCP_MAIN_CTL_ERREN_MASK BIT(0) +#define NOCP_MAIN_CTL_TRACEEN_MASK BIT(1) +#define NOCP_MAIN_CTL_PAYLOADEN_MASK BIT(2) +#define NOCP_MAIN_CTL_STATEN_MASK BIT(3) +#define NOCP_MAIN_CTL_ALARMEN_MASK BIT(4) +#define NOCP_MAIN_CTL_STATCONDDUMP_MASK BIT(5) +#define NOCP_MAIN_CTL_INTRUSIVEMODE_MASK BIT(6) + +/* NOCP_CFG_CTL register */ +#define NOCP_CFG_CTL_GLOBALEN_MASK BIT(0) +#define NOCP_CFG_CTL_ACTIVE_MASK BIT(1) + +/* NOCP_COUNTERS_x_SRC register */ +#define NOCP_CNT_SRC_INTEVENT_SHIFT 0 +#define NOCP_CNT_SRC_INTEVENT_MASK (0x1F << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_OFF_MASK (0x0 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_CYCLE_MASK (0x1 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_IDLE_MASK (0x2 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_XFER_MASK (0x3 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_BUSY_MASK (0x4 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_WAIT_MASK (0x5 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_PKT_MASK (0x6 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_BYTE_MASK (0x8 << NOCP_CNT_SRC_INTEVENT_SHIFT) +#define NOCP_CNT_SRC_INTEVENT_CHAIN_MASK (0x10 << NOCP_CNT_SRC_INTEVENT_SHIFT) + +/* NOCP_COUNTERS_x_ALARM_MODE register */ +#define NOCP_CNT_ALARM_MODE_SHIFT 0 +#define NOCP_CNT_ALARM_MODE_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT) +#define NOCP_CNT_ALARM_MODE_OFF_MASK (0x0 << NOCP_CNT_ALARM_MODE_SHIFT) +#define NOCP_CNT_ALARM_MODE_MIN_MASK (0x1 << NOCP_CNT_ALARM_MODE_SHIFT) +#define NOCP_CNT_ALARM_MODE_MAX_MASK (0x2 << NOCP_CNT_ALARM_MODE_SHIFT) +#define NOCP_CNT_ALARM_MODE_MIN_MAX_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT) + +#endif /* __EXYNOS_NOCP_H__ */ diff --git a/drivers/devfreq/event/exynos-ppmu.c b/drivers/devfreq/event/exynos-ppmu.c new file mode 100644 index 000000000..a443e7c42 --- /dev/null +++ b/drivers/devfreq/event/exynos-ppmu.c @@ -0,0 +1,724 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support + * + * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + * + * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/suspend.h> +#include <linux/devfreq-event.h> + +#include "exynos-ppmu.h" + +enum exynos_ppmu_type { + EXYNOS_TYPE_PPMU, + EXYNOS_TYPE_PPMU_V2, +}; + +struct exynos_ppmu_data { + struct clk *clk; +}; + +struct exynos_ppmu { + struct devfreq_event_dev **edev; + struct devfreq_event_desc *desc; + unsigned int num_events; + + struct device *dev; + struct regmap *regmap; + + struct exynos_ppmu_data ppmu; + enum exynos_ppmu_type ppmu_type; +}; + +#define PPMU_EVENT(name) \ + { "ppmu-event0-"#name, PPMU_PMNCNT0 }, \ + { "ppmu-event1-"#name, PPMU_PMNCNT1 }, \ + { "ppmu-event2-"#name, PPMU_PMNCNT2 }, \ + { "ppmu-event3-"#name, PPMU_PMNCNT3 } + +static struct __exynos_ppmu_events { + char *name; + int id; +} ppmu_events[] = { + /* For Exynos3250, Exynos4 and Exynos5260 */ + PPMU_EVENT(g3d), + PPMU_EVENT(fsys), + + /* For Exynos4 SoCs and Exynos3250 */ + PPMU_EVENT(dmc0), + PPMU_EVENT(dmc1), + PPMU_EVENT(cpu), + PPMU_EVENT(rightbus), + PPMU_EVENT(leftbus), + PPMU_EVENT(lcd0), + PPMU_EVENT(camif), + + /* Only for Exynos3250 and Exynos5260 */ + PPMU_EVENT(mfc), + + /* Only for Exynos4 SoCs */ + PPMU_EVENT(mfc-left), + PPMU_EVENT(mfc-right), + + /* Only for Exynos5260 SoCs */ + PPMU_EVENT(drex0-s0), + PPMU_EVENT(drex0-s1), + PPMU_EVENT(drex1-s0), + PPMU_EVENT(drex1-s1), + PPMU_EVENT(eagle), + PPMU_EVENT(kfc), + PPMU_EVENT(isp), + PPMU_EVENT(fimc), + PPMU_EVENT(gscl), + PPMU_EVENT(mscl), + PPMU_EVENT(fimd0x), + PPMU_EVENT(fimd1x), + + /* Only for Exynos5433 SoCs */ + PPMU_EVENT(d0-cpu), + PPMU_EVENT(d0-general), + PPMU_EVENT(d0-rt), + PPMU_EVENT(d1-cpu), + PPMU_EVENT(d1-general), + PPMU_EVENT(d1-rt), + + /* For Exynos5422 SoC, deprecated (backwards compatible) */ + PPMU_EVENT(dmc0_0), + PPMU_EVENT(dmc0_1), + PPMU_EVENT(dmc1_0), + PPMU_EVENT(dmc1_1), + /* For Exynos5422 SoC */ + PPMU_EVENT(dmc0-0), + PPMU_EVENT(dmc0-1), + PPMU_EVENT(dmc1-0), + PPMU_EVENT(dmc1-1), +}; + +static int __exynos_ppmu_find_ppmu_id(const char *edev_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) + if (!strcmp(edev_name, ppmu_events[i].name)) + return ppmu_events[i].id; + + return -EINVAL; +} + +static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) +{ + return __exynos_ppmu_find_ppmu_id(edev->desc->name); +} + +/* + * The devfreq-event ops structure for PPMU v1.1 + */ +static int exynos_ppmu_disable(struct devfreq_event_dev *edev) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + int ret; + u32 pmnc; + + /* Disable all counters */ + ret = regmap_write(info->regmap, PPMU_CNTENC, + PPMU_CCNT_MASK | + PPMU_PMCNT0_MASK | + PPMU_PMCNT1_MASK | + PPMU_PMCNT2_MASK | + PPMU_PMCNT3_MASK); + if (ret < 0) + return ret; + + /* Disable PPMU */ + ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_ppmu_set_event(struct devfreq_event_dev *edev) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + int id = exynos_ppmu_find_ppmu_id(edev); + int ret; + u32 pmnc, cntens; + + if (id < 0) + return id; + + /* Enable specific counter */ + ret = regmap_read(info->regmap, PPMU_CNTENS, &cntens); + if (ret < 0) + return ret; + + cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + ret = regmap_write(info->regmap, PPMU_CNTENS, cntens); + if (ret < 0) + return ret; + + /* Set the event of proper data type monitoring */ + ret = regmap_write(info->regmap, PPMU_BEVTxSEL(id), + edev->desc->event_type); + if (ret < 0) + return ret; + + /* Reset cycle counter/performance counter and enable PPMU */ + ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~(PPMU_PMNC_ENABLE_MASK + | PPMU_PMNC_COUNTER_RESET_MASK + | PPMU_PMNC_CC_RESET_MASK); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); + ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_ppmu_get_event(struct devfreq_event_dev *edev, + struct devfreq_event_data *edata) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + int id = exynos_ppmu_find_ppmu_id(edev); + unsigned int total_count, load_count; + unsigned int pmcnt3_high, pmcnt3_low; + unsigned int pmnc, cntenc; + int ret; + + if (id < 0) + return -EINVAL; + + /* Disable PPMU */ + ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); + if (ret < 0) + return ret; + + /* Read cycle count */ + ret = regmap_read(info->regmap, PPMU_CCNT, &total_count); + if (ret < 0) + return ret; + edata->total_count = total_count; + + /* Read performance count */ + switch (id) { + case PPMU_PMNCNT0: + case PPMU_PMNCNT1: + case PPMU_PMNCNT2: + ret = regmap_read(info->regmap, PPMU_PMNCT(id), &load_count); + if (ret < 0) + return ret; + edata->load_count = load_count; + break; + case PPMU_PMNCNT3: + ret = regmap_read(info->regmap, PPMU_PMCNT3_HIGH, &pmcnt3_high); + if (ret < 0) + return ret; + + ret = regmap_read(info->regmap, PPMU_PMCNT3_LOW, &pmcnt3_low); + if (ret < 0) + return ret; + + edata->load_count = ((pmcnt3_high << 8) | pmcnt3_low); + break; + default: + return -EINVAL; + } + + /* Disable specific counter */ + ret = regmap_read(info->regmap, PPMU_CNTENC, &cntenc); + if (ret < 0) + return ret; + + cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + ret = regmap_write(info->regmap, PPMU_CNTENC, cntenc); + if (ret < 0) + return ret; + + dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name, + edata->load_count, edata->total_count); + + return 0; +} + +static const struct devfreq_event_ops exynos_ppmu_ops = { + .disable = exynos_ppmu_disable, + .set_event = exynos_ppmu_set_event, + .get_event = exynos_ppmu_get_event, +}; + +/* + * The devfreq-event ops structure for PPMU v2.0 + */ +static int exynos_ppmu_v2_disable(struct devfreq_event_dev *edev) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + int ret; + u32 pmnc, clear; + + /* Disable all counters */ + clear = (PPMU_CCNT_MASK | PPMU_PMCNT0_MASK | PPMU_PMCNT1_MASK + | PPMU_PMCNT2_MASK | PPMU_PMCNT3_MASK); + ret = regmap_write(info->regmap, PPMU_V2_FLAG, clear); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_INTENC, clear); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CNTENC, clear); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CNT_RESET, clear); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG0, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG1, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG2, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CIG_RESULT, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CNT_AUTO, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CH_EV0_TYPE, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CH_EV1_TYPE, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CH_EV2_TYPE, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_CH_EV3_TYPE, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_SM_ID_V, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_SM_ID_A, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_V, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_A, 0x0); + if (ret < 0) + return ret; + + ret = regmap_write(info->regmap, PPMU_V2_INTERRUPT_RESET, 0x0); + if (ret < 0) + return ret; + + /* Disable PPMU */ + ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_ppmu_v2_set_event(struct devfreq_event_dev *edev) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + unsigned int pmnc, cntens; + int id = exynos_ppmu_find_ppmu_id(edev); + int ret; + + /* Enable all counters */ + ret = regmap_read(info->regmap, PPMU_V2_CNTENS, &cntens); + if (ret < 0) + return ret; + + cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + ret = regmap_write(info->regmap, PPMU_V2_CNTENS, cntens); + if (ret < 0) + return ret; + + /* Set the event of proper data type monitoring */ + ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id), + edev->desc->event_type); + if (ret < 0) + return ret; + + /* Reset cycle counter/performance counter and enable PPMU */ + ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~(PPMU_PMNC_ENABLE_MASK + | PPMU_PMNC_COUNTER_RESET_MASK + | PPMU_PMNC_CC_RESET_MASK + | PPMU_PMNC_CC_DIVIDER_MASK + | PPMU_V2_PMNC_START_MODE_MASK); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); + pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); + pmnc |= (PPMU_V2_MODE_MANUAL << PPMU_V2_PMNC_START_MODE_SHIFT); + + ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_ppmu_v2_get_event(struct devfreq_event_dev *edev, + struct devfreq_event_data *edata) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + int id = exynos_ppmu_find_ppmu_id(edev); + int ret; + unsigned int pmnc, cntenc; + unsigned int pmcnt_high, pmcnt_low; + unsigned int total_count, count; + unsigned long load_count = 0; + + /* Disable PPMU */ + ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); + if (ret < 0) + return ret; + + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); + if (ret < 0) + return ret; + + /* Read cycle count and performance count */ + ret = regmap_read(info->regmap, PPMU_V2_CCNT, &total_count); + if (ret < 0) + return ret; + edata->total_count = total_count; + + switch (id) { + case PPMU_PMNCNT0: + case PPMU_PMNCNT1: + case PPMU_PMNCNT2: + ret = regmap_read(info->regmap, PPMU_V2_PMNCT(id), &count); + if (ret < 0) + return ret; + load_count = count; + break; + case PPMU_PMNCNT3: + ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_HIGH, + &pmcnt_high); + if (ret < 0) + return ret; + + ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_LOW, &pmcnt_low); + if (ret < 0) + return ret; + + load_count = ((u64)((pmcnt_high & 0xff)) << 32)+ (u64)pmcnt_low; + break; + } + edata->load_count = load_count; + + /* Disable all counters */ + ret = regmap_read(info->regmap, PPMU_V2_CNTENC, &cntenc); + if (ret < 0) + return 0; + + cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + ret = regmap_write(info->regmap, PPMU_V2_CNTENC, cntenc); + if (ret < 0) + return ret; + + dev_dbg(&edev->dev, "%25s (load: %ld / %ld)\n", edev->desc->name, + edata->load_count, edata->total_count); + return 0; +} + +static const struct devfreq_event_ops exynos_ppmu_v2_ops = { + .disable = exynos_ppmu_v2_disable, + .set_event = exynos_ppmu_v2_set_event, + .get_event = exynos_ppmu_v2_get_event, +}; + +static const struct of_device_id exynos_ppmu_id_match[] = { + { + .compatible = "samsung,exynos-ppmu", + .data = (void *)EXYNOS_TYPE_PPMU, + }, { + .compatible = "samsung,exynos-ppmu-v2", + .data = (void *)EXYNOS_TYPE_PPMU_V2, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, exynos_ppmu_id_match); + +static int of_get_devfreq_events(struct device_node *np, + struct exynos_ppmu *info) +{ + struct devfreq_event_desc *desc; + struct device *dev = info->dev; + struct device_node *events_np, *node; + int i, j, count; + const struct of_device_id *of_id; + int ret; + + events_np = of_get_child_by_name(np, "events"); + if (!events_np) { + dev_err(dev, + "failed to get child node of devfreq-event devices\n"); + return -EINVAL; + } + + count = of_get_child_count(events_np); + desc = devm_kcalloc(dev, count, sizeof(*desc), GFP_KERNEL); + if (!desc) { + of_node_put(events_np); + return -ENOMEM; + } + info->num_events = count; + + of_id = of_match_device(exynos_ppmu_id_match, dev); + if (of_id) + info->ppmu_type = (enum exynos_ppmu_type)of_id->data; + else { + of_node_put(events_np); + return -EINVAL; + } + + j = 0; + for_each_child_of_node(events_np, node) { + for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) { + if (!ppmu_events[i].name) + continue; + + if (of_node_name_eq(node, ppmu_events[i].name)) + break; + } + + if (i == ARRAY_SIZE(ppmu_events)) { + dev_warn(dev, + "don't know how to configure events : %pOFn\n", + node); + continue; + } + + switch (info->ppmu_type) { + case EXYNOS_TYPE_PPMU: + desc[j].ops = &exynos_ppmu_ops; + break; + case EXYNOS_TYPE_PPMU_V2: + desc[j].ops = &exynos_ppmu_v2_ops; + break; + } + + desc[j].driver_data = info; + + of_property_read_string(node, "event-name", &desc[j].name); + ret = of_property_read_u32(node, "event-data-type", + &desc[j].event_type); + if (ret) { + /* Set the event of proper data type counting. + * Check if the data type has been defined in DT, + * use default if not. + */ + if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) { + /* Not all registers take the same value for + * read+write data count. + */ + switch (ppmu_events[i].id) { + case PPMU_PMNCNT0: + case PPMU_PMNCNT1: + case PPMU_PMNCNT2: + desc[j].event_type = PPMU_V2_RO_DATA_CNT + | PPMU_V2_WO_DATA_CNT; + break; + case PPMU_PMNCNT3: + desc[j].event_type = + PPMU_V2_EVT3_RW_DATA_CNT; + break; + } + } else { + desc[j].event_type = PPMU_RO_DATA_CNT | + PPMU_WO_DATA_CNT; + } + } + + j++; + } + info->desc = desc; + + of_node_put(events_np); + + return 0; +} + +static struct regmap_config exynos_ppmu_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int exynos_ppmu_parse_dt(struct platform_device *pdev, + struct exynos_ppmu *info) +{ + struct device *dev = info->dev; + struct device_node *np = dev->of_node; + struct resource *res; + void __iomem *base; + int ret = 0; + + if (!np) { + dev_err(dev, "failed to find devicetree node\n"); + return -EINVAL; + } + + /* Maps the memory mapped IO to control PPMU register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + exynos_ppmu_regmap_config.max_register = resource_size(res) - 4; + info->regmap = devm_regmap_init_mmio(dev, base, + &exynos_ppmu_regmap_config); + if (IS_ERR(info->regmap)) { + dev_err(dev, "failed to initialize regmap\n"); + return PTR_ERR(info->regmap); + } + + info->ppmu.clk = devm_clk_get(dev, "ppmu"); + if (IS_ERR(info->ppmu.clk)) { + info->ppmu.clk = NULL; + dev_warn(dev, "cannot get PPMU clock\n"); + } + + ret = of_get_devfreq_events(np, info); + if (ret < 0) { + dev_err(dev, "failed to parse exynos ppmu dt node\n"); + return ret; + } + + return 0; +} + +static int exynos_ppmu_probe(struct platform_device *pdev) +{ + struct exynos_ppmu *info; + struct devfreq_event_dev **edev; + struct devfreq_event_desc *desc; + int i, ret = 0, size; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + + /* Parse dt data to get resource */ + ret = exynos_ppmu_parse_dt(pdev, info); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to parse devicetree for resource\n"); + return ret; + } + desc = info->desc; + + size = sizeof(struct devfreq_event_dev *) * info->num_events; + info->edev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!info->edev) + return -ENOMEM; + + edev = info->edev; + platform_set_drvdata(pdev, info); + + for (i = 0; i < info->num_events; i++) { + edev[i] = devm_devfreq_event_add_edev(&pdev->dev, &desc[i]); + if (IS_ERR(edev[i])) { + dev_err(&pdev->dev, + "failed to add devfreq-event device\n"); + return PTR_ERR(edev[i]); + } + + pr_info("exynos-ppmu: new PPMU device registered %s (%s)\n", + dev_name(&pdev->dev), desc[i].name); + } + + ret = clk_prepare_enable(info->ppmu.clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare ppmu clock\n"); + return ret; + } + + return 0; +} + +static int exynos_ppmu_remove(struct platform_device *pdev) +{ + struct exynos_ppmu *info = platform_get_drvdata(pdev); + + clk_disable_unprepare(info->ppmu.clk); + + return 0; +} + +static struct platform_driver exynos_ppmu_driver = { + .probe = exynos_ppmu_probe, + .remove = exynos_ppmu_remove, + .driver = { + .name = "exynos-ppmu", + .of_match_table = exynos_ppmu_id_match, + }, +}; +module_platform_driver(exynos_ppmu_driver); + +MODULE_DESCRIPTION("Exynos PPMU(Platform Performance Monitoring Unit) driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/event/exynos-ppmu.h b/drivers/devfreq/event/exynos-ppmu.h new file mode 100644 index 000000000..97f667d0c --- /dev/null +++ b/drivers/devfreq/event/exynos-ppmu.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * exynos_ppmu.h - Exynos PPMU header file + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + */ + +#ifndef __EXYNOS_PPMU_H__ +#define __EXYNOS_PPMU_H__ + +enum ppmu_state { + PPMU_DISABLE = 0, + PPMU_ENABLE, +}; + +enum ppmu_counter { + PPMU_PMNCNT0 = 0, + PPMU_PMNCNT1, + PPMU_PMNCNT2, + PPMU_PMNCNT3, + + PPMU_PMNCNT_MAX, +}; + +/*** + * PPMUv1.1 Definitions + */ +enum ppmu_event_type { + PPMU_RO_BUSY_CYCLE_CNT = 0x0, + PPMU_WO_BUSY_CYCLE_CNT = 0x1, + PPMU_RW_BUSY_CYCLE_CNT = 0x2, + PPMU_RO_REQUEST_CNT = 0x3, + PPMU_WO_REQUEST_CNT = 0x4, + PPMU_RO_DATA_CNT = 0x5, + PPMU_WO_DATA_CNT = 0x6, + PPMU_RO_LATENCY = 0x12, + PPMU_WO_LATENCY = 0x16, +}; + +enum ppmu_reg { + /* PPC control register */ + PPMU_PMNC = 0x00, + PPMU_CNTENS = 0x10, + PPMU_CNTENC = 0x20, + PPMU_INTENS = 0x30, + PPMU_INTENC = 0x40, + PPMU_FLAG = 0x50, + + /* Cycle Counter and Performance Event Counter Register */ + PPMU_CCNT = 0x100, + PPMU_PMCNT0 = 0x110, + PPMU_PMCNT1 = 0x120, + PPMU_PMCNT2 = 0x130, + PPMU_PMCNT3_HIGH = 0x140, + PPMU_PMCNT3_LOW = 0x150, + + /* Bus Event Generator */ + PPMU_BEVT0SEL = 0x1000, + PPMU_BEVT1SEL = 0x1100, + PPMU_BEVT2SEL = 0x1200, + PPMU_BEVT3SEL = 0x1300, + PPMU_COUNTER_RESET = 0x1810, + PPMU_READ_OVERFLOW_CNT = 0x1810, + PPMU_READ_UNDERFLOW_CNT = 0x1814, + PPMU_WRITE_OVERFLOW_CNT = 0x1850, + PPMU_WRITE_UNDERFLOW_CNT = 0x1854, + PPMU_READ_PENDING_CNT = 0x1880, + PPMU_WRITE_PENDING_CNT = 0x1884 +}; + +/* PMNC register */ +#define PPMU_PMNC_CC_RESET_SHIFT 2 +#define PPMU_PMNC_COUNTER_RESET_SHIFT 1 +#define PPMU_PMNC_ENABLE_SHIFT 0 +#define PPMU_PMNC_START_MODE_MASK BIT(16) +#define PPMU_PMNC_CC_DIVIDER_MASK BIT(3) +#define PPMU_PMNC_CC_RESET_MASK BIT(2) +#define PPMU_PMNC_COUNTER_RESET_MASK BIT(1) +#define PPMU_PMNC_ENABLE_MASK BIT(0) + +/* CNTENS/CNTENC/INTENS/INTENC/FLAG register */ +#define PPMU_CCNT_MASK BIT(31) +#define PPMU_PMCNT3_MASK BIT(3) +#define PPMU_PMCNT2_MASK BIT(2) +#define PPMU_PMCNT1_MASK BIT(1) +#define PPMU_PMCNT0_MASK BIT(0) + +/* PPMU_PMNCTx/PPMU_BETxSEL registers */ +#define PPMU_PMNCT(x) (PPMU_PMCNT0 + (0x10 * x)) +#define PPMU_BEVTxSEL(x) (PPMU_BEVT0SEL + (0x100 * x)) + +/*** + * PPMU_V2.0 definitions + */ +enum ppmu_v2_mode { + PPMU_V2_MODE_MANUAL = 0, + PPMU_V2_MODE_AUTO = 1, + PPMU_V2_MODE_CIG = 2, /* CIG (Conditional Interrupt Generation) */ +}; + +enum ppmu_v2_event_type { + PPMU_V2_RO_DATA_CNT = 0x4, + PPMU_V2_WO_DATA_CNT = 0x5, + + PPMU_V2_EVT3_RW_DATA_CNT = 0x22, /* Only for Event3 */ +}; + +enum ppmu_V2_reg { + /* PPC control register */ + PPMU_V2_PMNC = 0x04, + PPMU_V2_CNTENS = 0x08, + PPMU_V2_CNTENC = 0x0c, + PPMU_V2_INTENS = 0x10, + PPMU_V2_INTENC = 0x14, + PPMU_V2_FLAG = 0x18, + + /* Cycle Counter and Performance Event Counter Register */ + PPMU_V2_CCNT = 0x48, + PPMU_V2_PMCNT0 = 0x34, + PPMU_V2_PMCNT1 = 0x38, + PPMU_V2_PMCNT2 = 0x3c, + PPMU_V2_PMCNT3_LOW = 0x40, + PPMU_V2_PMCNT3_HIGH = 0x44, + + /* Bus Event Generator */ + PPMU_V2_CIG_CFG0 = 0x1c, + PPMU_V2_CIG_CFG1 = 0x20, + PPMU_V2_CIG_CFG2 = 0x24, + PPMU_V2_CIG_RESULT = 0x28, + PPMU_V2_CNT_RESET = 0x2c, + PPMU_V2_CNT_AUTO = 0x30, + PPMU_V2_CH_EV0_TYPE = 0x200, + PPMU_V2_CH_EV1_TYPE = 0x204, + PPMU_V2_CH_EV2_TYPE = 0x208, + PPMU_V2_CH_EV3_TYPE = 0x20c, + PPMU_V2_SM_ID_V = 0x220, + PPMU_V2_SM_ID_A = 0x224, + PPMU_V2_SM_OTHERS_V = 0x228, + PPMU_V2_SM_OTHERS_A = 0x22c, + PPMU_V2_INTERRUPT_RESET = 0x260, +}; + +/* PMNC register */ +#define PPMU_V2_PMNC_START_MODE_SHIFT 20 +#define PPMU_V2_PMNC_START_MODE_MASK (0x3 << PPMU_V2_PMNC_START_MODE_SHIFT) + +#define PPMU_PMNC_CC_RESET_SHIFT 2 +#define PPMU_PMNC_COUNTER_RESET_SHIFT 1 +#define PPMU_PMNC_ENABLE_SHIFT 0 +#define PPMU_PMNC_START_MODE_MASK BIT(16) +#define PPMU_PMNC_CC_DIVIDER_MASK BIT(3) +#define PPMU_PMNC_CC_RESET_MASK BIT(2) +#define PPMU_PMNC_COUNTER_RESET_MASK BIT(1) +#define PPMU_PMNC_ENABLE_MASK BIT(0) + +#define PPMU_V2_PMNCT(x) (PPMU_V2_PMCNT0 + (0x4 * x)) +#define PPMU_V2_CH_EVx_TYPE(x) (PPMU_V2_CH_EV0_TYPE + (0x4 * x)) + +#endif /* __EXYNOS_PPMU_H__ */ diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c new file mode 100644 index 000000000..74893c06a --- /dev/null +++ b/drivers/devfreq/event/rockchip-dfi.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * Author: Lin Huang <hl@rock-chips.com> + */ + +#include <linux/clk.h> +#include <linux/devfreq-event.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/of.h> + +#include <soc/rockchip/rk3399_grf.h> + +#define RK3399_DMC_NUM_CH 2 + +/* DDRMON_CTRL */ +#define DDRMON_CTRL 0x04 +#define CLR_DDRMON_CTRL (0x1f0000 << 0) +#define LPDDR4_EN (0x10001 << 4) +#define HARDWARE_EN (0x10001 << 3) +#define LPDDR3_EN (0x10001 << 2) +#define SOFTWARE_EN (0x10001 << 1) +#define SOFTWARE_DIS (0x10000 << 1) +#define TIME_CNT_EN (0x10001 << 0) + +#define DDRMON_CH0_COUNT_NUM 0x28 +#define DDRMON_CH0_DFI_ACCESS_NUM 0x2c +#define DDRMON_CH1_COUNT_NUM 0x3c +#define DDRMON_CH1_DFI_ACCESS_NUM 0x40 + +struct dmc_usage { + u32 access; + u32 total; +}; + +/* + * The dfi controller can monitor DDR load. It has an upper and lower threshold + * for the operating points. Whenever the usage leaves these bounds an event is + * generated to indicate the DDR frequency should be changed. + */ +struct rockchip_dfi { + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct dmc_usage ch_usage[RK3399_DMC_NUM_CH]; + struct device *dev; + void __iomem *regs; + struct regmap *regmap_pmu; + struct clk *clk; +}; + +static void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + void __iomem *dfi_regs = info->regs; + u32 val; + u32 ddr_type; + + /* get ddr type */ + regmap_read(info->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val); + ddr_type = (val >> RK3399_PMUGRF_DDRTYPE_SHIFT) & + RK3399_PMUGRF_DDRTYPE_MASK; + + /* clear DDRMON_CTRL setting */ + writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL); + + /* set ddr type to dfi */ + if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR3) + writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL); + else if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR4) + writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL); + + /* enable count, use software mode */ + writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL); +} + +static void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + void __iomem *dfi_regs = info->regs; + + writel_relaxed(SOFTWARE_DIS, dfi_regs + DDRMON_CTRL); +} + +static int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + u32 tmp, max = 0; + u32 i, busier_ch = 0; + void __iomem *dfi_regs = info->regs; + + rockchip_dfi_stop_hardware_counter(edev); + + /* Find out which channel is busier */ + for (i = 0; i < RK3399_DMC_NUM_CH; i++) { + info->ch_usage[i].access = readl_relaxed(dfi_regs + + DDRMON_CH0_DFI_ACCESS_NUM + i * 20) * 4; + info->ch_usage[i].total = readl_relaxed(dfi_regs + + DDRMON_CH0_COUNT_NUM + i * 20); + tmp = info->ch_usage[i].access; + if (tmp > max) { + busier_ch = i; + max = tmp; + } + } + rockchip_dfi_start_hardware_counter(edev); + + return busier_ch; +} + +static int rockchip_dfi_disable(struct devfreq_event_dev *edev) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + + rockchip_dfi_stop_hardware_counter(edev); + clk_disable_unprepare(info->clk); + + return 0; +} + +static int rockchip_dfi_enable(struct devfreq_event_dev *edev) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret); + return ret; + } + + rockchip_dfi_start_hardware_counter(edev); + return 0; +} + +static int rockchip_dfi_set_event(struct devfreq_event_dev *edev) +{ + return 0; +} + +static int rockchip_dfi_get_event(struct devfreq_event_dev *edev, + struct devfreq_event_data *edata) +{ + struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); + int busier_ch; + + busier_ch = rockchip_dfi_get_busier_ch(edev); + + edata->load_count = info->ch_usage[busier_ch].access; + edata->total_count = info->ch_usage[busier_ch].total; + + return 0; +} + +static const struct devfreq_event_ops rockchip_dfi_ops = { + .disable = rockchip_dfi_disable, + .enable = rockchip_dfi_enable, + .get_event = rockchip_dfi_get_event, + .set_event = rockchip_dfi_set_event, +}; + +static const struct of_device_id rockchip_dfi_id_match[] = { + { .compatible = "rockchip,rk3399-dfi" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rockchip_dfi_id_match); + +static int rockchip_dfi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_dfi *data; + struct devfreq_event_desc *desc; + struct device_node *np = pdev->dev.of_node, *node; + + data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->regs)) + return PTR_ERR(data->regs); + + data->clk = devm_clk_get(dev, "pclk_ddr_mon"); + if (IS_ERR(data->clk)) + return dev_err_probe(dev, PTR_ERR(data->clk), + "Cannot get the clk pclk_ddr_mon\n"); + + node = of_parse_phandle(np, "rockchip,pmu", 0); + if (!node) + return dev_err_probe(&pdev->dev, -ENODEV, "Can't find pmu_grf registers\n"); + + data->regmap_pmu = syscon_node_to_regmap(node); + of_node_put(node); + if (IS_ERR(data->regmap_pmu)) + return PTR_ERR(data->regmap_pmu); + + data->dev = dev; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->ops = &rockchip_dfi_ops; + desc->driver_data = data; + desc->name = np->name; + data->desc = desc; + + data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc); + if (IS_ERR(data->edev)) { + dev_err(&pdev->dev, + "failed to add devfreq-event device\n"); + return PTR_ERR(data->edev); + } + + platform_set_drvdata(pdev, data); + + return 0; +} + +static struct platform_driver rockchip_dfi_driver = { + .probe = rockchip_dfi_probe, + .driver = { + .name = "rockchip-dfi", + .of_match_table = rockchip_dfi_id_match, + }, +}; +module_platform_driver(rockchip_dfi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); +MODULE_DESCRIPTION("Rockchip DFI driver"); diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c new file mode 100644 index 000000000..027e8f336 --- /dev/null +++ b/drivers/devfreq/exynos-bus.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic Exynos Bus frequency driver with DEVFREQ Framework + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + * + * This driver support Exynos Bus frequency feature by using + * DEVFREQ framework and is based on drivers/devfreq/exynos/exynos4_bus.c. + */ + +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/devfreq-event.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm_opp.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#define DEFAULT_SATURATION_RATIO 40 + +struct exynos_bus { + struct device *dev; + struct platform_device *icc_pdev; + + struct devfreq *devfreq; + struct devfreq_event_dev **edev; + unsigned int edev_count; + struct mutex lock; + + unsigned long curr_freq; + + int opp_token; + struct clk *clk; + unsigned int ratio; +}; + +/* + * Control the devfreq-event device to get the current state of bus + */ +#define exynos_bus_ops_edev(ops) \ +static int exynos_bus_##ops(struct exynos_bus *bus) \ +{ \ + int i, ret; \ + \ + for (i = 0; i < bus->edev_count; i++) { \ + if (!bus->edev[i]) \ + continue; \ + ret = devfreq_event_##ops(bus->edev[i]); \ + if (ret < 0) \ + return ret; \ + } \ + \ + return 0; \ +} +exynos_bus_ops_edev(enable_edev); +exynos_bus_ops_edev(disable_edev); +exynos_bus_ops_edev(set_event); + +static int exynos_bus_get_event(struct exynos_bus *bus, + struct devfreq_event_data *edata) +{ + struct devfreq_event_data event_data; + unsigned long load_count = 0, total_count = 0; + int i, ret = 0; + + for (i = 0; i < bus->edev_count; i++) { + if (!bus->edev[i]) + continue; + + ret = devfreq_event_get_event(bus->edev[i], &event_data); + if (ret < 0) + return ret; + + if (i == 0 || event_data.load_count > load_count) { + load_count = event_data.load_count; + total_count = event_data.total_count; + } + } + + edata->load_count = load_count; + edata->total_count = total_count; + + return ret; +} + +/* + * devfreq function for both simple-ondemand and passive governor + */ +static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + struct dev_pm_opp *new_opp; + int ret = 0; + + /* Get correct frequency for bus. */ + new_opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(new_opp)) { + dev_err(dev, "failed to get recommended opp instance\n"); + return PTR_ERR(new_opp); + } + + dev_pm_opp_put(new_opp); + + /* Change voltage and frequency according to new OPP level */ + mutex_lock(&bus->lock); + ret = dev_pm_opp_set_rate(dev, *freq); + if (!ret) + bus->curr_freq = *freq; + + mutex_unlock(&bus->lock); + + return ret; +} + +static int exynos_bus_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + struct devfreq_event_data edata; + int ret; + + stat->current_frequency = bus->curr_freq; + + ret = exynos_bus_get_event(bus, &edata); + if (ret < 0) { + dev_err(dev, "failed to get event from devfreq-event devices\n"); + stat->total_time = stat->busy_time = 0; + goto err; + } + + stat->busy_time = (edata.load_count * 100) / bus->ratio; + stat->total_time = edata.total_count; + + dev_dbg(dev, "Usage of devfreq-event : %lu/%lu\n", stat->busy_time, + stat->total_time); + +err: + ret = exynos_bus_set_event(bus); + if (ret < 0) { + dev_err(dev, "failed to set event to devfreq-event devices\n"); + return ret; + } + + return ret; +} + +static void exynos_bus_exit(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_disable_edev(bus); + if (ret < 0) + dev_warn(dev, "failed to disable the devfreq-event devices\n"); + + platform_device_unregister(bus->icc_pdev); + + dev_pm_opp_of_remove_table(dev); + clk_disable_unprepare(bus->clk); + dev_pm_opp_put_regulators(bus->opp_token); +} + +static void exynos_bus_passive_exit(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + + platform_device_unregister(bus->icc_pdev); + + dev_pm_opp_of_remove_table(dev); + clk_disable_unprepare(bus->clk); +} + +static int exynos_bus_parent_parse_of(struct device_node *np, + struct exynos_bus *bus) +{ + struct device *dev = bus->dev; + const char *supplies[] = { "vdd", NULL }; + int i, ret, count, size; + + ret = dev_pm_opp_set_regulators(dev, supplies); + if (ret < 0) { + dev_err(dev, "failed to set regulators %d\n", ret); + return ret; + } + + bus->opp_token = ret; + + /* + * Get the devfreq-event devices to get the current utilization of + * buses. This raw data will be used in devfreq ondemand governor. + */ + count = devfreq_event_get_edev_count(dev, "devfreq-events"); + if (count < 0) { + dev_err(dev, "failed to get the count of devfreq-event dev\n"); + ret = count; + goto err_regulator; + } + bus->edev_count = count; + + size = sizeof(*bus->edev) * count; + bus->edev = devm_kzalloc(dev, size, GFP_KERNEL); + if (!bus->edev) { + ret = -ENOMEM; + goto err_regulator; + } + + for (i = 0; i < count; i++) { + bus->edev[i] = devfreq_event_get_edev_by_phandle(dev, + "devfreq-events", i); + if (IS_ERR(bus->edev[i])) { + ret = -EPROBE_DEFER; + goto err_regulator; + } + } + + /* + * Optionally, Get the saturation ratio according to Exynos SoC + * When measuring the utilization of each AXI bus with devfreq-event + * devices, the measured real cycle might be much lower than the + * total cycle of bus during sampling rate. In result, the devfreq + * simple-ondemand governor might not decide to change the current + * frequency due to too utilization (= real cycle/total cycle). + * So, this property is used to adjust the utilization when calculating + * the busy_time in exynos_bus_get_dev_status(). + */ + if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio)) + bus->ratio = DEFAULT_SATURATION_RATIO; + + return 0; + +err_regulator: + dev_pm_opp_put_regulators(bus->opp_token); + + return ret; +} + +static int exynos_bus_parse_of(struct device_node *np, + struct exynos_bus *bus) +{ + struct device *dev = bus->dev; + struct dev_pm_opp *opp; + unsigned long rate; + int ret; + + /* Get the clock to provide each bus with source clock */ + bus->clk = devm_clk_get(dev, "bus"); + if (IS_ERR(bus->clk)) { + dev_err(dev, "failed to get bus clock\n"); + return PTR_ERR(bus->clk); + } + + ret = clk_prepare_enable(bus->clk); + if (ret < 0) { + dev_err(dev, "failed to get enable clock\n"); + return ret; + } + + /* Get the freq and voltage from OPP table to scale the bus freq */ + ret = dev_pm_opp_of_add_table(dev); + if (ret < 0) { + dev_err(dev, "failed to get OPP table\n"); + goto err_clk; + } + + rate = clk_get_rate(bus->clk); + + opp = devfreq_recommended_opp(dev, &rate, 0); + if (IS_ERR(opp)) { + dev_err(dev, "failed to find dev_pm_opp\n"); + ret = PTR_ERR(opp); + goto err_opp; + } + bus->curr_freq = dev_pm_opp_get_freq(opp); + dev_pm_opp_put(opp); + + return 0; + +err_opp: + dev_pm_opp_of_remove_table(dev); +err_clk: + clk_disable_unprepare(bus->clk); + + return ret; +} + +static int exynos_bus_profile_init(struct exynos_bus *bus, + struct devfreq_dev_profile *profile) +{ + struct device *dev = bus->dev; + struct devfreq_simple_ondemand_data *ondemand_data; + int ret; + + /* Initialize the struct profile and governor data for parent device */ + profile->polling_ms = 50; + profile->target = exynos_bus_target; + profile->get_dev_status = exynos_bus_get_dev_status; + profile->exit = exynos_bus_exit; + + ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL); + if (!ondemand_data) + return -ENOMEM; + + ondemand_data->upthreshold = 40; + ondemand_data->downdifferential = 5; + + /* Add devfreq device to monitor and handle the exynos bus */ + bus->devfreq = devm_devfreq_add_device(dev, profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + ondemand_data); + if (IS_ERR(bus->devfreq)) { + dev_err(dev, "failed to add devfreq device\n"); + return PTR_ERR(bus->devfreq); + } + + /* Register opp_notifier to catch the change of OPP */ + ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq); + if (ret < 0) { + dev_err(dev, "failed to register opp notifier\n"); + return ret; + } + + /* + * Enable devfreq-event to get raw data which is used to determine + * current bus load. + */ + ret = exynos_bus_enable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to enable devfreq-event devices\n"); + return ret; + } + + ret = exynos_bus_set_event(bus); + if (ret < 0) { + dev_err(dev, "failed to set event to devfreq-event devices\n"); + goto err_edev; + } + + return 0; + +err_edev: + if (exynos_bus_disable_edev(bus)) + dev_warn(dev, "failed to disable the devfreq-event devices\n"); + + return ret; +} + +static int exynos_bus_profile_init_passive(struct exynos_bus *bus, + struct devfreq_dev_profile *profile) +{ + struct device *dev = bus->dev; + struct devfreq_passive_data *passive_data; + struct devfreq *parent_devfreq; + + /* Initialize the struct profile and governor data for passive device */ + profile->target = exynos_bus_target; + profile->exit = exynos_bus_passive_exit; + + /* Get the instance of parent devfreq device */ + parent_devfreq = devfreq_get_devfreq_by_phandle(dev, "devfreq", 0); + if (IS_ERR(parent_devfreq)) + return -EPROBE_DEFER; + + passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL); + if (!passive_data) + return -ENOMEM; + + passive_data->parent = parent_devfreq; + + /* Add devfreq device for exynos bus with passive governor */ + bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE, + passive_data); + if (IS_ERR(bus->devfreq)) { + dev_err(dev, + "failed to add devfreq dev with passive governor\n"); + return PTR_ERR(bus->devfreq); + } + + return 0; +} + +static int exynos_bus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node, *node; + struct devfreq_dev_profile *profile; + struct exynos_bus *bus; + int ret, max_state; + unsigned long min_freq, max_freq; + bool passive = false; + + if (!np) { + dev_err(dev, "failed to find devicetree node\n"); + return -EINVAL; + } + + bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + mutex_init(&bus->lock); + bus->dev = &pdev->dev; + platform_set_drvdata(pdev, bus); + + profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL); + if (!profile) + return -ENOMEM; + + node = of_parse_phandle(dev->of_node, "devfreq", 0); + if (node) { + of_node_put(node); + passive = true; + } else { + ret = exynos_bus_parent_parse_of(np, bus); + if (ret < 0) + return ret; + } + + /* Parse the device-tree to get the resource information */ + ret = exynos_bus_parse_of(np, bus); + if (ret < 0) + goto err_reg; + + if (passive) + ret = exynos_bus_profile_init_passive(bus, profile); + else + ret = exynos_bus_profile_init(bus, profile); + + if (ret < 0) + goto err; + + /* Create child platform device for the interconnect provider */ + if (of_get_property(dev->of_node, "#interconnect-cells", NULL)) { + bus->icc_pdev = platform_device_register_data( + dev, "exynos-generic-icc", + PLATFORM_DEVID_AUTO, NULL, 0); + + if (IS_ERR(bus->icc_pdev)) { + ret = PTR_ERR(bus->icc_pdev); + goto err; + } + } + + max_state = bus->devfreq->max_state; + min_freq = (bus->devfreq->freq_table[0] / 1000); + max_freq = (bus->devfreq->freq_table[max_state - 1] / 1000); + pr_info("exynos-bus: new bus device registered: %s (%6ld KHz ~ %6ld KHz)\n", + dev_name(dev), min_freq, max_freq); + + return 0; + +err: + dev_pm_opp_of_remove_table(dev); + clk_disable_unprepare(bus->clk); +err_reg: + dev_pm_opp_put_regulators(bus->opp_token); + + return ret; +} + +static void exynos_bus_shutdown(struct platform_device *pdev) +{ + struct exynos_bus *bus = dev_get_drvdata(&pdev->dev); + + devfreq_suspend_device(bus->devfreq); +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_bus_resume(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_enable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to enable the devfreq-event devices\n"); + return ret; + } + + return 0; +} + +static int exynos_bus_suspend(struct device *dev) +{ + struct exynos_bus *bus = dev_get_drvdata(dev); + int ret; + + ret = exynos_bus_disable_edev(bus); + if (ret < 0) { + dev_err(dev, "failed to disable the devfreq-event devices\n"); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops exynos_bus_pm = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_bus_suspend, exynos_bus_resume) +}; + +static const struct of_device_id exynos_bus_of_match[] = { + { .compatible = "samsung,exynos-bus", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, exynos_bus_of_match); + +static struct platform_driver exynos_bus_platdrv = { + .probe = exynos_bus_probe, + .shutdown = exynos_bus_shutdown, + .driver = { + .name = "exynos-bus", + .pm = &exynos_bus_pm, + .of_match_table = of_match_ptr(exynos_bus_of_match), + }, +}; +module_platform_driver(exynos_bus_platdrv); + +MODULE_DESCRIPTION("Generic Exynos Bus frequency driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h new file mode 100644 index 000000000..0adfebc04 --- /dev/null +++ b/drivers/devfreq/governor.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * governor.h - internal header for devfreq governors. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This header is for devfreq governors in drivers/devfreq/ + */ + +#ifndef _GOVERNOR_H +#define _GOVERNOR_H + +#include <linux/devfreq.h> + +#define DEVFREQ_NAME_LEN 16 + +#define to_devfreq(DEV) container_of((DEV), struct devfreq, dev) + +/* Devfreq events */ +#define DEVFREQ_GOV_START 0x1 +#define DEVFREQ_GOV_STOP 0x2 +#define DEVFREQ_GOV_UPDATE_INTERVAL 0x3 +#define DEVFREQ_GOV_SUSPEND 0x4 +#define DEVFREQ_GOV_RESUME 0x5 + +#define DEVFREQ_MIN_FREQ 0 +#define DEVFREQ_MAX_FREQ ULONG_MAX + +/* + * Definition of the governor feature flags + * - DEVFREQ_GOV_FLAG_IMMUTABLE + * : This governor is never changeable to other governors. + * - DEVFREQ_GOV_FLAG_IRQ_DRIVEN + * : The devfreq won't schedule the work for this governor. + */ +#define DEVFREQ_GOV_FLAG_IMMUTABLE BIT(0) +#define DEVFREQ_GOV_FLAG_IRQ_DRIVEN BIT(1) + +/* + * Definition of governor attribute flags except for common sysfs attributes + * - DEVFREQ_GOV_ATTR_POLLING_INTERVAL + * : Indicate polling_interval sysfs attribute + * - DEVFREQ_GOV_ATTR_TIMER + * : Indicate timer sysfs attribute + */ +#define DEVFREQ_GOV_ATTR_POLLING_INTERVAL BIT(0) +#define DEVFREQ_GOV_ATTR_TIMER BIT(1) + +/** + * struct devfreq_cpu_data - Hold the per-cpu data + * @node: list node + * @dev: reference to cpu device. + * @first_cpu: the cpumask of the first cpu of a policy. + * @opp_table: reference to cpu opp table. + * @cur_freq: the current frequency of the cpu. + * @min_freq: the min frequency of the cpu. + * @max_freq: the max frequency of the cpu. + * + * This structure stores the required cpu_data of a cpu. + * This is auto-populated by the governor. + */ +struct devfreq_cpu_data { + struct list_head node; + + struct device *dev; + unsigned int first_cpu; + + struct opp_table *opp_table; + unsigned int cur_freq; + unsigned int min_freq; + unsigned int max_freq; +}; + +/** + * struct devfreq_governor - Devfreq policy governor + * @node: list node - contains registered devfreq governors + * @name: Governor's name + * @attrs: Governor's sysfs attribute flags + * @flags: Governor's feature flags + * @get_target_freq: Returns desired operating frequency for the device. + * Basically, get_target_freq will run + * devfreq_dev_profile.get_dev_status() to get the + * status of the device (load = busy_time / total_time). + * @event_handler: Callback for devfreq core framework to notify events + * to governors. Events include per device governor + * init and exit, opp changes out of devfreq, suspend + * and resume of per device devfreq during device idle. + * + * Note that the callbacks are called with devfreq->lock locked by devfreq. + */ +struct devfreq_governor { + struct list_head node; + + const char name[DEVFREQ_NAME_LEN]; + const u64 attrs; + const u64 flags; + int (*get_target_freq)(struct devfreq *this, unsigned long *freq); + int (*event_handler)(struct devfreq *devfreq, + unsigned int event, void *data); +}; + +void devfreq_monitor_start(struct devfreq *devfreq); +void devfreq_monitor_stop(struct devfreq *devfreq); +void devfreq_monitor_suspend(struct devfreq *devfreq); +void devfreq_monitor_resume(struct devfreq *devfreq); +void devfreq_update_interval(struct devfreq *devfreq, unsigned int *delay); + +int devfreq_add_governor(struct devfreq_governor *governor); +int devfreq_remove_governor(struct devfreq_governor *governor); + +int devm_devfreq_add_governor(struct device *dev, + struct devfreq_governor *governor); + +int devfreq_update_status(struct devfreq *devfreq, unsigned long freq); +int devfreq_update_target(struct devfreq *devfreq, unsigned long freq); +void devfreq_get_freq_range(struct devfreq *devfreq, unsigned long *min_freq, + unsigned long *max_freq); + +static inline int devfreq_update_stats(struct devfreq *df) +{ + if (!df->profile->get_dev_status) + return -EINVAL; + + return df->profile->get_dev_status(df->dev.parent, &df->last_status); +} +#endif /* _GOVERNOR_H */ diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c new file mode 100644 index 000000000..953cf9a1e --- /dev/null +++ b/drivers/devfreq/governor_passive.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_passive.c + * + * Copyright (C) 2016 Samsung Electronics + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * Author: MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/module.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/devfreq.h> +#include <linux/units.h> +#include "governor.h" + +static struct devfreq_cpu_data * +get_parent_cpu_data(struct devfreq_passive_data *p_data, + struct cpufreq_policy *policy) +{ + struct devfreq_cpu_data *parent_cpu_data; + + if (!p_data || !policy) + return NULL; + + list_for_each_entry(parent_cpu_data, &p_data->cpu_data_list, node) + if (parent_cpu_data->first_cpu == cpumask_first(policy->related_cpus)) + return parent_cpu_data; + + return NULL; +} + +static void delete_parent_cpu_data(struct devfreq_passive_data *p_data) +{ + struct devfreq_cpu_data *parent_cpu_data, *tmp; + + list_for_each_entry_safe(parent_cpu_data, tmp, &p_data->cpu_data_list, node) { + list_del(&parent_cpu_data->node); + + if (parent_cpu_data->opp_table) + dev_pm_opp_put_opp_table(parent_cpu_data->opp_table); + + kfree(parent_cpu_data); + } +} + +static unsigned long get_target_freq_by_required_opp(struct device *p_dev, + struct opp_table *p_opp_table, + struct opp_table *opp_table, + unsigned long *freq) +{ + struct dev_pm_opp *opp = NULL, *p_opp = NULL; + unsigned long target_freq; + + if (!p_dev || !p_opp_table || !opp_table || !freq) + return 0; + + p_opp = devfreq_recommended_opp(p_dev, freq, 0); + if (IS_ERR(p_opp)) + return 0; + + opp = dev_pm_opp_xlate_required_opp(p_opp_table, opp_table, p_opp); + dev_pm_opp_put(p_opp); + + if (IS_ERR(opp)) + return 0; + + target_freq = dev_pm_opp_get_freq(opp); + dev_pm_opp_put(opp); + + return target_freq; +} + +static int get_target_freq_with_cpufreq(struct devfreq *devfreq, + unsigned long *target_freq) +{ + struct devfreq_passive_data *p_data = + (struct devfreq_passive_data *)devfreq->data; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_policy *policy; + unsigned long cpu, cpu_cur, cpu_min, cpu_max, cpu_percent; + unsigned long dev_min, dev_max; + unsigned long freq = 0; + int ret = 0; + + for_each_online_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -EINVAL; + continue; + } + + parent_cpu_data = get_parent_cpu_data(p_data, policy); + if (!parent_cpu_data) { + cpufreq_cpu_put(policy); + continue; + } + + /* Get target freq via required opps */ + cpu_cur = parent_cpu_data->cur_freq * HZ_PER_KHZ; + freq = get_target_freq_by_required_opp(parent_cpu_data->dev, + parent_cpu_data->opp_table, + devfreq->opp_table, &cpu_cur); + if (freq) { + *target_freq = max(freq, *target_freq); + cpufreq_cpu_put(policy); + continue; + } + + /* Use interpolation if required opps is not available */ + devfreq_get_freq_range(devfreq, &dev_min, &dev_max); + + cpu_min = parent_cpu_data->min_freq; + cpu_max = parent_cpu_data->max_freq; + cpu_cur = parent_cpu_data->cur_freq; + + cpu_percent = ((cpu_cur - cpu_min) * 100) / (cpu_max - cpu_min); + freq = dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100); + + *target_freq = max(freq, *target_freq); + cpufreq_cpu_put(policy); + } + + return ret; +} + +static int get_target_freq_with_devfreq(struct devfreq *devfreq, + unsigned long *freq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent; + unsigned long child_freq = ULONG_MAX; + int i, count; + + /* Get target freq via required opps */ + child_freq = get_target_freq_by_required_opp(parent_devfreq->dev.parent, + parent_devfreq->opp_table, + devfreq->opp_table, freq); + if (child_freq) + goto out; + + /* Use interpolation if required opps is not available */ + for (i = 0; i < parent_devfreq->max_state; i++) + if (parent_devfreq->freq_table[i] == *freq) + break; + + if (i == parent_devfreq->max_state) + return -EINVAL; + + if (i < devfreq->max_state) { + child_freq = devfreq->freq_table[i]; + } else { + count = devfreq->max_state; + child_freq = devfreq->freq_table[count - 1]; + } + +out: + *freq = child_freq; + + return 0; +} + +static int devfreq_passive_get_target_freq(struct devfreq *devfreq, + unsigned long *freq) +{ + struct devfreq_passive_data *p_data = + (struct devfreq_passive_data *)devfreq->data; + int ret; + + if (!p_data) + return -EINVAL; + + /* + * If the devfreq device with passive governor has the specific method + * to determine the next frequency, should use the get_target_freq() + * of struct devfreq_passive_data. + */ + if (p_data->get_target_freq) + return p_data->get_target_freq(devfreq, freq); + + switch (p_data->parent_type) { + case DEVFREQ_PARENT_DEV: + ret = get_target_freq_with_devfreq(devfreq, freq); + break; + case CPUFREQ_PARENT_DEV: + ret = get_target_freq_with_cpufreq(devfreq, freq); + break; + default: + ret = -EINVAL; + dev_err(&devfreq->dev, "Invalid parent type\n"); + break; + } + + return ret; +} + +static int cpufreq_passive_notifier_call(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct devfreq_passive_data *p_data = + container_of(nb, struct devfreq_passive_data, nb); + struct devfreq *devfreq = (struct devfreq *)p_data->this; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_freqs *freqs = ptr; + unsigned int cur_freq; + int ret; + + if (event != CPUFREQ_POSTCHANGE || !freqs) + return 0; + + parent_cpu_data = get_parent_cpu_data(p_data, freqs->policy); + if (!parent_cpu_data || parent_cpu_data->cur_freq == freqs->new) + return 0; + + cur_freq = parent_cpu_data->cur_freq; + parent_cpu_data->cur_freq = freqs->new; + + mutex_lock(&devfreq->lock); + ret = devfreq_update_target(devfreq, freqs->new); + mutex_unlock(&devfreq->lock); + if (ret) { + parent_cpu_data->cur_freq = cur_freq; + dev_err(&devfreq->dev, "failed to update the frequency.\n"); + return ret; + } + + return 0; +} + +static int cpufreq_passive_unregister_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + int ret; + + if (p_data->nb.notifier_call) { + ret = cpufreq_unregister_notifier(&p_data->nb, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret < 0) + return ret; + } + + delete_parent_cpu_data(p_data); + + return 0; +} + +static int cpufreq_passive_register_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct device *dev = devfreq->dev.parent; + struct opp_table *opp_table = NULL; + struct devfreq_cpu_data *parent_cpu_data; + struct cpufreq_policy *policy; + struct device *cpu_dev; + unsigned int cpu; + int ret; + + p_data->cpu_data_list + = (struct list_head)LIST_HEAD_INIT(p_data->cpu_data_list); + + p_data->nb.notifier_call = cpufreq_passive_notifier_call; + ret = cpufreq_register_notifier(&p_data->nb, CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + dev_err(dev, "failed to register cpufreq notifier\n"); + p_data->nb.notifier_call = NULL; + goto err; + } + + for_each_possible_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -EPROBE_DEFER; + goto err; + } + + parent_cpu_data = get_parent_cpu_data(p_data, policy); + if (parent_cpu_data) { + cpufreq_cpu_put(policy); + continue; + } + + parent_cpu_data = kzalloc(sizeof(*parent_cpu_data), + GFP_KERNEL); + if (!parent_cpu_data) { + ret = -ENOMEM; + goto err_put_policy; + } + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + dev_err(dev, "failed to get cpu device\n"); + ret = -ENODEV; + goto err_free_cpu_data; + } + + opp_table = dev_pm_opp_get_opp_table(cpu_dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "failed to get opp_table of cpu%d\n", cpu); + ret = PTR_ERR(opp_table); + goto err_free_cpu_data; + } + + parent_cpu_data->dev = cpu_dev; + parent_cpu_data->opp_table = opp_table; + parent_cpu_data->first_cpu = cpumask_first(policy->related_cpus); + parent_cpu_data->cur_freq = policy->cur; + parent_cpu_data->min_freq = policy->cpuinfo.min_freq; + parent_cpu_data->max_freq = policy->cpuinfo.max_freq; + + list_add_tail(&parent_cpu_data->node, &p_data->cpu_data_list); + cpufreq_cpu_put(policy); + } + + mutex_lock(&devfreq->lock); + ret = devfreq_update_target(devfreq, 0L); + mutex_unlock(&devfreq->lock); + if (ret) + dev_err(dev, "failed to update the frequency\n"); + + return ret; + +err_free_cpu_data: + kfree(parent_cpu_data); +err_put_policy: + cpufreq_cpu_put(policy); +err: + + return ret; +} + +static int devfreq_passive_notifier_call(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct devfreq_passive_data *data + = container_of(nb, struct devfreq_passive_data, nb); + struct devfreq *devfreq = (struct devfreq *)data->this; + struct devfreq *parent = (struct devfreq *)data->parent; + struct devfreq_freqs *freqs = (struct devfreq_freqs *)ptr; + unsigned long freq = freqs->new; + int ret = 0; + + mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING); + switch (event) { + case DEVFREQ_PRECHANGE: + if (parent->previous_freq > freq) + ret = devfreq_update_target(devfreq, freq); + + break; + case DEVFREQ_POSTCHANGE: + if (parent->previous_freq < freq) + ret = devfreq_update_target(devfreq, freq); + break; + } + mutex_unlock(&devfreq->lock); + + if (ret < 0) + dev_warn(&devfreq->dev, + "failed to update devfreq using passive governor\n"); + + return NOTIFY_DONE; +} + +static int devfreq_passive_unregister_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent = (struct devfreq *)p_data->parent; + struct notifier_block *nb = &p_data->nb; + + return devfreq_unregister_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER); +} + +static int devfreq_passive_register_notifier(struct devfreq *devfreq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent = (struct devfreq *)p_data->parent; + struct notifier_block *nb = &p_data->nb; + + if (!parent) + return -EPROBE_DEFER; + + nb->notifier_call = devfreq_passive_notifier_call; + return devfreq_register_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER); +} + +static int devfreq_passive_event_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + int ret = 0; + + if (!p_data) + return -EINVAL; + + p_data->this = devfreq; + + switch (event) { + case DEVFREQ_GOV_START: + if (p_data->parent_type == DEVFREQ_PARENT_DEV) + ret = devfreq_passive_register_notifier(devfreq); + else if (p_data->parent_type == CPUFREQ_PARENT_DEV) + ret = cpufreq_passive_register_notifier(devfreq); + break; + case DEVFREQ_GOV_STOP: + if (p_data->parent_type == DEVFREQ_PARENT_DEV) + WARN_ON(devfreq_passive_unregister_notifier(devfreq)); + else if (p_data->parent_type == CPUFREQ_PARENT_DEV) + WARN_ON(cpufreq_passive_unregister_notifier(devfreq)); + break; + default: + break; + } + + return ret; +} + +static struct devfreq_governor devfreq_passive = { + .name = DEVFREQ_GOV_PASSIVE, + .flags = DEVFREQ_GOV_FLAG_IMMUTABLE, + .get_target_freq = devfreq_passive_get_target_freq, + .event_handler = devfreq_passive_event_handler, +}; + +static int __init devfreq_passive_init(void) +{ + return devfreq_add_governor(&devfreq_passive); +} +subsys_initcall(devfreq_passive_init); + +static void __exit devfreq_passive_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_passive); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); +} +module_exit(devfreq_passive_exit); + +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_DESCRIPTION("DEVFREQ Passive governor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c new file mode 100644 index 000000000..5dbc1e56e --- /dev/null +++ b/drivers/devfreq/governor_performance.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_performance.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/devfreq.h> +#include <linux/module.h> +#include "governor.h" + +static int devfreq_performance_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get floor value as + * said in devfreq.h + */ + *freq = DEVFREQ_MAX_FREQ; + return 0; +} + +static int devfreq_performance_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret = 0; + + if (event == DEVFREQ_GOV_START) { + mutex_lock(&devfreq->lock); + ret = update_devfreq(devfreq); + mutex_unlock(&devfreq->lock); + } + + return ret; +} + +static struct devfreq_governor devfreq_performance = { + .name = DEVFREQ_GOV_PERFORMANCE, + .get_target_freq = devfreq_performance_func, + .event_handler = devfreq_performance_handler, +}; + +static int __init devfreq_performance_init(void) +{ + return devfreq_add_governor(&devfreq_performance); +} +subsys_initcall(devfreq_performance_init); + +static void __exit devfreq_performance_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_performance); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_performance_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c new file mode 100644 index 000000000..4746af243 --- /dev/null +++ b/drivers/devfreq/governor_powersave.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_powersave.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/devfreq.h> +#include <linux/module.h> +#include "governor.h" + +static int devfreq_powersave_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get ceiling value as + * said in devfreq.h + */ + *freq = DEVFREQ_MIN_FREQ; + return 0; +} + +static int devfreq_powersave_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret = 0; + + if (event == DEVFREQ_GOV_START) { + mutex_lock(&devfreq->lock); + ret = update_devfreq(devfreq); + mutex_unlock(&devfreq->lock); + } + + return ret; +} + +static struct devfreq_governor devfreq_powersave = { + .name = DEVFREQ_GOV_POWERSAVE, + .get_target_freq = devfreq_powersave_func, + .event_handler = devfreq_powersave_handler, +}; + +static int __init devfreq_powersave_init(void) +{ + return devfreq_add_governor(&devfreq_powersave); +} +subsys_initcall(devfreq_powersave_init); + +static void __exit devfreq_powersave_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_powersave); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_powersave_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c new file mode 100644 index 000000000..d57b82a2b --- /dev/null +++ b/drivers/devfreq/governor_simpleondemand.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/devfreq.h> +#include <linux/math64.h> +#include "governor.h" + +/* Default constants for DevFreq-Simple-Ondemand (DFSO) */ +#define DFSO_UPTHRESHOLD (90) +#define DFSO_DOWNDIFFERENCTIAL (5) +static int devfreq_simple_ondemand_func(struct devfreq *df, + unsigned long *freq) +{ + int err; + struct devfreq_dev_status *stat; + unsigned long long a, b; + unsigned int dfso_upthreshold = DFSO_UPTHRESHOLD; + unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL; + struct devfreq_simple_ondemand_data *data = df->data; + + err = devfreq_update_stats(df); + if (err) + return err; + + stat = &df->last_status; + + if (data) { + if (data->upthreshold) + dfso_upthreshold = data->upthreshold; + if (data->downdifferential) + dfso_downdifferential = data->downdifferential; + } + if (dfso_upthreshold > 100 || + dfso_upthreshold < dfso_downdifferential) + return -EINVAL; + + /* Assume MAX if it is going to be divided by zero */ + if (stat->total_time == 0) { + *freq = DEVFREQ_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; + } + + /* Set MAX if it's busy enough */ + if (stat->busy_time * 100 > + stat->total_time * dfso_upthreshold) { + *freq = DEVFREQ_MAX_FREQ; + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat->current_frequency == 0) { + *freq = DEVFREQ_MAX_FREQ; + return 0; + } + + /* Keep the current frequency */ + if (stat->busy_time * 100 > + stat->total_time * (dfso_upthreshold - dfso_downdifferential)) { + *freq = stat->current_frequency; + return 0; + } + + /* Set the desired frequency based on the load */ + a = stat->busy_time; + a *= stat->current_frequency; + b = div_u64(a, stat->total_time); + b *= 100; + b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); + *freq = (unsigned long) b; + + return 0; +} + +static int devfreq_simple_ondemand_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_simple_ondemand = { + .name = DEVFREQ_GOV_SIMPLE_ONDEMAND, + .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL + | DEVFREQ_GOV_ATTR_TIMER, + .get_target_freq = devfreq_simple_ondemand_func, + .event_handler = devfreq_simple_ondemand_handler, +}; + +static int __init devfreq_simple_ondemand_init(void) +{ + return devfreq_add_governor(&devfreq_simple_ondemand); +} +subsys_initcall(devfreq_simple_ondemand_init); + +static void __exit devfreq_simple_ondemand_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_simple_ondemand); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_simple_ondemand_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c new file mode 100644 index 000000000..d69672cca --- /dev/null +++ b/drivers/devfreq/governor_userspace.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_userspace.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + */ + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/devfreq.h> +#include <linux/pm.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include "governor.h" + +struct userspace_data { + unsigned long user_frequency; + bool valid; +}; + +static int devfreq_userspace_func(struct devfreq *df, unsigned long *freq) +{ + struct userspace_data *data = df->governor_data; + + if (data->valid) + *freq = data->user_frequency; + else + *freq = df->previous_freq; /* No user freq specified yet */ + + return 0; +} + +static ssize_t set_freq_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->governor_data; + + sscanf(buf, "%lu", &wanted); + data->user_frequency = wanted; + data->valid = true; + err = update_devfreq(devfreq); + if (err == 0) + err = count; + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t set_freq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->governor_data; + + if (data->valid) + err = sprintf(buf, "%lu\n", data->user_frequency); + else + err = sprintf(buf, "undefined\n"); + mutex_unlock(&devfreq->lock); + return err; +} + +static DEVICE_ATTR_RW(set_freq); +static struct attribute *dev_entries[] = { + &dev_attr_set_freq.attr, + NULL, +}; +static const struct attribute_group dev_attr_group = { + .name = DEVFREQ_GOV_USERSPACE, + .attrs = dev_entries, +}; + +static int userspace_init(struct devfreq *devfreq) +{ + int err = 0; + struct userspace_data *data = kzalloc(sizeof(struct userspace_data), + GFP_KERNEL); + + if (!data) { + err = -ENOMEM; + goto out; + } + data->valid = false; + devfreq->governor_data = data; + + err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); +out: + return err; +} + +static void userspace_exit(struct devfreq *devfreq) +{ + /* + * Remove the sysfs entry, unless this is being called after + * device_del(), which should have done this already via kobject_del(). + */ + if (devfreq->dev.kobj.sd) + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group); + + kfree(devfreq->governor_data); + devfreq->governor_data = NULL; +} + +static int devfreq_userspace_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret = 0; + + switch (event) { + case DEVFREQ_GOV_START: + ret = userspace_init(devfreq); + break; + case DEVFREQ_GOV_STOP: + userspace_exit(devfreq); + break; + default: + break; + } + + return ret; +} + +static struct devfreq_governor devfreq_userspace = { + .name = DEVFREQ_GOV_USERSPACE, + .get_target_freq = devfreq_userspace_func, + .event_handler = devfreq_userspace_handler, +}; + +static int __init devfreq_userspace_init(void) +{ + return devfreq_add_governor(&devfreq_userspace); +} +subsys_initcall(devfreq_userspace_init); + +static void __exit devfreq_userspace_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_userspace); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_userspace_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/imx-bus.c b/drivers/devfreq/imx-bus.c new file mode 100644 index 000000000..a72706798 --- /dev/null +++ b/drivers/devfreq/imx-bus.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + */ + +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_opp.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct imx_bus { + struct devfreq_dev_profile profile; + struct devfreq *devfreq; + struct clk *clk; + struct platform_device *icc_pdev; +}; + +static int imx_bus_target(struct device *dev, + unsigned long *freq, u32 flags) +{ + struct dev_pm_opp *new_opp; + int ret; + + new_opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(new_opp)) { + ret = PTR_ERR(new_opp); + dev_err(dev, "failed to get recommended opp: %d\n", ret); + return ret; + } + dev_pm_opp_put(new_opp); + + return dev_pm_opp_set_rate(dev, *freq); +} + +static int imx_bus_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct imx_bus *priv = dev_get_drvdata(dev); + + *freq = clk_get_rate(priv->clk); + + return 0; +} + +static void imx_bus_exit(struct device *dev) +{ + struct imx_bus *priv = dev_get_drvdata(dev); + + dev_pm_opp_of_remove_table(dev); + platform_device_unregister(priv->icc_pdev); +} + +/* imx_bus_init_icc() - register matching icc provider if required */ +static int imx_bus_init_icc(struct device *dev) +{ + struct imx_bus *priv = dev_get_drvdata(dev); + const char *icc_driver_name; + + if (!of_get_property(dev->of_node, "#interconnect-cells", NULL)) + return 0; + if (!IS_ENABLED(CONFIG_INTERCONNECT_IMX)) { + dev_warn(dev, "imx interconnect drivers disabled\n"); + return 0; + } + + icc_driver_name = of_device_get_match_data(dev); + if (!icc_driver_name) { + dev_err(dev, "unknown interconnect driver\n"); + return 0; + } + + priv->icc_pdev = platform_device_register_data( + dev, icc_driver_name, -1, NULL, 0); + if (IS_ERR(priv->icc_pdev)) { + dev_err(dev, "failed to register icc provider %s: %ld\n", + icc_driver_name, PTR_ERR(priv->icc_pdev)); + return PTR_ERR(priv->icc_pdev); + } + + return 0; +} + +static int imx_bus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_bus *priv; + const char *gov = DEVFREQ_GOV_USERSPACE; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* + * Fetch the clock to adjust but don't explicitly enable. + * + * For imx bus clock clk_set_rate is safe no matter if the clock is on + * or off and some peripheral side-buses might be off unless enabled by + * drivers for devices on those specific buses. + * + * Rate adjustment on a disabled bus clock just takes effect later. + */ + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + dev_err(dev, "failed to fetch clk: %d\n", ret); + return ret; + } + platform_set_drvdata(pdev, priv); + + ret = dev_pm_opp_of_add_table(dev); + if (ret < 0) { + dev_err(dev, "failed to get OPP table\n"); + return ret; + } + + priv->profile.target = imx_bus_target; + priv->profile.exit = imx_bus_exit; + priv->profile.get_cur_freq = imx_bus_get_cur_freq; + priv->profile.initial_freq = clk_get_rate(priv->clk); + + priv->devfreq = devm_devfreq_add_device(dev, &priv->profile, + gov, NULL); + if (IS_ERR(priv->devfreq)) { + ret = PTR_ERR(priv->devfreq); + dev_err(dev, "failed to add devfreq device: %d\n", ret); + goto err; + } + + ret = imx_bus_init_icc(dev); + if (ret) + goto err; + + return 0; + +err: + dev_pm_opp_of_remove_table(dev); + return ret; +} + +static const struct of_device_id imx_bus_of_match[] = { + { .compatible = "fsl,imx8mq-noc", .data = "imx8mq-interconnect", }, + { .compatible = "fsl,imx8mm-noc", .data = "imx8mm-interconnect", }, + { .compatible = "fsl,imx8mn-noc", .data = "imx8mn-interconnect", }, + { .compatible = "fsl,imx8mp-noc", .data = "imx8mp-interconnect", }, + { .compatible = "fsl,imx8m-noc", }, + { .compatible = "fsl,imx8m-nic", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx_bus_of_match); + +static struct platform_driver imx_bus_platdrv = { + .probe = imx_bus_probe, + .driver = { + .name = "imx-bus-devfreq", + .of_match_table = imx_bus_of_match, + }, +}; +module_platform_driver(imx_bus_platdrv); + +MODULE_DESCRIPTION("Generic i.MX bus frequency scaling driver"); +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/imx8m-ddrc.c b/drivers/devfreq/imx8m-ddrc.c new file mode 100644 index 000000000..16636973e --- /dev/null +++ b/drivers/devfreq/imx8m-ddrc.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/devfreq.h> +#include <linux/pm_opp.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/arm-smccc.h> + +#define IMX_SIP_DDR_DVFS 0xc2000004 + +/* Query available frequencies. */ +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10 +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11 + +/* + * This should be in a 1:1 mapping with devicetree OPPs but + * firmware provides additional info. + */ +struct imx8m_ddrc_freq { + unsigned long rate; + unsigned long smcarg; + int dram_core_parent_index; + int dram_alt_parent_index; + int dram_apb_parent_index; +}; + +/* Hardware limitation */ +#define IMX8M_DDRC_MAX_FREQ_COUNT 4 + +/* + * i.MX8M DRAM Controller clocks have the following structure (abridged): + * + * +----------+ |\ +------+ + * | dram_pll |-------|M| dram_core | | + * +----------+ |U|---------->| D | + * /--|X| | D | + * dram_alt_root | |/ | R | + * | | C | + * +---------+ | | + * |FIX DIV/4| | | + * +---------+ | | + * composite: | | | + * +----------+ | | | + * | dram_alt |----/ | | + * +----------+ | | + * | dram_apb |-------------------->| | + * +----------+ +------+ + * + * The dram_pll is used for higher rates and dram_alt is used for lower rates. + * + * Frequency switching is implemented in TF-A (via SMC call) and can change the + * configuration of the clocks, including mux parents. The dram_alt and + * dram_apb clocks are "imx composite" and their parent can change too. + * + * We need to prepare/enable the new mux parents head of switching and update + * their information afterwards. + */ +struct imx8m_ddrc { + struct devfreq_dev_profile profile; + struct devfreq *devfreq; + + /* For frequency switching: */ + struct clk *dram_core; + struct clk *dram_pll; + struct clk *dram_alt; + struct clk *dram_apb; + + int freq_count; + struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT]; +}; + +static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv, + unsigned long rate) +{ + struct imx8m_ddrc_freq *freq; + int i; + + /* + * Firmware reports values in MT/s, so we round-down from Hz + * Rounding is extra generous to ensure a match. + */ + rate = DIV_ROUND_CLOSEST(rate, 250000); + for (i = 0; i < priv->freq_count; ++i) { + freq = &priv->freq_table[i]; + if (freq->rate == rate || + freq->rate + 1 == rate || + freq->rate - 1 == rate) + return freq; + } + + return NULL; +} + +static void imx8m_ddrc_smc_set_freq(int target_freq) +{ + struct arm_smccc_res res; + u32 online_cpus = 0; + int cpu; + + local_irq_disable(); + + for_each_online_cpu(cpu) + online_cpus |= (1 << (cpu * 8)); + + /* change the ddr freqency */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); +} + +static struct clk *clk_get_parent_by_index(struct clk *clk, int index) +{ + struct clk_hw *hw; + + hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index); + + return hw ? hw->clk : NULL; +} + +static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq) +{ + struct imx8m_ddrc *priv = dev_get_drvdata(dev); + struct clk *new_dram_core_parent; + struct clk *new_dram_alt_parent; + struct clk *new_dram_apb_parent; + int ret; + + /* + * Fetch new parents + * + * new_dram_alt_parent and new_dram_apb_parent are optional but + * new_dram_core_parent is not. + */ + new_dram_core_parent = clk_get_parent_by_index( + priv->dram_core, freq->dram_core_parent_index - 1); + if (!new_dram_core_parent) { + dev_err(dev, "failed to fetch new dram_core parent\n"); + return -EINVAL; + } + if (freq->dram_alt_parent_index) { + new_dram_alt_parent = clk_get_parent_by_index( + priv->dram_alt, + freq->dram_alt_parent_index - 1); + if (!new_dram_alt_parent) { + dev_err(dev, "failed to fetch new dram_alt parent\n"); + return -EINVAL; + } + } else + new_dram_alt_parent = NULL; + + if (freq->dram_apb_parent_index) { + new_dram_apb_parent = clk_get_parent_by_index( + priv->dram_apb, + freq->dram_apb_parent_index - 1); + if (!new_dram_apb_parent) { + dev_err(dev, "failed to fetch new dram_apb parent\n"); + return -EINVAL; + } + } else + new_dram_apb_parent = NULL; + + /* increase reference counts and ensure clks are ON before switch */ + ret = clk_prepare_enable(new_dram_core_parent); + if (ret) { + dev_err(dev, "failed to enable new dram_core parent: %d\n", + ret); + goto out; + } + ret = clk_prepare_enable(new_dram_alt_parent); + if (ret) { + dev_err(dev, "failed to enable new dram_alt parent: %d\n", + ret); + goto out_disable_core_parent; + } + ret = clk_prepare_enable(new_dram_apb_parent); + if (ret) { + dev_err(dev, "failed to enable new dram_apb parent: %d\n", + ret); + goto out_disable_alt_parent; + } + + imx8m_ddrc_smc_set_freq(freq->smcarg); + + /* update parents in clk tree after switch. */ + ret = clk_set_parent(priv->dram_core, new_dram_core_parent); + if (ret) + dev_warn(dev, "failed to set dram_core parent: %d\n", ret); + if (new_dram_alt_parent) { + ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent); + if (ret) + dev_warn(dev, "failed to set dram_alt parent: %d\n", + ret); + } + if (new_dram_apb_parent) { + ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent); + if (ret) + dev_warn(dev, "failed to set dram_apb parent: %d\n", + ret); + } + + /* + * Explicitly refresh dram PLL rate. + * + * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be + * automatically refreshed when clk_get_rate is called on children. + */ + clk_get_rate(priv->dram_pll); + + /* + * clk_set_parent transfer the reference count from old parent. + * now we drop extra reference counts used during the switch + */ + clk_disable_unprepare(new_dram_apb_parent); +out_disable_alt_parent: + clk_disable_unprepare(new_dram_alt_parent); +out_disable_core_parent: + clk_disable_unprepare(new_dram_core_parent); +out: + return ret; +} + +static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct imx8m_ddrc *priv = dev_get_drvdata(dev); + struct imx8m_ddrc_freq *freq_info; + struct dev_pm_opp *new_opp; + unsigned long old_freq, new_freq; + int ret; + + new_opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(new_opp)) { + ret = PTR_ERR(new_opp); + dev_err(dev, "failed to get recommended opp: %d\n", ret); + return ret; + } + dev_pm_opp_put(new_opp); + + old_freq = clk_get_rate(priv->dram_core); + if (*freq == old_freq) + return 0; + + freq_info = imx8m_ddrc_find_freq(priv, *freq); + if (!freq_info) + return -EINVAL; + + /* + * Read back the clk rate to verify switch was correct and so that + * we can report it on all error paths. + */ + ret = imx8m_ddrc_set_freq(dev, freq_info); + + new_freq = clk_get_rate(priv->dram_core); + if (ret) + dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n", + *freq, old_freq, ret, new_freq); + else if (*freq != new_freq) + dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n", + *freq, old_freq, new_freq); + else + dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n", + *freq, old_freq); + + return ret; +} + +static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct imx8m_ddrc *priv = dev_get_drvdata(dev); + + *freq = clk_get_rate(priv->dram_core); + + return 0; +} + +static int imx8m_ddrc_init_freq_info(struct device *dev) +{ + struct imx8m_ddrc *priv = dev_get_drvdata(dev); + struct arm_smccc_res res; + int index; + + /* An error here means DDR DVFS API not supported by firmware */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT, + 0, 0, 0, 0, 0, 0, &res); + priv->freq_count = res.a0; + if (priv->freq_count <= 0 || + priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT) + return -ENODEV; + + for (index = 0; index < priv->freq_count; ++index) { + struct imx8m_ddrc_freq *freq = &priv->freq_table[index]; + + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO, + index, 0, 0, 0, 0, 0, &res); + /* Result should be strictly positive */ + if ((long)res.a0 <= 0) + return -ENODEV; + + freq->rate = res.a0; + freq->smcarg = index; + freq->dram_core_parent_index = res.a1; + freq->dram_alt_parent_index = res.a2; + freq->dram_apb_parent_index = res.a3; + + /* dram_core has 2 options: dram_pll or dram_alt_root */ + if (freq->dram_core_parent_index != 1 && + freq->dram_core_parent_index != 2) + return -ENODEV; + /* dram_apb and dram_alt have exactly 8 possible parents */ + if (freq->dram_alt_parent_index > 8 || + freq->dram_apb_parent_index > 8) + return -ENODEV; + /* dram_core from alt requires explicit dram_alt parent */ + if (freq->dram_core_parent_index == 2 && + freq->dram_alt_parent_index == 0) + return -ENODEV; + } + + return 0; +} + +static int imx8m_ddrc_check_opps(struct device *dev) +{ + struct imx8m_ddrc *priv = dev_get_drvdata(dev); + struct imx8m_ddrc_freq *freq_info; + struct dev_pm_opp *opp; + unsigned long freq; + int i, opp_count; + + /* Enumerate DT OPPs and disable those not supported by firmware */ + opp_count = dev_pm_opp_get_opp_count(dev); + if (opp_count < 0) + return opp_count; + for (i = 0, freq = 0; i < opp_count; ++i, ++freq) { + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + if (IS_ERR(opp)) { + dev_err(dev, "Failed enumerating OPPs: %ld\n", + PTR_ERR(opp)); + return PTR_ERR(opp); + } + dev_pm_opp_put(opp); + + freq_info = imx8m_ddrc_find_freq(priv, freq); + if (!freq_info) { + dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n", + freq, DIV_ROUND_CLOSEST(freq, 250000)); + dev_pm_opp_disable(dev, freq); + } + } + + return 0; +} + +static void imx8m_ddrc_exit(struct device *dev) +{ + dev_pm_opp_of_remove_table(dev); +} + +static int imx8m_ddrc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8m_ddrc *priv; + const char *gov = DEVFREQ_GOV_USERSPACE; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + ret = imx8m_ddrc_init_freq_info(dev); + if (ret) { + dev_err(dev, "failed to init firmware freq info: %d\n", ret); + return ret; + } + + priv->dram_core = devm_clk_get(dev, "core"); + if (IS_ERR(priv->dram_core)) { + ret = PTR_ERR(priv->dram_core); + dev_err(dev, "failed to fetch core clock: %d\n", ret); + return ret; + } + priv->dram_pll = devm_clk_get(dev, "pll"); + if (IS_ERR(priv->dram_pll)) { + ret = PTR_ERR(priv->dram_pll); + dev_err(dev, "failed to fetch pll clock: %d\n", ret); + return ret; + } + priv->dram_alt = devm_clk_get(dev, "alt"); + if (IS_ERR(priv->dram_alt)) { + ret = PTR_ERR(priv->dram_alt); + dev_err(dev, "failed to fetch alt clock: %d\n", ret); + return ret; + } + priv->dram_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(priv->dram_apb)) { + ret = PTR_ERR(priv->dram_apb); + dev_err(dev, "failed to fetch apb clock: %d\n", ret); + return ret; + } + + ret = dev_pm_opp_of_add_table(dev); + if (ret < 0) { + dev_err(dev, "failed to get OPP table\n"); + return ret; + } + + ret = imx8m_ddrc_check_opps(dev); + if (ret < 0) + goto err; + + priv->profile.target = imx8m_ddrc_target; + priv->profile.exit = imx8m_ddrc_exit; + priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq; + priv->profile.initial_freq = clk_get_rate(priv->dram_core); + + priv->devfreq = devm_devfreq_add_device(dev, &priv->profile, + gov, NULL); + if (IS_ERR(priv->devfreq)) { + ret = PTR_ERR(priv->devfreq); + dev_err(dev, "failed to add devfreq device: %d\n", ret); + goto err; + } + + return 0; + +err: + dev_pm_opp_of_remove_table(dev); + return ret; +} + +static const struct of_device_id imx8m_ddrc_of_match[] = { + { .compatible = "fsl,imx8m-ddrc", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match); + +static struct platform_driver imx8m_ddrc_platdrv = { + .probe = imx8m_ddrc_probe, + .driver = { + .name = "imx8m-ddrc-devfreq", + .of_match_table = imx8m_ddrc_of_match, + }, +}; +module_platform_driver(imx8m_ddrc_platdrv); + +MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver"); +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/mtk-cci-devfreq.c b/drivers/devfreq/mtk-cci-devfreq.c new file mode 100644 index 000000000..e5458ada5 --- /dev/null +++ b/drivers/devfreq/mtk-cci-devfreq.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/regulator/consumer.h> + +struct mtk_ccifreq_platform_data { + int min_volt_shift; + int max_volt_shift; + int proc_max_volt; + int sram_min_volt; + int sram_max_volt; +}; + +struct mtk_ccifreq_drv { + struct device *dev; + struct devfreq *devfreq; + struct regulator *proc_reg; + struct regulator *sram_reg; + struct clk *cci_clk; + struct clk *inter_clk; + int inter_voltage; + unsigned long pre_freq; + /* Avoid race condition for regulators between notify and policy */ + struct mutex reg_lock; + struct notifier_block opp_nb; + const struct mtk_ccifreq_platform_data *soc_data; + int vtrack_max; +}; + +static int mtk_ccifreq_set_voltage(struct mtk_ccifreq_drv *drv, int new_voltage) +{ + const struct mtk_ccifreq_platform_data *soc_data = drv->soc_data; + struct device *dev = drv->dev; + int pre_voltage, pre_vsram, new_vsram, vsram, voltage, ret; + int retry_max = drv->vtrack_max; + + if (!drv->sram_reg) { + ret = regulator_set_voltage(drv->proc_reg, new_voltage, + drv->soc_data->proc_max_volt); + return ret; + } + + pre_voltage = regulator_get_voltage(drv->proc_reg); + if (pre_voltage < 0) { + dev_err(dev, "invalid vproc value: %d\n", pre_voltage); + return pre_voltage; + } + + pre_vsram = regulator_get_voltage(drv->sram_reg); + if (pre_vsram < 0) { + dev_err(dev, "invalid vsram value: %d\n", pre_vsram); + return pre_vsram; + } + + new_vsram = clamp(new_voltage + soc_data->min_volt_shift, + soc_data->sram_min_volt, soc_data->sram_max_volt); + + do { + if (pre_voltage <= new_voltage) { + vsram = clamp(pre_voltage + soc_data->max_volt_shift, + soc_data->sram_min_volt, new_vsram); + ret = regulator_set_voltage(drv->sram_reg, vsram, + soc_data->sram_max_volt); + if (ret) + return ret; + + if (vsram == soc_data->sram_max_volt || + new_vsram == soc_data->sram_min_volt) + voltage = new_voltage; + else + voltage = vsram - soc_data->min_volt_shift; + + ret = regulator_set_voltage(drv->proc_reg, voltage, + soc_data->proc_max_volt); + if (ret) { + regulator_set_voltage(drv->sram_reg, pre_vsram, + soc_data->sram_max_volt); + return ret; + } + } else if (pre_voltage > new_voltage) { + voltage = max(new_voltage, + pre_vsram - soc_data->max_volt_shift); + ret = regulator_set_voltage(drv->proc_reg, voltage, + soc_data->proc_max_volt); + if (ret) + return ret; + + if (voltage == new_voltage) + vsram = new_vsram; + else + vsram = max(new_vsram, + voltage + soc_data->min_volt_shift); + + ret = regulator_set_voltage(drv->sram_reg, vsram, + soc_data->sram_max_volt); + if (ret) { + regulator_set_voltage(drv->proc_reg, pre_voltage, + soc_data->proc_max_volt); + return ret; + } + } + + pre_voltage = voltage; + pre_vsram = vsram; + + if (--retry_max < 0) { + dev_err(dev, + "over loop count, failed to set voltage\n"); + return -EINVAL; + } + } while (voltage != new_voltage || vsram != new_vsram); + + return 0; +} + +static int mtk_ccifreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct mtk_ccifreq_drv *drv = dev_get_drvdata(dev); + struct clk *cci_pll = clk_get_parent(drv->cci_clk); + struct dev_pm_opp *opp; + unsigned long opp_rate; + int voltage, pre_voltage, inter_voltage, target_voltage, ret; + + if (!drv) + return -EINVAL; + + if (drv->pre_freq == *freq) + return 0; + + inter_voltage = drv->inter_voltage; + + opp_rate = *freq; + opp = devfreq_recommended_opp(dev, &opp_rate, 1); + if (IS_ERR(opp)) { + dev_err(dev, "failed to find opp for freq: %ld\n", opp_rate); + return PTR_ERR(opp); + } + + mutex_lock(&drv->reg_lock); + + voltage = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + pre_voltage = regulator_get_voltage(drv->proc_reg); + if (pre_voltage < 0) { + dev_err(dev, "invalid vproc value: %d\n", pre_voltage); + ret = pre_voltage; + goto out_unlock; + } + + /* scale up: set voltage first then freq. */ + target_voltage = max(inter_voltage, voltage); + if (pre_voltage <= target_voltage) { + ret = mtk_ccifreq_set_voltage(drv, target_voltage); + if (ret) { + dev_err(dev, "failed to scale up voltage\n"); + goto out_restore_voltage; + } + } + + /* switch the cci clock to intermediate clock source. */ + ret = clk_set_parent(drv->cci_clk, drv->inter_clk); + if (ret) { + dev_err(dev, "failed to re-parent cci clock\n"); + goto out_restore_voltage; + } + + /* set the original clock to target rate. */ + ret = clk_set_rate(cci_pll, *freq); + if (ret) { + dev_err(dev, "failed to set cci pll rate: %d\n", ret); + clk_set_parent(drv->cci_clk, cci_pll); + goto out_restore_voltage; + } + + /* switch the cci clock back to the original clock source. */ + ret = clk_set_parent(drv->cci_clk, cci_pll); + if (ret) { + dev_err(dev, "failed to re-parent cci clock\n"); + mtk_ccifreq_set_voltage(drv, inter_voltage); + goto out_unlock; + } + + /* + * If the new voltage is lower than the intermediate voltage or the + * original voltage, scale down to the new voltage. + */ + if (voltage < inter_voltage || voltage < pre_voltage) { + ret = mtk_ccifreq_set_voltage(drv, voltage); + if (ret) { + dev_err(dev, "failed to scale down voltage\n"); + goto out_unlock; + } + } + + drv->pre_freq = *freq; + mutex_unlock(&drv->reg_lock); + + return 0; + +out_restore_voltage: + mtk_ccifreq_set_voltage(drv, pre_voltage); + +out_unlock: + mutex_unlock(&drv->reg_lock); + return ret; +} + +static int mtk_ccifreq_opp_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct dev_pm_opp *opp = data; + struct mtk_ccifreq_drv *drv; + unsigned long freq, volt; + + drv = container_of(nb, struct mtk_ccifreq_drv, opp_nb); + + if (event == OPP_EVENT_ADJUST_VOLTAGE) { + freq = dev_pm_opp_get_freq(opp); + + mutex_lock(&drv->reg_lock); + /* current opp item is changed */ + if (freq == drv->pre_freq) { + volt = dev_pm_opp_get_voltage(opp); + mtk_ccifreq_set_voltage(drv, volt); + } + mutex_unlock(&drv->reg_lock); + } + + return 0; +} + +static struct devfreq_dev_profile mtk_ccifreq_profile = { + .target = mtk_ccifreq_target, +}; + +static int mtk_ccifreq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ccifreq_drv *drv; + struct devfreq_passive_data *passive_data; + struct dev_pm_opp *opp; + unsigned long rate, opp_volt; + int ret; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->dev = dev; + drv->soc_data = (const struct mtk_ccifreq_platform_data *) + of_device_get_match_data(&pdev->dev); + mutex_init(&drv->reg_lock); + platform_set_drvdata(pdev, drv); + + drv->cci_clk = devm_clk_get(dev, "cci"); + if (IS_ERR(drv->cci_clk)) { + ret = PTR_ERR(drv->cci_clk); + return dev_err_probe(dev, ret, "failed to get cci clk\n"); + } + + drv->inter_clk = devm_clk_get(dev, "intermediate"); + if (IS_ERR(drv->inter_clk)) { + ret = PTR_ERR(drv->inter_clk); + return dev_err_probe(dev, ret, + "failed to get intermediate clk\n"); + } + + drv->proc_reg = devm_regulator_get_optional(dev, "proc"); + if (IS_ERR(drv->proc_reg)) { + ret = PTR_ERR(drv->proc_reg); + return dev_err_probe(dev, ret, + "failed to get proc regulator\n"); + } + + ret = regulator_enable(drv->proc_reg); + if (ret) { + dev_err(dev, "failed to enable proc regulator\n"); + return ret; + } + + drv->sram_reg = devm_regulator_get_optional(dev, "sram"); + if (IS_ERR(drv->sram_reg)) { + ret = PTR_ERR(drv->sram_reg); + if (ret == -EPROBE_DEFER) + goto out_free_resources; + + drv->sram_reg = NULL; + } else { + ret = regulator_enable(drv->sram_reg); + if (ret) { + dev_err(dev, "failed to enable sram regulator\n"); + goto out_free_resources; + } + } + + /* + * We assume min voltage is 0 and tracking target voltage using + * min_volt_shift for each iteration. + * The retry_max is 3 times of expected iteration count. + */ + drv->vtrack_max = 3 * DIV_ROUND_UP(max(drv->soc_data->sram_max_volt, + drv->soc_data->proc_max_volt), + drv->soc_data->min_volt_shift); + + ret = clk_prepare_enable(drv->cci_clk); + if (ret) + goto out_free_resources; + + ret = dev_pm_opp_of_add_table(dev); + if (ret) { + dev_err(dev, "failed to add opp table: %d\n", ret); + goto out_disable_cci_clk; + } + + rate = clk_get_rate(drv->inter_clk); + opp = dev_pm_opp_find_freq_ceil(dev, &rate); + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + dev_err(dev, "failed to get intermediate opp: %d\n", ret); + goto out_remove_opp_table; + } + drv->inter_voltage = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + rate = U32_MAX; + opp = dev_pm_opp_find_freq_floor(drv->dev, &rate); + if (IS_ERR(opp)) { + dev_err(dev, "failed to get opp\n"); + ret = PTR_ERR(opp); + goto out_remove_opp_table; + } + + opp_volt = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + ret = mtk_ccifreq_set_voltage(drv, opp_volt); + if (ret) { + dev_err(dev, "failed to scale to highest voltage %lu in proc_reg\n", + opp_volt); + goto out_remove_opp_table; + } + + passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL); + if (!passive_data) { + ret = -ENOMEM; + goto out_remove_opp_table; + } + + passive_data->parent_type = CPUFREQ_PARENT_DEV; + drv->devfreq = devm_devfreq_add_device(dev, &mtk_ccifreq_profile, + DEVFREQ_GOV_PASSIVE, + passive_data); + if (IS_ERR(drv->devfreq)) { + ret = -EPROBE_DEFER; + dev_err(dev, "failed to add devfreq device: %ld\n", + PTR_ERR(drv->devfreq)); + goto out_remove_opp_table; + } + + drv->opp_nb.notifier_call = mtk_ccifreq_opp_notifier; + ret = dev_pm_opp_register_notifier(dev, &drv->opp_nb); + if (ret) { + dev_err(dev, "failed to register opp notifier: %d\n", ret); + goto out_remove_opp_table; + } + return 0; + +out_remove_opp_table: + dev_pm_opp_of_remove_table(dev); + +out_disable_cci_clk: + clk_disable_unprepare(drv->cci_clk); + +out_free_resources: + if (regulator_is_enabled(drv->proc_reg)) + regulator_disable(drv->proc_reg); + if (drv->sram_reg && regulator_is_enabled(drv->sram_reg)) + regulator_disable(drv->sram_reg); + + return ret; +} + +static int mtk_ccifreq_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ccifreq_drv *drv; + + drv = platform_get_drvdata(pdev); + + dev_pm_opp_unregister_notifier(dev, &drv->opp_nb); + dev_pm_opp_of_remove_table(dev); + clk_disable_unprepare(drv->cci_clk); + regulator_disable(drv->proc_reg); + if (drv->sram_reg) + regulator_disable(drv->sram_reg); + + return 0; +} + +static const struct mtk_ccifreq_platform_data mt8183_platform_data = { + .min_volt_shift = 100000, + .max_volt_shift = 200000, + .proc_max_volt = 1150000, +}; + +static const struct mtk_ccifreq_platform_data mt8186_platform_data = { + .min_volt_shift = 100000, + .max_volt_shift = 250000, + .proc_max_volt = 1118750, + .sram_min_volt = 850000, + .sram_max_volt = 1118750, +}; + +static const struct of_device_id mtk_ccifreq_machines[] = { + { .compatible = "mediatek,mt8183-cci", .data = &mt8183_platform_data }, + { .compatible = "mediatek,mt8186-cci", .data = &mt8186_platform_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_ccifreq_machines); + +static struct platform_driver mtk_ccifreq_platdrv = { + .probe = mtk_ccifreq_probe, + .remove = mtk_ccifreq_remove, + .driver = { + .name = "mtk-ccifreq", + .of_match_table = mtk_ccifreq_machines, + }, +}; +module_platform_driver(mtk_ccifreq_platdrv); + +MODULE_DESCRIPTION("MediaTek CCI devfreq driver"); +MODULE_AUTHOR("Jia-Wei Chang <jia-wei.chang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/rk3399_dmc.c b/drivers/devfreq/rk3399_dmc.c new file mode 100644 index 000000000..daff40702 --- /dev/null +++ b/drivers/devfreq/rk3399_dmc.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd. + * Author: Lin Huang <hl@rock-chips.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/devfreq.h> +#include <linux/devfreq-event.h> +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/rwsem.h> +#include <linux/suspend.h> + +#include <soc/rockchip/pm_domains.h> +#include <soc/rockchip/rk3399_grf.h> +#include <soc/rockchip/rockchip_sip.h> + +#define NS_TO_CYCLE(NS, MHz) (((NS) * (MHz)) / NSEC_PER_USEC) + +#define RK3399_SET_ODT_PD_0_SR_IDLE GENMASK(7, 0) +#define RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE GENMASK(15, 8) +#define RK3399_SET_ODT_PD_0_STANDBY_IDLE GENMASK(31, 16) + +#define RK3399_SET_ODT_PD_1_PD_IDLE GENMASK(11, 0) +#define RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE GENMASK(27, 16) + +#define RK3399_SET_ODT_PD_2_ODT_ENABLE BIT(0) + +struct rk3399_dmcfreq { + struct device *dev; + struct devfreq *devfreq; + struct devfreq_dev_profile profile; + struct devfreq_simple_ondemand_data ondemand_data; + struct clk *dmc_clk; + struct devfreq_event_dev *edev; + struct mutex lock; + struct regulator *vdd_center; + struct regmap *regmap_pmu; + unsigned long rate, target_rate; + unsigned long volt, target_volt; + unsigned int odt_dis_freq; + + unsigned int pd_idle_ns; + unsigned int sr_idle_ns; + unsigned int sr_mc_gate_idle_ns; + unsigned int srpd_lite_idle_ns; + unsigned int standby_idle_ns; + unsigned int ddr3_odt_dis_freq; + unsigned int lpddr3_odt_dis_freq; + unsigned int lpddr4_odt_dis_freq; + + unsigned int pd_idle_dis_freq; + unsigned int sr_idle_dis_freq; + unsigned int sr_mc_gate_idle_dis_freq; + unsigned int srpd_lite_idle_dis_freq; + unsigned int standby_idle_dis_freq; +}; + +static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); + struct dev_pm_opp *opp; + unsigned long old_clk_rate = dmcfreq->rate; + unsigned long target_volt, target_rate; + unsigned int ddrcon_mhz; + struct arm_smccc_res res; + int err; + + u32 odt_pd_arg0 = 0; + u32 odt_pd_arg1 = 0; + u32 odt_pd_arg2 = 0; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + target_rate = dev_pm_opp_get_freq(opp); + target_volt = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + if (dmcfreq->rate == target_rate) + return 0; + + mutex_lock(&dmcfreq->lock); + + /* + * Ensure power-domain transitions don't interfere with ARM Trusted + * Firmware power-domain idling. + */ + err = rockchip_pmu_block(); + if (err) { + dev_err(dev, "Failed to block PMU: %d\n", err); + goto out_unlock; + } + + /* + * Some idle parameters may be based on the DDR controller clock, which + * is half of the DDR frequency. + * pd_idle and standby_idle are based on the controller clock cycle. + * sr_idle_cycle, sr_mc_gate_idle_cycle, and srpd_lite_idle_cycle + * are based on the 1024 controller clock cycle + */ + ddrcon_mhz = target_rate / USEC_PER_SEC / 2; + + u32p_replace_bits(&odt_pd_arg1, + NS_TO_CYCLE(dmcfreq->pd_idle_ns, ddrcon_mhz), + RK3399_SET_ODT_PD_1_PD_IDLE); + u32p_replace_bits(&odt_pd_arg0, + NS_TO_CYCLE(dmcfreq->standby_idle_ns, ddrcon_mhz), + RK3399_SET_ODT_PD_0_STANDBY_IDLE); + u32p_replace_bits(&odt_pd_arg0, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->sr_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_0_SR_IDLE); + u32p_replace_bits(&odt_pd_arg0, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->sr_mc_gate_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE); + u32p_replace_bits(&odt_pd_arg1, + DIV_ROUND_UP(NS_TO_CYCLE(dmcfreq->srpd_lite_idle_ns, + ddrcon_mhz), 1024), + RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE); + + if (dmcfreq->regmap_pmu) { + if (target_rate >= dmcfreq->sr_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_SR_IDLE; + + if (target_rate >= dmcfreq->sr_mc_gate_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_SR_MC_GATE_IDLE; + + if (target_rate >= dmcfreq->standby_idle_dis_freq) + odt_pd_arg0 &= ~RK3399_SET_ODT_PD_0_STANDBY_IDLE; + + if (target_rate >= dmcfreq->pd_idle_dis_freq) + odt_pd_arg1 &= ~RK3399_SET_ODT_PD_1_PD_IDLE; + + if (target_rate >= dmcfreq->srpd_lite_idle_dis_freq) + odt_pd_arg1 &= ~RK3399_SET_ODT_PD_1_SRPD_LITE_IDLE; + + if (target_rate >= dmcfreq->odt_dis_freq) + odt_pd_arg2 |= RK3399_SET_ODT_PD_2_ODT_ENABLE; + + /* + * This makes a SMC call to the TF-A to set the DDR PD + * (power-down) timings and to enable or disable the + * ODT (on-die termination) resistors. + */ + arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, odt_pd_arg0, odt_pd_arg1, + ROCKCHIP_SIP_CONFIG_DRAM_SET_ODT_PD, odt_pd_arg2, + 0, 0, 0, &res); + } + + /* + * If frequency scaling from low to high, adjust voltage first. + * If frequency scaling from high to low, adjust frequency first. + */ + if (old_clk_rate < target_rate) { + err = regulator_set_voltage(dmcfreq->vdd_center, target_volt, + target_volt); + if (err) { + dev_err(dev, "Cannot set voltage %lu uV\n", + target_volt); + goto out; + } + } + + err = clk_set_rate(dmcfreq->dmc_clk, target_rate); + if (err) { + dev_err(dev, "Cannot set frequency %lu (%d)\n", target_rate, + err); + regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt, + dmcfreq->volt); + goto out; + } + + /* + * Check the dpll rate, + * There only two result we will get, + * 1. Ddr frequency scaling fail, we still get the old rate. + * 2. Ddr frequency scaling sucessful, we get the rate we set. + */ + dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk); + + /* If get the incorrect rate, set voltage to old value. */ + if (dmcfreq->rate != target_rate) { + dev_err(dev, "Got wrong frequency, Request %lu, Current %lu\n", + target_rate, dmcfreq->rate); + regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt, + dmcfreq->volt); + goto out; + } else if (old_clk_rate > target_rate) + err = regulator_set_voltage(dmcfreq->vdd_center, target_volt, + target_volt); + if (err) + dev_err(dev, "Cannot set voltage %lu uV\n", target_volt); + + dmcfreq->rate = target_rate; + dmcfreq->volt = target_volt; + +out: + rockchip_pmu_unblock(); +out_unlock: + mutex_unlock(&dmcfreq->lock); + return err; +} + +static int rk3399_dmcfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); + struct devfreq_event_data edata; + int ret = 0; + + ret = devfreq_event_get_event(dmcfreq->edev, &edata); + if (ret < 0) + return ret; + + stat->current_frequency = dmcfreq->rate; + stat->busy_time = edata.load_count; + stat->total_time = edata.total_count; + + return ret; +} + +static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); + + *freq = dmcfreq->rate; + + return 0; +} + +static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); + int ret = 0; + + ret = devfreq_event_disable_edev(dmcfreq->edev); + if (ret < 0) { + dev_err(dev, "failed to disable the devfreq-event devices\n"); + return ret; + } + + ret = devfreq_suspend_device(dmcfreq->devfreq); + if (ret < 0) { + dev_err(dev, "failed to suspend the devfreq devices\n"); + return ret; + } + + return 0; +} + +static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(dev); + int ret = 0; + + ret = devfreq_event_enable_edev(dmcfreq->edev); + if (ret < 0) { + dev_err(dev, "failed to enable the devfreq-event devices\n"); + return ret; + } + + ret = devfreq_resume_device(dmcfreq->devfreq); + if (ret < 0) { + dev_err(dev, "failed to resume the devfreq devices\n"); + return ret; + } + return ret; +} + +static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend, + rk3399_dmcfreq_resume); + +static int rk3399_dmcfreq_of_props(struct rk3399_dmcfreq *data, + struct device_node *np) +{ + int ret = 0; + + /* + * These are all optional, and serve as minimum bounds. Give them large + * (i.e., never "disabled") values if the DT doesn't specify one. + */ + data->pd_idle_dis_freq = + data->sr_idle_dis_freq = + data->sr_mc_gate_idle_dis_freq = + data->srpd_lite_idle_dis_freq = + data->standby_idle_dis_freq = UINT_MAX; + + ret |= of_property_read_u32(np, "rockchip,pd-idle-ns", + &data->pd_idle_ns); + ret |= of_property_read_u32(np, "rockchip,sr-idle-ns", + &data->sr_idle_ns); + ret |= of_property_read_u32(np, "rockchip,sr-mc-gate-idle-ns", + &data->sr_mc_gate_idle_ns); + ret |= of_property_read_u32(np, "rockchip,srpd-lite-idle-ns", + &data->srpd_lite_idle_ns); + ret |= of_property_read_u32(np, "rockchip,standby-idle-ns", + &data->standby_idle_ns); + ret |= of_property_read_u32(np, "rockchip,ddr3_odt_dis_freq", + &data->ddr3_odt_dis_freq); + ret |= of_property_read_u32(np, "rockchip,lpddr3_odt_dis_freq", + &data->lpddr3_odt_dis_freq); + ret |= of_property_read_u32(np, "rockchip,lpddr4_odt_dis_freq", + &data->lpddr4_odt_dis_freq); + + ret |= of_property_read_u32(np, "rockchip,pd-idle-dis-freq-hz", + &data->pd_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,sr-idle-dis-freq-hz", + &data->sr_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,sr-mc-gate-idle-dis-freq-hz", + &data->sr_mc_gate_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,srpd-lite-idle-dis-freq-hz", + &data->srpd_lite_idle_dis_freq); + ret |= of_property_read_u32(np, "rockchip,standby-idle-dis-freq-hz", + &data->standby_idle_dis_freq); + + return ret; +} + +static int rk3399_dmcfreq_probe(struct platform_device *pdev) +{ + struct arm_smccc_res res; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node, *node; + struct rk3399_dmcfreq *data; + int ret; + struct dev_pm_opp *opp; + u32 ddr_type; + u32 val; + + data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + + data->vdd_center = devm_regulator_get(dev, "center"); + if (IS_ERR(data->vdd_center)) + return dev_err_probe(dev, PTR_ERR(data->vdd_center), + "Cannot get the regulator \"center\"\n"); + + data->dmc_clk = devm_clk_get(dev, "dmc_clk"); + if (IS_ERR(data->dmc_clk)) + return dev_err_probe(dev, PTR_ERR(data->dmc_clk), + "Cannot get the clk dmc_clk\n"); + + data->edev = devfreq_event_get_edev_by_phandle(dev, "devfreq-events", 0); + if (IS_ERR(data->edev)) + return -EPROBE_DEFER; + + ret = devfreq_event_enable_edev(data->edev); + if (ret < 0) { + dev_err(dev, "failed to enable devfreq-event devices\n"); + return ret; + } + + rk3399_dmcfreq_of_props(data, np); + + node = of_parse_phandle(np, "rockchip,pmu", 0); + if (!node) + goto no_pmu; + + data->regmap_pmu = syscon_node_to_regmap(node); + of_node_put(node); + if (IS_ERR(data->regmap_pmu)) { + ret = PTR_ERR(data->regmap_pmu); + goto err_edev; + } + + regmap_read(data->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val); + ddr_type = (val >> RK3399_PMUGRF_DDRTYPE_SHIFT) & + RK3399_PMUGRF_DDRTYPE_MASK; + + switch (ddr_type) { + case RK3399_PMUGRF_DDRTYPE_DDR3: + data->odt_dis_freq = data->ddr3_odt_dis_freq; + break; + case RK3399_PMUGRF_DDRTYPE_LPDDR3: + data->odt_dis_freq = data->lpddr3_odt_dis_freq; + break; + case RK3399_PMUGRF_DDRTYPE_LPDDR4: + data->odt_dis_freq = data->lpddr4_odt_dis_freq; + break; + default: + ret = -EINVAL; + goto err_edev; + } + +no_pmu: + arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0, + ROCKCHIP_SIP_CONFIG_DRAM_INIT, + 0, 0, 0, 0, &res); + + /* + * We add a devfreq driver to our parent since it has a device tree node + * with operating points. + */ + if (devm_pm_opp_of_add_table(dev)) { + dev_err(dev, "Invalid operating-points in device tree.\n"); + ret = -EINVAL; + goto err_edev; + } + + data->ondemand_data.upthreshold = 25; + data->ondemand_data.downdifferential = 15; + + data->rate = clk_get_rate(data->dmc_clk); + + opp = devfreq_recommended_opp(dev, &data->rate, 0); + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + goto err_edev; + } + + data->rate = dev_pm_opp_get_freq(opp); + data->volt = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + data->profile = (struct devfreq_dev_profile) { + .polling_ms = 200, + .target = rk3399_dmcfreq_target, + .get_dev_status = rk3399_dmcfreq_get_dev_status, + .get_cur_freq = rk3399_dmcfreq_get_cur_freq, + .initial_freq = data->rate, + }; + + data->devfreq = devm_devfreq_add_device(dev, + &data->profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + &data->ondemand_data); + if (IS_ERR(data->devfreq)) { + ret = PTR_ERR(data->devfreq); + goto err_edev; + } + + devm_devfreq_register_opp_notifier(dev, data->devfreq); + + data->dev = dev; + platform_set_drvdata(pdev, data); + + return 0; + +err_edev: + devfreq_event_disable_edev(data->edev); + + return ret; +} + +static int rk3399_dmcfreq_remove(struct platform_device *pdev) +{ + struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(&pdev->dev); + + devfreq_event_disable_edev(dmcfreq->edev); + + return 0; +} + +static const struct of_device_id rk3399dmc_devfreq_of_match[] = { + { .compatible = "rockchip,rk3399-dmc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rk3399dmc_devfreq_of_match); + +static struct platform_driver rk3399_dmcfreq_driver = { + .probe = rk3399_dmcfreq_probe, + .remove = rk3399_dmcfreq_remove, + .driver = { + .name = "rk3399-dmc-freq", + .pm = &rk3399_dmcfreq_pm, + .of_match_table = rk3399dmc_devfreq_of_match, + }, +}; +module_platform_driver(rk3399_dmcfreq_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); +MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework"); diff --git a/drivers/devfreq/sun8i-a33-mbus.c b/drivers/devfreq/sun8i-a33-mbus.c new file mode 100644 index 000000000..13d322131 --- /dev/null +++ b/drivers/devfreq/sun8i-a33-mbus.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020-2021 Samuel Holland <samuel@sholland.org> +// + +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#define MBUS_CR 0x0000 +#define MBUS_CR_GET_DRAM_TYPE(x) (((x) >> 16) & 0x7) +#define MBUS_CR_DRAM_TYPE_DDR2 2 +#define MBUS_CR_DRAM_TYPE_DDR3 3 +#define MBUS_CR_DRAM_TYPE_DDR4 4 +#define MBUS_CR_DRAM_TYPE_LPDDR2 6 +#define MBUS_CR_DRAM_TYPE_LPDDR3 7 + +#define MBUS_TMR 0x000c +#define MBUS_TMR_PERIOD(x) ((x) - 1) + +#define MBUS_PMU_CFG 0x009c +#define MBUS_PMU_CFG_PERIOD(x) (((x) - 1) << 16) +#define MBUS_PMU_CFG_UNIT (0x3 << 1) +#define MBUS_PMU_CFG_UNIT_B (0x0 << 1) +#define MBUS_PMU_CFG_UNIT_KB (0x1 << 1) +#define MBUS_PMU_CFG_UNIT_MB (0x2 << 1) +#define MBUS_PMU_CFG_ENABLE (0x1 << 0) + +#define MBUS_PMU_BWCR(n) (0x00a0 + (0x04 * (n))) + +#define MBUS_TOTAL_BWCR MBUS_PMU_BWCR(5) +#define MBUS_TOTAL_BWCR_H616 MBUS_PMU_BWCR(13) + +#define MBUS_MDFSCR 0x0100 +#define MBUS_MDFSCR_BUFFER_TIMING (0x1 << 15) +#define MBUS_MDFSCR_PAD_HOLD (0x1 << 13) +#define MBUS_MDFSCR_BYPASS (0x1 << 4) +#define MBUS_MDFSCR_MODE (0x1 << 1) +#define MBUS_MDFSCR_MODE_DFS (0x0 << 1) +#define MBUS_MDFSCR_MODE_CFS (0x1 << 1) +#define MBUS_MDFSCR_START (0x1 << 0) + +#define MBUS_MDFSMRMR 0x0108 + +#define DRAM_PWRCTL 0x0004 +#define DRAM_PWRCTL_SELFREF_EN (0x1 << 0) + +#define DRAM_RFSHTMG 0x0090 +#define DRAM_RFSHTMG_TREFI(x) ((x) << 16) +#define DRAM_RFSHTMG_TRFC(x) ((x) << 0) + +#define DRAM_VTFCR 0x00b8 +#define DRAM_VTFCR_VTF_ENABLE (0x3 << 8) + +#define DRAM_ODTMAP 0x0120 + +#define DRAM_DX_MAX 4 + +#define DRAM_DXnGCR0(n) (0x0344 + 0x80 * (n)) +#define DRAM_DXnGCR0_DXODT (0x3 << 4) +#define DRAM_DXnGCR0_DXODT_DYNAMIC (0x0 << 4) +#define DRAM_DXnGCR0_DXODT_ENABLED (0x1 << 4) +#define DRAM_DXnGCR0_DXODT_DISABLED (0x2 << 4) +#define DRAM_DXnGCR0_DXEN (0x1 << 0) + +struct sun8i_a33_mbus_variant { + u32 min_dram_divider; + u32 max_dram_divider; + u32 odt_freq_mhz; +}; + +struct sun8i_a33_mbus { + const struct sun8i_a33_mbus_variant *variant; + void __iomem *reg_dram; + void __iomem *reg_mbus; + struct clk *clk_bus; + struct clk *clk_dram; + struct clk *clk_mbus; + struct devfreq *devfreq_dram; + struct devfreq_simple_ondemand_data gov_data; + struct devfreq_dev_profile profile; + u32 data_width; + u32 nominal_bw; + u32 odtmap; + u32 tREFI_ns; + u32 tRFC_ns; + unsigned long freq_table[]; +}; + +/* + * The unit for this value is (MBUS clock cycles / MBUS_TMR_PERIOD). When + * MBUS_TMR_PERIOD is programmed to match the MBUS clock frequency in MHz, as + * it is during DRAM init and during probe, the resulting unit is microseconds. + */ +static int pmu_period = 50000; +module_param(pmu_period, int, 0644); +MODULE_PARM_DESC(pmu_period, "Bandwidth measurement period (microseconds)"); + +static u32 sun8i_a33_mbus_get_peak_bw(struct sun8i_a33_mbus *priv) +{ + /* Returns the peak transfer (in KiB) during any single PMU period. */ + return readl_relaxed(priv->reg_mbus + MBUS_TOTAL_BWCR); +} + +static void sun8i_a33_mbus_restart_pmu_counters(struct sun8i_a33_mbus *priv) +{ + u32 pmu_cfg = MBUS_PMU_CFG_PERIOD(pmu_period) | MBUS_PMU_CFG_UNIT_KB; + + /* All PMU counters are cleared on a disable->enable transition. */ + writel_relaxed(pmu_cfg, + priv->reg_mbus + MBUS_PMU_CFG); + writel_relaxed(pmu_cfg | MBUS_PMU_CFG_ENABLE, + priv->reg_mbus + MBUS_PMU_CFG); + +} + +static void sun8i_a33_mbus_update_nominal_bw(struct sun8i_a33_mbus *priv, + u32 ddr_freq_mhz) +{ + /* + * Nominal bandwidth (KiB per PMU period): + * + * DDR transfers microseconds KiB + * ------------- * ------------ * -------- + * microsecond PMU period transfer + */ + priv->nominal_bw = ddr_freq_mhz * pmu_period * priv->data_width / 1024; +} + +static int sun8i_a33_mbus_set_dram_freq(struct sun8i_a33_mbus *priv, + unsigned long freq) +{ + u32 ddr_freq_mhz = freq / USEC_PER_SEC; /* DDR */ + u32 dram_freq_mhz = ddr_freq_mhz / 2; /* SDR */ + u32 mctl_freq_mhz = dram_freq_mhz / 2; /* HDR */ + u32 dxodt, mdfscr, pwrctl, vtfcr; + u32 i, tREFI_32ck, tRFC_ck; + int ret; + + /* The rate change is not effective until the MDFS process runs. */ + ret = clk_set_rate(priv->clk_dram, freq); + if (ret) + return ret; + + /* Disable automatic self-refesh and VTF before starting MDFS. */ + pwrctl = readl_relaxed(priv->reg_dram + DRAM_PWRCTL) & + ~DRAM_PWRCTL_SELFREF_EN; + writel_relaxed(pwrctl, priv->reg_dram + DRAM_PWRCTL); + vtfcr = readl_relaxed(priv->reg_dram + DRAM_VTFCR); + writel_relaxed(vtfcr & ~DRAM_VTFCR_VTF_ENABLE, + priv->reg_dram + DRAM_VTFCR); + + /* Set up MDFS and enable double buffering for timing registers. */ + mdfscr = MBUS_MDFSCR_MODE_DFS | + MBUS_MDFSCR_BYPASS | + MBUS_MDFSCR_PAD_HOLD | + MBUS_MDFSCR_BUFFER_TIMING; + writel(mdfscr, priv->reg_mbus + MBUS_MDFSCR); + + /* Update the buffered copy of RFSHTMG. */ + tREFI_32ck = priv->tREFI_ns * mctl_freq_mhz / 1000 / 32; + tRFC_ck = DIV_ROUND_UP(priv->tRFC_ns * mctl_freq_mhz, 1000); + writel(DRAM_RFSHTMG_TREFI(tREFI_32ck) | DRAM_RFSHTMG_TRFC(tRFC_ck), + priv->reg_dram + DRAM_RFSHTMG); + + /* Enable ODT if needed, or disable it to save power. */ + if (priv->odtmap && dram_freq_mhz > priv->variant->odt_freq_mhz) { + dxodt = DRAM_DXnGCR0_DXODT_DYNAMIC; + writel(priv->odtmap, priv->reg_dram + DRAM_ODTMAP); + } else { + dxodt = DRAM_DXnGCR0_DXODT_DISABLED; + writel(0, priv->reg_dram + DRAM_ODTMAP); + } + for (i = 0; i < DRAM_DX_MAX; ++i) { + void __iomem *reg = priv->reg_dram + DRAM_DXnGCR0(i); + + writel((readl(reg) & ~DRAM_DXnGCR0_DXODT) | dxodt, reg); + } + + dev_dbg(priv->devfreq_dram->dev.parent, + "Setting DRAM to %u MHz, tREFI=%u, tRFC=%u, ODT=%s\n", + dram_freq_mhz, tREFI_32ck, tRFC_ck, + dxodt == DRAM_DXnGCR0_DXODT_DYNAMIC ? "dynamic" : "disabled"); + + /* Trigger hardware MDFS. */ + writel(mdfscr | MBUS_MDFSCR_START, priv->reg_mbus + MBUS_MDFSCR); + ret = readl_poll_timeout_atomic(priv->reg_mbus + MBUS_MDFSCR, mdfscr, + !(mdfscr & MBUS_MDFSCR_START), 10, 1000); + if (ret) + return ret; + + /* Disable double buffering. */ + writel(0, priv->reg_mbus + MBUS_MDFSCR); + + /* Restore VTF configuration. */ + writel_relaxed(vtfcr, priv->reg_dram + DRAM_VTFCR); + + /* Enable automatic self-refresh at the lowest frequency only. */ + if (freq == priv->freq_table[0]) + pwrctl |= DRAM_PWRCTL_SELFREF_EN; + writel_relaxed(pwrctl, priv->reg_dram + DRAM_PWRCTL); + + sun8i_a33_mbus_restart_pmu_counters(priv); + sun8i_a33_mbus_update_nominal_bw(priv, ddr_freq_mhz); + + return 0; +} + +static int sun8i_a33_mbus_set_dram_target(struct device *dev, + unsigned long *freq, u32 flags) +{ + struct sun8i_a33_mbus *priv = dev_get_drvdata(dev); + struct devfreq *devfreq = priv->devfreq_dram; + struct dev_pm_opp *opp; + int ret; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + dev_pm_opp_put(opp); + + if (*freq == devfreq->previous_freq) + return 0; + + ret = sun8i_a33_mbus_set_dram_freq(priv, *freq); + if (ret) { + dev_warn(dev, "failed to set DRAM frequency: %d\n", ret); + *freq = devfreq->previous_freq; + } + + return ret; +} + +static int sun8i_a33_mbus_get_dram_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct sun8i_a33_mbus *priv = dev_get_drvdata(dev); + + stat->busy_time = sun8i_a33_mbus_get_peak_bw(priv); + stat->total_time = priv->nominal_bw; + stat->current_frequency = priv->devfreq_dram->previous_freq; + + sun8i_a33_mbus_restart_pmu_counters(priv); + + dev_dbg(dev, "Using %lu/%lu (%lu%%) at %lu MHz\n", + stat->busy_time, stat->total_time, + DIV_ROUND_CLOSEST(stat->busy_time * 100, stat->total_time), + stat->current_frequency / USEC_PER_SEC); + + return 0; +} + +static int sun8i_a33_mbus_hw_init(struct device *dev, + struct sun8i_a33_mbus *priv, + unsigned long ddr_freq) +{ + u32 i, mbus_cr, mbus_freq_mhz; + + /* Choose tREFI and tRFC to match the configured DRAM type. */ + mbus_cr = readl_relaxed(priv->reg_mbus + MBUS_CR); + switch (MBUS_CR_GET_DRAM_TYPE(mbus_cr)) { + case MBUS_CR_DRAM_TYPE_DDR2: + case MBUS_CR_DRAM_TYPE_DDR3: + case MBUS_CR_DRAM_TYPE_DDR4: + priv->tREFI_ns = 7800; + priv->tRFC_ns = 350; + break; + case MBUS_CR_DRAM_TYPE_LPDDR2: + case MBUS_CR_DRAM_TYPE_LPDDR3: + priv->tREFI_ns = 3900; + priv->tRFC_ns = 210; + break; + default: + return -EINVAL; + } + + /* Save ODTMAP so it can be restored when raising the frequency. */ + priv->odtmap = readl_relaxed(priv->reg_dram + DRAM_ODTMAP); + + /* Compute the DRAM data bus width by counting enabled DATx8 blocks. */ + for (i = 0; i < DRAM_DX_MAX; ++i) { + void __iomem *reg = priv->reg_dram + DRAM_DXnGCR0(i); + + if (!(readl_relaxed(reg) & DRAM_DXnGCR0_DXEN)) + break; + } + priv->data_width = i; + + dev_dbg(dev, "Detected %u-bit %sDDRx with%s ODT\n", + priv->data_width * 8, + MBUS_CR_GET_DRAM_TYPE(mbus_cr) > 4 ? "LP" : "", + priv->odtmap ? "" : "out"); + + /* Program MBUS_TMR such that the PMU period unit is microseconds. */ + mbus_freq_mhz = clk_get_rate(priv->clk_mbus) / USEC_PER_SEC; + writel_relaxed(MBUS_TMR_PERIOD(mbus_freq_mhz), + priv->reg_mbus + MBUS_TMR); + + /* "Master Ready Mask Register" bits must be set or MDFS will block. */ + writel_relaxed(0xffffffff, priv->reg_mbus + MBUS_MDFSMRMR); + + sun8i_a33_mbus_restart_pmu_counters(priv); + sun8i_a33_mbus_update_nominal_bw(priv, ddr_freq / USEC_PER_SEC); + + return 0; +} + +static int __maybe_unused sun8i_a33_mbus_suspend(struct device *dev) +{ + struct sun8i_a33_mbus *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk_bus); + + return 0; +} + +static int __maybe_unused sun8i_a33_mbus_resume(struct device *dev) +{ + struct sun8i_a33_mbus *priv = dev_get_drvdata(dev); + + return clk_prepare_enable(priv->clk_bus); +} + +static int sun8i_a33_mbus_probe(struct platform_device *pdev) +{ + const struct sun8i_a33_mbus_variant *variant; + struct device *dev = &pdev->dev; + struct sun8i_a33_mbus *priv; + unsigned long base_freq; + unsigned int max_state; + const char *err; + int i, ret; + + variant = device_get_match_data(dev); + if (!variant) + return -EINVAL; + + max_state = variant->max_dram_divider - variant->min_dram_divider + 1; + + priv = devm_kzalloc(dev, struct_size(priv, freq_table, max_state), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->variant = variant; + + priv->reg_dram = devm_platform_ioremap_resource_byname(pdev, "dram"); + if (IS_ERR(priv->reg_dram)) + return PTR_ERR(priv->reg_dram); + + priv->reg_mbus = devm_platform_ioremap_resource_byname(pdev, "mbus"); + if (IS_ERR(priv->reg_mbus)) + return PTR_ERR(priv->reg_mbus); + + priv->clk_bus = devm_clk_get(dev, "bus"); + if (IS_ERR(priv->clk_bus)) + return dev_err_probe(dev, PTR_ERR(priv->clk_bus), + "failed to get bus clock\n"); + + priv->clk_dram = devm_clk_get(dev, "dram"); + if (IS_ERR(priv->clk_dram)) + return dev_err_probe(dev, PTR_ERR(priv->clk_dram), + "failed to get dram clock\n"); + + priv->clk_mbus = devm_clk_get(dev, "mbus"); + if (IS_ERR(priv->clk_mbus)) + return dev_err_probe(dev, PTR_ERR(priv->clk_mbus), + "failed to get mbus clock\n"); + + ret = clk_prepare_enable(priv->clk_bus); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable bus clock\n"); + + /* Lock the DRAM clock rate to keep priv->nominal_bw in sync. */ + ret = clk_rate_exclusive_get(priv->clk_dram); + if (ret) { + err = "failed to lock dram clock rate\n"; + goto err_disable_bus; + } + + /* Lock the MBUS clock rate to keep MBUS_TMR_PERIOD in sync. */ + ret = clk_rate_exclusive_get(priv->clk_mbus); + if (ret) { + err = "failed to lock mbus clock rate\n"; + goto err_unlock_dram; + } + + priv->gov_data.upthreshold = 10; + priv->gov_data.downdifferential = 5; + + priv->profile.initial_freq = clk_get_rate(priv->clk_dram); + priv->profile.polling_ms = 1000; + priv->profile.target = sun8i_a33_mbus_set_dram_target; + priv->profile.get_dev_status = sun8i_a33_mbus_get_dram_status; + priv->profile.freq_table = priv->freq_table; + priv->profile.max_state = max_state; + + ret = devm_pm_opp_set_clkname(dev, "dram"); + if (ret) { + err = "failed to add OPP table\n"; + goto err_unlock_mbus; + } + + base_freq = clk_get_rate(clk_get_parent(priv->clk_dram)); + for (i = 0; i < max_state; ++i) { + unsigned int div = variant->max_dram_divider - i; + + priv->freq_table[i] = base_freq / div; + + ret = dev_pm_opp_add(dev, priv->freq_table[i], 0); + if (ret) { + err = "failed to add OPPs\n"; + goto err_remove_opps; + } + } + + ret = sun8i_a33_mbus_hw_init(dev, priv, priv->profile.initial_freq); + if (ret) { + err = "failed to init hardware\n"; + goto err_remove_opps; + } + + priv->devfreq_dram = devfreq_add_device(dev, &priv->profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + &priv->gov_data); + if (IS_ERR(priv->devfreq_dram)) { + ret = PTR_ERR(priv->devfreq_dram); + err = "failed to add devfreq device\n"; + goto err_remove_opps; + } + + /* + * This must be set manually after registering the devfreq device, + * because there is no way to select a dynamic OPP as the suspend OPP. + */ + priv->devfreq_dram->suspend_freq = priv->freq_table[0]; + + return 0; + +err_remove_opps: + dev_pm_opp_remove_all_dynamic(dev); +err_unlock_mbus: + clk_rate_exclusive_put(priv->clk_mbus); +err_unlock_dram: + clk_rate_exclusive_put(priv->clk_dram); +err_disable_bus: + clk_disable_unprepare(priv->clk_bus); + + return dev_err_probe(dev, ret, err); +} + +static int sun8i_a33_mbus_remove(struct platform_device *pdev) +{ + struct sun8i_a33_mbus *priv = platform_get_drvdata(pdev); + unsigned long initial_freq = priv->profile.initial_freq; + struct device *dev = &pdev->dev; + int ret; + + devfreq_remove_device(priv->devfreq_dram); + + ret = sun8i_a33_mbus_set_dram_freq(priv, initial_freq); + if (ret) + dev_warn(dev, "failed to restore DRAM frequency: %d\n", ret); + + dev_pm_opp_remove_all_dynamic(dev); + clk_rate_exclusive_put(priv->clk_mbus); + clk_rate_exclusive_put(priv->clk_dram); + clk_disable_unprepare(priv->clk_bus); + + return 0; +} + +static const struct sun8i_a33_mbus_variant sun50i_a64_mbus = { + .min_dram_divider = 1, + .max_dram_divider = 4, + .odt_freq_mhz = 400, +}; + +static const struct of_device_id sun8i_a33_mbus_of_match[] = { + { .compatible = "allwinner,sun50i-a64-mbus", .data = &sun50i_a64_mbus }, + { .compatible = "allwinner,sun50i-h5-mbus", .data = &sun50i_a64_mbus }, + { }, +}; +MODULE_DEVICE_TABLE(of, sun8i_a33_mbus_of_match); + +static SIMPLE_DEV_PM_OPS(sun8i_a33_mbus_pm_ops, + sun8i_a33_mbus_suspend, sun8i_a33_mbus_resume); + +static struct platform_driver sun8i_a33_mbus_driver = { + .probe = sun8i_a33_mbus_probe, + .remove = sun8i_a33_mbus_remove, + .driver = { + .name = "sun8i-a33-mbus", + .of_match_table = sun8i_a33_mbus_of_match, + .pm = pm_ptr(&sun8i_a33_mbus_pm_ops), + }, +}; +module_platform_driver(sun8i_a33_mbus_driver); + +MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>"); +MODULE_DESCRIPTION("Allwinner sun8i/sun50i MBUS DEVFREQ Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c new file mode 100644 index 000000000..503376b89 --- /dev/null +++ b/drivers/devfreq/tegra30-devfreq.c @@ -0,0 +1,983 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * A devfreq driver for NVIDIA Tegra SoCs + * + * Copyright (c) 2014 NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2014 Google, Inc + */ + +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/devfreq.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/reset.h> +#include <linux/workqueue.h> + +#include <soc/tegra/fuse.h> + +#include "governor.h" + +#define ACTMON_GLB_STATUS 0x0 +#define ACTMON_GLB_PERIOD_CTRL 0x4 + +#define ACTMON_DEV_CTRL 0x0 +#define ACTMON_DEV_CTRL_K_VAL_SHIFT 10 +#define ACTMON_DEV_CTRL_ENB_PERIODIC BIT(18) +#define ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN BIT(20) +#define ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN BIT(21) +#define ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_NUM_SHIFT 23 +#define ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_NUM_SHIFT 26 +#define ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN BIT(29) +#define ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN BIT(30) +#define ACTMON_DEV_CTRL_ENB BIT(31) + +#define ACTMON_DEV_CTRL_STOP 0x00000000 + +#define ACTMON_DEV_UPPER_WMARK 0x4 +#define ACTMON_DEV_LOWER_WMARK 0x8 +#define ACTMON_DEV_INIT_AVG 0xc +#define ACTMON_DEV_AVG_UPPER_WMARK 0x10 +#define ACTMON_DEV_AVG_LOWER_WMARK 0x14 +#define ACTMON_DEV_COUNT_WEIGHT 0x18 +#define ACTMON_DEV_AVG_COUNT 0x20 +#define ACTMON_DEV_INTR_STATUS 0x24 + +#define ACTMON_INTR_STATUS_CLEAR 0xffffffff + +#define ACTMON_DEV_INTR_CONSECUTIVE_UPPER BIT(31) +#define ACTMON_DEV_INTR_CONSECUTIVE_LOWER BIT(30) + +#define ACTMON_ABOVE_WMARK_WINDOW 1 +#define ACTMON_BELOW_WMARK_WINDOW 3 +#define ACTMON_BOOST_FREQ_STEP 16000 + +/* + * ACTMON_AVERAGE_WINDOW_LOG2: default value for @DEV_CTRL_K_VAL, which + * translates to 2 ^ (K_VAL + 1). ex: 2 ^ (6 + 1) = 128 + */ +#define ACTMON_AVERAGE_WINDOW_LOG2 6 +#define ACTMON_SAMPLING_PERIOD 12 /* ms */ +#define ACTMON_DEFAULT_AVG_BAND 6 /* 1/10 of % */ + +#define KHZ 1000 + +#define KHZ_MAX (ULONG_MAX / KHZ) + +/* Assume that the bus is saturated if the utilization is 25% */ +#define BUS_SATURATION_RATIO 25 + +/** + * struct tegra_devfreq_device_config - configuration specific to an ACTMON + * device + * + * Coefficients and thresholds are percentages unless otherwise noted + */ +struct tegra_devfreq_device_config { + u32 offset; + u32 irq_mask; + + /* Factors applied to boost_freq every consecutive watermark breach */ + unsigned int boost_up_coeff; + unsigned int boost_down_coeff; + + /* Define the watermark bounds when applied to the current avg */ + unsigned int boost_up_threshold; + unsigned int boost_down_threshold; + + /* + * Threshold of activity (cycles translated to kHz) below which the + * CPU frequency isn't to be taken into account. This is to avoid + * increasing the EMC frequency when the CPU is very busy but not + * accessing the bus often. + */ + u32 avg_dependency_threshold; +}; + +enum tegra_actmon_device { + MCALL = 0, + MCCPU, +}; + +static const struct tegra_devfreq_device_config tegra124_device_configs[] = { + { + /* MCALL: All memory accesses (including from the CPUs) */ + .offset = 0x1c0, + .irq_mask = 1 << 26, + .boost_up_coeff = 200, + .boost_down_coeff = 50, + .boost_up_threshold = 60, + .boost_down_threshold = 40, + }, + { + /* MCCPU: memory accesses from the CPUs */ + .offset = 0x200, + .irq_mask = 1 << 25, + .boost_up_coeff = 800, + .boost_down_coeff = 40, + .boost_up_threshold = 27, + .boost_down_threshold = 10, + .avg_dependency_threshold = 16000, /* 16MHz in kHz units */ + }, +}; + +static const struct tegra_devfreq_device_config tegra30_device_configs[] = { + { + /* MCALL: All memory accesses (including from the CPUs) */ + .offset = 0x1c0, + .irq_mask = 1 << 26, + .boost_up_coeff = 200, + .boost_down_coeff = 50, + .boost_up_threshold = 20, + .boost_down_threshold = 10, + }, + { + /* MCCPU: memory accesses from the CPUs */ + .offset = 0x200, + .irq_mask = 1 << 25, + .boost_up_coeff = 800, + .boost_down_coeff = 40, + .boost_up_threshold = 27, + .boost_down_threshold = 10, + .avg_dependency_threshold = 16000, /* 16MHz in kHz units */ + }, +}; + +/** + * struct tegra_devfreq_device - state specific to an ACTMON device + * + * Frequencies are in kHz. + */ +struct tegra_devfreq_device { + const struct tegra_devfreq_device_config *config; + void __iomem *regs; + + /* Average event count sampled in the last interrupt */ + u32 avg_count; + + /* + * Extra frequency to increase the target by due to consecutive + * watermark breaches. + */ + unsigned long boost_freq; + + /* Optimal frequency calculated from the stats for this device */ + unsigned long target_freq; +}; + +struct tegra_devfreq_soc_data { + const struct tegra_devfreq_device_config *configs; + /* Weight value for count measurements */ + unsigned int count_weight; +}; + +struct tegra_devfreq { + struct devfreq *devfreq; + + struct reset_control *reset; + struct clk *clock; + void __iomem *regs; + + struct clk *emc_clock; + unsigned long max_freq; + unsigned long cur_freq; + struct notifier_block clk_rate_change_nb; + + struct delayed_work cpufreq_update_work; + struct notifier_block cpu_rate_change_nb; + + struct tegra_devfreq_device devices[2]; + + unsigned int irq; + + bool started; + + const struct tegra_devfreq_soc_data *soc; +}; + +struct tegra_actmon_emc_ratio { + unsigned long cpu_freq; + unsigned long emc_freq; +}; + +static const struct tegra_actmon_emc_ratio actmon_emc_ratios[] = { + { 1400000, KHZ_MAX }, + { 1200000, 750000 }, + { 1100000, 600000 }, + { 1000000, 500000 }, + { 800000, 375000 }, + { 500000, 200000 }, + { 250000, 100000 }, +}; + +static u32 actmon_readl(struct tegra_devfreq *tegra, u32 offset) +{ + return readl_relaxed(tegra->regs + offset); +} + +static void actmon_writel(struct tegra_devfreq *tegra, u32 val, u32 offset) +{ + writel_relaxed(val, tegra->regs + offset); +} + +static u32 device_readl(struct tegra_devfreq_device *dev, u32 offset) +{ + return readl_relaxed(dev->regs + offset); +} + +static void device_writel(struct tegra_devfreq_device *dev, u32 val, + u32 offset) +{ + writel_relaxed(val, dev->regs + offset); +} + +static unsigned long do_percent(unsigned long long val, unsigned int pct) +{ + val = val * pct; + do_div(val, 100); + + /* + * High freq + high boosting percent + large polling interval are + * resulting in integer overflow when watermarks are calculated. + */ + return min_t(u64, val, U32_MAX); +} + +static void tegra_devfreq_update_avg_wmark(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 avg_band_freq = tegra->max_freq * ACTMON_DEFAULT_AVG_BAND / KHZ; + u32 band = avg_band_freq * tegra->devfreq->profile->polling_ms; + u32 avg; + + avg = min(dev->avg_count, U32_MAX - band); + device_writel(dev, avg + band, ACTMON_DEV_AVG_UPPER_WMARK); + + avg = max(dev->avg_count, band); + device_writel(dev, avg - band, ACTMON_DEV_AVG_LOWER_WMARK); +} + +static void tegra_devfreq_update_wmark(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 val = tegra->cur_freq * tegra->devfreq->profile->polling_ms; + + device_writel(dev, do_percent(val, dev->config->boost_up_threshold), + ACTMON_DEV_UPPER_WMARK); + + device_writel(dev, do_percent(val, dev->config->boost_down_threshold), + ACTMON_DEV_LOWER_WMARK); +} + +static void actmon_isr_device(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 intr_status, dev_ctrl; + + dev->avg_count = device_readl(dev, ACTMON_DEV_AVG_COUNT); + tegra_devfreq_update_avg_wmark(tegra, dev); + + intr_status = device_readl(dev, ACTMON_DEV_INTR_STATUS); + dev_ctrl = device_readl(dev, ACTMON_DEV_CTRL); + + if (intr_status & ACTMON_DEV_INTR_CONSECUTIVE_UPPER) { + /* + * new_boost = min(old_boost * up_coef + step, max_freq) + */ + dev->boost_freq = do_percent(dev->boost_freq, + dev->config->boost_up_coeff); + dev->boost_freq += ACTMON_BOOST_FREQ_STEP; + + dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + + if (dev->boost_freq >= tegra->max_freq) { + dev_ctrl &= ~ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + dev->boost_freq = tegra->max_freq; + } + } else if (intr_status & ACTMON_DEV_INTR_CONSECUTIVE_LOWER) { + /* + * new_boost = old_boost * down_coef + * or 0 if (old_boost * down_coef < step / 2) + */ + dev->boost_freq = do_percent(dev->boost_freq, + dev->config->boost_down_coeff); + + dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + + if (dev->boost_freq < (ACTMON_BOOST_FREQ_STEP >> 1)) { + dev_ctrl &= ~ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + dev->boost_freq = 0; + } + } + + device_writel(dev, dev_ctrl, ACTMON_DEV_CTRL); + + device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS); +} + +static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra, + unsigned long cpu_freq) +{ + unsigned int i; + const struct tegra_actmon_emc_ratio *ratio = actmon_emc_ratios; + + for (i = 0; i < ARRAY_SIZE(actmon_emc_ratios); i++, ratio++) { + if (cpu_freq >= ratio->cpu_freq) { + if (ratio->emc_freq >= tegra->max_freq) + return tegra->max_freq; + else + return ratio->emc_freq; + } + } + + return 0; +} + +static unsigned long actmon_device_target_freq(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + unsigned int avg_sustain_coef; + unsigned long target_freq; + + target_freq = dev->avg_count / tegra->devfreq->profile->polling_ms; + avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; + target_freq = do_percent(target_freq, avg_sustain_coef); + + return target_freq; +} + +static void actmon_update_target(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + unsigned long cpu_freq = 0; + unsigned long static_cpu_emc_freq = 0; + + dev->target_freq = actmon_device_target_freq(tegra, dev); + + if (dev->config->avg_dependency_threshold && + dev->config->avg_dependency_threshold <= dev->target_freq) { + cpu_freq = cpufreq_quick_get(0); + static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); + + dev->target_freq += dev->boost_freq; + dev->target_freq = max(dev->target_freq, static_cpu_emc_freq); + } else { + dev->target_freq += dev->boost_freq; + } +} + +static irqreturn_t actmon_thread_isr(int irq, void *data) +{ + struct tegra_devfreq *tegra = data; + bool handled = false; + unsigned int i; + u32 val; + + mutex_lock(&tegra->devfreq->lock); + + val = actmon_readl(tegra, ACTMON_GLB_STATUS); + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + if (val & tegra->devices[i].config->irq_mask) { + actmon_isr_device(tegra, tegra->devices + i); + handled = true; + } + } + + if (handled) + update_devfreq(tegra->devfreq); + + mutex_unlock(&tegra->devfreq->lock); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int tegra_actmon_clk_notify_cb(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + struct clk_notifier_data *data = ptr; + struct tegra_devfreq *tegra; + struct tegra_devfreq_device *dev; + unsigned int i; + + if (action != POST_RATE_CHANGE) + return NOTIFY_OK; + + tegra = container_of(nb, struct tegra_devfreq, clk_rate_change_nb); + + tegra->cur_freq = data->new_rate / KHZ; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = &tegra->devices[i]; + + tegra_devfreq_update_wmark(tegra, dev); + } + + return NOTIFY_OK; +} + +static void tegra_actmon_delayed_update(struct work_struct *work) +{ + struct tegra_devfreq *tegra = container_of(work, struct tegra_devfreq, + cpufreq_update_work.work); + + mutex_lock(&tegra->devfreq->lock); + update_devfreq(tegra->devfreq); + mutex_unlock(&tegra->devfreq->lock); +} + +static unsigned long +tegra_actmon_cpufreq_contribution(struct tegra_devfreq *tegra, + unsigned int cpu_freq) +{ + struct tegra_devfreq_device *actmon_dev = &tegra->devices[MCCPU]; + unsigned long static_cpu_emc_freq, dev_freq; + + dev_freq = actmon_device_target_freq(tegra, actmon_dev); + + /* check whether CPU's freq is taken into account at all */ + if (dev_freq < actmon_dev->config->avg_dependency_threshold) + return 0; + + static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); + + if (dev_freq + actmon_dev->boost_freq >= static_cpu_emc_freq) + return 0; + + return static_cpu_emc_freq; +} + +static int tegra_actmon_cpu_notify_cb(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + struct cpufreq_freqs *freqs = ptr; + struct tegra_devfreq *tegra; + unsigned long old, new, delay; + + if (action != CPUFREQ_POSTCHANGE) + return NOTIFY_OK; + + tegra = container_of(nb, struct tegra_devfreq, cpu_rate_change_nb); + + /* + * Quickly check whether CPU frequency should be taken into account + * at all, without blocking CPUFreq's core. + */ + if (mutex_trylock(&tegra->devfreq->lock)) { + old = tegra_actmon_cpufreq_contribution(tegra, freqs->old); + new = tegra_actmon_cpufreq_contribution(tegra, freqs->new); + mutex_unlock(&tegra->devfreq->lock); + + /* + * If CPU's frequency shouldn't be taken into account at + * the moment, then there is no need to update the devfreq's + * state because ISR will re-check CPU's frequency on the + * next interrupt. + */ + if (old == new) + return NOTIFY_OK; + } + + /* + * CPUFreq driver should support CPUFREQ_ASYNC_NOTIFICATION in order + * to allow asynchronous notifications. This means we can't block + * here for too long, otherwise CPUFreq's core will complain with a + * warning splat. + */ + delay = msecs_to_jiffies(ACTMON_SAMPLING_PERIOD); + schedule_delayed_work(&tegra->cpufreq_update_work, delay); + + return NOTIFY_OK; +} + +static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 val = 0; + + /* reset boosting on governor's restart */ + dev->boost_freq = 0; + + dev->target_freq = tegra->cur_freq; + + dev->avg_count = tegra->cur_freq * tegra->devfreq->profile->polling_ms; + device_writel(dev, dev->avg_count, ACTMON_DEV_INIT_AVG); + + tegra_devfreq_update_avg_wmark(tegra, dev); + tegra_devfreq_update_wmark(tegra, dev); + + device_writel(dev, tegra->soc->count_weight, ACTMON_DEV_COUNT_WEIGHT); + device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS); + + val |= ACTMON_DEV_CTRL_ENB_PERIODIC; + val |= (ACTMON_AVERAGE_WINDOW_LOG2 - 1) + << ACTMON_DEV_CTRL_K_VAL_SHIFT; + val |= (ACTMON_BELOW_WMARK_WINDOW - 1) + << ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_NUM_SHIFT; + val |= (ACTMON_ABOVE_WMARK_WINDOW - 1) + << ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_NUM_SHIFT; + val |= ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN; + val |= ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN; + val |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + val |= ACTMON_DEV_CTRL_ENB; + + device_writel(dev, val, ACTMON_DEV_CTRL); +} + +static void tegra_actmon_stop_devices(struct tegra_devfreq *tegra) +{ + struct tegra_devfreq_device *dev = tegra->devices; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++, dev++) { + device_writel(dev, ACTMON_DEV_CTRL_STOP, ACTMON_DEV_CTRL); + device_writel(dev, ACTMON_INTR_STATUS_CLEAR, + ACTMON_DEV_INTR_STATUS); + } +} + +static int tegra_actmon_resume(struct tegra_devfreq *tegra) +{ + unsigned int i; + int err; + + if (!tegra->devfreq->profile->polling_ms || !tegra->started) + return 0; + + actmon_writel(tegra, tegra->devfreq->profile->polling_ms - 1, + ACTMON_GLB_PERIOD_CTRL); + + /* + * CLK notifications are needed in order to reconfigure the upper + * consecutive watermark in accordance to the actual clock rate + * to avoid unnecessary upper interrupts. + */ + err = clk_notifier_register(tegra->emc_clock, + &tegra->clk_rate_change_nb); + if (err) { + dev_err(tegra->devfreq->dev.parent, + "Failed to register rate change notifier\n"); + return err; + } + + tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) + tegra_actmon_configure_device(tegra, &tegra->devices[i]); + + /* + * We are estimating CPU's memory bandwidth requirement based on + * amount of memory accesses and system's load, judging by CPU's + * frequency. We also don't want to receive events about CPU's + * frequency transaction when governor is stopped, hence notifier + * is registered dynamically. + */ + err = cpufreq_register_notifier(&tegra->cpu_rate_change_nb, + CPUFREQ_TRANSITION_NOTIFIER); + if (err) { + dev_err(tegra->devfreq->dev.parent, + "Failed to register rate change notifier: %d\n", err); + goto err_stop; + } + + enable_irq(tegra->irq); + + return 0; + +err_stop: + tegra_actmon_stop_devices(tegra); + + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); + + return err; +} + +static int tegra_actmon_start(struct tegra_devfreq *tegra) +{ + int ret = 0; + + if (!tegra->started) { + tegra->started = true; + + ret = tegra_actmon_resume(tegra); + if (ret) + tegra->started = false; + } + + return ret; +} + +static void tegra_actmon_pause(struct tegra_devfreq *tegra) +{ + if (!tegra->devfreq->profile->polling_ms || !tegra->started) + return; + + disable_irq(tegra->irq); + + cpufreq_unregister_notifier(&tegra->cpu_rate_change_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + cancel_delayed_work_sync(&tegra->cpufreq_update_work); + + tegra_actmon_stop_devices(tegra); + + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); +} + +static void tegra_actmon_stop(struct tegra_devfreq *tegra) +{ + tegra_actmon_pause(tegra); + tegra->started = false; +} + +static int tegra_devfreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct dev_pm_opp *opp; + int ret; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) { + dev_err(dev, "Failed to find opp for %lu Hz\n", *freq); + return PTR_ERR(opp); + } + + ret = dev_pm_opp_set_opp(dev, opp); + dev_pm_opp_put(opp); + + return ret; +} + +static int tegra_devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct tegra_devfreq *tegra = dev_get_drvdata(dev); + struct tegra_devfreq_device *actmon_dev; + unsigned long cur_freq; + + cur_freq = READ_ONCE(tegra->cur_freq); + + /* To be used by the tegra governor */ + stat->private_data = tegra; + + /* The below are to be used by the other governors */ + stat->current_frequency = cur_freq * KHZ; + + actmon_dev = &tegra->devices[MCALL]; + + /* Number of cycles spent on memory access */ + stat->busy_time = device_readl(actmon_dev, ACTMON_DEV_AVG_COUNT); + + /* The bus can be considered to be saturated way before 100% */ + stat->busy_time *= 100 / BUS_SATURATION_RATIO; + + /* Number of cycles in a sampling period */ + stat->total_time = tegra->devfreq->profile->polling_ms * cur_freq; + + stat->busy_time = min(stat->busy_time, stat->total_time); + + return 0; +} + +static struct devfreq_dev_profile tegra_devfreq_profile = { + .polling_ms = ACTMON_SAMPLING_PERIOD, + .target = tegra_devfreq_target, + .get_dev_status = tegra_devfreq_get_dev_status, + .is_cooling_device = true, +}; + +static int tegra_governor_get_target(struct devfreq *devfreq, + unsigned long *freq) +{ + struct devfreq_dev_status *stat; + struct tegra_devfreq *tegra; + struct tegra_devfreq_device *dev; + unsigned long target_freq = 0; + unsigned int i; + int err; + + err = devfreq_update_stats(devfreq); + if (err) + return err; + + stat = &devfreq->last_status; + + tegra = stat->private_data; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = &tegra->devices[i]; + + actmon_update_target(tegra, dev); + + target_freq = max(target_freq, dev->target_freq); + } + + /* + * tegra-devfreq driver operates with KHz units, while OPP table + * entries use Hz units. Hence we need to convert the units for the + * devfreq core. + */ + *freq = target_freq * KHZ; + + return 0; +} + +static int tegra_governor_event_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + struct tegra_devfreq *tegra = dev_get_drvdata(devfreq->dev.parent); + unsigned int *new_delay = data; + int ret = 0; + + /* + * Couple devfreq-device with the governor early because it is + * needed at the moment of governor's start (used by ISR). + */ + tegra->devfreq = devfreq; + + switch (event) { + case DEVFREQ_GOV_START: + devfreq_monitor_start(devfreq); + ret = tegra_actmon_start(tegra); + break; + + case DEVFREQ_GOV_STOP: + tegra_actmon_stop(tegra); + devfreq_monitor_stop(devfreq); + break; + + case DEVFREQ_GOV_UPDATE_INTERVAL: + /* + * ACTMON hardware supports up to 256 milliseconds for the + * sampling period. + */ + if (*new_delay > 256) { + ret = -EINVAL; + break; + } + + tegra_actmon_pause(tegra); + devfreq_update_interval(devfreq, new_delay); + ret = tegra_actmon_resume(tegra); + break; + + case DEVFREQ_GOV_SUSPEND: + tegra_actmon_stop(tegra); + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + ret = tegra_actmon_start(tegra); + break; + } + + return ret; +} + +static struct devfreq_governor tegra_devfreq_governor = { + .name = "tegra_actmon", + .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL, + .flags = DEVFREQ_GOV_FLAG_IMMUTABLE + | DEVFREQ_GOV_FLAG_IRQ_DRIVEN, + .get_target_freq = tegra_governor_get_target, + .event_handler = tegra_governor_event_handler, +}; + +static void devm_tegra_devfreq_deinit_hw(void *data) +{ + struct tegra_devfreq *tegra = data; + + reset_control_reset(tegra->reset); + clk_disable_unprepare(tegra->clock); +} + +static int devm_tegra_devfreq_init_hw(struct device *dev, + struct tegra_devfreq *tegra) +{ + int err; + + err = clk_prepare_enable(tegra->clock); + if (err) { + dev_err(dev, "Failed to prepare and enable ACTMON clock\n"); + return err; + } + + err = devm_add_action_or_reset(dev, devm_tegra_devfreq_deinit_hw, + tegra); + if (err) + return err; + + err = reset_control_reset(tegra->reset); + if (err) { + dev_err(dev, "Failed to reset hardware: %d\n", err); + return err; + } + + return err; +} + +static int tegra_devfreq_config_clks_nop(struct device *dev, + struct opp_table *opp_table, + struct dev_pm_opp *opp, void *data, + bool scaling_down) +{ + /* We want to skip clk configuration via dev_pm_opp_set_opp() */ + return 0; +} + +static int tegra_devfreq_probe(struct platform_device *pdev) +{ + u32 hw_version = BIT(tegra_sku_info.soc_speedo_id); + struct tegra_devfreq_device *dev; + struct tegra_devfreq *tegra; + struct devfreq *devfreq; + unsigned int i; + long rate; + int err; + const char *clk_names[] = { "actmon", NULL }; + struct dev_pm_opp_config config = { + .supported_hw = &hw_version, + .supported_hw_count = 1, + .clk_names = clk_names, + .config_clks = tegra_devfreq_config_clks_nop, + }; + + tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + tegra->soc = of_device_get_match_data(&pdev->dev); + + tegra->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tegra->regs)) + return PTR_ERR(tegra->regs); + + tegra->reset = devm_reset_control_get(&pdev->dev, "actmon"); + if (IS_ERR(tegra->reset)) { + dev_err(&pdev->dev, "Failed to get reset\n"); + return PTR_ERR(tegra->reset); + } + + tegra->clock = devm_clk_get(&pdev->dev, "actmon"); + if (IS_ERR(tegra->clock)) { + dev_err(&pdev->dev, "Failed to get actmon clock\n"); + return PTR_ERR(tegra->clock); + } + + tegra->emc_clock = devm_clk_get(&pdev->dev, "emc"); + if (IS_ERR(tegra->emc_clock)) + return dev_err_probe(&pdev->dev, PTR_ERR(tegra->emc_clock), + "Failed to get emc clock\n"); + + err = platform_get_irq(pdev, 0); + if (err < 0) + return err; + + tegra->irq = err; + + irq_set_status_flags(tegra->irq, IRQ_NOAUTOEN); + + err = devm_request_threaded_irq(&pdev->dev, tegra->irq, NULL, + actmon_thread_isr, IRQF_ONESHOT, + "tegra-devfreq", tegra); + if (err) { + dev_err(&pdev->dev, "Interrupt request failed: %d\n", err); + return err; + } + + err = devm_pm_opp_set_config(&pdev->dev, &config); + if (err) { + dev_err(&pdev->dev, "Failed to set OPP config: %d\n", err); + return err; + } + + err = devm_pm_opp_of_add_table_indexed(&pdev->dev, 0); + if (err) { + dev_err(&pdev->dev, "Failed to add OPP table: %d\n", err); + return err; + } + + err = devm_tegra_devfreq_init_hw(&pdev->dev, tegra); + if (err) + return err; + + rate = clk_round_rate(tegra->emc_clock, ULONG_MAX); + if (rate <= 0) { + dev_err(&pdev->dev, "Failed to round clock rate: %ld\n", rate); + return rate ?: -EINVAL; + } + + tegra->max_freq = rate / KHZ; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = tegra->devices + i; + dev->config = tegra->soc->configs + i; + dev->regs = tegra->regs + dev->config->offset; + } + + platform_set_drvdata(pdev, tegra); + + tegra->clk_rate_change_nb.notifier_call = tegra_actmon_clk_notify_cb; + tegra->cpu_rate_change_nb.notifier_call = tegra_actmon_cpu_notify_cb; + + INIT_DELAYED_WORK(&tegra->cpufreq_update_work, + tegra_actmon_delayed_update); + + err = devm_devfreq_add_governor(&pdev->dev, &tegra_devfreq_governor); + if (err) { + dev_err(&pdev->dev, "Failed to add governor: %d\n", err); + return err; + } + + tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock); + + devfreq = devm_devfreq_add_device(&pdev->dev, &tegra_devfreq_profile, + "tegra_actmon", NULL); + if (IS_ERR(devfreq)) { + dev_err(&pdev->dev, "Failed to add device: %pe\n", devfreq); + return PTR_ERR(devfreq); + } + + return 0; +} + +static const struct tegra_devfreq_soc_data tegra124_soc = { + .configs = tegra124_device_configs, + + /* + * Activity counter is incremented every 256 memory transactions, + * and each transaction takes 4 EMC clocks. + */ + .count_weight = 4 * 256, +}; + +static const struct tegra_devfreq_soc_data tegra30_soc = { + .configs = tegra30_device_configs, + .count_weight = 2 * 256, +}; + +static const struct of_device_id tegra_devfreq_of_match[] = { + { .compatible = "nvidia,tegra30-actmon", .data = &tegra30_soc, }, + { .compatible = "nvidia,tegra124-actmon", .data = &tegra124_soc, }, + { }, +}; + +MODULE_DEVICE_TABLE(of, tegra_devfreq_of_match); + +static struct platform_driver tegra_devfreq_driver = { + .probe = tegra_devfreq_probe, + .driver = { + .name = "tegra-devfreq", + .of_match_table = tegra_devfreq_of_match, + }, +}; +module_platform_driver(tegra_devfreq_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Tegra devfreq driver"); +MODULE_AUTHOR("Tomeu Vizoso <tomeu.vizoso@collabora.com>"); |