Merge tag 'pm+acpi-3.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm

Pull ACPI and power management updates from Rafael Wysocki:
 "We have a few new features this time, including a new SFI-based
  cpufreq driver, a new devfreq driver for Tegra Activity Monitor, a new
  devfreq class for providing its governors with raw utilization data
  and a new ACPI driver for AMD SoCs.

  Still, the majority of changes here are reworks of existing code to
  make it more straightforward or to prepare it for implementing new
  features on top of it.  The primary example is the rework of ACPI
  resources handling from Jiang Liu, Thomas Gleixner and Lv Zheng with
  support for IOAPIC hotplug implemented on top of it, but there is
  quite a number of changes of this kind in the cpufreq core, ACPICA,
  ACPI EC driver, ACPI processor driver and the generic power domains
  core code too.

  The most active developer is Viresh Kumar with his cpufreq changes.

  Specifics:

   - Rework of the core ACPI resources parsing code to fix issues in it
     and make using resource offsets more convenient and consolidation
     of some resource-handing code in a couple of places that have grown
     analagous data structures and code to cover the the same gap in the
     core (Jiang Liu, Thomas Gleixner, Lv Zheng).

   - ACPI-based IOAPIC hotplug support on top of the resources handling
     rework (Jiang Liu, Yinghai Lu).

   - ACPICA update to upstream release 20150204 including an interrupt
     handling rework that allows drivers to install raw handlers for
     ACPI GPEs which then become entirely responsible for the given GPE
     and the ACPICA core code won't touch it (Lv Zheng, David E Box,
     Octavian Purdila).

   - ACPI EC driver rework to fix several concurrency issues and other
     problems related to events handling on top of the ACPICA's new
     support for raw GPE handlers (Lv Zheng).

   - New ACPI driver for AMD SoCs analogous to the LPSS (Low-Power
     Subsystem) driver for Intel chips (Ken Xue).

   - Two minor fixes of the ACPI LPSS driver (Heikki Krogerus, Jarkko
     Nikula).

   - Two new blacklist entries for machines (Samsung 730U3E/740U3E and
     510R) where the native backlight interface doesn't work correctly
     while the ACPI one does (Hans de Goede).

   - Rework of the ACPI processor driver's handling of idle states to
     make the code more straightforward and less bloated overall (Rafael
     J Wysocki).

   - Assorted minor fixes related to ACPI and SFI (Andreas Ruprecht,
     Andy Shevchenko, Hanjun Guo, Jan Beulich, Rafael J Wysocki, Yaowei
     Bai).

   - PCI core power management modification to avoid resuming (some)
     runtime-suspended devices during system suspend if they are in the
     right states already (Rafael J Wysocki).

   - New SFI-based cpufreq driver for Intel platforms using SFI
     (Srinidhi Kasagar).

   - cpufreq core fixes, cleanups and simplifications (Viresh Kumar,
     Doug Anderson, Wolfram Sang).

   - SkyLake CPU support and other updates for the intel_pstate driver
     (Kristen Carlson Accardi, Srinivas Pandruvada).

   - cpufreq-dt driver cleanup (Markus Elfring).

   - Init fix for the ARM big.LITTLE cpuidle driver (Sudeep Holla).

   - Generic power domains core code fixes and cleanups (Ulf Hansson).

   - Operating Performance Points (OPP) core code cleanups and kernel
     documentation update (Nishanth Menon).

   - New dabugfs interface to make the list of PM QoS constraints
     available to user space (Nishanth Menon).

   - New devfreq driver for Tegra Activity Monitor (Tomeu Vizoso).

   - New devfreq class (devfreq_event) to provide raw utilization data
     to devfreq governors (Chanwoo Choi).

   - Assorted minor fixes and cleanups related to power management
     (Andreas Ruprecht, Krzysztof Kozlowski, Rickard Strandqvist, Pavel
     Machek, Todd E Brandt, Wonhong Kwon).

   - turbostat updates (Len Brown) and cpupower Makefile improvement
     (Sriram Raghunathan)"

* tag 'pm+acpi-3.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (151 commits)
  tools/power turbostat: relax dependency on APERF_MSR
  tools/power turbostat: relax dependency on invariant TSC
  Merge branch 'pci/host-generic' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci into acpi-resources
  tools/power turbostat: decode MSR_*_PERF_LIMIT_REASONS
  tools/power turbostat: relax dependency on root permission
  ACPI / video: Add disable_native_backlight quirk for Samsung 510R
  ACPI / PM: Remove unneeded nested #ifdef
  USB / PM: Remove unneeded #ifdef and associated dead code
  intel_pstate: provide option to only use intel_pstate with HWP
  ACPI / EC: Add GPE reference counting debugging messages
  ACPI / EC: Add query flushing support
  ACPI / EC: Refine command storm prevention support
  ACPI / EC: Add command flushing support.
  ACPI / EC: Introduce STARTED/STOPPED flags to replace BLOCKED flag
  ACPI: add AMD ACPI2Platform device support for x86 system
  ACPI / table: remove duplicate NULL check for the handler of acpi_table_parse()
  ACPI / EC: Update revision due to raw handler mode.
  ACPI / EC: Reduce ec_poll() by referencing the last register access timestamp.
  ACPI / EC: Fix several GPE handling issues by deploying ACPI_GPE_DISPATCH_RAW_HANDLER mode.
  ACPICA: Events: Enable APIs to allow interrupt/polling adaptive request based GPE handling model
  ...
This commit is contained in:
Linus Torvalds
2015-02-10 15:09:41 -08:00
299 changed files with 5325 additions and 1837 deletions
+12
View File
@@ -88,4 +88,16 @@ config ARM_EXYNOS5_BUS_DEVFREQ
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
+5
View File
@@ -1,4 +1,5 @@
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
@@ -7,3 +8,7 @@ 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/
+494
View File
@@ -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");
+25
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
# Exynos DEVFREQ Event Drivers
obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
+374
View File
@@ -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 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)) {
ret = PTR_ERR(edev);
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");
+93
View File
@@ -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__ */
File diff suppressed because it is too large Load Diff