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

Commit cdb56cbf authored by Markus Mayer's avatar Markus Mayer Committed by Rafael J. Wysocki
Browse files

cpufreq: bmips-cpufreq: CPUfreq driver for Broadcom's BMIPS SoCs



Add the MIPS CPUfreq driver. This driver currently supports CPUfreq on
BMIPS5xxx-based SoCs.

Signed-off-by: default avatarMarkus Mayer <mmayer@broadcom.com>
Acked-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Acked-by: default avatarViresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent a8d709b0
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -263,6 +263,16 @@ config IA64_ACPI_CPUFREQ
endif

if MIPS
config BMIPS_CPUFREQ
	tristate "BMIPS CPUfreq Driver"
	help
	  This option adds a CPUfreq driver for BMIPS processors with
	  support for configurable CPU frequency.

	  For now, BMIPS5 chips are supported (such as the Broadcom 7425).

	  If in doubt, say N.

config LOONGSON2_CPUFREQ
	tristate "Loongson2 CPUFreq Driver"
	help
+1 −0
Original line number Diff line number Diff line
@@ -98,6 +98,7 @@ obj-$(CONFIG_POWERNV_CPUFREQ) += powernv-cpufreq.o
# Other platform drivers
obj-$(CONFIG_AVR32_AT32AP_CPUFREQ)	+= at32ap-cpufreq.o
obj-$(CONFIG_BFIN_CPU_FREQ)		+= blackfin-cpufreq.o
obj-$(CONFIG_BMIPS_CPUFREQ)		+= bmips-cpufreq.o
obj-$(CONFIG_CRIS_MACH_ARTPEC3)		+= cris-artpec3-cpufreq.o
obj-$(CONFIG_ETRAXFS)			+= cris-etraxfs-cpufreq.o
obj-$(CONFIG_IA64_ACPI_CPUFREQ)		+= ia64-acpi-cpufreq.o
+188 −0
Original line number Diff line number Diff line
/*
 * CPU frequency scaling for Broadcom BMIPS SoCs
 *
 * Copyright (c) 2017 Broadcom
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/slab.h>

/* for mips_hpt_frequency */
#include <asm/time.h>

#define BMIPS_CPUFREQ_PREFIX	"bmips"
#define BMIPS_CPUFREQ_NAME	BMIPS_CPUFREQ_PREFIX "-cpufreq"

#define TRANSITION_LATENCY	(25 * 1000)	/* 25 us */

#define BMIPS5_CLK_DIV_SET_SHIFT	0x7
#define BMIPS5_CLK_DIV_SHIFT		0x4
#define BMIPS5_CLK_DIV_MASK		0xf

enum bmips_type {
	BMIPS5000,
	BMIPS5200,
};

struct cpufreq_compat {
	const char *compatible;
	unsigned int bmips_type;
	unsigned int clk_mult;
	unsigned int max_freqs;
};

#define BMIPS(c, t, m, f) { \
	.compatible = c, \
	.bmips_type = (t), \
	.clk_mult = (m), \
	.max_freqs = (f), \
}

static struct cpufreq_compat bmips_cpufreq_compat[] = {
	BMIPS("brcm,bmips5000", BMIPS5000, 8, 4),
	BMIPS("brcm,bmips5200", BMIPS5200, 8, 4),
	{ }
};

static struct cpufreq_compat *priv;

static int htp_freq_to_cpu_freq(unsigned int clk_mult)
{
	return mips_hpt_frequency * clk_mult / 1000;
}

static struct cpufreq_frequency_table *
bmips_cpufreq_get_freq_table(const struct cpufreq_policy *policy)
{
	struct cpufreq_frequency_table *table;
	unsigned long cpu_freq;
	int i;

	cpu_freq = htp_freq_to_cpu_freq(priv->clk_mult);

	table = kmalloc((priv->max_freqs + 1) * sizeof(*table), GFP_KERNEL);
	if (!table)
		return ERR_PTR(-ENOMEM);

	for (i = 0; i < priv->max_freqs; i++) {
		table[i].frequency = cpu_freq / (1 << i);
		table[i].driver_data = i;
	}
	table[i].frequency = CPUFREQ_TABLE_END;

	return table;
}

static unsigned int bmips_cpufreq_get(unsigned int cpu)
{
	unsigned int div;
	uint32_t mode;

	switch (priv->bmips_type) {
	case BMIPS5200:
	case BMIPS5000:
		mode = read_c0_brcm_mode();
		div = ((mode >> BMIPS5_CLK_DIV_SHIFT) & BMIPS5_CLK_DIV_MASK);
		break;
	default:
		div = 0;
	}

	return htp_freq_to_cpu_freq(priv->clk_mult) / (1 << div);
}

static int bmips_cpufreq_target_index(struct cpufreq_policy *policy,
				      unsigned int index)
{
	unsigned int div = policy->freq_table[index].driver_data;

	switch (priv->bmips_type) {
	case BMIPS5200:
	case BMIPS5000:
		change_c0_brcm_mode(BMIPS5_CLK_DIV_MASK << BMIPS5_CLK_DIV_SHIFT,
				    (1 << BMIPS5_CLK_DIV_SET_SHIFT) |
				    (div << BMIPS5_CLK_DIV_SHIFT));
		break;
	default:
		return -ENOTSUPP;
	}

	return 0;
}

static int bmips_cpufreq_exit(struct cpufreq_policy *policy)
{
	kfree(policy->freq_table);

	return 0;
}

static int bmips_cpufreq_init(struct cpufreq_policy *policy)
{
	struct cpufreq_frequency_table *freq_table;
	int ret;

	freq_table = bmips_cpufreq_get_freq_table(policy);
	if (IS_ERR(freq_table)) {
		ret = PTR_ERR(freq_table);
		pr_err("%s: couldn't determine frequency table (%d).\n",
			BMIPS_CPUFREQ_NAME, ret);
		return ret;
	}

	ret = cpufreq_generic_init(policy, freq_table, TRANSITION_LATENCY);
	if (ret)
		bmips_cpufreq_exit(policy);
	else
		pr_info("%s: registered\n", BMIPS_CPUFREQ_NAME);

	return ret;
}

static struct cpufreq_driver bmips_cpufreq_driver = {
	.flags		= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
	.verify		= cpufreq_generic_frequency_table_verify,
	.target_index	= bmips_cpufreq_target_index,
	.get		= bmips_cpufreq_get,
	.init		= bmips_cpufreq_init,
	.exit		= bmips_cpufreq_exit,
	.attr		= cpufreq_generic_attr,
	.name		= BMIPS_CPUFREQ_PREFIX,
};

static int __init bmips_cpufreq_probe(void)
{
	struct cpufreq_compat *cc;
	struct device_node *np;

	for (cc = bmips_cpufreq_compat; cc->compatible; cc++) {
		np = of_find_compatible_node(NULL, "cpu", cc->compatible);
		if (np) {
			of_node_put(np);
			priv = cc;
			break;
		}
	}

	/* We hit the guard element of the array. No compatible CPU found. */
	if (!cc->compatible)
		return -ENODEV;

	return cpufreq_register_driver(&bmips_cpufreq_driver);
}
device_initcall(bmips_cpufreq_probe);

MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>");
MODULE_DESCRIPTION("CPUfreq driver for Broadcom BMIPS SoCs");
MODULE_LICENSE("GPL");