123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- From 92ce45fb875d7c3e021cc454482fe0687ff54f29 Mon Sep 17 00:00:00 2001
- From: Gregory CLEMENT <gregory.clement@free-electrons.com>
- Date: Thu, 14 Dec 2017 16:00:05 +0100
- Subject: cpufreq: Add DVFS support for Armada 37xx
- This patch adds DVFS support for the Armada 37xx SoCs
- There are up to four CPU frequency loads for Armada 37xx controlled by
- the hardware.
- This driver associates the CPU load level to a frequency, then the
- hardware will switch while selecting a load level.
- The hardware also can associate a voltage for each level (AVS support)
- but it is not yet supported
- Tested-by: Andre Heider <a.heider@gmail.com>
- Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
- Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
- Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
- ---
- drivers/cpufreq/Kconfig.arm | 7 +
- drivers/cpufreq/Makefile | 1 +
- drivers/cpufreq/armada-37xx-cpufreq.c | 241 ++++++++++++++++++++++++++++++++++
- 3 files changed, 249 insertions(+)
- create mode 100644 drivers/cpufreq/armada-37xx-cpufreq.c
- --- a/drivers/cpufreq/Kconfig.arm
- +++ b/drivers/cpufreq/Kconfig.arm
- @@ -2,6 +2,13 @@
- # ARM CPU Frequency scaling drivers
- #
-
- +config ARM_ARMADA_37XX_CPUFREQ
- + tristate "Armada 37xx CPUFreq support"
- + depends on ARCH_MVEBU
- + help
- + This adds the CPUFreq driver support for Marvell Armada 37xx SoCs.
- + The Armada 37xx PMU supports 4 frequency and VDD levels.
- +
- # big LITTLE core layer and glue drivers
- config ARM_BIG_LITTLE_CPUFREQ
- tristate "Generic ARM big LITTLE CPUfreq driver"
- --- a/drivers/cpufreq/Makefile
- +++ b/drivers/cpufreq/Makefile
- @@ -52,6 +52,7 @@ obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) +=
- # LITTLE drivers, so that it is probed last.
- obj-$(CONFIG_ARM_DT_BL_CPUFREQ) += arm_big_little_dt.o
-
- +obj-$(CONFIG_ARM_ARMADA_37XX_CPUFREQ) += armada-37xx-cpufreq.o
- obj-$(CONFIG_ARM_BRCMSTB_AVS_CPUFREQ) += brcmstb-avs-cpufreq.o
- obj-$(CONFIG_ARCH_DAVINCI) += davinci-cpufreq.o
- obj-$(CONFIG_ARM_EXYNOS5440_CPUFREQ) += exynos5440-cpufreq.o
- --- /dev/null
- +++ b/drivers/cpufreq/armada-37xx-cpufreq.c
- @@ -0,0 +1,241 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * CPU frequency scaling support for Armada 37xx platform.
- + *
- + * Copyright (C) 2017 Marvell
- + *
- + * Gregory CLEMENT <gregory.clement@free-electrons.com>
- + */
- +
- +#include <linux/clk.h>
- +#include <linux/cpu.h>
- +#include <linux/cpufreq.h>
- +#include <linux/err.h>
- +#include <linux/interrupt.h>
- +#include <linux/io.h>
- +#include <linux/mfd/syscon.h>
- +#include <linux/module.h>
- +#include <linux/of_address.h>
- +#include <linux/of_device.h>
- +#include <linux/of_irq.h>
- +#include <linux/platform_device.h>
- +#include <linux/pm_opp.h>
- +#include <linux/regmap.h>
- +#include <linux/slab.h>
- +
- +/* Power management in North Bridge register set */
- +#define ARMADA_37XX_NB_L0L1 0x18
- +#define ARMADA_37XX_NB_L2L3 0x1C
- +#define ARMADA_37XX_NB_TBG_DIV_OFF 13
- +#define ARMADA_37XX_NB_TBG_DIV_MASK 0x7
- +#define ARMADA_37XX_NB_CLK_SEL_OFF 11
- +#define ARMADA_37XX_NB_CLK_SEL_MASK 0x1
- +#define ARMADA_37XX_NB_CLK_SEL_TBG 0x1
- +#define ARMADA_37XX_NB_TBG_SEL_OFF 9
- +#define ARMADA_37XX_NB_TBG_SEL_MASK 0x3
- +#define ARMADA_37XX_NB_VDD_SEL_OFF 6
- +#define ARMADA_37XX_NB_VDD_SEL_MASK 0x3
- +#define ARMADA_37XX_NB_CONFIG_SHIFT 16
- +#define ARMADA_37XX_NB_DYN_MOD 0x24
- +#define ARMADA_37XX_NB_CLK_SEL_EN BIT(26)
- +#define ARMADA_37XX_NB_TBG_EN BIT(28)
- +#define ARMADA_37XX_NB_DIV_EN BIT(29)
- +#define ARMADA_37XX_NB_VDD_EN BIT(30)
- +#define ARMADA_37XX_NB_DFS_EN BIT(31)
- +#define ARMADA_37XX_NB_CPU_LOAD 0x30
- +#define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3
- +#define ARMADA_37XX_DVFS_LOAD_0 0
- +#define ARMADA_37XX_DVFS_LOAD_1 1
- +#define ARMADA_37XX_DVFS_LOAD_2 2
- +#define ARMADA_37XX_DVFS_LOAD_3 3
- +
- +/*
- + * On Armada 37xx the Power management manages 4 level of CPU load,
- + * each level can be associated with a CPU clock source, a CPU
- + * divider, a VDD level, etc...
- + */
- +#define LOAD_LEVEL_NR 4
- +
- +struct armada_37xx_dvfs {
- + u32 cpu_freq_max;
- + u8 divider[LOAD_LEVEL_NR];
- +};
- +
- +static struct armada_37xx_dvfs armada_37xx_dvfs[] = {
- + {.cpu_freq_max = 1200*1000*1000, .divider = {1, 2, 4, 6} },
- + {.cpu_freq_max = 1000*1000*1000, .divider = {1, 2, 4, 5} },
- + {.cpu_freq_max = 800*1000*1000, .divider = {1, 2, 3, 4} },
- + {.cpu_freq_max = 600*1000*1000, .divider = {2, 4, 5, 6} },
- +};
- +
- +static struct armada_37xx_dvfs *armada_37xx_cpu_freq_info_get(u32 freq)
- +{
- + int i;
- +
- + for (i = 0; i < ARRAY_SIZE(armada_37xx_dvfs); i++) {
- + if (freq == armada_37xx_dvfs[i].cpu_freq_max)
- + return &armada_37xx_dvfs[i];
- + }
- +
- + pr_err("Unsupported CPU frequency %d MHz\n", freq/1000000);
- + return NULL;
- +}
- +
- +/*
- + * Setup the four level managed by the hardware. Once the four level
- + * will be configured then the DVFS will be enabled.
- + */
- +static void __init armada37xx_cpufreq_dvfs_setup(struct regmap *base,
- + struct clk *clk, u8 *divider)
- +{
- + int load_lvl;
- + struct clk *parent;
- +
- + for (load_lvl = 0; load_lvl < LOAD_LEVEL_NR; load_lvl++) {
- + unsigned int reg, mask, val, offset = 0;
- +
- + if (load_lvl <= ARMADA_37XX_DVFS_LOAD_1)
- + reg = ARMADA_37XX_NB_L0L1;
- + else
- + reg = ARMADA_37XX_NB_L2L3;
- +
- + if (load_lvl == ARMADA_37XX_DVFS_LOAD_0 ||
- + load_lvl == ARMADA_37XX_DVFS_LOAD_2)
- + offset += ARMADA_37XX_NB_CONFIG_SHIFT;
- +
- + /* Set cpu clock source, for all the level we use TBG */
- + val = ARMADA_37XX_NB_CLK_SEL_TBG << ARMADA_37XX_NB_CLK_SEL_OFF;
- + mask = (ARMADA_37XX_NB_CLK_SEL_MASK
- + << ARMADA_37XX_NB_CLK_SEL_OFF);
- +
- + /*
- + * Set cpu divider based on the pre-computed array in
- + * order to have balanced step.
- + */
- + val |= divider[load_lvl] << ARMADA_37XX_NB_TBG_DIV_OFF;
- + mask |= (ARMADA_37XX_NB_TBG_DIV_MASK
- + << ARMADA_37XX_NB_TBG_DIV_OFF);
- +
- + /* Set VDD divider which is actually the load level. */
- + val |= load_lvl << ARMADA_37XX_NB_VDD_SEL_OFF;
- + mask |= (ARMADA_37XX_NB_VDD_SEL_MASK
- + << ARMADA_37XX_NB_VDD_SEL_OFF);
- +
- + val <<= offset;
- + mask <<= offset;
- +
- + regmap_update_bits(base, reg, mask, val);
- + }
- +
- + /*
- + * Set cpu clock source, for all the level we keep the same
- + * clock source that the one already configured. For this one
- + * we need to use the clock framework
- + */
- + parent = clk_get_parent(clk);
- + clk_set_parent(clk, parent);
- +}
- +
- +static void __init armada37xx_cpufreq_disable_dvfs(struct regmap *base)
- +{
- + unsigned int reg = ARMADA_37XX_NB_DYN_MOD,
- + mask = ARMADA_37XX_NB_DFS_EN;
- +
- + regmap_update_bits(base, reg, mask, 0);
- +}
- +
- +static void __init armada37xx_cpufreq_enable_dvfs(struct regmap *base)
- +{
- + unsigned int val, reg = ARMADA_37XX_NB_CPU_LOAD,
- + mask = ARMADA_37XX_NB_CPU_LOAD_MASK;
- +
- + /* Start with the highest load (0) */
- + val = ARMADA_37XX_DVFS_LOAD_0;
- + regmap_update_bits(base, reg, mask, val);
- +
- + /* Now enable DVFS for the CPUs */
- + reg = ARMADA_37XX_NB_DYN_MOD;
- + mask = ARMADA_37XX_NB_CLK_SEL_EN | ARMADA_37XX_NB_TBG_EN |
- + ARMADA_37XX_NB_DIV_EN | ARMADA_37XX_NB_VDD_EN |
- + ARMADA_37XX_NB_DFS_EN;
- +
- + regmap_update_bits(base, reg, mask, mask);
- +}
- +
- +static int __init armada37xx_cpufreq_driver_init(void)
- +{
- + struct armada_37xx_dvfs *dvfs;
- + struct platform_device *pdev;
- + unsigned int cur_frequency;
- + struct regmap *nb_pm_base;
- + struct device *cpu_dev;
- + int load_lvl, ret;
- + struct clk *clk;
- +
- + nb_pm_base =
- + syscon_regmap_lookup_by_compatible("marvell,armada-3700-nb-pm");
- +
- + if (IS_ERR(nb_pm_base))
- + return -ENODEV;
- +
- + /* Before doing any configuration on the DVFS first, disable it */
- + armada37xx_cpufreq_disable_dvfs(nb_pm_base);
- +
- + /*
- + * On CPU 0 register the operating points supported (which are
- + * the nominal CPU frequency and full integer divisions of
- + * it).
- + */
- + cpu_dev = get_cpu_device(0);
- + if (!cpu_dev) {
- + dev_err(cpu_dev, "Cannot get CPU\n");
- + return -ENODEV;
- + }
- +
- + clk = clk_get(cpu_dev, 0);
- + if (IS_ERR(clk)) {
- + dev_err(cpu_dev, "Cannot get clock for CPU0\n");
- + return PTR_ERR(clk);
- + }
- +
- + /* Get nominal (current) CPU frequency */
- + cur_frequency = clk_get_rate(clk);
- + if (!cur_frequency) {
- + dev_err(cpu_dev, "Failed to get clock rate for CPU\n");
- + return -EINVAL;
- + }
- +
- + dvfs = armada_37xx_cpu_freq_info_get(cur_frequency);
- + if (!dvfs)
- + return -EINVAL;
- +
- + armada37xx_cpufreq_dvfs_setup(nb_pm_base, clk, dvfs->divider);
- +
- + for (load_lvl = ARMADA_37XX_DVFS_LOAD_0; load_lvl < LOAD_LEVEL_NR;
- + load_lvl++) {
- + unsigned long freq = cur_frequency / dvfs->divider[load_lvl];
- +
- + ret = dev_pm_opp_add(cpu_dev, freq, 0);
- + if (ret) {
- + /* clean-up the already added opp before leaving */
- + while (load_lvl-- > ARMADA_37XX_DVFS_LOAD_0) {
- + freq = cur_frequency / dvfs->divider[load_lvl];
- + dev_pm_opp_remove(cpu_dev, freq);
- + }
- + return ret;
- + }
- + }
- +
- + /* Now that everything is setup, enable the DVFS at hardware level */
- + armada37xx_cpufreq_enable_dvfs(nb_pm_base);
- +
- + pdev = platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
- +
- + return PTR_ERR_OR_ZERO(pdev);
- +}
- +/* late_initcall, to guarantee the driver is loaded after A37xx clock driver */
- +late_initcall(armada37xx_cpufreq_driver_init);
- +
- +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>");
- +MODULE_DESCRIPTION("Armada 37xx cpufreq driver");
- +MODULE_LICENSE("GPL");
|