Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0fa51886 authored by Praveen Chidambaram's avatar Praveen Chidambaram Committed by Stephen Boyd
Browse files

msm: dcvs: Add 'msm-dcvs' cpufreq governor



The 'msm-dcvs' CPUFreq governor interfaces the msm_dcvs driver frequency
change requests with the CPUFreq framework.

Change-Id: I950e5b09f568412760d9b022f59f208c6bcb54ce
Signed-off-by: default avatarPraveen Chidambaram <pchidamb@codeaurora.org>
Signed-off-by: default avatarStephen Boyd <sboyd@codeaurora.org>
parent ee3406fe
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
/* Copyright (c) 2012, 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.
 *
 */
#ifndef _ARCH_ARM_MACH_MSM_MSM_DCVS_H
#define _ARCH_ARM_MACH_MSM_MSM_DCVS_H

#include <mach/msm_dcvs_scm.h>

#define CORE_NAME_MAX (32)
#define CORES_MAX (10)

enum msm_core_idle_state {
	MSM_DCVS_IDLE_ENTER,
	MSM_DCVS_IDLE_EXIT,
};

enum msm_core_control_event {
	MSM_DCVS_ENABLE_IDLE_PULSE,
	MSM_DCVS_DISABLE_IDLE_PULSE,
	MSM_DCVS_ENABLE_HIGH_LATENCY_MODES,
	MSM_DCVS_DISABLE_HIGH_LATENCY_MODES,
};

/**
 * struct msm_dcvs_idle
 *
 * API for idle code to register and send idle enter/exit
 * notifications to msm_dcvs driver.
 */
struct msm_dcvs_idle {
	const char *core_name;
	/* Enable/Disable idle state/notifications */
	int (*enable)(struct msm_dcvs_idle *self,
			enum msm_core_control_event event);
};

/**
 * msm_dcvs_idle_source_register
 * @drv: Pointer to the source driver
 * @return: Handle to be used for sending idle state notifications.
 *
 * Register the idle driver with the msm_dcvs driver to send idle
 * state notifications for the core.
 */
extern int msm_dcvs_idle_source_register(struct msm_dcvs_idle *drv);

/**
 * msm_dcvs_idle_source_unregister
 * @drv: Pointer to the source driver
 * @return:
 *	0 on success
 *	-EINVAL
 *
 * Description: Unregister the idle driver with the msm_dcvs driver
 */
extern int msm_dcvs_idle_source_unregister(struct msm_dcvs_idle *drv);

/**
 * msm_dcvs_idle
 * @handle: Handle provided back at registration
 * @state: The enter/exit idle state the core is in
 * @iowaited: iowait in us
 * on iMSM_DCVS_IDLE_EXIT.
 * @return:
 *	0 on success,
 *	-ENOSYS,
 *	-EINVAL,
 *	SCM return values
 *
 * Send idle state notifications to the msm_dcvs driver
 */
int msm_dcvs_idle(int handle, enum msm_core_idle_state state,
		uint32_t iowaited);

/**
 * struct msm_dcvs_core_info
 *
 * Core specific information used by algorithm. Need to provide this
 * before the sink driver can be registered.
 */
struct msm_dcvs_core_info {
	struct msm_dcvs_freq_entry *freq_tbl;
	struct msm_dcvs_core_param core_param;
	struct msm_dcvs_algo_param algo_param;
};

/**
 * msm_dcvs_register_core
 * @core_name: Unique name identifier for the core.
 * @group_id: Cores that are to be grouped for synchronized frequency scaling
 * @info: The core specific algorithm parameters.
 * @return :
 *	0 on success,
 *	-ENOSYS,
 *	-ENOMEM
 *
 * Register the core with msm_dcvs driver. Done once at init before calling
 * msm_dcvs_freq_sink_register
 * Cores that need to run synchronously must share the same group id.
 * If a core doesnt care to be in any group, the group_id should be 0.
 */
extern int msm_dcvs_register_core(const char *core_name, uint32_t group_id,
		struct msm_dcvs_core_info *info);

/**
 * struct msm_dcvs_freq
 *
 * API for clock driver code to register and receive frequency change
 * request for the core from the msm_dcvs driver.
 */
struct msm_dcvs_freq {
	const char *core_name;
	/* Callback from msm_dcvs to set the core frequency */
	int (*set_frequency)(struct msm_dcvs_freq *self,
			unsigned int freq);
	unsigned int (*get_frequency)(struct msm_dcvs_freq *self);
};

/**
 * msm_dcvs_freq_sink_register
 * @drv: The sink driver
 * @return: Handle unique to the core.
 *
 * Register the clock driver code with the msm_dvs driver to get notified about
 * frequency change requests.
 */
extern int msm_dcvs_freq_sink_register(struct msm_dcvs_freq *drv);

/**
 * msm_dcvs_freq_sink_unregister
 * @drv: The sink driver
 * @return:
 *	0 on success,
 *	-EINVAL
 *
 * Unregister the sink driver for the core. This will cause the source driver
 * for the core to stop sending idle pulses.
 */
extern int msm_dcvs_freq_sink_unregister(struct msm_dcvs_freq *drv);

#endif
+168 −0
Original line number Diff line number Diff line
/* Copyright (c) 2012, 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.
 *
 */
#ifndef _ARCH_ARM_MACH_MSM_MSM_DCVS_SCM_H
#define _ARCH_ARM_MACH_MSM_MSM_DCVS_SCM_H

enum msm_dcvs_scm_event {
	MSM_DCVS_SCM_IDLE_ENTER,
	MSM_DCVS_SCM_IDLE_EXIT,
	MSM_DCVS_SCM_QOS_TIMER_EXPIRED,
	MSM_DCVS_SCM_CLOCK_FREQ_UPDATE,
	MSM_DCVS_SCM_ENABLE_CORE,
	MSM_DCVS_SCM_RESET_CORE,
};

struct msm_dcvs_algo_param {
	uint32_t slack_time_us;
	uint32_t scale_slack_time;
	uint32_t scale_slack_time_pct;
	uint32_t disable_pc_threshold;
	uint32_t em_window_size;
	uint32_t em_max_util_pct;
	uint32_t ss_window_size;
	uint32_t ss_util_pct;
	uint32_t ss_iobusy_conv;
};

struct msm_dcvs_freq_entry {
	uint32_t freq; /* Core freq in MHz */
	uint32_t idle_energy;
	uint32_t active_energy;
};

struct msm_dcvs_core_param {
	uint32_t max_time_us;
	uint32_t num_freq; /* number of msm_dcvs_freq_entry passed */
};


#ifdef CONFIG_MSM_DCVS
/**
 * Initialize DCVS algorithm in TrustZone.
 * Must call before invoking any other DCVS call into TZ.
 *
 * @size: Size of buffer in bytes
 *
 * @return:
 *	0 on success.
 *	-EEXIST: DCVS algorithm already initialized.
 *	-EINVAL: Invalid args.
 */
extern int msm_dcvs_scm_init(size_t size);

/**
 * Create an empty core group
 *
 * @return:
 *	0 on success.
 *	-ENOMEM: Insufficient memory.
 *	-EINVAL: Invalid args.
 */
extern int msm_dcvs_scm_create_group(uint32_t id);

/**
 * Registers cores as part of a group
 *
 * @core_id: The core identifier that will be used for communication with DCVS
 * @group_id: The group to which this core will be added to.
 * @param: The core parameters
 * @freq: Array of frequency and energy values
 *
 * @return:
 *	0 on success.
 *	-ENOMEM: Insufficient memory.
 *	-EINVAL: Invalid args.
 */
extern int msm_dcvs_scm_register_core(uint32_t core_id, uint32_t group_id,
		struct msm_dcvs_core_param *param,
		struct msm_dcvs_freq_entry *freq);

/**
 * Set DCVS algorithm parameters
 *
 * @core_id: The algorithm parameters specific for the core
 * @param: The param data structure
 *
 * @return:
 *	0 on success.
 *	-EINVAL: Invalid args.
 */
extern int msm_dcvs_scm_set_algo_params(uint32_t core_id,
		struct msm_dcvs_algo_param *param);

/**
 * Do an SCM call.
 *
 * @core_id: The core identifier.
 * @event_id: The event that occured.
 *	Possible values:
 *	MSM_DCVS_SCM_IDLE_ENTER
 *		@param0: unused
 *		@param1: unused
 *		@ret0: unused
 *		@ret1: unused
 *	MSM_DCVS_SCM_IDLE_EXIT
 *		@param0: Did the core iowait
 *		@param1: unused
 *		@ret0: New clock frequency for the core in KHz
 *		@ret1: New QoS timer value for the core in usec
 *	MSM_DCVS_SCM_QOS_TIMER_EXPIRED
 *		@param0: unused
 *		@param1: unused
 *		@ret0: New clock frequency for the core in KHz
 *		@ret1: unused
 *	MSM_DCVS_SCM_CLOCK_FREQ_UPDATE
 *		@param0: active clock frequency of the core in KHz
 *		@param1: time taken in usec to switch to the frequency
 *		@ret0: New QoS timer value for the core in usec
 *		@ret1: unused
 *	MSM_DCVS_SCM_ENABLE_CORE
 *		@param0: enable(1) or disable(0) core
 *		@param1: active clock frequency of the core in KHz
 *		@ret0: New clock frequency for the core in KHz
 *		@ret1: unused
 *	MSM_DCVS_SCM_RESET_CORE
 *		@param0: active clock frequency of the core in KHz
 *		@param1: unused
 *		@ret0: New clock frequency for the core in KHz
 *		@ret1: unused
 * @return:
 *	0 on success,
 *	SCM return values
 */
extern int msm_dcvs_scm_event(uint32_t core_id,
		enum msm_dcvs_scm_event event_id,
		uint32_t param0, uint32_t param1,
		uint32_t *ret0, uint32_t *ret1);

#else
static inline int msm_dcvs_scm_init(uint32_t phy, size_t bytes)
{ return -ENOSYS; }
static inline int msm_dcvs_scm_create_group(uint32_t id)
{ return -ENOSYS; }
static inline int msm_dcvs_scm_register_core(uint32_t core_id,
		uint32_t group_id,
		struct msm_dcvs_core_param *param,
		struct msm_dcvs_freq_entry *freq)
{ return -ENOSYS; }
static inline int msm_dcvs_scm_set_algo_params(uint32_t core_id,
		struct msm_dcvs_algo_param *param)
{ return -ENOSYS; }
static inline int msm_dcvs_scm_event(uint32_t core_id,
		enum msm_dcvs_scm_event event_id,
		uint32_t param0, uint32_t param1,
		uint32_t *ret0, uint32_t *ret1)
{ return -ENOSYS; }
#endif

#endif
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o
obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE)	+= cpufreq_conservative.o
obj-$(CONFIG_CPU_FREQ_GOV_INTERACTIVE)	+= cpufreq_interactive.o
obj-$(CONFIG_CPU_FREQ_GOV_COMMON)		+= cpufreq_governor.o
obj-$(CONFIG_MSM_DCVS)			+= cpufreq_gov_msm.o

# CPUfreq cross-arch helpers
obj-$(CONFIG_CPU_FREQ_TABLE)		+= freq_table.o
+176 −0
Original line number Diff line number Diff line
/* Copyright (c) 2012, 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.
 *
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/kobject.h>
#include <linux/cpufreq.h>
#include <linux/platform_device.h>
#include <mach/msm_dcvs.h>

struct msm_gov {
	int cpu;
	unsigned int cur_freq;
	unsigned int min_freq;
	unsigned int max_freq;
	struct msm_dcvs_freq gov_notifier;
	struct cpufreq_policy *policy;
};

static DEFINE_PER_CPU_SHARED_ALIGNED(struct mutex, gov_mutex);
static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_gov, msm_gov_info);
static char core_name[NR_CPUS][10];

static void msm_gov_check_limits(struct cpufreq_policy *policy)
{
	struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu);

	if (policy->max < gov->cur_freq)
		__cpufreq_driver_target(policy, policy->max,
				CPUFREQ_RELATION_H);
	else if (policy->min > gov->min_freq)
		__cpufreq_driver_target(policy, policy->min,
				CPUFREQ_RELATION_L);
	else
		__cpufreq_driver_target(policy, gov->cur_freq,
				CPUFREQ_RELATION_L);

	gov->cur_freq = policy->cur;
	gov->min_freq = policy->min;
	gov->max_freq = policy->max;
}

static int msm_dcvs_freq_set(struct msm_dcvs_freq *self,
		unsigned int freq)
{
	int ret = -EINVAL;
	struct msm_gov *gov =
		container_of(self, struct msm_gov, gov_notifier);

	mutex_lock(&per_cpu(gov_mutex, gov->cpu));

	if (freq < gov->min_freq)
		freq = gov->min_freq;
	if (freq > gov->max_freq)
		freq = gov->max_freq;

	ret = __cpufreq_driver_target(gov->policy, freq, CPUFREQ_RELATION_L);
	gov->cur_freq = gov->policy->cur;

	mutex_unlock(&per_cpu(gov_mutex, gov->cpu));

	if (!ret)
		return gov->cur_freq;

	return ret;
}

static unsigned int msm_dcvs_freq_get(struct msm_dcvs_freq *self)
{
	struct msm_gov *gov =
		container_of(self, struct msm_gov, gov_notifier);

	return gov->cur_freq;
}

static int cpufreq_governor_msm(struct cpufreq_policy *policy,
		unsigned int event)
{
	unsigned int cpu = policy->cpu;
	int ret = 0;
	int handle = 0;
	struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu);
	struct msm_dcvs_freq *dcvs_notifier =
			&(per_cpu(msm_gov_info, cpu).gov_notifier);

	switch (event) {
	case CPUFREQ_GOV_START:
		if (!cpu_online(cpu))
			return -EINVAL;
		BUG_ON(!policy->cur);
		mutex_lock(&per_cpu(gov_mutex, cpu));
		per_cpu(msm_gov_info, cpu).cpu = cpu;
		gov->policy = policy;
		dcvs_notifier->core_name = core_name[cpu];
		dcvs_notifier->set_frequency = msm_dcvs_freq_set;
		dcvs_notifier->get_frequency = msm_dcvs_freq_get;
		handle = msm_dcvs_freq_sink_register(dcvs_notifier);
		BUG_ON(handle < 0);
		msm_gov_check_limits(policy);
		mutex_unlock(&per_cpu(gov_mutex, cpu));
		break;

	case CPUFREQ_GOV_STOP:
		mutex_lock(&per_cpu(gov_mutex, cpu));
		msm_dcvs_freq_sink_unregister(dcvs_notifier);
		mutex_unlock(&per_cpu(gov_mutex, cpu));
		break;

	case CPUFREQ_GOV_LIMITS:
		mutex_lock(&per_cpu(gov_mutex, cpu));
		msm_gov_check_limits(policy);
		mutex_unlock(&per_cpu(gov_mutex, cpu));
		break;
	};

	return ret;
}

struct cpufreq_governor cpufreq_gov_msm = {
	.name = "msm-dcvs",
	.governor = cpufreq_governor_msm,
	.owner = THIS_MODULE,
};

static int msm_gov_probe(struct platform_device *pdev)
{
	int ret = 0;
	int cpu;
	uint32_t group_id = 0x43505530; /* CPU0 */
	struct msm_dcvs_core_info *core = NULL;

	core = pdev->dev.platform_data;

	for_each_possible_cpu(cpu) {
		mutex_init(&per_cpu(gov_mutex, cpu));
		snprintf(core_name[cpu], 10, "cpu%d", cpu);
		ret = msm_dcvs_register_core(core_name[cpu], group_id, core);
		if (ret)
			pr_err("Unable to register core for %d\n", cpu);
	}

	return cpufreq_register_governor(&cpufreq_gov_msm);
}

static int msm_gov_remove(struct platform_device *pdev)
{
	platform_set_drvdata(pdev, NULL);
	return 0;
}

static struct platform_driver msm_gov_driver = {
	.probe = msm_gov_probe,
	.remove = msm_gov_remove,
	.driver = {
		.name = "msm_dcvs_gov",
		.owner = THIS_MODULE,
	},
};

static int __init cpufreq_gov_msm_init(void)
{
	return platform_driver_register(&msm_gov_driver);
}
late_initcall(cpufreq_gov_msm_init);