diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/devfreq |
Initial import
Diffstat (limited to 'drivers/devfreq')
-rw-r--r-- | drivers/devfreq/Kconfig | 103 | ||||
-rw-r--r-- | drivers/devfreq/Makefile | 14 | ||||
-rw-r--r-- | drivers/devfreq/devfreq-event.c | 494 | ||||
-rw-r--r-- | drivers/devfreq/devfreq.c | 1224 | ||||
-rw-r--r-- | drivers/devfreq/event/Kconfig | 25 | ||||
-rw-r--r-- | drivers/devfreq/event/Makefile | 2 | ||||
-rw-r--r-- | drivers/devfreq/event/exynos-ppmu.c | 374 | ||||
-rw-r--r-- | drivers/devfreq/event/exynos-ppmu.h | 93 | ||||
-rw-r--r-- | drivers/devfreq/exynos/Makefile | 3 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos4_bus.c | 1055 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos4_bus.h | 110 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos5_bus.c | 431 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos_ppmu.c | 119 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos_ppmu.h | 86 | ||||
-rw-r--r-- | drivers/devfreq/governor.h | 41 | ||||
-rw-r--r-- | drivers/devfreq/governor_performance.c | 67 | ||||
-rw-r--r-- | drivers/devfreq/governor_powersave.c | 64 | ||||
-rw-r--r-- | drivers/devfreq/governor_simpleondemand.c | 147 | ||||
-rw-r--r-- | drivers/devfreq/governor_userspace.c | 162 | ||||
-rw-r--r-- | drivers/devfreq/tegra-devfreq.c | 785 |
20 files changed, 5399 insertions, 0 deletions
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig new file mode 100644 index 000000000..64281bb2f --- /dev/null +++ b/drivers/devfreq/Kconfig @@ -0,0 +1,103 @@ +menuconfig PM_DEVFREQ + bool "Generic Dynamic Voltage and Frequency Scaling (DVFS) support" + select SRCU + 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/.../power/devfreq_set_freq. + Otherwise, the governor does not change the frequnecy + given at the initialization. + +comment "DEVFREQ Drivers" + +config ARM_EXYNOS4_BUS_DEVFREQ + bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" + depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_OPP + help + This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int) + and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int). + It reads PPMU counters of memory controllers and adjusts + the operating frequencies and voltages with OPP support. + This does not yet operate with optimal voltages. + +config ARM_EXYNOS5_BUS_DEVFREQ + tristate "ARM Exynos5250 Bus DEVFREQ Driver" + depends on SOC_EXYNOS5250 + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_OPP + help + This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int). + It reads PPMU counters of memory controllers and adjusts the + operating frequencies and voltages with OPP support. + +config ARM_TEGRA_DEVFREQ + tristate "Tegra DEVFREQ Driver" + depends on ARCH_TEGRA_124_SOC + select DEVFREQ_GOV_SIMPLE_ONDEMAND + select PM_OPP + 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. + +source "drivers/devfreq/event/Kconfig" + +endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile new file mode 100644 index 000000000..5134f9ee9 --- /dev/null +++ b/drivers/devfreq/Makefile @@ -0,0 +1,14 @@ +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 + +# DEVFREQ Drivers +obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/ +obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/ +obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-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..f304a0289 --- /dev/null +++ b/drivers/devfreq/devfreq-event.c @@ -0,0 +1,494 @@ +/* + * 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 program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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/module.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 + * @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, + int index) +{ + struct device_node *node; + struct devfreq_event_dev *edev; + + if (!dev->of_node) { + dev_err(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, "devfreq-events", index); + if (!node) { + dev_err(dev, "failed to get phandle in %s node\n", + dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + + mutex_lock(&devfreq_event_list_lock); + list_for_each_entry(edev, &devfreq_event_list, node) { + if (!strcmp(edev->desc->name, node->name)) + goto out; + } + edev = NULL; +out: + mutex_unlock(&devfreq_event_list_lock); + + if (!edev) { + dev_err(dev, "unable to get devfreq-event device : %s\n", + node->name); + 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 + * + * Note that this function return the count of devfreq-event devices. + */ +int devfreq_event_get_edev_count(struct device *dev) +{ + int count; + + if (!dev->of_node) { + dev_err(dev, "device does not have a device node entry\n"); + return -EINVAL; + } + + count = of_property_count_elems_of_size(dev->of_node, "devfreq-events", + sizeof(u32)); + if (count < 0 ) { + dev_err(dev, + "failed to get the count of devfreq-event in %s node\n", + dev->of_node->full_name); + 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 decriptor 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(0); + 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) - 1); + 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. + * @dev : the devfreq-event device + * + * Note that this function remove 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 decriptor 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); + +static void __exit devfreq_event_exit(void) +{ + class_destroy(devfreq_event_class); +} +module_exit(devfreq_event_exit); + +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_DESCRIPTION("DEVFREQ-Event class support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c new file mode 100644 index 000000000..ca1b362d7 --- /dev/null +++ b/drivers/devfreq/devfreq.c @@ -0,0 +1,1224 @@ +/* + * devfreq: Generic Dynamic Voltage and Frequency Scaling (DVFS) Framework + * for Non-CPU Devices. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.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 "governor.h" + +static struct class *devfreq_class; + +/* + * 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); + +/** + * 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; + + if (unlikely(IS_ERR_OR_NULL(dev))) { + pr_err("DEVFREQ: %s: Invalid parameters\n", __func__); + return ERR_PTR(-EINVAL); + } + WARN(!mutex_is_locked(&devfreq_list_lock), + "devfreq_list_lock must be locked."); + + list_for_each_entry(tmp_devfreq, &devfreq_list, node) { + if (tmp_devfreq->dev.parent == dev) + return tmp_devfreq; + } + + return ERR_PTR(-ENODEV); +} + +/** + * 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->profile->max_state; lev++) + if (freq == devfreq->profile->freq_table[lev]) + return lev; + + return -EINVAL; +} + +/** + * devfreq_update_status() - Update statistics of devfreq behavior + * @devfreq: the devfreq instance + * @freq: the update target frequency + */ +static int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) +{ + int lev, prev_lev, ret = 0; + unsigned long cur_time; + + cur_time = jiffies; + + prev_lev = devfreq_get_freq_level(devfreq, devfreq->previous_freq); + if (prev_lev < 0) { + ret = prev_lev; + goto out; + } + + devfreq->time_in_state[prev_lev] += + cur_time - devfreq->last_stat_updated; + + lev = devfreq_get_freq_level(devfreq, freq); + if (lev < 0) { + ret = lev; + goto out; + } + + if (lev != prev_lev) { + devfreq->trans_table[(prev_lev * + devfreq->profile->max_state) + lev]++; + devfreq->total_trans++; + } + +out: + devfreq->last_stat_updated = cur_time; + return ret; +} + +/** + * 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; + + if (unlikely(IS_ERR_OR_NULL(name))) { + pr_err("DEVFREQ: %s: Invalid parameters\n", __func__); + return ERR_PTR(-EINVAL); + } + WARN(!mutex_is_locked(&devfreq_list_lock), + "devfreq_list_lock must be locked."); + + 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); +} + +/* 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) +{ + unsigned long freq; + int err = 0; + u32 flags = 0; + + if (!mutex_is_locked(&devfreq->lock)) { + WARN(true, "devfreq->lock must be locked by the caller.\n"); + return -EINVAL; + } + + if (!devfreq->governor) + return -EINVAL; + + /* Reevaluate the proper frequency */ + err = devfreq->governor->get_target_freq(devfreq, &freq); + if (err) + return err; + + /* + * Adjust the freuqency with user freq and QoS. + * + * List from the highest proiority + * max_freq (probably called by thermal when it's too hot) + * min_freq + */ + + if (devfreq->min_freq && freq < devfreq->min_freq) { + freq = devfreq->min_freq; + flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */ + } + if (devfreq->max_freq && freq > devfreq->max_freq) { + freq = devfreq->max_freq; + flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */ + } + + err = devfreq->profile->target(devfreq->dev.parent, &freq, flags); + if (err) + return err; + + if (devfreq->profile->freq_table) + if (devfreq_update_status(devfreq, freq)) + dev_err(&devfreq->dev, + "Couldn't update frequency transition information.\n"); + + devfreq->previous_freq = freq; + return err; +} +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); +} + +/** + * devfreq_monitor_start() - Start load monitoring of devfreq instance + * @devfreq: the devfreq instance. + * + * Helper function for starting devfreq device load monitoing. 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) +{ + INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor); + 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 monitoing. 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) +{ + 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 monitoing. 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); + 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 monitoing. 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 (!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)); + + devfreq->last_stat_updated = jiffies; + 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_interval_update() - 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_INTERVAL event. + */ +void devfreq_interval_update(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 (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_interval_update); + +/** + * 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 ret; + + mutex_lock(&devfreq->lock); + ret = update_devfreq(devfreq); + mutex_unlock(&devfreq->lock); + + return ret; +} + +/** + * _remove_devfreq() - Remove devfreq from the list and release its resources. + * @devfreq: the devfreq struct + */ +static void _remove_devfreq(struct devfreq *devfreq) +{ + mutex_lock(&devfreq_list_lock); + if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) { + mutex_unlock(&devfreq_list_lock); + dev_warn(&devfreq->dev, "releasing devfreq which doesn't exist\n"); + return; + } + list_del(&devfreq->node); + mutex_unlock(&devfreq_list_lock); + + if (devfreq->governor) + devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_STOP, NULL); + + if (devfreq->profile->exit) + devfreq->profile->exit(devfreq->dev.parent); + + mutex_destroy(&devfreq->lock); + kfree(devfreq); +} + +/** + * devfreq_dev_release() - Callback for struct device to release the device. + * @dev: the devfreq device + * + * This calls _remove_devfreq() if _remove_devfreq() is not called. + */ +static void devfreq_dev_release(struct device *dev) +{ + struct devfreq *devfreq = to_devfreq(dev); + + _remove_devfreq(devfreq); +} + +/** + * 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: private data for the governor. The devfreq framework does not + * touch this value. + */ +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; + 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: Unable to create devfreq for the device. It already has one.\n", __func__); + err = -EINVAL; + goto err_out; + } + + devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL); + if (!devfreq) { + dev_err(dev, "%s: Unable to create devfreq for the device\n", + __func__); + 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; + devfreq->profile = profile; + strncpy(devfreq->governor_name, governor_name, DEVFREQ_NAME_LEN); + devfreq->previous_freq = profile->initial_freq; + devfreq->data = data; + devfreq->nb.notifier_call = devfreq_notifier_call; + + devfreq->trans_table = devm_kzalloc(dev, sizeof(unsigned int) * + devfreq->profile->max_state * + devfreq->profile->max_state, + GFP_KERNEL); + devfreq->time_in_state = devm_kzalloc(dev, sizeof(unsigned int) * + devfreq->profile->max_state, + GFP_KERNEL); + devfreq->last_stat_updated = jiffies; + + dev_set_name(&devfreq->dev, "%s", dev_name(dev)); + err = device_register(&devfreq->dev); + if (err) { + put_device(&devfreq->dev); + mutex_unlock(&devfreq->lock); + goto err_dev; + } + + mutex_unlock(&devfreq->lock); + + mutex_lock(&devfreq_list_lock); + list_add(&devfreq->node, &devfreq_list); + + governor = find_devfreq_governor(devfreq->governor_name); + if (!IS_ERR(governor)) + devfreq->governor = governor; + if (devfreq->governor) + err = devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_START, NULL); + mutex_unlock(&devfreq_list_lock); + if (err) { + dev_err(dev, "%s: Unable to start governor for the device\n", + __func__); + goto err_init; + } + + return devfreq; + +err_init: + list_del(&devfreq->node); + device_unregister(&devfreq->dev); +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; + + device_unregister(&devfreq->dev); + put_device(&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: private data for the governor. The devfreq framework does not + * touch this value. + * + * 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 ERR_PTR(-ENOMEM); + } + + *ptr = devfreq; + devres_add(dev, ptr); + + return devfreq; +} +EXPORT_SYMBOL(devm_devfreq_add_device); + +/** + * devm_devfreq_remove_device() - Resource-managed devfreq_remove_device() + * @dev: the device to add 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) +{ + if (!devfreq) + return -EINVAL; + + if (!devfreq->governor) + return 0; + + return devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_SUSPEND, NULL); +} +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) +{ + if (!devfreq) + return -EINVAL; + + if (!devfreq->governor) + return 0; + + return devfreq->governor->event_handler(devfreq, + DEVFREQ_GOV_RESUME, NULL); +} +EXPORT_SYMBOL(devfreq_resume_device); + +/** + * 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); + +/** + * devfreq_remove_device() - 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 governor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!to_devfreq(dev)->governor) + return -EINVAL; + + return sprintf(buf, "%s\n", to_devfreq(dev)->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]; + struct devfreq_governor *governor; + + ret = sscanf(buf, "%" __stringify(DEVFREQ_NAME_LEN) "s", str_governor); + if (ret != 1) + return -EINVAL; + + mutex_lock(&devfreq_list_lock); + governor = find_devfreq_governor(str_governor); + if (IS_ERR(governor)) { + ret = PTR_ERR(governor); + goto out; + } + if (df->governor == governor) + goto out; + + if (df->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; + } + } + df->governor = governor; + strncpy(df->governor_name, governor->name, DEVFREQ_NAME_LEN); + 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: + 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_governor *tmp_governor; + ssize_t count = 0; + + mutex_lock(&devfreq_list_lock); + list_for_each_entry(tmp_governor, &devfreq_governor_list, node) + count += scnprintf(&buf[count], (PAGE_SIZE - count - 2), + "%s ", tmp_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 *devfreq = to_devfreq(dev); + + if (devfreq->profile->get_cur_freq && + !devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq)) + return sprintf(buf, "%lu\n", freq); + + return sprintf(buf, "%lu\n", devfreq->previous_freq); +} +static DEVICE_ATTR_RO(cur_freq); + +static ssize_t target_freq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->previous_freq); +} +static DEVICE_ATTR_RO(target_freq); + +static ssize_t polling_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", to_devfreq(dev)->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_INTERVAL, &value); + ret = count; + + return ret; +} +static DEVICE_ATTR_RW(polling_interval); + +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; + unsigned long max; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + return -EINVAL; + + mutex_lock(&df->lock); + max = df->max_freq; + if (value && max && value > max) { + ret = -EINVAL; + goto unlock; + } + + df->min_freq = value; + update_devfreq(df); + ret = count; +unlock: + mutex_unlock(&df->lock); + return ret; +} + +static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->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; + unsigned long min; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + return -EINVAL; + + mutex_lock(&df->lock); + min = df->min_freq; + if (value && min && value < min) { + ret = -EINVAL; + goto unlock; + } + + df->max_freq = value; + update_devfreq(df); + ret = count; +unlock: + mutex_unlock(&df->lock); + return ret; +} +static DEVICE_ATTR_RW(min_freq); + +static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->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); + struct device *dev = df->dev.parent; + struct dev_pm_opp *opp; + ssize_t count = 0; + unsigned long freq = 0; + + rcu_read_lock(); + do { + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + if (IS_ERR(opp)) + break; + + count += scnprintf(&buf[count], (PAGE_SIZE - count - 2), + "%lu ", freq); + freq++; + } while (1); + rcu_read_unlock(); + + /* 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 *devfreq = to_devfreq(dev); + ssize_t len; + int i, j; + unsigned int max_state = devfreq->profile->max_state; + + if (!devfreq->stop_polling && + devfreq_update_status(devfreq, devfreq->previous_freq)) + return 0; + + len = sprintf(buf, " From : To\n"); + len += sprintf(buf + len, " :"); + for (i = 0; i < max_state; i++) + len += sprintf(buf + len, "%8u", + devfreq->profile->freq_table[i]); + + len += sprintf(buf + len, " time(ms)\n"); + + for (i = 0; i < max_state; i++) { + if (devfreq->profile->freq_table[i] + == devfreq->previous_freq) { + len += sprintf(buf + len, "*"); + } else { + len += sprintf(buf + len, " "); + } + len += sprintf(buf + len, "%8u:", + devfreq->profile->freq_table[i]); + for (j = 0; j < max_state; j++) + len += sprintf(buf + len, "%8u", + devfreq->trans_table[(i * max_state) + j]); + len += sprintf(buf + len, "%10u\n", + jiffies_to_msecs(devfreq->time_in_state[i])); + } + + len += sprintf(buf + len, "Total transition : %u\n", + devfreq->total_trans); + return len; +} +static DEVICE_ATTR_RO(trans_stat); + +static struct attribute *devfreq_attrs[] = { + &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_polling_interval.attr, + &dev_attr_min_freq.attr, + &dev_attr_max_freq.attr, + &dev_attr_trans_stat.attr, + NULL, +}; +ATTRIBUTE_GROUPS(devfreq); + +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; + + return 0; +} +subsys_initcall(devfreq_init); + +static void __exit devfreq_exit(void) +{ + class_destroy(devfreq_class); + destroy_workqueue(devfreq_wq); +} +module_exit(devfreq_exit); + +/* + * The followings 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. + * + * Locking: This function must be called under rcu_read_lock(). opp is a rcu + * protected pointer. The reason for the same is that the opp pointer which is + * returned will remain valid for use with opp_get_{voltage, freq} only while + * under the locked area. The pointer returned must be used prior to unlocking + * with rcu_read_unlock() to maintain the integrity of the pointer. + */ +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) +{ + struct srcu_notifier_head *nh; + int ret = 0; + + rcu_read_lock(); + nh = dev_pm_opp_get_notifier(dev); + if (IS_ERR(nh)) + ret = PTR_ERR(nh); + rcu_read_unlock(); + if (!ret) + ret = srcu_notifier_chain_register(nh, &devfreq->nb); + + return ret; +} +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) +{ + struct srcu_notifier_head *nh; + int ret = 0; + + rcu_read_lock(); + nh = dev_pm_opp_get_notifier(dev); + if (IS_ERR(nh)) + ret = PTR_ERR(nh); + rcu_read_unlock(); + if (!ret) + ret = srcu_notifier_chain_unregister(nh, &devfreq->nb); + + return ret; +} +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); + +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_DESCRIPTION("devfreq class support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig new file mode 100644 index 000000000..a11720aff --- /dev/null +++ b/drivers/devfreq/event/Kconfig @@ -0,0 +1,25 @@ +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_PPMU + bool "EXYNOS PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver" + depends on ARCH_EXYNOS + 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. + +endif # PM_DEVFREQ_EVENT diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile new file mode 100644 index 000000000..be146ead7 --- /dev/null +++ b/drivers/devfreq/event/Makefile @@ -0,0 +1,2 @@ +# Exynos DEVFREQ Event Drivers +obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o diff --git a/drivers/devfreq/event/exynos-ppmu.c b/drivers/devfreq/event/exynos-ppmu.c new file mode 100644 index 000000000..7d99d13ba --- /dev/null +++ b/drivers/devfreq/event/exynos-ppmu.c @@ -0,0 +1,374 @@ +/* + * exynos_ppmu.c - EXYNOS PPMU (Platform Performance Monitoring Unit) support + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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/mutex.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <linux/devfreq-event.h> + +#include "exynos-ppmu.h" + +struct exynos_ppmu_data { + void __iomem *base; + struct clk *clk; +}; + +struct exynos_ppmu { + struct devfreq_event_dev **edev; + struct devfreq_event_desc *desc; + unsigned int num_events; + + struct device *dev; + struct mutex lock; + + struct exynos_ppmu_data ppmu; +}; + +#define PPMU_EVENT(name) \ + { "ppmu-event0-"#name, PPMU_PMNCNT0 }, \ + { "ppmu-event1-"#name, PPMU_PMNCNT1 }, \ + { "ppmu-event2-"#name, PPMU_PMNCNT2 }, \ + { "ppmu-event3-"#name, PPMU_PMNCNT3 } + +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), + { /* sentinel */ }, +}; + +static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) + if (!strcmp(edev->desc->name, ppmu_events[i].name)) + return ppmu_events[i].id; + + return -EINVAL; +} + +static int exynos_ppmu_disable(struct devfreq_event_dev *edev) +{ + struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); + u32 pmnc; + + /* Disable all counters */ + __raw_writel(PPMU_CCNT_MASK | + PPMU_PMCNT0_MASK | + PPMU_PMCNT1_MASK | + PPMU_PMCNT2_MASK | + PPMU_PMCNT3_MASK, + info->ppmu.base + PPMU_CNTENC); + + /* Disable PPMU */ + pmnc = __raw_readl(info->ppmu.base + PPMU_PMNC); + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + __raw_writel(pmnc, info->ppmu.base + PPMU_PMNC); + + 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); + u32 pmnc, cntens; + + if (id < 0) + return id; + + /* Enable specific counter */ + cntens = __raw_readl(info->ppmu.base + PPMU_CNTENS); + cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + __raw_writel(cntens, info->ppmu.base + PPMU_CNTENS); + + /* Set the event of Read/Write data count */ + __raw_writel(PPMU_RO_DATA_CNT | PPMU_WO_DATA_CNT, + info->ppmu.base + PPMU_BEVTxSEL(id)); + + /* Reset cycle counter/performance counter and enable PPMU */ + pmnc = __raw_readl(info->ppmu.base + PPMU_PMNC); + 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); + __raw_writel(pmnc, info->ppmu.base + PPMU_PMNC); + + 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); + u32 pmnc, cntenc; + + if (id < 0) + return -EINVAL; + + /* Disable PPMU */ + pmnc = __raw_readl(info->ppmu.base + PPMU_PMNC); + pmnc &= ~PPMU_PMNC_ENABLE_MASK; + __raw_writel(pmnc, info->ppmu.base + PPMU_PMNC); + + /* Read cycle count */ + edata->total_count = __raw_readl(info->ppmu.base + PPMU_CCNT); + + /* Read performance count */ + switch (id) { + case PPMU_PMNCNT0: + case PPMU_PMNCNT1: + case PPMU_PMNCNT2: + edata->load_count + = __raw_readl(info->ppmu.base + PPMU_PMNCT(id)); + break; + case PPMU_PMNCNT3: + edata->load_count = + ((__raw_readl(info->ppmu.base + PPMU_PMCNT3_HIGH) << 8) + | __raw_readl(info->ppmu.base + PPMU_PMCNT3_LOW)); + break; + default: + return -EINVAL; + } + + /* Disable specific counter */ + cntenc = __raw_readl(info->ppmu.base + PPMU_CNTENC); + cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); + __raw_writel(cntenc, info->ppmu.base + PPMU_CNTENC); + + 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, +}; + +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; + + 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_kzalloc(dev, sizeof(*desc) * count, GFP_KERNEL); + if (!desc) + return -ENOMEM; + info->num_events = count; + + 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_cmp(node->name, ppmu_events[i].name)) + break; + } + + if (i == ARRAY_SIZE(ppmu_events)) { + dev_warn(dev, + "don't know how to configure events : %s\n", + node->name); + continue; + } + + desc[j].ops = &exynos_ppmu_ops; + desc[j].driver_data = info; + + of_property_read_string(node, "event-name", &desc[j].name); + + j++; + + of_node_put(node); + } + info->desc = desc; + + of_node_put(events_np); + + return 0; +} + +static int exynos_ppmu_parse_dt(struct exynos_ppmu *info) +{ + struct device *dev = info->dev; + struct device_node *np = dev->of_node; + 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 */ + info->ppmu.base = of_iomap(np, 0); + if (IS_ERR_OR_NULL(info->ppmu.base)) { + dev_err(dev, "failed to map memory region\n"); + return -ENOMEM; + } + + 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"); + goto err; + } + + return 0; + +err: + iounmap(info->ppmu.base); + + return ret; +} + +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; + + mutex_init(&info->lock); + info->dev = &pdev->dev; + + /* Parse dt data to get resource */ + ret = exynos_ppmu_parse_dt(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) { + dev_err(&pdev->dev, + "failed to allocate memory devfreq-event devices\n"); + 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])) { + ret = PTR_ERR(edev[i]); + dev_err(&pdev->dev, + "failed to add devfreq-event device\n"); + goto err; + } + } + + clk_prepare_enable(info->ppmu.clk); + + return 0; +err: + iounmap(info->ppmu.base); + + return ret; +} + +static int exynos_ppmu_remove(struct platform_device *pdev) +{ + struct exynos_ppmu *info = platform_get_drvdata(pdev); + + clk_disable_unprepare(info->ppmu.clk); + iounmap(info->ppmu.base); + + return 0; +} + +static struct of_device_id exynos_ppmu_id_match[] = { + { .compatible = "samsung,exynos-ppmu", }, + { /* sentinel */ }, +}; + +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..4e831d48c --- /dev/null +++ b/drivers/devfreq/event/exynos-ppmu.h @@ -0,0 +1,93 @@ +/* + * exynos_ppmu.h - EXYNOS PPMU header file + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Author : Chanwoo Choi <cw00.choi@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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, +}; + +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)) + +#endif /* __EXYNOS_PPMU_H__ */ diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile new file mode 100644 index 000000000..49bc9175f --- /dev/null +++ b/drivers/devfreq/exynos/Makefile @@ -0,0 +1,3 @@ +# Exynos DEVFREQ Drivers +obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos_ppmu.o exynos4_bus.o +obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o diff --git a/drivers/devfreq/exynos/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c new file mode 100644 index 000000000..da9509205 --- /dev/null +++ b/drivers/devfreq/exynos/exynos4_bus.c @@ -0,0 +1,1055 @@ +/* drivers/devfreq/exynos4210_memorybus.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * EXYNOS4 - Memory/Bus clock frequency scaling support in DEVFREQ framework + * This version supports EXYNOS4210 only. This changes bus frequencies + * and vddint voltages. Exynos4412/4212 should be able to be supported + * with minor modifications. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <linux/pm_opp.h> +#include <linux/devfreq.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> + +#include <mach/map.h> + +#include "exynos_ppmu.h" +#include "exynos4_bus.h" + +#define MAX_SAFEVOLT 1200000 /* 1.2V */ + +enum exynos4_busf_type { + TYPE_BUSF_EXYNOS4210, + TYPE_BUSF_EXYNOS4x12, +}; + +/* Assume that the bus is saturated if the utilization is 40% */ +#define BUS_SATURATION_RATIO 40 + +enum busclk_level_idx { + LV_0 = 0, + LV_1, + LV_2, + LV_3, + LV_4, + _LV_END +}; + +enum exynos_ppmu_idx { + PPMU_DMC0, + PPMU_DMC1, + PPMU_END, +}; + +#define EX4210_LV_MAX LV_2 +#define EX4x12_LV_MAX LV_4 +#define EX4210_LV_NUM (LV_2 + 1) +#define EX4x12_LV_NUM (LV_4 + 1) + +/** + * struct busfreq_opp_info - opp information for bus + * @rate: Frequency in hertz + * @volt: Voltage in microvolts corresponding to this OPP + */ +struct busfreq_opp_info { + unsigned long rate; + unsigned long volt; +}; + +struct busfreq_data { + enum exynos4_busf_type type; + struct device *dev; + struct devfreq *devfreq; + bool disabled; + struct regulator *vdd_int; + struct regulator *vdd_mif; /* Exynos4412/4212 only */ + struct busfreq_opp_info curr_oppinfo; + struct busfreq_ppmu_data ppmu_data; + + struct notifier_block pm_notifier; + struct mutex lock; + + /* Dividers calculated at boot/probe-time */ + unsigned int dmc_divtable[_LV_END]; /* DMC0 */ + unsigned int top_divtable[_LV_END]; +}; + +/* 4210 controls clock of mif and voltage of int */ +static struct bus_opp_table exynos4210_busclk_table[] = { + {LV_0, 400000, 1150000}, + {LV_1, 267000, 1050000}, + {LV_2, 133000, 1025000}, + {0, 0, 0}, +}; + +/* + * MIF is the main control knob clock for Exynos4x12 MIF/INT + * clock and voltage of both mif/int are controlled. + */ +static struct bus_opp_table exynos4x12_mifclk_table[] = { + {LV_0, 400000, 1100000}, + {LV_1, 267000, 1000000}, + {LV_2, 160000, 950000}, + {LV_3, 133000, 950000}, + {LV_4, 100000, 950000}, + {0, 0, 0}, +}; + +/* + * INT is not the control knob of 4x12. LV_x is not meant to represent + * the current performance. (MIF does) + */ +static struct bus_opp_table exynos4x12_intclk_table[] = { + {LV_0, 200000, 1000000}, + {LV_1, 160000, 950000}, + {LV_2, 133000, 925000}, + {LV_3, 100000, 900000}, + {0, 0, 0}, +}; + +/* TODO: asv volt definitions are "__initdata"? */ +/* Some chips have different operating voltages */ +static unsigned int exynos4210_asv_volt[][EX4210_LV_NUM] = { + {1150000, 1050000, 1050000}, + {1125000, 1025000, 1025000}, + {1100000, 1000000, 1000000}, + {1075000, 975000, 975000}, + {1050000, 950000, 950000}, +}; + +static unsigned int exynos4x12_mif_step_50[][EX4x12_LV_NUM] = { + /* 400 267 160 133 100 */ + {1050000, 950000, 900000, 900000, 900000}, /* ASV0 */ + {1050000, 950000, 900000, 900000, 900000}, /* ASV1 */ + {1050000, 950000, 900000, 900000, 900000}, /* ASV2 */ + {1050000, 900000, 900000, 900000, 900000}, /* ASV3 */ + {1050000, 900000, 900000, 900000, 850000}, /* ASV4 */ + {1050000, 900000, 900000, 850000, 850000}, /* ASV5 */ + {1050000, 900000, 850000, 850000, 850000}, /* ASV6 */ + {1050000, 900000, 850000, 850000, 850000}, /* ASV7 */ + {1050000, 900000, 850000, 850000, 850000}, /* ASV8 */ +}; + +static unsigned int exynos4x12_int_volt[][EX4x12_LV_NUM] = { + /* 200 160 133 100 */ + {1000000, 950000, 925000, 900000}, /* ASV0 */ + {975000, 925000, 925000, 900000}, /* ASV1 */ + {950000, 925000, 900000, 875000}, /* ASV2 */ + {950000, 900000, 900000, 875000}, /* ASV3 */ + {925000, 875000, 875000, 875000}, /* ASV4 */ + {900000, 850000, 850000, 850000}, /* ASV5 */ + {900000, 850000, 850000, 850000}, /* ASV6 */ + {900000, 850000, 850000, 850000}, /* ASV7 */ + {900000, 850000, 850000, 850000}, /* ASV8 */ +}; + +/*** Clock Divider Data for Exynos4210 ***/ +static unsigned int exynos4210_clkdiv_dmc0[][8] = { + /* + * Clock divider value for following + * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD + * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } + */ + + /* DMC L0: 400MHz */ + { 3, 1, 1, 1, 1, 1, 3, 1 }, + /* DMC L1: 266.7MHz */ + { 4, 1, 1, 2, 1, 1, 3, 1 }, + /* DMC L2: 133MHz */ + { 5, 1, 1, 5, 1, 1, 3, 1 }, +}; +static unsigned int exynos4210_clkdiv_top[][5] = { + /* + * Clock divider value for following + * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } + */ + /* ACLK200 L0: 200MHz */ + { 3, 7, 4, 5, 1 }, + /* ACLK200 L1: 160MHz */ + { 4, 7, 5, 6, 1 }, + /* ACLK200 L2: 133MHz */ + { 5, 7, 7, 7, 1 }, +}; +static unsigned int exynos4210_clkdiv_lr_bus[][2] = { + /* + * Clock divider value for following + * { DIVGDL/R, DIVGPL/R } + */ + /* ACLK_GDL/R L1: 200MHz */ + { 3, 1 }, + /* ACLK_GDL/R L2: 160MHz */ + { 4, 1 }, + /* ACLK_GDL/R L3: 133MHz */ + { 5, 1 }, +}; + +/*** Clock Divider Data for Exynos4212/4412 ***/ +static unsigned int exynos4x12_clkdiv_dmc0[][6] = { + /* + * Clock divider value for following + * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD + * DIVDMCP} + */ + + /* DMC L0: 400MHz */ + {3, 1, 1, 1, 1, 1}, + /* DMC L1: 266.7MHz */ + {4, 1, 1, 2, 1, 1}, + /* DMC L2: 160MHz */ + {5, 1, 1, 4, 1, 1}, + /* DMC L3: 133MHz */ + {5, 1, 1, 5, 1, 1}, + /* DMC L4: 100MHz */ + {7, 1, 1, 7, 1, 1}, +}; +static unsigned int exynos4x12_clkdiv_dmc1[][6] = { + /* + * Clock divider value for following + * { G2DACP, DIVC2C, DIVC2C_ACLK } + */ + + /* DMC L0: 400MHz */ + {3, 1, 1}, + /* DMC L1: 266.7MHz */ + {4, 2, 1}, + /* DMC L2: 160MHz */ + {5, 4, 1}, + /* DMC L3: 133MHz */ + {5, 5, 1}, + /* DMC L4: 100MHz */ + {7, 7, 1}, +}; +static unsigned int exynos4x12_clkdiv_top[][5] = { + /* + * Clock divider value for following + * { DIVACLK266_GPS, DIVACLK100, DIVACLK160, + DIVACLK133, DIVONENAND } + */ + + /* ACLK_GDL/R L0: 200MHz */ + {2, 7, 4, 5, 1}, + /* ACLK_GDL/R L1: 200MHz */ + {2, 7, 4, 5, 1}, + /* ACLK_GDL/R L2: 160MHz */ + {4, 7, 5, 7, 1}, + /* ACLK_GDL/R L3: 133MHz */ + {4, 7, 5, 7, 1}, + /* ACLK_GDL/R L4: 100MHz */ + {7, 7, 7, 7, 1}, +}; +static unsigned int exynos4x12_clkdiv_lr_bus[][2] = { + /* + * Clock divider value for following + * { DIVGDL/R, DIVGPL/R } + */ + + /* ACLK_GDL/R L0: 200MHz */ + {3, 1}, + /* ACLK_GDL/R L1: 200MHz */ + {3, 1}, + /* ACLK_GDL/R L2: 160MHz */ + {4, 1}, + /* ACLK_GDL/R L3: 133MHz */ + {5, 1}, + /* ACLK_GDL/R L4: 100MHz */ + {7, 1}, +}; +static unsigned int exynos4x12_clkdiv_sclkip[][3] = { + /* + * Clock divider value for following + * { DIVMFC, DIVJPEG, DIVFIMC0~3} + */ + + /* SCLK_MFC: 200MHz */ + {3, 3, 4}, + /* SCLK_MFC: 200MHz */ + {3, 3, 4}, + /* SCLK_MFC: 160MHz */ + {4, 4, 5}, + /* SCLK_MFC: 133MHz */ + {5, 5, 5}, + /* SCLK_MFC: 100MHz */ + {7, 7, 7}, +}; + + +static int exynos4210_set_busclk(struct busfreq_data *data, + struct busfreq_opp_info *oppi) +{ + unsigned int index; + unsigned int tmp; + + for (index = LV_0; index < EX4210_LV_NUM; index++) + if (oppi->rate == exynos4210_busclk_table[index].clk) + break; + + if (index == EX4210_LV_NUM) + return -EINVAL; + + /* Change Divider - DMC0 */ + tmp = data->dmc_divtable[index]; + + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); + } while (tmp & 0x11111111); + + /* Change Divider - TOP */ + tmp = data->top_divtable[index]; + + __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); + } while (tmp & 0x11111); + + /* Change Divider - LEFTBUS */ + tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); + + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | + (exynos4210_clkdiv_lr_bus[index][1] << + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); + } while (tmp & 0x11); + + /* Change Divider - RIGHTBUS */ + tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); + + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | + (exynos4210_clkdiv_lr_bus[index][1] << + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); + } while (tmp & 0x11); + + return 0; +} + +static int exynos4x12_set_busclk(struct busfreq_data *data, + struct busfreq_opp_info *oppi) +{ + unsigned int index; + unsigned int tmp; + + for (index = LV_0; index < EX4x12_LV_NUM; index++) + if (oppi->rate == exynos4x12_mifclk_table[index].clk) + break; + + if (index == EX4x12_LV_NUM) + return -EINVAL; + + /* Change Divider - DMC0 */ + tmp = data->dmc_divtable[index]; + + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); + } while (tmp & 0x11111111); + + /* Change Divider - DMC1 */ + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC1); + + tmp &= ~(EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK | + EXYNOS4_CLKDIV_DMC1_C2C_MASK | + EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK); + + tmp |= ((exynos4x12_clkdiv_dmc1[index][0] << + EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) | + (exynos4x12_clkdiv_dmc1[index][1] << + EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) | + (exynos4x12_clkdiv_dmc1[index][2] << + EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC1); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC1); + } while (tmp & 0x111111); + + /* Change Divider - TOP */ + tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); + + tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK | + EXYNOS4_CLKDIV_TOP_ACLK100_MASK | + EXYNOS4_CLKDIV_TOP_ACLK160_MASK | + EXYNOS4_CLKDIV_TOP_ACLK133_MASK | + EXYNOS4_CLKDIV_TOP_ONENAND_MASK); + + tmp |= ((exynos4x12_clkdiv_top[index][0] << + EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) | + (exynos4x12_clkdiv_top[index][1] << + EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | + (exynos4x12_clkdiv_top[index][2] << + EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | + (exynos4x12_clkdiv_top[index][3] << + EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | + (exynos4x12_clkdiv_top[index][4] << + EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); + } while (tmp & 0x11111); + + /* Change Divider - LEFTBUS */ + tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); + + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | + (exynos4x12_clkdiv_lr_bus[index][1] << + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); + } while (tmp & 0x11); + + /* Change Divider - RIGHTBUS */ + tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); + + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | + (exynos4x12_clkdiv_lr_bus[index][1] << + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); + } while (tmp & 0x11); + + /* Change Divider - MFC */ + tmp = __raw_readl(EXYNOS4_CLKDIV_MFC); + + tmp &= ~(EXYNOS4_CLKDIV_MFC_MASK); + + tmp |= ((exynos4x12_clkdiv_sclkip[index][0] << + EXYNOS4_CLKDIV_MFC_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_MFC); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_MFC); + } while (tmp & 0x1); + + /* Change Divider - JPEG */ + tmp = __raw_readl(EXYNOS4_CLKDIV_CAM1); + + tmp &= ~(EXYNOS4_CLKDIV_CAM1_JPEG_MASK); + + tmp |= ((exynos4x12_clkdiv_sclkip[index][1] << + EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_CAM1); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); + } while (tmp & 0x1); + + /* Change Divider - FIMC0~3 */ + tmp = __raw_readl(EXYNOS4_CLKDIV_CAM); + + tmp &= ~(EXYNOS4_CLKDIV_CAM_FIMC0_MASK | EXYNOS4_CLKDIV_CAM_FIMC1_MASK | + EXYNOS4_CLKDIV_CAM_FIMC2_MASK | EXYNOS4_CLKDIV_CAM_FIMC3_MASK); + + tmp |= ((exynos4x12_clkdiv_sclkip[index][2] << + EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) | + (exynos4x12_clkdiv_sclkip[index][2] << + EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) | + (exynos4x12_clkdiv_sclkip[index][2] << + EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) | + (exynos4x12_clkdiv_sclkip[index][2] << + EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT)); + + __raw_writel(tmp, EXYNOS4_CLKDIV_CAM); + + do { + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); + } while (tmp & 0x1111); + + return 0; +} + +static int exynos4x12_get_intspec(unsigned long mifclk) +{ + int i = 0; + + while (exynos4x12_intclk_table[i].clk) { + if (exynos4x12_intclk_table[i].clk <= mifclk) + return i; + i++; + } + + return -EINVAL; +} + +static int exynos4_bus_setvolt(struct busfreq_data *data, + struct busfreq_opp_info *oppi, + struct busfreq_opp_info *oldoppi) +{ + int err = 0, tmp; + unsigned long volt = oppi->volt; + + switch (data->type) { + case TYPE_BUSF_EXYNOS4210: + /* OPP represents DMC clock + INT voltage */ + err = regulator_set_voltage(data->vdd_int, volt, + MAX_SAFEVOLT); + break; + case TYPE_BUSF_EXYNOS4x12: + /* OPP represents MIF clock + MIF voltage */ + err = regulator_set_voltage(data->vdd_mif, volt, + MAX_SAFEVOLT); + if (err) + break; + + tmp = exynos4x12_get_intspec(oppi->rate); + if (tmp < 0) { + err = tmp; + regulator_set_voltage(data->vdd_mif, + oldoppi->volt, + MAX_SAFEVOLT); + break; + } + err = regulator_set_voltage(data->vdd_int, + exynos4x12_intclk_table[tmp].volt, + MAX_SAFEVOLT); + /* Try to recover */ + if (err) + regulator_set_voltage(data->vdd_mif, + oldoppi->volt, + MAX_SAFEVOLT); + break; + default: + err = -EINVAL; + } + + return err; +} + +static int exynos4_bus_target(struct device *dev, unsigned long *_freq, + u32 flags) +{ + int err = 0; + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct busfreq_data *data = platform_get_drvdata(pdev); + struct dev_pm_opp *opp; + unsigned long freq; + unsigned long old_freq = data->curr_oppinfo.rate; + struct busfreq_opp_info new_oppinfo; + + rcu_read_lock(); + opp = devfreq_recommended_opp(dev, _freq, flags); + if (IS_ERR(opp)) { + rcu_read_unlock(); + return PTR_ERR(opp); + } + new_oppinfo.rate = dev_pm_opp_get_freq(opp); + new_oppinfo.volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + freq = new_oppinfo.rate; + + if (old_freq == freq) + return 0; + + dev_dbg(dev, "targeting %lukHz %luuV\n", freq, new_oppinfo.volt); + + mutex_lock(&data->lock); + + if (data->disabled) + goto out; + + if (old_freq < freq) + err = exynos4_bus_setvolt(data, &new_oppinfo, + &data->curr_oppinfo); + if (err) + goto out; + + if (old_freq != freq) { + switch (data->type) { + case TYPE_BUSF_EXYNOS4210: + err = exynos4210_set_busclk(data, &new_oppinfo); + break; + case TYPE_BUSF_EXYNOS4x12: + err = exynos4x12_set_busclk(data, &new_oppinfo); + break; + default: + err = -EINVAL; + } + } + if (err) + goto out; + + if (old_freq > freq) + err = exynos4_bus_setvolt(data, &new_oppinfo, + &data->curr_oppinfo); + if (err) + goto out; + + data->curr_oppinfo = new_oppinfo; +out: + mutex_unlock(&data->lock); + return err; +} + +static int exynos4_bus_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct busfreq_data *data = dev_get_drvdata(dev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; + int busier; + + exynos_read_ppmu(ppmu_data); + busier = exynos_get_busier_ppmu(ppmu_data); + stat->current_frequency = data->curr_oppinfo.rate; + + /* Number of cycles spent on memory access */ + stat->busy_time = ppmu_data->ppmu[busier].count[PPMU_PMNCNT3]; + stat->busy_time *= 100 / BUS_SATURATION_RATIO; + stat->total_time = ppmu_data->ppmu[busier].ccnt; + + /* If the counters have overflown, retry */ + if (ppmu_data->ppmu[busier].ccnt_overflow || + ppmu_data->ppmu[busier].count_overflow[0]) + return -EAGAIN; + + return 0; +} + +static struct devfreq_dev_profile exynos4_devfreq_profile = { + .initial_freq = 400000, + .polling_ms = 50, + .target = exynos4_bus_target, + .get_dev_status = exynos4_bus_get_dev_status, +}; + +static int exynos4210_init_tables(struct busfreq_data *data) +{ + u32 tmp; + int mgrp; + int i, err = 0; + + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); + for (i = LV_0; i < EX4210_LV_NUM; i++) { + tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | + EXYNOS4_CLKDIV_DMC0_DPHY_MASK | + EXYNOS4_CLKDIV_DMC0_DMC_MASK | + EXYNOS4_CLKDIV_DMC0_DMCD_MASK | + EXYNOS4_CLKDIV_DMC0_DMCP_MASK | + EXYNOS4_CLKDIV_DMC0_COPY2_MASK | + EXYNOS4_CLKDIV_DMC0_CORETI_MASK); + + tmp |= ((exynos4210_clkdiv_dmc0[i][0] << + EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | + (exynos4210_clkdiv_dmc0[i][1] << + EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | + (exynos4210_clkdiv_dmc0[i][2] << + EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | + (exynos4210_clkdiv_dmc0[i][3] << + EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | + (exynos4210_clkdiv_dmc0[i][4] << + EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | + (exynos4210_clkdiv_dmc0[i][5] << + EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) | + (exynos4210_clkdiv_dmc0[i][6] << + EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) | + (exynos4210_clkdiv_dmc0[i][7] << + EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)); + + data->dmc_divtable[i] = tmp; + } + + tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); + for (i = LV_0; i < EX4210_LV_NUM; i++) { + tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK | + EXYNOS4_CLKDIV_TOP_ACLK100_MASK | + EXYNOS4_CLKDIV_TOP_ACLK160_MASK | + EXYNOS4_CLKDIV_TOP_ACLK133_MASK | + EXYNOS4_CLKDIV_TOP_ONENAND_MASK); + + tmp |= ((exynos4210_clkdiv_top[i][0] << + EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) | + (exynos4210_clkdiv_top[i][1] << + EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | + (exynos4210_clkdiv_top[i][2] << + EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | + (exynos4210_clkdiv_top[i][3] << + EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | + (exynos4210_clkdiv_top[i][4] << + EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); + + data->top_divtable[i] = tmp; + } + + /* + * TODO: init tmp based on busfreq_data + * (device-tree or platform-data) + */ + tmp = 0; /* Max voltages for the reliability of the unknown */ + + pr_debug("ASV Group of Exynos4 is %d\n", tmp); + /* Use merged grouping for voltage */ + switch (tmp) { + case 0: + mgrp = 0; + break; + case 1: + case 2: + mgrp = 1; + break; + case 3: + case 4: + mgrp = 2; + break; + case 5: + case 6: + mgrp = 3; + break; + case 7: + mgrp = 4; + break; + default: + pr_warn("Unknown ASV Group. Use max voltage.\n"); + mgrp = 0; + } + + for (i = LV_0; i < EX4210_LV_NUM; i++) + exynos4210_busclk_table[i].volt = exynos4210_asv_volt[mgrp][i]; + + for (i = LV_0; i < EX4210_LV_NUM; i++) { + err = dev_pm_opp_add(data->dev, exynos4210_busclk_table[i].clk, + exynos4210_busclk_table[i].volt); + if (err) { + dev_err(data->dev, "Cannot add opp entries.\n"); + return err; + } + } + + + return 0; +} + +static int exynos4x12_init_tables(struct busfreq_data *data) +{ + unsigned int i; + unsigned int tmp; + int ret; + + /* Enable pause function for DREX2 DVFS */ + tmp = __raw_readl(EXYNOS4_DMC_PAUSE_CTRL); + tmp |= EXYNOS4_DMC_PAUSE_ENABLE; + __raw_writel(tmp, EXYNOS4_DMC_PAUSE_CTRL); + + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); + + for (i = 0; i < EX4x12_LV_NUM; i++) { + tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | + EXYNOS4_CLKDIV_DMC0_DPHY_MASK | + EXYNOS4_CLKDIV_DMC0_DMC_MASK | + EXYNOS4_CLKDIV_DMC0_DMCD_MASK | + EXYNOS4_CLKDIV_DMC0_DMCP_MASK); + + tmp |= ((exynos4x12_clkdiv_dmc0[i][0] << + EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | + (exynos4x12_clkdiv_dmc0[i][1] << + EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | + (exynos4x12_clkdiv_dmc0[i][2] << + EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | + (exynos4x12_clkdiv_dmc0[i][3] << + EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | + (exynos4x12_clkdiv_dmc0[i][4] << + EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | + (exynos4x12_clkdiv_dmc0[i][5] << + EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT)); + + data->dmc_divtable[i] = tmp; + } + + tmp = 0; /* Max voltages for the reliability of the unknown */ + + if (tmp > 8) + tmp = 0; + pr_debug("ASV Group of Exynos4x12 is %d\n", tmp); + + for (i = 0; i < EX4x12_LV_NUM; i++) { + exynos4x12_mifclk_table[i].volt = + exynos4x12_mif_step_50[tmp][i]; + exynos4x12_intclk_table[i].volt = + exynos4x12_int_volt[tmp][i]; + } + + for (i = 0; i < EX4x12_LV_NUM; i++) { + ret = dev_pm_opp_add(data->dev, exynos4x12_mifclk_table[i].clk, + exynos4x12_mifclk_table[i].volt); + if (ret) { + dev_err(data->dev, "Fail to add opp entries.\n"); + return ret; + } + } + + return 0; +} + +static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct busfreq_data *data = container_of(this, struct busfreq_data, + pm_notifier); + struct dev_pm_opp *opp; + struct busfreq_opp_info new_oppinfo; + unsigned long maxfreq = ULONG_MAX; + int err = 0; + + switch (event) { + case PM_SUSPEND_PREPARE: + /* Set Fastest and Deactivate DVFS */ + mutex_lock(&data->lock); + + data->disabled = true; + + rcu_read_lock(); + opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(data->dev, "%s: unable to find a min freq\n", + __func__); + mutex_unlock(&data->lock); + return PTR_ERR(opp); + } + new_oppinfo.rate = dev_pm_opp_get_freq(opp); + new_oppinfo.volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + + err = exynos4_bus_setvolt(data, &new_oppinfo, + &data->curr_oppinfo); + if (err) + goto unlock; + + switch (data->type) { + case TYPE_BUSF_EXYNOS4210: + err = exynos4210_set_busclk(data, &new_oppinfo); + break; + case TYPE_BUSF_EXYNOS4x12: + err = exynos4x12_set_busclk(data, &new_oppinfo); + break; + default: + err = -EINVAL; + } + if (err) + goto unlock; + + data->curr_oppinfo = new_oppinfo; +unlock: + mutex_unlock(&data->lock); + if (err) + return err; + return NOTIFY_OK; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + /* Reactivate */ + mutex_lock(&data->lock); + data->disabled = false; + mutex_unlock(&data->lock); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int exynos4_busfreq_probe(struct platform_device *pdev) +{ + struct busfreq_data *data; + struct busfreq_ppmu_data *ppmu_data; + struct dev_pm_opp *opp; + struct device *dev = &pdev->dev; + int err = 0; + + data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + + ppmu_data = &data->ppmu_data; + ppmu_data->ppmu_end = PPMU_END; + ppmu_data->ppmu = devm_kzalloc(dev, + sizeof(struct exynos_ppmu) * PPMU_END, + GFP_KERNEL); + if (!ppmu_data->ppmu) { + dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); + return -ENOMEM; + } + + data->type = pdev->id_entry->driver_data; + ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0; + ppmu_data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1; + data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event; + data->dev = dev; + mutex_init(&data->lock); + + switch (data->type) { + case TYPE_BUSF_EXYNOS4210: + err = exynos4210_init_tables(data); + break; + case TYPE_BUSF_EXYNOS4x12: + err = exynos4x12_init_tables(data); + break; + default: + dev_err(dev, "Cannot determine the device id %d\n", data->type); + err = -EINVAL; + } + if (err) { + dev_err(dev, "Cannot initialize busfreq table %d\n", + data->type); + return err; + } + + data->vdd_int = devm_regulator_get(dev, "vdd_int"); + if (IS_ERR(data->vdd_int)) { + dev_err(dev, "Cannot get the regulator \"vdd_int\"\n"); + return PTR_ERR(data->vdd_int); + } + if (data->type == TYPE_BUSF_EXYNOS4x12) { + data->vdd_mif = devm_regulator_get(dev, "vdd_mif"); + if (IS_ERR(data->vdd_mif)) { + dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n"); + return PTR_ERR(data->vdd_mif); + } + } + + rcu_read_lock(); + opp = dev_pm_opp_find_freq_floor(dev, + &exynos4_devfreq_profile.initial_freq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(dev, "Invalid initial frequency %lu kHz.\n", + exynos4_devfreq_profile.initial_freq); + return PTR_ERR(opp); + } + data->curr_oppinfo.rate = dev_pm_opp_get_freq(opp); + data->curr_oppinfo.volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + + platform_set_drvdata(pdev, data); + + data->devfreq = devm_devfreq_add_device(dev, &exynos4_devfreq_profile, + "simple_ondemand", NULL); + if (IS_ERR(data->devfreq)) + return PTR_ERR(data->devfreq); + + /* + * Start PPMU (Performance Profiling Monitoring Unit) to check + * utilization of each IP in the Exynos4 SoC. + */ + busfreq_mon_reset(ppmu_data); + + /* Register opp_notifier for Exynos4 busfreq */ + err = devm_devfreq_register_opp_notifier(dev, data->devfreq); + if (err < 0) { + dev_err(dev, "Failed to register opp notifier\n"); + return err; + } + + /* Register pm_notifier for Exynos4 busfreq */ + err = register_pm_notifier(&data->pm_notifier); + if (err) { + dev_err(dev, "Failed to setup pm notifier\n"); + return err; + } + + return 0; +} + +static int exynos4_busfreq_remove(struct platform_device *pdev) +{ + struct busfreq_data *data = platform_get_drvdata(pdev); + + /* Unregister all of notifier chain */ + unregister_pm_notifier(&data->pm_notifier); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos4_busfreq_resume(struct device *dev) +{ + struct busfreq_data *data = dev_get_drvdata(dev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; + + busfreq_mon_reset(ppmu_data); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume); + +static const struct platform_device_id exynos4_busfreq_id[] = { + { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 }, + { "exynos4412-busfreq", TYPE_BUSF_EXYNOS4x12 }, + { "exynos4212-busfreq", TYPE_BUSF_EXYNOS4x12 }, + { }, +}; + +static struct platform_driver exynos4_busfreq_driver = { + .probe = exynos4_busfreq_probe, + .remove = exynos4_busfreq_remove, + .id_table = exynos4_busfreq_id, + .driver = { + .name = "exynos4-busfreq", + .pm = &exynos4_busfreq_pm_ops, + }, +}; + +static int __init exynos4_busfreq_init(void) +{ + return platform_driver_register(&exynos4_busfreq_driver); +} +late_initcall(exynos4_busfreq_init); + +static void __exit exynos4_busfreq_exit(void) +{ + platform_driver_unregister(&exynos4_busfreq_driver); +} +module_exit(exynos4_busfreq_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("EXYNOS4 busfreq driver with devfreq framework"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); diff --git a/drivers/devfreq/exynos/exynos4_bus.h b/drivers/devfreq/exynos/exynos4_bus.h new file mode 100644 index 000000000..94c73c18d --- /dev/null +++ b/drivers/devfreq/exynos/exynos4_bus.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS4 BUS header + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __DEVFREQ_EXYNOS4_BUS_H +#define __DEVFREQ_EXYNOS4_BUS_H __FILE__ + +#include <mach/map.h> + +#define EXYNOS4_CLKDIV_LEFTBUS (S5P_VA_CMU + 0x04500) +#define EXYNOS4_CLKDIV_STAT_LEFTBUS (S5P_VA_CMU + 0x04600) + +#define EXYNOS4_CLKDIV_RIGHTBUS (S5P_VA_CMU + 0x08500) +#define EXYNOS4_CLKDIV_STAT_RIGHTBUS (S5P_VA_CMU + 0x08600) + +#define EXYNOS4_CLKDIV_TOP (S5P_VA_CMU + 0x0C510) +#define EXYNOS4_CLKDIV_CAM (S5P_VA_CMU + 0x0C520) +#define EXYNOS4_CLKDIV_MFC (S5P_VA_CMU + 0x0C528) + +#define EXYNOS4_CLKDIV_STAT_TOP (S5P_VA_CMU + 0x0C610) +#define EXYNOS4_CLKDIV_STAT_MFC (S5P_VA_CMU + 0x0C628) + +#define EXYNOS4210_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x0C930) +#define EXYNOS4212_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x04930) + +#define EXYNOS4_CLKDIV_DMC0 (S5P_VA_CMU + 0x10500) +#define EXYNOS4_CLKDIV_DMC1 (S5P_VA_CMU + 0x10504) +#define EXYNOS4_CLKDIV_STAT_DMC0 (S5P_VA_CMU + 0x10600) +#define EXYNOS4_CLKDIV_STAT_DMC1 (S5P_VA_CMU + 0x10604) + +#define EXYNOS4_DMC_PAUSE_CTRL (S5P_VA_CMU + 0x11094) +#define EXYNOS4_DMC_PAUSE_ENABLE (1 << 0) + +#define EXYNOS4_CLKDIV_DMC0_ACP_SHIFT (0) +#define EXYNOS4_CLKDIV_DMC0_ACP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT (4) +#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT (8) +#define EXYNOS4_CLKDIV_DMC0_DPHY_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_DMC_SHIFT (12) +#define EXYNOS4_CLKDIV_DMC0_DMC_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT (16) +#define EXYNOS4_CLKDIV_DMC0_DMCD_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT (20) +#define EXYNOS4_CLKDIV_DMC0_DMCP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT (24) +#define EXYNOS4_CLKDIV_DMC0_COPY2_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) +#define EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT (28) +#define EXYNOS4_CLKDIV_DMC0_CORETI_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT) + +#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT (0) +#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK (0xf << EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) +#define EXYNOS4_CLKDIV_DMC1_C2C_SHIFT (4) +#define EXYNOS4_CLKDIV_DMC1_C2C_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) +#define EXYNOS4_CLKDIV_DMC1_PWI_SHIFT (8) +#define EXYNOS4_CLKDIV_DMC1_PWI_MASK (0xf << EXYNOS4_CLKDIV_DMC1_PWI_SHIFT) +#define EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT (12) +#define EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT) +#define EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT (16) +#define EXYNOS4_CLKDIV_DMC1_DVSEM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT) +#define EXYNOS4_CLKDIV_DMC1_DPM_SHIFT (24) +#define EXYNOS4_CLKDIV_DMC1_DPM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DPM_SHIFT) + +#define EXYNOS4_CLKDIV_MFC_SHIFT (0) +#define EXYNOS4_CLKDIV_MFC_MASK (0x7 << EXYNOS4_CLKDIV_MFC_SHIFT) + +#define EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT (0) +#define EXYNOS4_CLKDIV_TOP_ACLK200_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT (4) +#define EXYNOS4_CLKDIV_TOP_ACLK100_MASK (0xF << EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT (8) +#define EXYNOS4_CLKDIV_TOP_ACLK160_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT (12) +#define EXYNOS4_CLKDIV_TOP_ACLK133_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT (16) +#define EXYNOS4_CLKDIV_TOP_ONENAND_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT (20) +#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) +#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT (24) +#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT) + +#define EXYNOS4_CLKDIV_BUS_GDLR_SHIFT (0) +#define EXYNOS4_CLKDIV_BUS_GDLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) +#define EXYNOS4_CLKDIV_BUS_GPLR_SHIFT (4) +#define EXYNOS4_CLKDIV_BUS_GPLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GPLR_SHIFT) + +#define EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT (0) +#define EXYNOS4_CLKDIV_CAM_FIMC0_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) +#define EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT (4) +#define EXYNOS4_CLKDIV_CAM_FIMC1_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) +#define EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT (8) +#define EXYNOS4_CLKDIV_CAM_FIMC2_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) +#define EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT (12) +#define EXYNOS4_CLKDIV_CAM_FIMC3_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT) + +#define EXYNOS4_CLKDIV_CAM1 (S5P_VA_CMU + 0x0C568) + +#define EXYNOS4_CLKDIV_STAT_CAM1 (S5P_VA_CMU + 0x0C668) + +#define EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT (0) +#define EXYNOS4_CLKDIV_CAM1_JPEG_MASK (0xf << EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT) + +#endif /* __DEVFREQ_EXYNOS4_BUS_H */ diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c new file mode 100644 index 000000000..297ea30d4 --- /dev/null +++ b/drivers/devfreq/exynos/exynos5_bus.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS5 INT clock frequency scaling support using DEVFREQ framework + * Based on work done by Jonghwan Choi <jhbird.choi@samsung.com> + * Support for only EXYNOS5250 is present. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/devfreq.h> +#include <linux/io.h> +#include <linux/pm_opp.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/pm_qos.h> +#include <linux/regulator/consumer.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> + +#include "exynos_ppmu.h" + +#define MAX_SAFEVOLT 1100000 /* 1.10V */ +/* Assume that the bus is saturated if the utilization is 25% */ +#define INT_BUS_SATURATION_RATIO 25 + +enum int_level_idx { + LV_0, + LV_1, + LV_2, + LV_3, + LV_4, + _LV_END +}; + +enum exynos_ppmu_list { + PPMU_RIGHT, + PPMU_END, +}; + +struct busfreq_data_int { + struct device *dev; + struct devfreq *devfreq; + struct regulator *vdd_int; + struct busfreq_ppmu_data ppmu_data; + unsigned long curr_freq; + bool disabled; + + struct notifier_block pm_notifier; + struct mutex lock; + struct pm_qos_request int_req; + struct clk *int_clk; +}; + +struct int_bus_opp_table { + unsigned int idx; + unsigned long clk; + unsigned long volt; +}; + +static struct int_bus_opp_table exynos5_int_opp_table[] = { + {LV_0, 266000, 1025000}, + {LV_1, 200000, 1025000}, + {LV_2, 160000, 1025000}, + {LV_3, 133000, 1025000}, + {LV_4, 100000, 1025000}, + {0, 0, 0}, +}; + +static int exynos5_int_setvolt(struct busfreq_data_int *data, + unsigned long volt) +{ + return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT); +} + +static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq, + u32 flags) +{ + int err = 0; + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct busfreq_data_int *data = platform_get_drvdata(pdev); + struct dev_pm_opp *opp; + unsigned long old_freq, freq; + unsigned long volt; + + rcu_read_lock(); + opp = devfreq_recommended_opp(dev, _freq, flags); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(dev, "%s: Invalid OPP.\n", __func__); + return PTR_ERR(opp); + } + + freq = dev_pm_opp_get_freq(opp); + volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + + old_freq = data->curr_freq; + + if (old_freq == freq) + return 0; + + dev_dbg(dev, "targeting %lukHz %luuV\n", freq, volt); + + mutex_lock(&data->lock); + + if (data->disabled) + goto out; + + if (freq > exynos5_int_opp_table[0].clk) + pm_qos_update_request(&data->int_req, freq * 16 / 1000); + else + pm_qos_update_request(&data->int_req, -1); + + if (old_freq < freq) + err = exynos5_int_setvolt(data, volt); + if (err) + goto out; + + err = clk_set_rate(data->int_clk, freq * 1000); + + if (err) + goto out; + + if (old_freq > freq) + err = exynos5_int_setvolt(data, volt); + if (err) + goto out; + + data->curr_freq = freq; +out: + mutex_unlock(&data->lock); + return err; +} + +static int exynos5_int_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct busfreq_data_int *data = platform_get_drvdata(pdev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; + int busier_dmc; + + exynos_read_ppmu(ppmu_data); + busier_dmc = exynos_get_busier_ppmu(ppmu_data); + + stat->current_frequency = data->curr_freq; + + /* Number of cycles spent on memory access */ + stat->busy_time = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3]; + stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO; + stat->total_time = ppmu_data->ppmu[busier_dmc].ccnt; + + return 0; +} + +static struct devfreq_dev_profile exynos5_devfreq_int_profile = { + .initial_freq = 160000, + .polling_ms = 100, + .target = exynos5_busfreq_int_target, + .get_dev_status = exynos5_int_get_dev_status, +}; + +static int exynos5250_init_int_tables(struct busfreq_data_int *data) +{ + int i, err = 0; + + for (i = LV_0; i < _LV_END; i++) { + err = dev_pm_opp_add(data->dev, exynos5_int_opp_table[i].clk, + exynos5_int_opp_table[i].volt); + if (err) { + dev_err(data->dev, "Cannot add opp entries.\n"); + return err; + } + } + + return 0; +} + +static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct busfreq_data_int *data = container_of(this, + struct busfreq_data_int, pm_notifier); + struct dev_pm_opp *opp; + unsigned long maxfreq = ULONG_MAX; + unsigned long freq; + unsigned long volt; + int err = 0; + + switch (event) { + case PM_SUSPEND_PREPARE: + /* Set Fastest and Deactivate DVFS */ + mutex_lock(&data->lock); + + data->disabled = true; + + rcu_read_lock(); + opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + err = PTR_ERR(opp); + goto unlock; + } + freq = dev_pm_opp_get_freq(opp); + volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + + err = exynos5_int_setvolt(data, volt); + if (err) + goto unlock; + + err = clk_set_rate(data->int_clk, freq * 1000); + + if (err) + goto unlock; + + data->curr_freq = freq; +unlock: + mutex_unlock(&data->lock); + if (err) + return NOTIFY_BAD; + return NOTIFY_OK; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + /* Reactivate */ + mutex_lock(&data->lock); + data->disabled = false; + mutex_unlock(&data->lock); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int exynos5_busfreq_int_probe(struct platform_device *pdev) +{ + struct busfreq_data_int *data; + struct busfreq_ppmu_data *ppmu_data; + struct dev_pm_opp *opp; + struct device *dev = &pdev->dev; + struct device_node *np; + unsigned long initial_freq; + unsigned long initial_volt; + int err = 0; + int i; + + data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int), + GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + + ppmu_data = &data->ppmu_data; + ppmu_data->ppmu_end = PPMU_END; + ppmu_data->ppmu = devm_kzalloc(dev, + sizeof(struct exynos_ppmu) * PPMU_END, + GFP_KERNEL); + if (!ppmu_data->ppmu) { + dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); + return -ENOMEM; + } + + np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu"); + if (np == NULL) { + pr_err("Unable to find PPMU node\n"); + return -ENOENT; + } + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + /* map PPMU memory region */ + ppmu_data->ppmu[i].hw_base = of_iomap(np, i); + if (ppmu_data->ppmu[i].hw_base == NULL) { + dev_err(&pdev->dev, "failed to map memory region\n"); + return -ENOMEM; + } + } + data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event; + data->dev = dev; + mutex_init(&data->lock); + + err = exynos5250_init_int_tables(data); + if (err) + return err; + + data->vdd_int = devm_regulator_get(dev, "vdd_int"); + if (IS_ERR(data->vdd_int)) { + dev_err(dev, "Cannot get the regulator \"vdd_int\"\n"); + return PTR_ERR(data->vdd_int); + } + + data->int_clk = devm_clk_get(dev, "int_clk"); + if (IS_ERR(data->int_clk)) { + dev_err(dev, "Cannot get clock \"int_clk\"\n"); + return PTR_ERR(data->int_clk); + } + + rcu_read_lock(); + opp = dev_pm_opp_find_freq_floor(dev, + &exynos5_devfreq_int_profile.initial_freq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(dev, "Invalid initial frequency %lu kHz.\n", + exynos5_devfreq_int_profile.initial_freq); + return PTR_ERR(opp); + } + initial_freq = dev_pm_opp_get_freq(opp); + initial_volt = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + data->curr_freq = initial_freq; + + err = clk_set_rate(data->int_clk, initial_freq * 1000); + if (err) { + dev_err(dev, "Failed to set initial frequency\n"); + return err; + } + + err = exynos5_int_setvolt(data, initial_volt); + if (err) + return err; + + platform_set_drvdata(pdev, data); + + busfreq_mon_reset(ppmu_data); + + data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile, + "simple_ondemand", NULL); + if (IS_ERR(data->devfreq)) + return PTR_ERR(data->devfreq); + + err = devm_devfreq_register_opp_notifier(dev, data->devfreq); + if (err < 0) { + dev_err(dev, "Failed to register opp notifier\n"); + return err; + } + + err = register_pm_notifier(&data->pm_notifier); + if (err) { + dev_err(dev, "Failed to setup pm notifier\n"); + return err; + } + + /* TODO: Add a new QOS class for int/mif bus */ + pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1); + + return 0; +} + +static int exynos5_busfreq_int_remove(struct platform_device *pdev) +{ + struct busfreq_data_int *data = platform_get_drvdata(pdev); + + pm_qos_remove_request(&data->int_req); + unregister_pm_notifier(&data->pm_notifier); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos5_busfreq_int_resume(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct busfreq_data_int *data = platform_get_drvdata(pdev); + struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; + + busfreq_mon_reset(ppmu_data); + return 0; +} +static const struct dev_pm_ops exynos5_busfreq_int_pm = { + .resume = exynos5_busfreq_int_resume, +}; +#endif +static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL, + exynos5_busfreq_int_resume); + +/* platform device pointer for exynos5 devfreq device. */ +static struct platform_device *exynos5_devfreq_pdev; + +static struct platform_driver exynos5_busfreq_int_driver = { + .probe = exynos5_busfreq_int_probe, + .remove = exynos5_busfreq_int_remove, + .driver = { + .name = "exynos5-bus-int", + .pm = &exynos5_busfreq_int_pm_ops, + }, +}; + +static int __init exynos5_busfreq_int_init(void) +{ + int ret; + + ret = platform_driver_register(&exynos5_busfreq_int_driver); + if (ret < 0) + goto out; + + exynos5_devfreq_pdev = + platform_device_register_simple("exynos5-bus-int", -1, NULL, 0); + if (IS_ERR(exynos5_devfreq_pdev)) { + ret = PTR_ERR(exynos5_devfreq_pdev); + goto out1; + } + + return 0; +out1: + platform_driver_unregister(&exynos5_busfreq_int_driver); +out: + return ret; +} +late_initcall(exynos5_busfreq_int_init); + +static void __exit exynos5_busfreq_int_exit(void) +{ + platform_device_unregister(exynos5_devfreq_pdev); + platform_driver_unregister(&exynos5_busfreq_int_driver); +} +module_exit(exynos5_busfreq_int_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework"); diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c new file mode 100644 index 000000000..97b75e513 --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS - PPMU support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/io.h> + +#include "exynos_ppmu.h" + +void exynos_ppmu_reset(void __iomem *ppmu_base) +{ + __raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base); + __raw_writel(PPMU_ENABLE_CYCLE | + PPMU_ENABLE_COUNT0 | + PPMU_ENABLE_COUNT1 | + PPMU_ENABLE_COUNT2 | + PPMU_ENABLE_COUNT3, + ppmu_base + PPMU_CNTENS); +} + +void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, + unsigned int evt) +{ + __raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch)); +} + +void exynos_ppmu_start(void __iomem *ppmu_base) +{ + __raw_writel(PPMU_ENABLE, ppmu_base); +} + +void exynos_ppmu_stop(void __iomem *ppmu_base) +{ + __raw_writel(PPMU_DISABLE, ppmu_base); +} + +unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch) +{ + unsigned int total; + + if (ch == PPMU_PMNCNT3) + total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) | + __raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1))); + else + total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch)); + + return total; +} + +void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data) +{ + unsigned int i; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base; + + /* Reset the performance and cycle counters */ + exynos_ppmu_reset(ppmu_base); + + /* Setup count registers to monitor read/write transactions */ + ppmu_data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT; + exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3, + ppmu_data->ppmu[i].event[PPMU_PMNCNT3]); + + exynos_ppmu_start(ppmu_base); + } +} +EXPORT_SYMBOL(busfreq_mon_reset); + +void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data) +{ + int i, j; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base; + + exynos_ppmu_stop(ppmu_base); + + /* Update local data from PPMU */ + ppmu_data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT); + + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { + if (ppmu_data->ppmu[i].event[j] == 0) + ppmu_data->ppmu[i].count[j] = 0; + else + ppmu_data->ppmu[i].count[j] = + exynos_ppmu_read(ppmu_base, j); + } + } + + busfreq_mon_reset(ppmu_data); +} +EXPORT_SYMBOL(exynos_read_ppmu); + +int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data) +{ + unsigned int count = 0; + int i, j, busy = 0; + + for (i = 0; i < ppmu_data->ppmu_end; i++) { + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) { + if (ppmu_data->ppmu[i].count[j] > count) { + count = ppmu_data->ppmu[i].count[j]; + busy = i; + } + } + } + + return busy; +} +EXPORT_SYMBOL(exynos_get_busier_ppmu); diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h new file mode 100644 index 000000000..71f17ba35 --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS PPMU header + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __DEVFREQ_EXYNOS_PPMU_H +#define __DEVFREQ_EXYNOS_PPMU_H __FILE__ + +#include <linux/ktime.h> + +/* For PPMU Control */ +#define PPMU_ENABLE BIT(0) +#define PPMU_DISABLE 0x0 +#define PPMU_CYCLE_RESET BIT(1) +#define PPMU_COUNTER_RESET BIT(2) + +#define PPMU_ENABLE_COUNT0 BIT(0) +#define PPMU_ENABLE_COUNT1 BIT(1) +#define PPMU_ENABLE_COUNT2 BIT(2) +#define PPMU_ENABLE_COUNT3 BIT(3) +#define PPMU_ENABLE_CYCLE BIT(31) + +#define PPMU_CNTENS 0x10 +#define PPMU_FLAG 0x50 +#define PPMU_CCNT_OVERFLOW BIT(31) +#define PPMU_CCNT 0x100 + +#define PPMU_PMCNT0 0x110 +#define PPMU_PMCNT_OFFSET 0x10 +#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x)) + +#define PPMU_BEVT0SEL 0x1000 +#define PPMU_BEVTSEL_OFFSET 0x100 +#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET)) + +/* For Event Selection */ +#define RD_DATA_COUNT 0x5 +#define WR_DATA_COUNT 0x6 +#define RDWR_DATA_COUNT 0x7 + +enum ppmu_counter { + PPMU_PMNCNT0, + PPMU_PMCCNT1, + PPMU_PMNCNT2, + PPMU_PMNCNT3, + PPMU_PMNCNT_MAX, +}; + +struct bus_opp_table { + unsigned int idx; + unsigned long clk; + unsigned long volt; +}; + +struct exynos_ppmu { + void __iomem *hw_base; + unsigned int ccnt; + unsigned int event[PPMU_PMNCNT_MAX]; + unsigned int count[PPMU_PMNCNT_MAX]; + unsigned long long ns; + ktime_t reset_time; + bool ccnt_overflow; + bool count_overflow[PPMU_PMNCNT_MAX]; +}; + +struct busfreq_ppmu_data { + struct exynos_ppmu *ppmu; + int ppmu_end; +}; + +void exynos_ppmu_reset(void __iomem *ppmu_base); +void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, + unsigned int evt); +void exynos_ppmu_start(void __iomem *ppmu_base); +void exynos_ppmu_stop(void __iomem *ppmu_base); +unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch); +void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data); +void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data); +int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data); +#endif /* __DEVFREQ_EXYNOS_PPMU_H */ diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h new file mode 100644 index 000000000..fad7d6321 --- /dev/null +++ b/drivers/devfreq/governor.h @@ -0,0 +1,41 @@ +/* + * governor.h - internal header for devfreq governors. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This header is for devfreq governors in drivers/devfreq/ + */ + +#ifndef _GOVERNOR_H +#define _GOVERNOR_H + +#include <linux/devfreq.h> + +#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_INTERVAL 0x3 +#define DEVFREQ_GOV_SUSPEND 0x4 +#define DEVFREQ_GOV_RESUME 0x5 + +/* Caution: devfreq->lock must be locked before calling update_devfreq */ +extern int update_devfreq(struct devfreq *devfreq); + +extern void devfreq_monitor_start(struct devfreq *devfreq); +extern void devfreq_monitor_stop(struct devfreq *devfreq); +extern void devfreq_monitor_suspend(struct devfreq *devfreq); +extern void devfreq_monitor_resume(struct devfreq *devfreq); +extern void devfreq_interval_update(struct devfreq *devfreq, + unsigned int *delay); + +extern int devfreq_add_governor(struct devfreq_governor *governor); +extern int devfreq_remove_governor(struct devfreq_governor *governor); + +#endif /* _GOVERNOR_H */ diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c new file mode 100644 index 000000000..c72f942f3 --- /dev/null +++ b/drivers/devfreq/governor_performance.c @@ -0,0 +1,67 @@ +/* + * linux/drivers/devfreq/governor_performance.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 + */ + if (!df->max_freq) + *freq = UINT_MAX; + else + *freq = df->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 = "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..0c6bed567 --- /dev/null +++ b/drivers/devfreq/governor_powersave.c @@ -0,0 +1,64 @@ +/* + * linux/drivers/devfreq/governor_powersave.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 = df->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 = "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..0720ba84c --- /dev/null +++ b/drivers/devfreq/governor_simpleondemand.c @@ -0,0 +1,147 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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) +{ + struct devfreq_dev_status stat; + int err = df->profile->get_dev_status(df->dev.parent, &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; + unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX; + + if (err) + return err; + + 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 = max; + 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 = max; + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat.current_frequency == 0) { + *freq = max; + 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; + + if (df->min_freq && *freq < df->min_freq) + *freq = df->min_freq; + if (df->max_freq && *freq > df->max_freq) + *freq = df->max_freq; + + 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_INTERVAL: + devfreq_interval_update(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 = "simple_ondemand", + .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..35de6e83c --- /dev/null +++ b/drivers/devfreq/governor_userspace.c @@ -0,0 +1,162 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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->data; + + if (data->valid) { + unsigned long adjusted_freq = data->user_frequency; + + if (df->max_freq && adjusted_freq > df->max_freq) + adjusted_freq = df->max_freq; + + if (df->min_freq && adjusted_freq < df->min_freq) + adjusted_freq = df->min_freq; + + *freq = adjusted_freq; + } else { + *freq = df->previous_freq; /* No user freq specified yet */ + } + return 0; +} + +static ssize_t store_freq(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->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 show_freq(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->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(set_freq, 0644, show_freq, store_freq); +static struct attribute *dev_entries[] = { + &dev_attr_set_freq.attr, + NULL, +}; +static struct attribute_group dev_attr_group = { + .name = "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->data = data; + + err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); +out: + return err; +} + +static void userspace_exit(struct devfreq *devfreq) +{ + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group); + kfree(devfreq->data); + devfreq->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 = "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/tegra-devfreq.c b/drivers/devfreq/tegra-devfreq.c new file mode 100644 index 000000000..13a1a6e81 --- /dev/null +++ b/drivers/devfreq/tegra-devfreq.c @@ -0,0 +1,785 @@ +/* + * A devfreq driver for NVIDIA Tegra SoCs + * + * Copyright (c) 2014 NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2014 Google, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/devfreq.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/reset.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_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 + +/* + * Activity counter is incremented every 256 memory transactions, and each + * transaction takes 4 EMC clocks for Tegra124; So the COUNT_WEIGHT is + * 4 * 256 = 1024. + */ +#define ACTMON_COUNT_WEIGHT 0x400 + +/* + * 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 + +/* 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) 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 struct tegra_devfreq_device_config actmon_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 = 90, + .boost_up_threshold = 27, + .boost_down_threshold = 10, + .avg_dependency_threshold = 50000, + }, +}; + +/** + * 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; + spinlock_t lock; + + /* 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 { + 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 rate_change_nb; + + struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)]; +}; + +struct tegra_actmon_emc_ratio { + unsigned long cpu_freq; + unsigned long emc_freq; +}; + +static struct tegra_actmon_emc_ratio actmon_emc_ratios[] = { + { 1400000, ULONG_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(tegra->regs + offset); +} + +static void actmon_writel(struct tegra_devfreq *tegra, u32 val, u32 offset) +{ + writel(val, tegra->regs + offset); +} + +static u32 device_readl(struct tegra_devfreq_device *dev, u32 offset) +{ + return readl(dev->regs + offset); +} + +static void device_writel(struct tegra_devfreq_device *dev, u32 val, + u32 offset) +{ + writel(val, dev->regs + offset); +} + +static unsigned long do_percent(unsigned long val, unsigned int pct) +{ + return val * pct / 100; +} + +static void tegra_devfreq_update_avg_wmark(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 avg = dev->avg_count; + u32 avg_band_freq = tegra->max_freq * ACTMON_DEFAULT_AVG_BAND / KHZ; + u32 band = avg_band_freq * ACTMON_SAMPLING_PERIOD; + + 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 * ACTMON_SAMPLING_PERIOD; + + 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_write_barrier(struct tegra_devfreq *tegra) +{ + /* ensure the update has reached the ACTMON */ + wmb(); + actmon_readl(tegra, ACTMON_GLB_STATUS); +} + +static void actmon_isr_device(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + unsigned long flags; + u32 intr_status, dev_ctrl; + + spin_lock_irqsave(&dev->lock, flags); + + 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->boost_freq = tegra->max_freq; + else + dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + } 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->boost_freq = 0; + else + dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + } + + if (dev->config->avg_dependency_threshold) { + if (dev->avg_count >= dev->config->avg_dependency_threshold) + dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + else if (dev->boost_freq == 0) + dev_ctrl &= ~ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + } + + device_writel(dev, dev_ctrl, ACTMON_DEV_CTRL); + + device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS); + + actmon_write_barrier(tegra); + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static irqreturn_t actmon_isr(int irq, void *data) +{ + struct tegra_devfreq *tegra = data; + bool handled = false; + unsigned int i; + u32 val; + + 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; + } + } + + return handled ? IRQ_WAKE_THREAD : IRQ_NONE; +} + +static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra, + unsigned long cpu_freq) +{ + unsigned int i; + 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 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; + unsigned int avg_sustain_coef; + unsigned long flags; + + if (dev->config->avg_dependency_threshold) { + cpu_freq = cpufreq_get(0); + static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); + } + + spin_lock_irqsave(&dev->lock, flags); + + dev->target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD; + avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; + dev->target_freq = do_percent(dev->target_freq, avg_sustain_coef); + dev->target_freq += dev->boost_freq; + + if (dev->avg_count >= dev->config->avg_dependency_threshold) + dev->target_freq = max(dev->target_freq, static_cpu_emc_freq); + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static irqreturn_t actmon_thread_isr(int irq, void *data) +{ + struct tegra_devfreq *tegra = data; + + mutex_lock(&tegra->devfreq->lock); + update_devfreq(tegra->devfreq); + mutex_unlock(&tegra->devfreq->lock); + + return IRQ_HANDLED; +} + +static int tegra_actmon_rate_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; + unsigned long flags; + + if (action != POST_RATE_CHANGE) + return NOTIFY_OK; + + tegra = container_of(nb, struct tegra_devfreq, rate_change_nb); + + tegra->cur_freq = data->new_rate / KHZ; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = &tegra->devices[i]; + + spin_lock_irqsave(&dev->lock, flags); + tegra_devfreq_update_wmark(tegra, dev); + spin_unlock_irqrestore(&dev->lock, flags); + } + + actmon_write_barrier(tegra); + + return NOTIFY_OK; +} + +static void tegra_actmon_enable_interrupts(struct tegra_devfreq *tegra) +{ + struct tegra_devfreq_device *dev; + u32 val; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = &tegra->devices[i]; + + val = device_readl(dev, ACTMON_DEV_CTRL); + val |= ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN; + val |= ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN; + val |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + val |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + + device_writel(dev, val, ACTMON_DEV_CTRL); + } + + actmon_write_barrier(tegra); +} + +static void tegra_actmon_disable_interrupts(struct tegra_devfreq *tegra) +{ + struct tegra_devfreq_device *dev; + u32 val; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { + dev = &tegra->devices[i]; + + val = device_readl(dev, ACTMON_DEV_CTRL); + val &= ~ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN; + val &= ~ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN; + val &= ~ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + val &= ~ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; + + device_writel(dev, val, ACTMON_DEV_CTRL); + } + + actmon_write_barrier(tegra); +} + +static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + u32 val = 0; + + dev->target_freq = tegra->cur_freq; + + dev->avg_count = tegra->cur_freq * ACTMON_SAMPLING_PERIOD; + 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, ACTMON_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_ENB; + + device_writel(dev, val, ACTMON_DEV_CTRL); + + actmon_write_barrier(tegra); +} + +static int tegra_devfreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct tegra_devfreq *tegra = dev_get_drvdata(dev); + struct dev_pm_opp *opp; + unsigned long rate = *freq * KHZ; + + rcu_read_lock(); + opp = devfreq_recommended_opp(dev, &rate, flags); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(dev, "Failed to find opp for %lu KHz\n", *freq); + return PTR_ERR(opp); + } + rate = dev_pm_opp_get_freq(opp); + rcu_read_unlock(); + + clk_set_min_rate(tegra->emc_clock, rate); + clk_set_rate(tegra->emc_clock, 0); + + return 0; +} + +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; + + stat->current_frequency = tegra->cur_freq; + + /* To be used by the tegra governor */ + stat->private_data = tegra; + + /* The below are to be used by the other governors */ + + 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 = ACTMON_SAMPLING_PERIOD * tegra->cur_freq; + + stat->busy_time = min(stat->busy_time, stat->total_time); + + return 0; +} + +static struct devfreq_dev_profile tegra_devfreq_profile = { + .polling_ms = 0, + .target = tegra_devfreq_target, + .get_dev_status = tegra_devfreq_get_dev_status, +}; + +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->profile->get_dev_status(devfreq->dev.parent, &stat); + if (err) + return err; + + 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); + } + + *freq = target_freq; + + return 0; +} + +static int tegra_governor_event_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + struct tegra_devfreq *tegra; + int ret = 0; + + tegra = dev_get_drvdata(devfreq->dev.parent); + + switch (event) { + case DEVFREQ_GOV_START: + devfreq_monitor_start(devfreq); + tegra_actmon_enable_interrupts(tegra); + break; + + case DEVFREQ_GOV_STOP: + tegra_actmon_disable_interrupts(tegra); + devfreq_monitor_stop(devfreq); + break; + + case DEVFREQ_GOV_SUSPEND: + tegra_actmon_disable_interrupts(tegra); + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + tegra_actmon_enable_interrupts(tegra); + break; + } + + return ret; +} + +static struct devfreq_governor tegra_devfreq_governor = { + .name = "tegra_actmon", + .get_target_freq = tegra_governor_get_target, + .event_handler = tegra_governor_event_handler, +}; + +static int tegra_devfreq_probe(struct platform_device *pdev) +{ + struct tegra_devfreq *tegra; + struct tegra_devfreq_device *dev; + struct resource *res; + unsigned int i; + unsigned long rate; + int irq; + int err; + + tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + tegra->regs = devm_ioremap_resource(&pdev->dev, res); + 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)) { + dev_err(&pdev->dev, "Failed to get emc clock\n"); + return PTR_ERR(tegra->emc_clock); + } + + clk_set_rate(tegra->emc_clock, ULONG_MAX); + + tegra->rate_change_nb.notifier_call = tegra_actmon_rate_notify_cb; + err = clk_notifier_register(tegra->emc_clock, &tegra->rate_change_nb); + if (err) { + dev_err(&pdev->dev, + "Failed to register rate change notifier\n"); + return err; + } + + reset_control_assert(tegra->reset); + + err = clk_prepare_enable(tegra->clock); + if (err) { + dev_err(&pdev->dev, + "Failed to prepare and enable ACTMON clock\n"); + return err; + } + + reset_control_deassert(tegra->reset); + + tegra->max_freq = clk_round_rate(tegra->emc_clock, ULONG_MAX) / KHZ; + tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ; + + actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1, + ACTMON_GLB_PERIOD_CTRL); + + for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) { + dev = tegra->devices + i; + dev->config = actmon_device_configs + i; + dev->regs = tegra->regs + dev->config->offset; + spin_lock_init(&dev->lock); + + tegra_actmon_configure_device(tegra, dev); + } + + for (rate = 0; rate <= tegra->max_freq * KHZ; rate++) { + rate = clk_round_rate(tegra->emc_clock, rate); + dev_pm_opp_add(&pdev->dev, rate, 0); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, tegra); + + err = devm_request_threaded_irq(&pdev->dev, irq, actmon_isr, + actmon_thread_isr, IRQF_SHARED, + "tegra-devfreq", tegra); + if (err) { + dev_err(&pdev->dev, "Interrupt request failed\n"); + return err; + } + + tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock); + tegra->devfreq = devm_devfreq_add_device(&pdev->dev, + &tegra_devfreq_profile, + "tegra_actmon", + NULL); + + return 0; +} + +static int tegra_devfreq_remove(struct platform_device *pdev) +{ + struct tegra_devfreq *tegra = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + u32 val; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) { + val = device_readl(&tegra->devices[i], ACTMON_DEV_CTRL); + val &= ~ACTMON_DEV_CTRL_ENB; + device_writel(&tegra->devices[i], val, ACTMON_DEV_CTRL); + } + + actmon_write_barrier(tegra); + + devm_free_irq(&pdev->dev, irq, tegra); + + clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb); + + clk_disable_unprepare(tegra->clock); + + return 0; +} + +static const struct of_device_id tegra_devfreq_of_match[] = { + { .compatible = "nvidia,tegra124-actmon" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, tegra_devfreq_of_match); + +static struct platform_driver tegra_devfreq_driver = { + .probe = tegra_devfreq_probe, + .remove = tegra_devfreq_remove, + .driver = { + .name = "tegra-devfreq", + .of_match_table = tegra_devfreq_of_match, + }, +}; + +static int __init tegra_devfreq_init(void) +{ + int ret = 0; + + ret = devfreq_add_governor(&tegra_devfreq_governor); + if (ret) { + pr_err("%s: failed to add governor: %d\n", __func__, ret); + return ret; + } + + ret = platform_driver_register(&tegra_devfreq_driver); + if (ret) + devfreq_remove_governor(&tegra_devfreq_governor); + + return ret; +} +module_init(tegra_devfreq_init) + +static void __exit tegra_devfreq_exit(void) +{ + int ret = 0; + + platform_driver_unregister(&tegra_devfreq_driver); + + ret = devfreq_remove_governor(&tegra_devfreq_governor); + if (ret) + pr_err("%s: failed to remove governor: %d\n", __func__, ret); +} +module_exit(tegra_devfreq_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Tegra devfreq driver"); +MODULE_AUTHOR("Tomeu Vizoso <tomeu.vizoso@collabora.com>"); |