msm: devfreq_cpubw: Add devfreq driver for CPU to DDR bandwidth voting

This driver registers itself as a devfreq device that allows devfreq
governors to make CPU to DDR IB/AB bandwidth votes. This driver allows the
governors to be agnostic of the bandwidth voting APIs, the number of CPU
master ports or DDR slave ports, the actual port numbers, the system
topology, the available DDR frequencies, etc.

Change-Id: If055ddd580afd41f9668b111e6c09a047488b2e0
Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
This commit is contained in:
Saravana Kannan 2013-11-22 16:46:16 -08:00
parent f0f3dd3bb3
commit a2dd9de8c7
4 changed files with 278 additions and 0 deletions

View File

@ -0,0 +1,30 @@
MSM CPU bandwidth device
cpubw is a device that represents the CPU subsystem master ports in a MSM SoC
and the related info that is needed to make CPU to DDR bandwidth votes.
Required properties:
- compatible: Must be "qcom,cpubw"
- qcom,cpu-mem-ports: A list of tuples where each tuple consists of a bus
master (CPU subsystem) port number and a bus slave
(memory) port number.
- qcom,bw-tbl: A list of meaningful instantaneous bandwidth values
(in MB/s) that can be requested from the CPU
subsystem to DDR. The list of values depend on the
supported DDR frequencies and the bus widths.
Example:
qcom,cpubw {
compatible = "qcom,cpubw";
qcom,cpu-mem-ports = <1 512>, <2 512>;
qcom,bw-tbl =
< 572 /* 75 MHz */ >,
< 1144 /* 150 MHz */ >,
< 1525 /* 200 MHz */ >,
< 2342 /* 307 MHz */ >,
< 3509 /* 460 MHz */ >,
< 4684 /* 614 MHz */ >,
< 6103 /* 800 MHz */ >,
< 7102 /* 931 MHz */ >;
};

View File

@ -11,6 +11,8 @@ config ARCH_MSM8974
select MSM_SCM
select MSM_GPIOMUX
select MSM_MULTIMEDIA_USE_ION
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select MSM_SPM_V2
select MSM_L2_SPM
@ -66,6 +68,8 @@ config ARCH_APQ8084
select MSM_RPM_LOG
select USB_ARCH_HAS_XHCI
select KRAIT_REGULATOR
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select ENABLE_VMALLOC_SAVINGS
select MSM_ULTRASOUND_B
@ -75,6 +79,8 @@ config ARCH_MPQ8092
bool "MPQ8092"
select REGULATOR
select ARCH_MSM_KRAITMP
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select GPIO_MSM_V3
select ARM_GIC
select HAVE_ARM_ARCH_TIMER
@ -103,6 +109,8 @@ config ARCH_FSM9900
select MSM_SCM
select HAVE_ARM_ARCH_TIMER
select MSM_GPIOMUX
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select MSM_NATIVE_RESTART
select REGULATOR
@ -133,6 +141,8 @@ config ARCH_MSMKRYPTON
select QMI_ENCDEC
select MSM_JTAG_MM if CORESIGHT_ETM
select MSM_CORTEX_A7
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
config ARCH_MSM8610
bool "MSM8610"
@ -159,6 +169,8 @@ config ARCH_MSM8610
select MSM_CORTEX_A7
select CPU_FREQ_MSM
select CPU_FREQ
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select MSM_RUN_QUEUE_STATS
select ARM_HAS_SG_CHAIN
@ -200,6 +212,8 @@ config ARCH_MSM8226
select MSM_CORTEX_A7
select CPU_FREQ_MSM
select CPU_FREQ
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select MSM_RUN_QUEUE_STATS
select ARM_HAS_SG_CHAIN
@ -224,6 +238,8 @@ config ARCH_MSMSAMARIUM
select CPU_V7
select HAVE_ARM_ARCH_TIMER
select MSM_SCM
select PM_DEVFREQ
select MSM_DEVFREQ_CPUBW
select MSM_PIL
select MSM_GPIOMUX
select MSM_SPM_V2
@ -807,6 +823,19 @@ config MSM_CPU_FREQ_MIN
endif # CPU_FREQ_MSM
config MSM_DEVFREQ_CPUBW
bool "Devfreq device for CPU<->DDR IB/AB BW voting"
depends on PM_DEVFREQ
select DEVFREQ_GOV_PERFORMANCE
select DEVFREQ_GOV_POWERSAVE
select DEVFREQ_GOV_USERSPACE
default n
help
Different devfreq governors use this devfreq device to make CPU to
DDR IB/AB bandwidth votes. This driver provides a SoC topology
agnostic interface to so that some of the devfreq governors can be
shared across SoCs.
config MSM_AVS_HW
bool "Enable Adaptive Voltage Scaling (AVS)"
default n

View File

@ -173,6 +173,7 @@ obj-$(CONFIG_MSM_SMCMOD) += smcmod.o
obj-$(CONFIG_ARCH_MSM8974) += msm_mpmctr.o
obj-$(CONFIG_MSM_CPR_REGULATOR) += cpr-regulator.o
obj-$(CONFIG_CPU_FREQ_MSM) += cpufreq.o
obj-$(CONFIG_MSM_DEVFREQ_CPUBW) += devfreq_cpubw.o
obj-$(CONFIG_ARCH_RANDOM) += early_random.o

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "cpubw: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/devfreq.h>
#include <linux/of.h>
#include <trace/events/power.h>
#include <mach/msm_bus.h>
#include <mach/msm_bus_board.h>
/* Has to be ULL to prevent overflow where this macro is used. */
#define MBYTE (1ULL << 20)
#define MAX_PATHS 2
static struct msm_bus_vectors vectors[MAX_PATHS * 2];
static struct msm_bus_paths bw_levels[] = {
{ .vectors = &vectors[0] },
{ .vectors = &vectors[MAX_PATHS] },
};
static struct msm_bus_scale_pdata bw_data = {
.usecase = bw_levels,
.num_usecases = ARRAY_SIZE(bw_levels),
.name = "devfreq_cpubw",
.active_only = 1,
};
static int num_paths;
static u32 bus_client;
static int set_bw(int new_ib, int new_ab)
{
static int cur_idx, cur_ab, cur_ib;
int i, ret;
if (cur_ib == new_ib && cur_ab == new_ab)
return 0;
i = (cur_idx + 1) % ARRAY_SIZE(bw_levels);
bw_levels[i].vectors[0].ib = new_ib * MBYTE;
bw_levels[i].vectors[0].ab = new_ab / num_paths * MBYTE;
bw_levels[i].vectors[1].ib = new_ib * MBYTE;
bw_levels[i].vectors[1].ab = new_ab / num_paths * MBYTE;
pr_debug("BW MBps: AB: %d IB: %d\n", new_ab, new_ib);
ret = msm_bus_scale_client_update_request(bus_client, i);
if (ret) {
pr_err("bandwidth request failed (%d)\n", ret);
} else {
cur_idx = i;
cur_ib = new_ib;
cur_ab = new_ab;
}
return ret;
}
static void find_freq(struct devfreq_dev_profile *p, unsigned long *freq,
u32 flags)
{
int i;
unsigned long atmost, atleast, f;
atmost = p->freq_table[0];
atleast = p->freq_table[p->max_state-1];
for (i = 0; i < p->max_state; i++) {
f = p->freq_table[i];
if (f <= *freq)
atmost = max(f, atmost);
if (f >= *freq)
atleast = min(f, atleast);
}
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND)
*freq = atmost;
else
*freq = atleast;
}
struct devfreq_dev_profile cpubw_profile;
static long gov_ab;
int cpubw_target(struct device *dev, unsigned long *freq, u32 flags)
{
find_freq(&cpubw_profile, freq, flags);
return set_bw(*freq, gov_ab);
}
static struct devfreq_governor_data gov_data[] = {
{ .name = "performance" },
{ .name = "powersave" },
{ .name = "userspace" },
};
struct devfreq_dev_profile cpubw_profile = {
.polling_ms = 50,
.target = cpubw_target,
.governor_data = gov_data,
.num_governor_data = ARRAY_SIZE(gov_data),
};
#define PROP_PORTS "qcom,cpu-mem-ports"
#define PROP_TBL "qcom,bw-tbl"
static int __init cpubw_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct devfreq_dev_profile *p = &cpubw_profile;
struct devfreq *df;
u32 *data, ports[MAX_PATHS * 2];
int ret, len, i;
if (of_find_property(dev->of_node, PROP_PORTS, &len)) {
len /= sizeof(ports[0]);
if (len % 2 || len > ARRAY_SIZE(ports)) {
dev_err(dev, "Unexpected number of ports\n");
return -EINVAL;
}
ret = of_property_read_u32_array(dev->of_node, PROP_PORTS,
ports, len);
if (ret)
return ret;
num_paths = len / 2;
} else {
return -EINVAL;
}
for (i = 0; i < num_paths; i++) {
bw_levels[0].vectors[i].src = ports[2 * i];
bw_levels[0].vectors[i].dst = ports[2 * i + 1];
bw_levels[1].vectors[i].src = ports[2 * i];
bw_levels[1].vectors[i].dst = ports[2 * i + 1];
}
bw_levels[0].num_paths = num_paths;
bw_levels[1].num_paths = num_paths;
if (of_find_property(dev->of_node, PROP_TBL, &len)) {
len /= sizeof(*data);
data = devm_kzalloc(dev, len * sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
p->freq_table = devm_kzalloc(dev,
len * sizeof(*p->freq_table),
GFP_KERNEL);
if (!p->freq_table)
return -ENOMEM;
ret = of_property_read_u32_array(dev->of_node, PROP_TBL,
data, len);
if (ret)
return ret;
for (i = 0; i < len; i++)
p->freq_table[i] = data[i];
p->max_state = len;
}
bus_client = msm_bus_scale_register_client(&bw_data);
if (!bus_client) {
dev_err(dev, "Unable to register bus client\n");
return -ENODEV;
}
df = devfreq_add_device(dev, &cpubw_profile, "powersave", NULL);
if (IS_ERR(df)) {
msm_bus_scale_unregister_client(bus_client);
return PTR_ERR(df);
}
return 0;
}
static struct of_device_id match_table[] = {
{ .compatible = "qcom,cpubw" },
{}
};
static struct platform_driver cpubw_driver = {
.driver = {
.name = "cpubw",
.of_match_table = match_table,
.owner = THIS_MODULE,
},
};
static int __init cpubw_init(void)
{
platform_driver_probe(&cpubw_driver, cpubw_probe);
return 0;
}
device_initcall(cpubw_init);
MODULE_DESCRIPTION("CPU DDR bandwidth voting driver MSM CPUs");
MODULE_LICENSE("GPL v2");