1816 lines
48 KiB
C
1816 lines
48 KiB
C
|
/*
|
||
|
* Copyright (c) 2014-2015, 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/module.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/clk/msm-clk-provider.h>
|
||
|
#include <linux/clk/msm-clk.h>
|
||
|
#include <linux/clk/msm-clock-generic.h>
|
||
|
#include <linux/cpu.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/of_platform.h>
|
||
|
#include <linux/pm_opp.h>
|
||
|
#include <linux/pm_qos.h>
|
||
|
|
||
|
#include <asm/cputype.h>
|
||
|
|
||
|
#include <soc/qcom/scm.h>
|
||
|
#include <soc/qcom/clock-pll.h>
|
||
|
#include <soc/qcom/clock-local2.h>
|
||
|
#include <soc/qcom/clock-alpha-pll.h>
|
||
|
#include <soc/qcom/kryo-l2-accessors.h>
|
||
|
|
||
|
#include <dt-bindings/clock/msm-clocks-8996.h>
|
||
|
|
||
|
#include "clock.h"
|
||
|
#include "vdd-level-8994.h"
|
||
|
|
||
|
enum {
|
||
|
APC0_PLL_BASE,
|
||
|
APC1_PLL_BASE,
|
||
|
CBF_PLL_BASE,
|
||
|
APC0_BASE,
|
||
|
APC1_BASE,
|
||
|
CBF_BASE,
|
||
|
EFUSE_BASE,
|
||
|
DEBUG_BASE,
|
||
|
NUM_BASES
|
||
|
};
|
||
|
|
||
|
static char *base_names[] = {
|
||
|
"pwrcl_pll",
|
||
|
"perfcl_pll",
|
||
|
"cbf_pll",
|
||
|
"pwrcl_mux",
|
||
|
"perfcl_mux",
|
||
|
"cbf_mux",
|
||
|
"efuse",
|
||
|
"debug",
|
||
|
};
|
||
|
|
||
|
static void *vbases[NUM_BASES];
|
||
|
static bool cpu_clocks_v3;
|
||
|
|
||
|
static DEFINE_VDD_REGULATORS(vdd_dig, VDD_DIG_NUM, 1, vdd_corner, NULL);
|
||
|
|
||
|
/* Power cluster primary PLL */
|
||
|
#define C0_PLL_MODE 0x0
|
||
|
#define C0_PLL_L_VAL 0x4
|
||
|
#define C0_PLL_ALPHA 0x8
|
||
|
#define C0_PLL_USER_CTL 0x10
|
||
|
#define C0_PLL_CONFIG_CTL 0x18
|
||
|
#define C0_PLL_CONFIG_CTL_HI 0x1C
|
||
|
#define C0_PLL_STATUS 0x28
|
||
|
#define C0_PLL_TEST_CTL_LO 0x20
|
||
|
#define C0_PLL_TEST_CTL_HI 0x24
|
||
|
|
||
|
/* Power cluster alt PLL */
|
||
|
#define C0_PLLA_MODE 0x100
|
||
|
#define C0_PLLA_L_VAL 0x104
|
||
|
#define C0_PLLA_ALPHA 0x108
|
||
|
#define C0_PLLA_USER_CTL 0x110
|
||
|
#define C0_PLLA_CONFIG_CTL 0x118
|
||
|
#define C0_PLLA_STATUS 0x128
|
||
|
#define C0_PLLA_TEST_CTL_LO 0x120
|
||
|
|
||
|
/* Perf cluster primary PLL */
|
||
|
#define C1_PLL_MODE 0x0
|
||
|
#define C1_PLL_L_VAL 0x4
|
||
|
#define C1_PLL_ALPHA 0x8
|
||
|
#define C1_PLL_USER_CTL 0x10
|
||
|
#define C1_PLL_CONFIG_CTL 0x18
|
||
|
#define C1_PLL_CONFIG_CTL_HI 0x1C
|
||
|
#define C1_PLL_STATUS 0x28
|
||
|
#define C1_PLL_TEST_CTL_LO 0x20
|
||
|
#define C1_PLL_TEST_CTL_HI 0x24
|
||
|
|
||
|
/* Perf cluster alt PLL */
|
||
|
#define C1_PLLA_MODE 0x100
|
||
|
#define C1_PLLA_L_VAL 0x104
|
||
|
#define C1_PLLA_ALPHA 0x108
|
||
|
#define C1_PLLA_USER_CTL 0x110
|
||
|
#define C1_PLLA_CONFIG_CTL 0x118
|
||
|
#define C1_PLLA_STATUS 0x128
|
||
|
#define C1_PLLA_TEST_CTL_LO 0x120
|
||
|
|
||
|
#define CBF_PLL_MODE 0x0
|
||
|
#define CBF_PLL_L_VAL 0x8
|
||
|
#define CBF_PLL_ALPHA 0x10
|
||
|
#define CBF_PLL_USER_CTL 0x18
|
||
|
#define CBF_PLL_CONFIG_CTL 0x20
|
||
|
#define CBF_PLL_CONFIG_CTL_HI 0x24
|
||
|
#define CBF_PLL_STATUS 0x28
|
||
|
#define CBF_PLL_TEST_CTL_LO 0x30
|
||
|
#define CBF_PLL_TEST_CTL_HI 0x34
|
||
|
|
||
|
#define APC_DIAG_OFFSET 0x48
|
||
|
#define MUX_OFFSET 0x40
|
||
|
|
||
|
DEFINE_EXT_CLK(xo_ao, NULL);
|
||
|
DEFINE_CLK_DUMMY(alpha_xo_ao, 19200000);
|
||
|
DEFINE_EXT_CLK(sys_apcsaux_clk_gcc, NULL);
|
||
|
DEFINE_FIXED_SLAVE_DIV_CLK(sys_apcsaux_clk, 2, &sys_apcsaux_clk_gcc.c);
|
||
|
|
||
|
#define L2ACDCR_REG 0x580ULL
|
||
|
#define L2ACDTD_REG 0x581ULL
|
||
|
#define L2ACDDVMRC_REG 0x584ULL
|
||
|
#define L2ACDSSCR_REG 0x589ULL
|
||
|
|
||
|
#define EFUSE_SHIFT 29
|
||
|
#define EFUSE_MASK 0x7
|
||
|
|
||
|
/* ACD static settings */
|
||
|
static int acdtd_val_pwrcl = 0x00006A11;
|
||
|
static int acdtd_val_perfcl = 0x00006A11;
|
||
|
static int dvmrc_val = 0x000E0F0F;
|
||
|
static int acdsscr_val = 0x00000601;
|
||
|
static int acdcr_val_pwrcl = 0x002D5FFD;
|
||
|
module_param(acdcr_val_pwrcl, int, 0444);
|
||
|
static int acdcr_val_perfcl = 0x002D5FFD;
|
||
|
module_param(acdcr_val_perfcl, int, 0444);
|
||
|
int enable_acd = 1;
|
||
|
module_param(enable_acd, int, 0444);
|
||
|
|
||
|
#define WRITE_L2ACDCR(val) \
|
||
|
set_l2_indirect_reg(L2ACDCR_REG, (val))
|
||
|
#define WRITE_L2ACDTD(val) \
|
||
|
set_l2_indirect_reg(L2ACDTD_REG, (val))
|
||
|
#define WRITE_L2ACDDVMRC(val) \
|
||
|
set_l2_indirect_reg(L2ACDDVMRC_REG, (val))
|
||
|
#define WRITE_L2ACDSSCR(val) \
|
||
|
set_l2_indirect_reg(L2ACDSSCR_REG, (val))
|
||
|
|
||
|
#define READ_L2ACDCR(reg) \
|
||
|
(reg = get_l2_indirect_reg(L2ACDCR_REG))
|
||
|
#define READ_L2ACDTD(reg) \
|
||
|
(reg = get_l2_indirect_reg(L2ACDTD_REG))
|
||
|
#define READ_L2ACDDVMRC(reg) \
|
||
|
(reg = get_l2_indirect_reg(L2ACDDVMRC_REG))
|
||
|
#define READ_L2ACDSSCR(reg) \
|
||
|
(reg = get_l2_indirect_reg(L2ACDSSCR_REG))
|
||
|
|
||
|
static struct pll_clk perfcl_pll = {
|
||
|
.mode_reg = (void __iomem *)C1_PLL_MODE,
|
||
|
.l_reg = (void __iomem *)C1_PLL_L_VAL,
|
||
|
.alpha_reg = (void __iomem *)C1_PLL_ALPHA,
|
||
|
.config_reg = (void __iomem *)C1_PLL_USER_CTL,
|
||
|
.config_ctl_reg = (void __iomem *)C1_PLL_CONFIG_CTL,
|
||
|
.config_ctl_hi_reg = (void __iomem *)C1_PLL_CONFIG_CTL_HI,
|
||
|
.status_reg = (void __iomem *)C1_PLL_MODE,
|
||
|
.test_ctl_lo_reg = (void __iomem *)C1_PLL_TEST_CTL_LO,
|
||
|
.test_ctl_hi_reg = (void __iomem *)C1_PLL_TEST_CTL_HI,
|
||
|
.pgm_test_ctl_enable = true,
|
||
|
.init_test_ctl = true,
|
||
|
.masks = {
|
||
|
.pre_div_mask = BIT(12),
|
||
|
.post_div_mask = BM(9, 8),
|
||
|
.mn_en_mask = BIT(24),
|
||
|
.main_output_mask = BIT(0),
|
||
|
.early_output_mask = BIT(3),
|
||
|
.apc_pdn_mask = BIT(24),
|
||
|
.lock_mask = BIT(31),
|
||
|
},
|
||
|
.vals = {
|
||
|
.post_div_masked = 0x100,
|
||
|
.pre_div_masked = 0x0,
|
||
|
.test_ctl_hi_val = 0x00004000,
|
||
|
.test_ctl_lo_val = 0x04000000,
|
||
|
.config_ctl_val = 0x200D4AA8,
|
||
|
.config_ctl_hi_val = 0x002,
|
||
|
},
|
||
|
.min_rate = 600000000,
|
||
|
.max_rate = 3000000000,
|
||
|
.src_rate = 19200000,
|
||
|
.base = &vbases[APC1_PLL_BASE],
|
||
|
.c = {
|
||
|
.always_on = true,
|
||
|
.parent = &xo_ao.c,
|
||
|
.dbg_name = "perfcl_pll",
|
||
|
.ops = &clk_ops_variable_rate_pll_hwfsm,
|
||
|
VDD_DIG_FMAX_MAP1(LOW, 3000000000),
|
||
|
CLK_INIT(perfcl_pll.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct alpha_pll_masks alt_pll_masks = {
|
||
|
.lock_mask = BIT(31),
|
||
|
.active_mask = BIT(30),
|
||
|
.vco_mask = BM(21, 20) >> 20,
|
||
|
.vco_shift = 20,
|
||
|
.alpha_en_mask = BIT(24),
|
||
|
.output_mask = 0xf,
|
||
|
.post_div_mask = 0xf00,
|
||
|
};
|
||
|
|
||
|
static struct alpha_pll_vco_tbl alt_pll_vco_modes[] = {
|
||
|
VCO(3, 250000000, 500000000),
|
||
|
VCO(2, 500000000, 750000000),
|
||
|
VCO(1, 750000000, 1000000000),
|
||
|
VCO(0, 1000000000, 2150400000),
|
||
|
};
|
||
|
|
||
|
static struct alpha_pll_clk perfcl_alt_pll = {
|
||
|
.masks = &alt_pll_masks,
|
||
|
.base = &vbases[APC1_PLL_BASE],
|
||
|
.offset = C1_PLLA_MODE,
|
||
|
.vco_tbl = alt_pll_vco_modes,
|
||
|
.num_vco = ARRAY_SIZE(alt_pll_vco_modes),
|
||
|
.enable_config = 0x9, /* Main and early outputs */
|
||
|
.post_div_config = 0x100, /* Div-2 */
|
||
|
.config_ctl_val = 0x4001051B,
|
||
|
.offline_bit_workaround = true,
|
||
|
.c = {
|
||
|
.always_on = true,
|
||
|
.parent = &alpha_xo_ao.c,
|
||
|
.dbg_name = "perfcl_alt_pll",
|
||
|
.ops = &clk_ops_alpha_pll_hwfsm,
|
||
|
CLK_INIT(perfcl_alt_pll.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct pll_clk pwrcl_pll = {
|
||
|
.mode_reg = (void __iomem *)C0_PLL_MODE,
|
||
|
.l_reg = (void __iomem *)C0_PLL_L_VAL,
|
||
|
.alpha_reg = (void __iomem *)C0_PLL_ALPHA,
|
||
|
.config_reg = (void __iomem *)C0_PLL_USER_CTL,
|
||
|
.config_ctl_reg = (void __iomem *)C0_PLL_CONFIG_CTL,
|
||
|
.config_ctl_hi_reg = (void __iomem *)C0_PLL_CONFIG_CTL_HI,
|
||
|
.status_reg = (void __iomem *)C0_PLL_MODE,
|
||
|
.test_ctl_lo_reg = (void __iomem *)C0_PLL_TEST_CTL_LO,
|
||
|
.test_ctl_hi_reg = (void __iomem *)C0_PLL_TEST_CTL_HI,
|
||
|
.pgm_test_ctl_enable = true,
|
||
|
.init_test_ctl = true,
|
||
|
.masks = {
|
||
|
.pre_div_mask = BIT(12),
|
||
|
.post_div_mask = BM(9, 8),
|
||
|
.mn_en_mask = BIT(24),
|
||
|
.main_output_mask = BIT(0),
|
||
|
.early_output_mask = BIT(3),
|
||
|
.apc_pdn_mask = BIT(24),
|
||
|
.lock_mask = BIT(31),
|
||
|
},
|
||
|
.vals = {
|
||
|
.post_div_masked = 0x100,
|
||
|
.pre_div_masked = 0x0,
|
||
|
.test_ctl_hi_val = 0x00004000,
|
||
|
.test_ctl_lo_val = 0x04000000,
|
||
|
.config_ctl_val = 0x200D4AA8,
|
||
|
.config_ctl_hi_val = 0x002,
|
||
|
},
|
||
|
.min_rate = 600000000,
|
||
|
.max_rate = 3000000000,
|
||
|
.src_rate = 19200000,
|
||
|
.base = &vbases[APC0_PLL_BASE],
|
||
|
.c = {
|
||
|
.always_on = true,
|
||
|
.parent = &xo_ao.c,
|
||
|
.dbg_name = "pwrcl_pll",
|
||
|
.ops = &clk_ops_variable_rate_pll_hwfsm,
|
||
|
VDD_DIG_FMAX_MAP1(LOW, 3000000000),
|
||
|
CLK_INIT(pwrcl_pll.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct alpha_pll_clk pwrcl_alt_pll = {
|
||
|
.masks = &alt_pll_masks,
|
||
|
.base = &vbases[APC0_PLL_BASE],
|
||
|
.offset = C0_PLLA_MODE,
|
||
|
.vco_tbl = alt_pll_vco_modes,
|
||
|
.num_vco = ARRAY_SIZE(alt_pll_vco_modes),
|
||
|
.enable_config = 0x9, /* Main and early outputs */
|
||
|
.post_div_config = 0x100, /* Div-2 */
|
||
|
.config_ctl_val = 0x4001051B,
|
||
|
.offline_bit_workaround = true,
|
||
|
.c = {
|
||
|
.always_on = true,
|
||
|
.dbg_name = "pwrcl_alt_pll",
|
||
|
.parent = &alpha_xo_ao.c,
|
||
|
.ops = &clk_ops_alpha_pll_hwfsm,
|
||
|
CLK_INIT(pwrcl_alt_pll.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static DEFINE_SPINLOCK(mux_reg_lock);
|
||
|
|
||
|
DEFINE_FIXED_DIV_CLK(pwrcl_pll_main, 2, &pwrcl_pll.c);
|
||
|
DEFINE_FIXED_DIV_CLK(perfcl_pll_main, 2, &perfcl_pll.c);
|
||
|
|
||
|
static void __cpu_mux_set_sel(struct mux_clk *mux, int sel)
|
||
|
{
|
||
|
u32 regval;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&mux_reg_lock, flags);
|
||
|
|
||
|
if (mux->priv)
|
||
|
regval = scm_io_read(*(u32 *)mux->priv + mux->offset);
|
||
|
else
|
||
|
regval = readl_relaxed(*mux->base + mux->offset);
|
||
|
|
||
|
regval &= ~(mux->mask << mux->shift);
|
||
|
regval |= (sel & mux->mask) << mux->shift;
|
||
|
|
||
|
if (mux->priv)
|
||
|
scm_io_write(*(u32 *)mux->priv + mux->offset, regval);
|
||
|
else
|
||
|
writel_relaxed(regval, *mux->base + mux->offset);
|
||
|
|
||
|
spin_unlock_irqrestore(&mux_reg_lock, flags);
|
||
|
|
||
|
/* Ensure switch request goes through before returning */
|
||
|
mb();
|
||
|
/* Hardware mandated delay */
|
||
|
udelay(5);
|
||
|
}
|
||
|
|
||
|
/* It is assumed that the mux enable state is locked in this function */
|
||
|
static int cpu_mux_set_sel(struct mux_clk *mux, int sel)
|
||
|
{
|
||
|
__cpu_mux_set_sel(mux, sel);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int cpu_mux_get_sel(struct mux_clk *mux)
|
||
|
{
|
||
|
u32 regval;
|
||
|
|
||
|
if (mux->priv)
|
||
|
regval = scm_io_read(*(u32 *)mux->priv + mux->offset);
|
||
|
else
|
||
|
regval = readl_relaxed(*mux->base + mux->offset);
|
||
|
return (regval >> mux->shift) & mux->mask;
|
||
|
}
|
||
|
|
||
|
static int cpu_mux_enable(struct mux_clk *mux)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void cpu_mux_disable(struct mux_clk *mux)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/* It is assumed that the mux enable state is locked in this function */
|
||
|
static int cpu_debug_mux_set_sel(struct mux_clk *mux, int sel)
|
||
|
{
|
||
|
__cpu_mux_set_sel(mux, sel);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int cpu_debug_mux_get_sel(struct mux_clk *mux)
|
||
|
{
|
||
|
u32 regval = readl_relaxed(*mux->base + mux->offset);
|
||
|
return (regval >> mux->shift) & mux->mask;
|
||
|
}
|
||
|
|
||
|
static int cpu_debug_mux_enable(struct mux_clk *mux)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
/* Enable debug clocks */
|
||
|
val = readl_relaxed(vbases[APC0_BASE] + APC_DIAG_OFFSET);
|
||
|
val |= BM(11, 8);
|
||
|
writel_relaxed(val, vbases[APC0_BASE] + APC_DIAG_OFFSET);
|
||
|
|
||
|
val = readl_relaxed(vbases[APC1_BASE] + APC_DIAG_OFFSET);
|
||
|
val |= BM(11, 8);
|
||
|
writel_relaxed(val, vbases[APC1_BASE] + APC_DIAG_OFFSET);
|
||
|
|
||
|
/* Ensure enable request goes through for correct measurement*/
|
||
|
mb();
|
||
|
udelay(5);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void cpu_debug_mux_disable(struct mux_clk *mux)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
/* Disable debug clocks */
|
||
|
val = readl_relaxed(vbases[APC0_BASE] + APC_DIAG_OFFSET);
|
||
|
val &= ~BM(11, 8);
|
||
|
writel_relaxed(val, vbases[APC0_BASE] + APC_DIAG_OFFSET);
|
||
|
|
||
|
val = readl_relaxed(vbases[APC1_BASE] + APC_DIAG_OFFSET);
|
||
|
val &= ~BM(11, 8);
|
||
|
writel_relaxed(val, vbases[APC1_BASE] + APC_DIAG_OFFSET);
|
||
|
}
|
||
|
|
||
|
static struct clk_mux_ops cpu_mux_ops = {
|
||
|
.enable = cpu_mux_enable,
|
||
|
.disable = cpu_mux_disable,
|
||
|
.set_mux_sel = cpu_mux_set_sel,
|
||
|
.get_mux_sel = cpu_mux_get_sel,
|
||
|
};
|
||
|
|
||
|
static struct clk_mux_ops cpu_debug_mux_ops = {
|
||
|
.enable = cpu_debug_mux_enable,
|
||
|
.disable = cpu_debug_mux_disable,
|
||
|
.set_mux_sel = cpu_debug_mux_set_sel,
|
||
|
.get_mux_sel = cpu_debug_mux_get_sel,
|
||
|
};
|
||
|
|
||
|
static struct mux_clk pwrcl_lf_mux = {
|
||
|
.offset = MUX_OFFSET,
|
||
|
MUX_SRC_LIST(
|
||
|
{ &pwrcl_pll_main.c, 1 },
|
||
|
{ &sys_apcsaux_clk.c, 3 },
|
||
|
),
|
||
|
.ops = &cpu_mux_ops,
|
||
|
.mask = 0x3,
|
||
|
.shift = 2,
|
||
|
.base = &vbases[APC0_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "pwrcl_lf_mux",
|
||
|
.flags = CLKFLAG_NO_RATE_CACHE,
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
CLK_INIT(pwrcl_lf_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct mux_clk pwrcl_hf_mux = {
|
||
|
.offset = MUX_OFFSET,
|
||
|
MUX_SRC_LIST(
|
||
|
/* This may be changed by acd_init to select the ACD leg */
|
||
|
{ &pwrcl_pll.c, 1 },
|
||
|
{ &pwrcl_lf_mux.c, 0 },
|
||
|
),
|
||
|
.ops = &cpu_mux_ops,
|
||
|
.mask = 0x3,
|
||
|
.shift = 0,
|
||
|
.base = &vbases[APC0_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "pwrcl_hf_mux",
|
||
|
.flags = CLKFLAG_NO_RATE_CACHE,
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
CLK_INIT(pwrcl_hf_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct mux_clk perfcl_lf_mux = {
|
||
|
.offset = MUX_OFFSET,
|
||
|
MUX_SRC_LIST(
|
||
|
{ &perfcl_pll_main.c, 1 },
|
||
|
{ &sys_apcsaux_clk.c, 3 },
|
||
|
),
|
||
|
.ops = &cpu_mux_ops,
|
||
|
.mask = 0x3,
|
||
|
.shift = 2,
|
||
|
.base = &vbases[APC1_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "perfcl_lf_mux",
|
||
|
.flags = CLKFLAG_NO_RATE_CACHE,
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
CLK_INIT(perfcl_lf_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct mux_clk perfcl_hf_mux = {
|
||
|
.offset = MUX_OFFSET,
|
||
|
MUX_SRC_LIST(
|
||
|
/* This may be changed by acd_init to select the ACD leg */
|
||
|
{ &perfcl_pll.c, 1 },
|
||
|
{ &perfcl_lf_mux.c, 0 },
|
||
|
),
|
||
|
.ops = &cpu_mux_ops,
|
||
|
.mask = 0x3,
|
||
|
.shift = 0,
|
||
|
.base = &vbases[APC1_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "perfcl_hf_mux",
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
CLK_INIT(perfcl_hf_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct clk_src clk_src_perfcl_hf_mux_alt[] = {
|
||
|
{ &perfcl_alt_pll.c, 3 },
|
||
|
{ &perfcl_lf_mux.c, 0 },
|
||
|
};
|
||
|
|
||
|
static struct clk_src clk_src_pwrcl_hf_mux_alt[] = {
|
||
|
{ &pwrcl_alt_pll.c, 3 },
|
||
|
{ &pwrcl_lf_mux.c, 0 },
|
||
|
};
|
||
|
|
||
|
static struct clk_src clk_src_perfcl_lf_mux_alt[] = {
|
||
|
{ &sys_apcsaux_clk.c, 3 },
|
||
|
};
|
||
|
|
||
|
static struct clk_src clk_src_pwrcl_lf_mux_alt[] = {
|
||
|
{ &sys_apcsaux_clk.c, 3 },
|
||
|
};
|
||
|
|
||
|
struct cpu_clk_8996 {
|
||
|
u32 cpu_reg_mask;
|
||
|
struct clk *alt_pll;
|
||
|
unsigned long *alt_pll_freqs;
|
||
|
unsigned long alt_pll_thresh;
|
||
|
int n_alt_pll_freqs;
|
||
|
struct clk c;
|
||
|
bool hw_low_power_ctrl;
|
||
|
int pm_qos_latency;
|
||
|
cpumask_t cpumask;
|
||
|
struct pm_qos_request req;
|
||
|
bool do_half_rate;
|
||
|
bool has_acd;
|
||
|
};
|
||
|
|
||
|
static inline struct cpu_clk_8996 *to_cpu_clk_8996(struct clk *c)
|
||
|
{
|
||
|
return container_of(c, struct cpu_clk_8996, c);
|
||
|
}
|
||
|
|
||
|
static enum handoff cpu_clk_8996_handoff(struct clk *c)
|
||
|
{
|
||
|
c->rate = clk_get_rate(c->parent);
|
||
|
|
||
|
return HANDOFF_DISABLED_CLK;
|
||
|
}
|
||
|
|
||
|
static long cpu_clk_8996_round_rate(struct clk *c, unsigned long rate)
|
||
|
{
|
||
|
return clk_round_rate(c->parent, rate);
|
||
|
}
|
||
|
|
||
|
static unsigned long alt_pll_perfcl_freqs[] = {
|
||
|
307200000,
|
||
|
556800000,
|
||
|
};
|
||
|
|
||
|
static void do_nothing(void *unused) { }
|
||
|
|
||
|
/* ACD programming */
|
||
|
static struct cpu_clk_8996 perfcl_clk;
|
||
|
static struct cpu_clk_8996 pwrcl_clk;
|
||
|
|
||
|
static void cpu_clock_8996_acd_init(void);
|
||
|
|
||
|
static void cpu_clk_8996_disable(struct clk *c)
|
||
|
{
|
||
|
struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
|
||
|
|
||
|
if (!enable_acd)
|
||
|
return;
|
||
|
|
||
|
/* Ensure that we switch to GPLL0 across D5 */
|
||
|
if (cpuclk == &pwrcl_clk)
|
||
|
writel_relaxed(0x3C, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
else if (cpuclk == &perfcl_clk)
|
||
|
writel_relaxed(0x3C, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
}
|
||
|
|
||
|
#define MAX_PLL_MAIN_FREQ 595200000
|
||
|
|
||
|
/*
|
||
|
* Returns the max safe frequency that will guarantee we switch to main output
|
||
|
*/
|
||
|
unsigned long acd_safe_freq(unsigned long freq)
|
||
|
{
|
||
|
/*
|
||
|
* If we're running at less than double the max PLL main rate,
|
||
|
* just return half the rate. This will ensure we switch to
|
||
|
* the main output, without violating voltage constraints
|
||
|
* that might happen if we choose to go with MAX_PLL_MAIN_FREQ.
|
||
|
*/
|
||
|
if (freq > MAX_PLL_MAIN_FREQ && freq <= MAX_PLL_MAIN_FREQ*2)
|
||
|
return freq/2;
|
||
|
|
||
|
/*
|
||
|
* We're higher than the max main output, and higher than twice
|
||
|
* the max main output. Safe to go to the max main output.
|
||
|
*/
|
||
|
if (freq > MAX_PLL_MAIN_FREQ)
|
||
|
return MAX_PLL_MAIN_FREQ;
|
||
|
|
||
|
/* Shouldn't get here, just return the safest rate possible */
|
||
|
return sys_apcsaux_clk.c.rate;
|
||
|
}
|
||
|
|
||
|
static int cpu_clk_8996_pre_set_rate(struct clk *c, unsigned long rate)
|
||
|
{
|
||
|
struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
|
||
|
int ret;
|
||
|
bool hw_low_power_ctrl = cpuclk->hw_low_power_ctrl;
|
||
|
bool on_acd_leg = c->rate > MAX_PLL_MAIN_FREQ;
|
||
|
bool increase_freq = rate > c->rate;
|
||
|
|
||
|
/*
|
||
|
* If hardware control of the clock tree is enabled during power
|
||
|
* collapse, setup a PM QOS request to prevent power collapse and
|
||
|
* wake up one of the CPUs in this clock domain, to ensure software
|
||
|
* control while the clock rate is being switched.
|
||
|
*/
|
||
|
if (hw_low_power_ctrl) {
|
||
|
memset(&cpuclk->req, 0, sizeof(cpuclk->req));
|
||
|
cpuclk->req.cpus_affine = cpuclk->cpumask;
|
||
|
cpuclk->req.type = PM_QOS_REQ_AFFINE_CORES;
|
||
|
pm_qos_add_request(&cpuclk->req, PM_QOS_CPU_DMA_LATENCY,
|
||
|
cpuclk->pm_qos_latency);
|
||
|
|
||
|
ret = smp_call_function_any(&cpuclk->cpumask, do_nothing,
|
||
|
NULL, 1);
|
||
|
}
|
||
|
|
||
|
/* Select a non-ACD'ed safe source across voltage and freq switches */
|
||
|
if (enable_acd && cpuclk->has_acd && on_acd_leg && increase_freq) {
|
||
|
/*
|
||
|
* We're on the ACD leg, and the voltage will be
|
||
|
* scaled before clk_set_rate. Switch to the main output.
|
||
|
*/
|
||
|
return clk_set_rate(c->parent, acd_safe_freq(c->rate));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void cpu_clk_8996_post_set_rate(struct clk *c, unsigned long start_rate)
|
||
|
{
|
||
|
struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
|
||
|
int ret;
|
||
|
bool hw_low_power_ctrl = cpuclk->hw_low_power_ctrl;
|
||
|
bool on_acd_leg = c->rate > MAX_PLL_MAIN_FREQ;
|
||
|
bool decrease_freq = start_rate > c->rate;
|
||
|
|
||
|
if (cpuclk->has_acd && enable_acd && on_acd_leg && decrease_freq) {
|
||
|
ret = clk_set_rate(c->parent, c->rate);
|
||
|
if (ret)
|
||
|
pr_err("Unable to reset parent rate!\n");
|
||
|
}
|
||
|
|
||
|
if (hw_low_power_ctrl)
|
||
|
pm_qos_remove_request(&cpuclk->req);
|
||
|
}
|
||
|
|
||
|
static int cpu_clk_8996_set_rate(struct clk *c, unsigned long rate)
|
||
|
{
|
||
|
struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
|
||
|
int ret, err_ret;
|
||
|
unsigned long alt_pll_prev_rate;
|
||
|
unsigned long alt_pll_rate;
|
||
|
unsigned long n_alt_freqs = cpuclk->n_alt_pll_freqs;
|
||
|
bool on_acd_leg = rate > MAX_PLL_MAIN_FREQ;
|
||
|
bool decrease_freq = rate < c->rate;
|
||
|
|
||
|
if (cpuclk->alt_pll && (n_alt_freqs > 0)) {
|
||
|
alt_pll_prev_rate = cpuclk->alt_pll->rate;
|
||
|
alt_pll_rate = cpuclk->alt_pll_freqs[0];
|
||
|
if (rate > cpuclk->alt_pll_thresh)
|
||
|
alt_pll_rate = cpuclk->alt_pll_freqs[1];
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_lock(&scm_lmh_lock);
|
||
|
ret = clk_set_rate(cpuclk->alt_pll, alt_pll_rate);
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_unlock(&scm_lmh_lock);
|
||
|
if (ret) {
|
||
|
pr_err("failed to set rate %lu on alt_pll when setting %lu on %s (%d)\n",
|
||
|
alt_pll_rate, rate, c->dbg_name, ret);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Special handling needed for the case when using the div-2 output.
|
||
|
* Since the PLL needs to be running at twice the requested rate,
|
||
|
* we need to switch the mux first and then change the PLL rate.
|
||
|
* Otherwise the current voltage may not suffice with the PLL running at
|
||
|
* 2 * rate. Example: switching from 768MHz down to 550Mhz - if we raise
|
||
|
* the PLL to 1.1GHz, that's undervolting the system. So, we switch to
|
||
|
* the div-2 mux select first (by requesting rate/2), allowing the CPU
|
||
|
* to run at 384MHz. Then, we ramp up the PLL to 1.1GHz, allowing the
|
||
|
* CPU frequency to ramp up from 384MHz to 550MHz.
|
||
|
*/
|
||
|
if (cpuclk->do_half_rate
|
||
|
&& c->rate > 600000000 && rate < 600000000) {
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_lock(&scm_lmh_lock);
|
||
|
ret = clk_set_rate(c->parent, c->rate/2);
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_unlock(&scm_lmh_lock);
|
||
|
if (ret) {
|
||
|
pr_err("failed to set rate %lu on %s (%d)\n",
|
||
|
c->rate/2, c->dbg_name, ret);
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_lock(&scm_lmh_lock);
|
||
|
ret = clk_set_rate(c->parent, rate);
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_unlock(&scm_lmh_lock);
|
||
|
if (ret) {
|
||
|
pr_err("failed to set rate %lu on %s (%d)\n",
|
||
|
rate, c->dbg_name, ret);
|
||
|
goto set_rate_fail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If we're on the ACD leg and decreasing freq, voltage will be changed
|
||
|
* after this function. Switch to main output.
|
||
|
*/
|
||
|
if (enable_acd && cpuclk->has_acd && decrease_freq && on_acd_leg)
|
||
|
return clk_set_rate(c->parent, acd_safe_freq(c->rate));
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
set_rate_fail:
|
||
|
/* Restore parent rate if we halved it */
|
||
|
if (cpuclk->do_half_rate && c->rate > 600000000 && rate < 600000000) {
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_lock(&scm_lmh_lock);
|
||
|
err_ret = clk_set_rate(c->parent, c->rate);
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_unlock(&scm_lmh_lock);
|
||
|
if (err_ret)
|
||
|
pr_err("failed to restore %s rate to %lu\n",
|
||
|
c->dbg_name, c->rate);
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
if (cpuclk->alt_pll && (n_alt_freqs > 0)) {
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_lock(&scm_lmh_lock);
|
||
|
err_ret = clk_set_rate(cpuclk->alt_pll, alt_pll_prev_rate);
|
||
|
if (!cpu_clocks_v3)
|
||
|
mutex_unlock(&scm_lmh_lock);
|
||
|
if (err_ret)
|
||
|
pr_err("failed to reset rate to %lu on alt pll after failing to set %lu on %s (%d)\n",
|
||
|
alt_pll_prev_rate, rate, c->dbg_name, err_ret);
|
||
|
}
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops clk_ops_cpu_8996 = {
|
||
|
.disable = cpu_clk_8996_disable,
|
||
|
.set_rate = cpu_clk_8996_set_rate,
|
||
|
.pre_set_rate = cpu_clk_8996_pre_set_rate,
|
||
|
.post_set_rate = cpu_clk_8996_post_set_rate,
|
||
|
.round_rate = cpu_clk_8996_round_rate,
|
||
|
.handoff = cpu_clk_8996_handoff,
|
||
|
};
|
||
|
|
||
|
DEFINE_VDD_REGS_INIT(vdd_pwrcl, 1);
|
||
|
|
||
|
#define PERFCL_LATENCY_NO_L2_PC_US (1)
|
||
|
#define PWRCL_LATENCY_NO_L2_PC_US (1)
|
||
|
|
||
|
static struct cpu_clk_8996 pwrcl_clk = {
|
||
|
.cpu_reg_mask = 0x3,
|
||
|
.pm_qos_latency = PWRCL_LATENCY_NO_L2_PC_US,
|
||
|
.do_half_rate = true,
|
||
|
.c = {
|
||
|
.parent = &pwrcl_hf_mux.c,
|
||
|
.dbg_name = "pwrcl_clk",
|
||
|
.ops = &clk_ops_cpu_8996,
|
||
|
.vdd_class = &vdd_pwrcl,
|
||
|
CLK_INIT(pwrcl_clk.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
DEFINE_VDD_REGS_INIT(vdd_perfcl, 1);
|
||
|
|
||
|
static struct cpu_clk_8996 perfcl_clk = {
|
||
|
.cpu_reg_mask = 0x103,
|
||
|
.alt_pll = &perfcl_alt_pll.c,
|
||
|
.alt_pll_freqs = alt_pll_perfcl_freqs,
|
||
|
.alt_pll_thresh = 1190400000,
|
||
|
.n_alt_pll_freqs = ARRAY_SIZE(alt_pll_perfcl_freqs),
|
||
|
.pm_qos_latency = PERFCL_LATENCY_NO_L2_PC_US,
|
||
|
.do_half_rate = true,
|
||
|
.c = {
|
||
|
.parent = &perfcl_hf_mux.c,
|
||
|
.dbg_name = "perfcl_clk",
|
||
|
.ops = &clk_ops_cpu_8996,
|
||
|
.vdd_class = &vdd_perfcl,
|
||
|
CLK_INIT(perfcl_clk.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static DEFINE_SPINLOCK(acd_lock);
|
||
|
|
||
|
static struct clk *mpidr_to_clk(void)
|
||
|
{
|
||
|
u64 hwid = read_cpuid_mpidr() & 0xFFF;
|
||
|
|
||
|
if ((hwid | pwrcl_clk.cpu_reg_mask) == pwrcl_clk.cpu_reg_mask)
|
||
|
return &pwrcl_clk.c;
|
||
|
if ((hwid | perfcl_clk.cpu_reg_mask) == perfcl_clk.cpu_reg_mask)
|
||
|
return &perfcl_clk.c;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
#define SSSCTL_OFFSET 0x160
|
||
|
|
||
|
/*
|
||
|
* This *has* to be called on the intended CPU
|
||
|
*/
|
||
|
static void cpu_clock_8996_acd_init(void)
|
||
|
{
|
||
|
u64 l2acdtd;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&acd_lock, flags);
|
||
|
|
||
|
if (!enable_acd) {
|
||
|
spin_unlock_irqrestore(&acd_lock, flags);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
READ_L2ACDTD(l2acdtd);
|
||
|
|
||
|
/* If we have init'ed and the config is still present, return */
|
||
|
if (mpidr_to_clk() == &pwrcl_clk.c && l2acdtd == acdtd_val_pwrcl) {
|
||
|
spin_unlock_irqrestore(&acd_lock, flags);
|
||
|
return;
|
||
|
} else if (mpidr_to_clk() == &pwrcl_clk.c) {
|
||
|
WRITE_L2ACDTD(acdtd_val_pwrcl);
|
||
|
}
|
||
|
|
||
|
if (mpidr_to_clk() == &perfcl_clk.c && l2acdtd == acdtd_val_perfcl) {
|
||
|
spin_unlock_irqrestore(&acd_lock, flags);
|
||
|
return;
|
||
|
} else if (mpidr_to_clk() == &perfcl_clk.c) {
|
||
|
WRITE_L2ACDTD(acdtd_val_perfcl);
|
||
|
}
|
||
|
|
||
|
/* Initial ACD for *this* cluster */
|
||
|
WRITE_L2ACDDVMRC(dvmrc_val);
|
||
|
WRITE_L2ACDSSCR(acdsscr_val);
|
||
|
|
||
|
if (mpidr_to_clk() == &pwrcl_clk.c) {
|
||
|
if (vbases[APC0_BASE])
|
||
|
writel_relaxed(0x0000000F, vbases[APC0_BASE] +
|
||
|
SSSCTL_OFFSET);
|
||
|
/* Ensure SSSCTL config goes through before enabling ACD. */
|
||
|
mb();
|
||
|
WRITE_L2ACDCR(acdcr_val_pwrcl);
|
||
|
} else {
|
||
|
WRITE_L2ACDCR(acdcr_val_perfcl);
|
||
|
if (vbases[APC1_BASE])
|
||
|
writel_relaxed(0x0000000F, vbases[APC1_BASE] +
|
||
|
SSSCTL_OFFSET);
|
||
|
/* Ensure SSSCTL config goes through before enabling ACD. */
|
||
|
mb();
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&acd_lock, flags);
|
||
|
}
|
||
|
|
||
|
static struct clk *logical_cpu_to_clk(int cpu)
|
||
|
{
|
||
|
struct device_node *cpu_node;
|
||
|
const u32 *cell;
|
||
|
u64 hwid;
|
||
|
static struct clk *cpu_clk_map[NR_CPUS];
|
||
|
|
||
|
if (cpu_clk_map[cpu])
|
||
|
return cpu_clk_map[cpu];
|
||
|
|
||
|
cpu_node = of_get_cpu_node(cpu, NULL);
|
||
|
if (!cpu_node)
|
||
|
goto fail;
|
||
|
|
||
|
cell = of_get_property(cpu_node, "reg", NULL);
|
||
|
if (!cell) {
|
||
|
pr_err("%s: missing reg property\n", cpu_node->full_name);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
hwid = of_read_number(cell, of_n_addr_cells(cpu_node));
|
||
|
if ((hwid | pwrcl_clk.cpu_reg_mask) == pwrcl_clk.cpu_reg_mask) {
|
||
|
cpu_clk_map[cpu] = &pwrcl_clk.c;
|
||
|
return &pwrcl_clk.c;
|
||
|
}
|
||
|
if ((hwid | perfcl_clk.cpu_reg_mask) == perfcl_clk.cpu_reg_mask) {
|
||
|
cpu_clk_map[cpu] = &perfcl_clk.c;
|
||
|
return &perfcl_clk.c;
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct pll_clk cbf_pll = {
|
||
|
.mode_reg = (void __iomem *)CBF_PLL_MODE,
|
||
|
.l_reg = (void __iomem *)CBF_PLL_L_VAL,
|
||
|
.alpha_reg = (void __iomem *)CBF_PLL_ALPHA,
|
||
|
.config_reg = (void __iomem *)CBF_PLL_USER_CTL,
|
||
|
.config_ctl_reg = (void __iomem *)CBF_PLL_CONFIG_CTL,
|
||
|
.config_ctl_hi_reg = (void __iomem *)CBF_PLL_CONFIG_CTL_HI,
|
||
|
.status_reg = (void __iomem *)CBF_PLL_MODE,
|
||
|
.test_ctl_lo_reg = (void __iomem *)CBF_PLL_TEST_CTL_LO,
|
||
|
.test_ctl_hi_reg = (void __iomem *)CBF_PLL_TEST_CTL_HI,
|
||
|
.pgm_test_ctl_enable = true,
|
||
|
.init_test_ctl = true,
|
||
|
.masks = {
|
||
|
.pre_div_mask = BIT(12),
|
||
|
.post_div_mask = BM(9, 8),
|
||
|
.mn_en_mask = BIT(24),
|
||
|
.main_output_mask = BIT(0),
|
||
|
.early_output_mask = BIT(3),
|
||
|
.apc_pdn_mask = BIT(24),
|
||
|
.lock_mask = BIT(31),
|
||
|
},
|
||
|
.vals = {
|
||
|
.post_div_masked = 0x100,
|
||
|
.pre_div_masked = 0x0,
|
||
|
.test_ctl_hi_val = 0x00004000,
|
||
|
.test_ctl_lo_val = 0x04000000,
|
||
|
.config_ctl_val = 0x200D4AA8,
|
||
|
.config_ctl_hi_val = 0x002,
|
||
|
},
|
||
|
.min_rate = 600000000,
|
||
|
.max_rate = 3000000000,
|
||
|
.src_rate = 19200000,
|
||
|
.base = &vbases[CBF_PLL_BASE],
|
||
|
.c = {
|
||
|
.parent = &xo_ao.c,
|
||
|
.dbg_name = "cbf_pll",
|
||
|
.ops = &clk_ops_variable_rate_pll_hwfsm,
|
||
|
VDD_DIG_FMAX_MAP1(LOW, 3000000000),
|
||
|
CLK_INIT(cbf_pll.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
DEFINE_FIXED_DIV_CLK(cbf_pll_main, 2, &cbf_pll.c);
|
||
|
|
||
|
#define CBF_MUX_OFFSET 0x018
|
||
|
|
||
|
DEFINE_VDD_REGS_INIT(vdd_cbf, 1);
|
||
|
|
||
|
static struct mux_clk cbf_hf_mux = {
|
||
|
.offset = CBF_MUX_OFFSET,
|
||
|
MUX_SRC_LIST(
|
||
|
{ &cbf_pll.c, 1 },
|
||
|
{ &cbf_pll_main.c, 2 },
|
||
|
{ &sys_apcsaux_clk.c, 3 },
|
||
|
),
|
||
|
.en_mask = 0,
|
||
|
.ops = &cpu_mux_ops,
|
||
|
.mask = 0x3,
|
||
|
.shift = 0,
|
||
|
.base = &vbases[CBF_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "cbf_hf_mux",
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
.flags = CLKFLAG_NO_RATE_CACHE,
|
||
|
CLK_INIT(cbf_hf_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct cpu_clk_8996 cbf_clk = {
|
||
|
.do_half_rate = true,
|
||
|
.c = {
|
||
|
.parent = &cbf_hf_mux.c,
|
||
|
.dbg_name = "cbf_clk",
|
||
|
.ops = &clk_ops_cpu_8996,
|
||
|
.vdd_class = &vdd_cbf,
|
||
|
CLK_INIT(cbf_clk.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
#define APCS_CLK_DIAG 0x78
|
||
|
|
||
|
DEFINE_FIXED_SLAVE_DIV_CLK(pwrcl_div_clk, 16, &pwrcl_clk.c);
|
||
|
DEFINE_FIXED_SLAVE_DIV_CLK(perfcl_div_clk, 16, &perfcl_clk.c);
|
||
|
|
||
|
static struct mux_clk cpu_debug_mux = {
|
||
|
.offset = APCS_CLK_DIAG,
|
||
|
MUX_SRC_LIST(
|
||
|
{ &cbf_clk.c, 0x01 },
|
||
|
{ &pwrcl_div_clk.c, 0x11 },
|
||
|
{ &perfcl_div_clk.c, 0x21 },
|
||
|
),
|
||
|
MUX_REC_SRC_LIST(
|
||
|
&cbf_clk.c,
|
||
|
&pwrcl_div_clk.c,
|
||
|
&perfcl_div_clk.c,
|
||
|
),
|
||
|
.ops = &cpu_debug_mux_ops,
|
||
|
.mask = 0xFF,
|
||
|
.shift = 8,
|
||
|
.base = &vbases[DEBUG_BASE],
|
||
|
.c = {
|
||
|
.dbg_name = "cpu_debug_mux",
|
||
|
.ops = &clk_ops_gen_mux,
|
||
|
.flags = CLKFLAG_NO_RATE_CACHE,
|
||
|
CLK_INIT(cpu_debug_mux.c),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct clk_lookup cpu_clocks_8996[] = {
|
||
|
CLK_LIST(pwrcl_clk),
|
||
|
CLK_LIST(pwrcl_pll),
|
||
|
CLK_LIST(pwrcl_alt_pll),
|
||
|
CLK_LIST(pwrcl_pll_main),
|
||
|
CLK_LIST(pwrcl_hf_mux),
|
||
|
CLK_LIST(pwrcl_lf_mux),
|
||
|
|
||
|
CLK_LIST(perfcl_clk),
|
||
|
CLK_LIST(perfcl_pll),
|
||
|
CLK_LIST(perfcl_alt_pll),
|
||
|
CLK_LIST(perfcl_pll_main),
|
||
|
CLK_LIST(perfcl_hf_mux),
|
||
|
CLK_LIST(perfcl_lf_mux),
|
||
|
|
||
|
CLK_LIST(cbf_pll),
|
||
|
CLK_LIST(cbf_hf_mux),
|
||
|
CLK_LIST(cbf_clk),
|
||
|
|
||
|
CLK_LIST(xo_ao),
|
||
|
CLK_LIST(sys_apcsaux_clk),
|
||
|
|
||
|
CLK_LIST(cpu_debug_mux),
|
||
|
};
|
||
|
|
||
|
static int of_get_fmax_vdd_class(struct platform_device *pdev, struct clk *c,
|
||
|
char *prop_name)
|
||
|
{
|
||
|
struct device_node *of = pdev->dev.of_node;
|
||
|
int prop_len, i;
|
||
|
struct clk_vdd_class *vdd = c->vdd_class;
|
||
|
u32 *array;
|
||
|
|
||
|
if (!of_find_property(of, prop_name, &prop_len)) {
|
||
|
dev_err(&pdev->dev, "missing %s\n", prop_name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
prop_len /= sizeof(u32);
|
||
|
if (prop_len % 2) {
|
||
|
dev_err(&pdev->dev, "bad length %d\n", prop_len);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
prop_len /= 2;
|
||
|
vdd->level_votes = devm_kzalloc(&pdev->dev, prop_len * sizeof(int),
|
||
|
GFP_KERNEL);
|
||
|
if (!vdd->level_votes)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
vdd->vdd_uv = devm_kzalloc(&pdev->dev, prop_len * sizeof(int),
|
||
|
GFP_KERNEL);
|
||
|
if (!vdd->vdd_uv)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
c->fmax = devm_kzalloc(&pdev->dev, prop_len * sizeof(unsigned long),
|
||
|
GFP_KERNEL);
|
||
|
if (!c->fmax)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
array = devm_kzalloc(&pdev->dev,
|
||
|
prop_len * sizeof(u32) * 2, GFP_KERNEL);
|
||
|
if (!array)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
of_property_read_u32_array(of, prop_name, array, prop_len * 2);
|
||
|
for (i = 0; i < prop_len; i++) {
|
||
|
c->fmax[i] = array[2 * i];
|
||
|
vdd->vdd_uv[i] = array[2 * i + 1];
|
||
|
}
|
||
|
|
||
|
devm_kfree(&pdev->dev, array);
|
||
|
vdd->num_levels = prop_len;
|
||
|
vdd->cur_level = prop_len;
|
||
|
c->num_fmax = prop_len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int cpu_clock_8996_resources_init(struct platform_device *pdev)
|
||
|
{
|
||
|
struct resource *res;
|
||
|
struct clk *c;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(base_names); i++) {
|
||
|
|
||
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||
|
base_names[i]);
|
||
|
if (!res) {
|
||
|
dev_err(&pdev->dev,
|
||
|
"Unable to get platform resource for %s",
|
||
|
base_names[i]);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
vbases[i] = devm_ioremap(&pdev->dev, res->start,
|
||
|
resource_size(res));
|
||
|
if (!vbases[i]) {
|
||
|
dev_err(&pdev->dev, "Unable to map in base %s\n",
|
||
|
base_names[i]);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vdd_dig.regulator[0] = devm_regulator_get(&pdev->dev, "vdd-dig");
|
||
|
if (IS_ERR(vdd_dig.regulator[0])) {
|
||
|
if (PTR_ERR(vdd_dig.regulator[0]) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get the CX regulator");
|
||
|
return PTR_ERR(vdd_dig.regulator[0]);
|
||
|
}
|
||
|
|
||
|
vdd_pwrcl.regulator[0] = devm_regulator_get(&pdev->dev, "vdd-pwrcl");
|
||
|
if (IS_ERR(vdd_pwrcl.regulator[0])) {
|
||
|
if (PTR_ERR(vdd_pwrcl.regulator[0]) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get the pwrcl vreg\n");
|
||
|
return PTR_ERR(vdd_pwrcl.regulator[0]);
|
||
|
}
|
||
|
|
||
|
vdd_perfcl.regulator[0] = devm_regulator_get(&pdev->dev, "vdd-perfcl");
|
||
|
if (IS_ERR(vdd_perfcl.regulator[0])) {
|
||
|
if (PTR_ERR(vdd_perfcl.regulator[0]) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get the perfcl vreg\n");
|
||
|
return PTR_ERR(vdd_perfcl.regulator[0]);
|
||
|
}
|
||
|
|
||
|
/* Leakage constraints disallow a turbo vote during bootup */
|
||
|
vdd_perfcl.skip_handoff = true;
|
||
|
|
||
|
vdd_cbf.regulator[0] = devm_regulator_get(&pdev->dev, "vdd-cbf");
|
||
|
if (IS_ERR(vdd_cbf.regulator[0])) {
|
||
|
if (PTR_ERR(vdd_cbf.regulator[0]) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get the cbf vreg\n");
|
||
|
return PTR_ERR(vdd_cbf.regulator[0]);
|
||
|
}
|
||
|
|
||
|
c = devm_clk_get(&pdev->dev, "xo_ao");
|
||
|
if (IS_ERR(c)) {
|
||
|
if (PTR_ERR(c) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get xo (rc = %ld)!\n",
|
||
|
PTR_ERR(c));
|
||
|
return PTR_ERR(c);
|
||
|
}
|
||
|
xo_ao.c.parent = c;
|
||
|
|
||
|
c = devm_clk_get(&pdev->dev, "aux_clk");
|
||
|
if (IS_ERR(c)) {
|
||
|
if (PTR_ERR(c) != -EPROBE_DEFER)
|
||
|
dev_err(&pdev->dev, "Unable to get gpll0 (rc = %ld)!\n",
|
||
|
PTR_ERR(c));
|
||
|
return PTR_ERR(c);
|
||
|
}
|
||
|
sys_apcsaux_clk_gcc.c.parent = c;
|
||
|
|
||
|
vdd_perfcl.use_max_uV = true;
|
||
|
vdd_pwrcl.use_max_uV = true;
|
||
|
vdd_cbf.use_max_uV = true;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int add_opp(struct clk *c, struct device *dev, unsigned long max_rate)
|
||
|
{
|
||
|
unsigned long rate = 0;
|
||
|
int level;
|
||
|
int uv;
|
||
|
int *vdd_uv = c->vdd_class->vdd_uv;
|
||
|
struct regulator *reg = c->vdd_class->regulator[0];
|
||
|
long ret;
|
||
|
bool first = true;
|
||
|
int j = 1;
|
||
|
|
||
|
while (1) {
|
||
|
rate = c->fmax[j++];
|
||
|
level = find_vdd_level(c, rate);
|
||
|
if (level <= 0) {
|
||
|
pr_warn("clock-cpu: no corner for %lu.\n", rate);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
uv = regulator_list_corner_voltage(reg, vdd_uv[level]);
|
||
|
if (uv < 0) {
|
||
|
pr_warn("clock-cpu: no uv for %lu.\n", rate);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = dev_pm_opp_add(dev, rate, uv);
|
||
|
if (ret) {
|
||
|
pr_warn("clock-cpu: failed to add OPP for %lu\n", rate);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Print the OPP pair for the lowest and highest frequency for
|
||
|
* each device that we're populating. This is important since
|
||
|
* this information will be used by thermal mitigation and the
|
||
|
* scheduler.
|
||
|
*/
|
||
|
if ((rate >= max_rate) || first) {
|
||
|
/* one time print at bootup */
|
||
|
pr_info("clock-cpu-8996: set OPP pair (%lu Hz, %d uv) on %s\n",
|
||
|
rate, uv, dev_name(dev));
|
||
|
if (first)
|
||
|
first = false;
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void populate_opp_table(struct platform_device *pdev)
|
||
|
{
|
||
|
unsigned long pwrcl_fmax, perfcl_fmax, cbf_fmax;
|
||
|
struct device_node *cbf_node;
|
||
|
struct platform_device *cbf_dev;
|
||
|
int cpu;
|
||
|
|
||
|
pwrcl_fmax = pwrcl_clk.c.fmax[pwrcl_clk.c.num_fmax - 1];
|
||
|
perfcl_fmax = perfcl_clk.c.fmax[perfcl_clk.c.num_fmax - 1];
|
||
|
cbf_fmax = cbf_clk.c.fmax[cbf_clk.c.num_fmax - 1];
|
||
|
|
||
|
for_each_possible_cpu(cpu) {
|
||
|
if (logical_cpu_to_clk(cpu) == &pwrcl_clk.c) {
|
||
|
WARN(add_opp(&pwrcl_clk.c, get_cpu_device(cpu),
|
||
|
pwrcl_fmax),
|
||
|
"Failed to add OPP levels for power cluster\n");
|
||
|
}
|
||
|
if (logical_cpu_to_clk(cpu) == &perfcl_clk.c) {
|
||
|
WARN(add_opp(&perfcl_clk.c, get_cpu_device(cpu),
|
||
|
perfcl_fmax),
|
||
|
"Failed to add OPP levels for perf cluster\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cbf_node = of_parse_phandle(pdev->dev.of_node, "cbf-dev", 0);
|
||
|
if (!cbf_node) {
|
||
|
pr_err("can't find the CBF dt node\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
cbf_dev = of_find_device_by_node(cbf_node);
|
||
|
if (!cbf_dev) {
|
||
|
pr_err("can't find the CBF dt device\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
WARN(add_opp(&cbf_clk.c, &cbf_dev->dev, cbf_fmax),
|
||
|
"Failed to add OPP levels for CBF\n");
|
||
|
}
|
||
|
|
||
|
static int perfclspeedbin;
|
||
|
|
||
|
unsigned long pwrcl_early_boot_rate = 883200000;
|
||
|
unsigned long perfcl_early_boot_rate = 883200000;
|
||
|
unsigned long cbf_early_boot_rate = 614400000;
|
||
|
unsigned long alt_pll_early_boot_rate = 307200000;
|
||
|
|
||
|
static int cpu_clock_8996_driver_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int ret, cpu;
|
||
|
unsigned long pwrclrate, perfclrate, cbfrate;
|
||
|
int pvs_ver = 0;
|
||
|
u32 pte_efuse;
|
||
|
char perfclspeedbinstr[] = "qcom,perfcl-speedbinXX-vXX";
|
||
|
char pwrclspeedbinstr[] = "qcom,pwrcl-speedbinXX-vXX";
|
||
|
char cbfspeedbinstr[] = "qcom,cbf-speedbinXX-vXX";
|
||
|
|
||
|
pwrcl_pll_main.c.flags = CLKFLAG_NO_RATE_CACHE;
|
||
|
perfcl_pll_main.c.flags = CLKFLAG_NO_RATE_CACHE;
|
||
|
cbf_pll_main.c.flags = CLKFLAG_NO_RATE_CACHE;
|
||
|
pwrcl_clk.hw_low_power_ctrl = true;
|
||
|
perfcl_clk.hw_low_power_ctrl = true;
|
||
|
|
||
|
ret = cpu_clock_8996_resources_init(pdev);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "resources init failed\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
pte_efuse = readl_relaxed(vbases[EFUSE_BASE]);
|
||
|
perfclspeedbin = ((pte_efuse >> EFUSE_SHIFT) & EFUSE_MASK);
|
||
|
dev_info(&pdev->dev, "using perf/pwr/cbf speed bin %u and pvs_ver %d\n",
|
||
|
perfclspeedbin, pvs_ver);
|
||
|
|
||
|
snprintf(perfclspeedbinstr, ARRAY_SIZE(perfclspeedbinstr),
|
||
|
"qcom,perfcl-speedbin%d-v%d", perfclspeedbin, pvs_ver);
|
||
|
|
||
|
ret = of_get_fmax_vdd_class(pdev, &perfcl_clk.c, perfclspeedbinstr);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't get speed bin for perfcl. Falling back to zero.\n");
|
||
|
ret = of_get_fmax_vdd_class(pdev, &perfcl_clk.c,
|
||
|
"qcom,perfcl-speedbin0-v0");
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Unable to retrieve plan for perf. Bailing...\n");
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
snprintf(pwrclspeedbinstr, ARRAY_SIZE(pwrclspeedbinstr),
|
||
|
"qcom,pwrcl-speedbin%d-v%d", perfclspeedbin, pvs_ver);
|
||
|
|
||
|
ret = of_get_fmax_vdd_class(pdev, &pwrcl_clk.c, pwrclspeedbinstr);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't get speed bin for pwrcl. Falling back to zero.\n");
|
||
|
ret = of_get_fmax_vdd_class(pdev, &pwrcl_clk.c,
|
||
|
"qcom,pwrcl-speedbin0-v0");
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Unable to retrieve plan for pwrcl\n");
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
snprintf(cbfspeedbinstr, ARRAY_SIZE(cbfspeedbinstr),
|
||
|
"qcom,cbf-speedbin%d-v%d", perfclspeedbin, pvs_ver);
|
||
|
|
||
|
ret = of_get_fmax_vdd_class(pdev, &cbf_clk.c, cbfspeedbinstr);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't get speed bin for cbf. Falling back to zero.\n");
|
||
|
ret = of_get_fmax_vdd_class(pdev, &cbf_clk.c,
|
||
|
"qcom,cbf-speedbin0-v0");
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Unable to retrieve plan for cbf\n");
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
get_online_cpus();
|
||
|
|
||
|
for_each_possible_cpu(cpu) {
|
||
|
if (logical_cpu_to_clk(cpu) == &pwrcl_clk.c)
|
||
|
cpumask_set_cpu(cpu, &pwrcl_clk.cpumask);
|
||
|
if (logical_cpu_to_clk(cpu) == &perfcl_clk.c)
|
||
|
cpumask_set_cpu(cpu, &perfcl_clk.cpumask);
|
||
|
}
|
||
|
|
||
|
ret = of_msm_clock_register(pdev->dev.of_node, cpu_clocks_8996,
|
||
|
ARRAY_SIZE(cpu_clocks_8996));
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Unable to register CPU clocks.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
for_each_online_cpu(cpu) {
|
||
|
WARN(clk_prepare_enable(&cbf_clk.c),
|
||
|
"Failed to enable cbf clock.\n");
|
||
|
WARN(clk_prepare_enable(logical_cpu_to_clk(cpu)),
|
||
|
"Failed to enable clock for cpu %d\n", cpu);
|
||
|
}
|
||
|
|
||
|
pwrclrate = clk_get_rate(&pwrcl_clk.c);
|
||
|
perfclrate = clk_get_rate(&perfcl_clk.c);
|
||
|
cbfrate = clk_get_rate(&cbf_clk.c);
|
||
|
|
||
|
if (!pwrclrate) {
|
||
|
dev_err(&pdev->dev, "Unknown pwrcl rate. Setting safe rate\n");
|
||
|
ret = clk_set_rate(&pwrcl_clk.c, sys_apcsaux_clk.c.rate);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't set a safe rate on A53.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
pwrclrate = sys_apcsaux_clk.c.rate;
|
||
|
}
|
||
|
|
||
|
if (!perfclrate) {
|
||
|
dev_err(&pdev->dev, "Unknown perfcl rate. Setting safe rate\n");
|
||
|
ret = clk_set_rate(&perfcl_clk.c, sys_apcsaux_clk.c.rate);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't set a safe rate on perf.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
perfclrate = sys_apcsaux_clk.c.rate;
|
||
|
}
|
||
|
|
||
|
if (!cbfrate) {
|
||
|
dev_err(&pdev->dev, "Unknown CBF rate. Setting safe rate\n");
|
||
|
cbfrate = sys_apcsaux_clk.c.rate;
|
||
|
ret = clk_set_rate(&cbf_clk.c, cbfrate);
|
||
|
if (ret) {
|
||
|
dev_err(&pdev->dev, "Can't set a safe rate on CBF.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Permanently enable the cluster PLLs */
|
||
|
clk_prepare_enable(&perfcl_pll.c);
|
||
|
clk_prepare_enable(&pwrcl_pll.c);
|
||
|
clk_prepare_enable(&perfcl_alt_pll.c);
|
||
|
clk_prepare_enable(&pwrcl_alt_pll.c);
|
||
|
clk_prepare_enable(&cbf_pll.c);
|
||
|
|
||
|
/* Set the early boot rate. This may also switch us to the ACD leg */
|
||
|
clk_set_rate(&pwrcl_clk.c, pwrcl_early_boot_rate);
|
||
|
clk_set_rate(&perfcl_clk.c, perfcl_early_boot_rate);
|
||
|
|
||
|
populate_opp_table(pdev);
|
||
|
|
||
|
put_online_cpus();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct of_device_id match_table[] = {
|
||
|
{ .compatible = "qcom,cpu-clock-8996" },
|
||
|
{ .compatible = "qcom,cpu-clock-8996-v3" },
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
static struct platform_driver cpu_clock_8996_driver = {
|
||
|
.probe = cpu_clock_8996_driver_probe,
|
||
|
.driver = {
|
||
|
.name = "cpu-clock-8996",
|
||
|
.of_match_table = match_table,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init cpu_clock_8996_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&cpu_clock_8996_driver);
|
||
|
}
|
||
|
arch_initcall(cpu_clock_8996_init);
|
||
|
|
||
|
static void __exit cpu_clock_8996_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&cpu_clock_8996_driver);
|
||
|
}
|
||
|
module_exit(cpu_clock_8996_exit);
|
||
|
|
||
|
#define APC0_BASE_PHY 0x06400000
|
||
|
#define APC1_BASE_PHY 0x06480000
|
||
|
#define CBF_BASE_PHY 0x09A11000
|
||
|
#define CBF_PLL_BASE_PHY 0x09A20000
|
||
|
#define AUX_BASE_PHY 0x09820050
|
||
|
|
||
|
#define CLK_CTL_OFFSET 0x44
|
||
|
#define PSCTL_OFFSET 0x164
|
||
|
#define AUTO_CLK_SEL_BIT BIT(8)
|
||
|
#define CBF_AUTO_CLK_SEL_BIT BIT(6)
|
||
|
#define AUTO_CLK_SEL_ALWAYS_ON_MASK BM(5, 4)
|
||
|
#define AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL (0x3 << 4)
|
||
|
|
||
|
#define HF_MUX_MASK 0x3
|
||
|
#define LF_MUX_MASK 0x3
|
||
|
#define LF_MUX_SHIFT 0x2
|
||
|
#define HF_MUX_SEL_EARLY_PLL 0x1
|
||
|
#define HF_MUX_SEL_LF_MUX 0x1
|
||
|
#define LF_MUX_SEL_ALT_PLL 0x1
|
||
|
|
||
|
static int use_alt_pll;
|
||
|
module_param(use_alt_pll, int, 0444);
|
||
|
|
||
|
static int clock_cpu_8996_cpu_callback(struct notifier_block *nfb,
|
||
|
unsigned long action, void *hcpu)
|
||
|
{
|
||
|
if (!enable_acd)
|
||
|
return NOTIFY_OK;
|
||
|
|
||
|
switch (action & ~CPU_TASKS_FROZEN) {
|
||
|
|
||
|
case CPU_STARTING:
|
||
|
/* This is needed for the first time that CPUs come up */
|
||
|
cpu_clock_8996_acd_init();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
|
||
|
static struct notifier_block __refdata clock_cpu_8996_cpu_notifier = {
|
||
|
.notifier_call = clock_cpu_8996_cpu_callback,
|
||
|
};
|
||
|
|
||
|
int __init cpu_clock_8996_early_init(void)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
void __iomem *auxbase;
|
||
|
u32 regval;
|
||
|
struct device_node *ofnode;
|
||
|
|
||
|
ofnode = of_find_compatible_node(NULL, NULL,
|
||
|
"qcom,cpu-clock-8996-v3");
|
||
|
if (ofnode)
|
||
|
cpu_clocks_v3 = true;
|
||
|
else {
|
||
|
ofnode = of_find_compatible_node(NULL, NULL,
|
||
|
"qcom,cpu-clock-8996");
|
||
|
if (!ofnode)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
pr_info("clock-cpu-8996: configuring clocks for the perf cluster\n");
|
||
|
|
||
|
if (cpu_clocks_v3) {
|
||
|
pwrcl_alt_pll.offline_bit_workaround = false;
|
||
|
perfcl_alt_pll.offline_bit_workaround = false;
|
||
|
pwrcl_pll.pgm_test_ctl_enable = false;
|
||
|
perfcl_pll.pgm_test_ctl_enable = false;
|
||
|
pwrcl_pll.vals.config_ctl_val = 0x200d4828;
|
||
|
pwrcl_pll.vals.config_ctl_hi_val = 0x006;
|
||
|
perfcl_pll.vals.config_ctl_val = 0x200d4828;
|
||
|
perfcl_pll.vals.config_ctl_hi_val = 0x006;
|
||
|
cbf_pll.vals.config_ctl_val = 0x200d4828;
|
||
|
cbf_pll.vals.config_ctl_hi_val = 0x006;
|
||
|
pwrcl_pll.vals.test_ctl_lo_val = 0x1C000000;
|
||
|
perfcl_pll.vals.test_ctl_lo_val = 0x1C000000;
|
||
|
cbf_pll.vals.test_ctl_lo_val = 0x1C000000;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* We definitely don't want to parse DT here - this is too early and in
|
||
|
* the critical path for boot timing. Just ioremap the bases.
|
||
|
*/
|
||
|
vbases[APC0_BASE] = ioremap(APC0_BASE_PHY, SZ_4K);
|
||
|
if (!vbases[APC0_BASE]) {
|
||
|
WARN(1, "Unable to ioremap power mux base. Can't configure CPU clocks\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
vbases[APC1_BASE] = ioremap(APC1_BASE_PHY, SZ_4K);
|
||
|
if (!vbases[APC1_BASE]) {
|
||
|
WARN(1, "Unable to ioremap perf mux base. Can't configure CPU clocks\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto apc1_fail;
|
||
|
}
|
||
|
|
||
|
vbases[CBF_BASE] = ioremap(CBF_BASE_PHY, SZ_4K);
|
||
|
if (!vbases[CBF_BASE]) {
|
||
|
WARN(1, "Unable to ioremap cbf mux base. Can't configure CPU clocks\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto cbf_map_fail;
|
||
|
}
|
||
|
|
||
|
vbases[CBF_PLL_BASE] = ioremap(CBF_PLL_BASE_PHY, SZ_4K);
|
||
|
if (!vbases[CBF_BASE]) {
|
||
|
WARN(1, "Unable to ioremap cbf pll base. Can't configure CPU clocks\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto cbf_pll_map_fail;
|
||
|
}
|
||
|
|
||
|
vbases[APC0_PLL_BASE] = vbases[APC0_BASE];
|
||
|
vbases[APC1_PLL_BASE] = vbases[APC1_BASE];
|
||
|
|
||
|
auxbase = ioremap(AUX_BASE_PHY, SZ_4K);
|
||
|
if (!auxbase) {
|
||
|
WARN(1, "Unable to ioremap aux base. Can't configure CPU clocks\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto auxbase_fail;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set GPLL0 divider for div-2 to get 300Mhz. This divider
|
||
|
* can be programmed dynamically.
|
||
|
*/
|
||
|
regval = readl_relaxed(auxbase);
|
||
|
regval &= ~BM(17, 16);
|
||
|
regval |= 0x1 << 16;
|
||
|
writel_relaxed(regval, auxbase);
|
||
|
|
||
|
/* Ensure write goes through before selecting the aux clock */
|
||
|
mb();
|
||
|
udelay(5);
|
||
|
|
||
|
/* Select GPLL0 for 300MHz for the perf cluster */
|
||
|
writel_relaxed(0xC, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
|
||
|
/* Select GPLL0 for 300MHz for the power cluster */
|
||
|
writel_relaxed(0xC, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
|
||
|
/* Select GPLL0 for 300MHz on the CBF */
|
||
|
writel_relaxed(0x3, vbases[CBF_BASE] + CBF_MUX_OFFSET);
|
||
|
|
||
|
/* Ensure write goes through before PLLs are reconfigured */
|
||
|
mb();
|
||
|
udelay(5);
|
||
|
|
||
|
/* Set the auto clock sel always-on source to GPLL0/2 (300MHz) */
|
||
|
regval = readl_relaxed(vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
regval &= ~AUTO_CLK_SEL_ALWAYS_ON_MASK;
|
||
|
regval |= AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL;
|
||
|
writel_relaxed(regval, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
|
||
|
regval = readl_relaxed(vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
regval &= ~AUTO_CLK_SEL_ALWAYS_ON_MASK;
|
||
|
regval |= AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL;
|
||
|
writel_relaxed(regval, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
|
||
|
regval = readl_relaxed(vbases[CBF_BASE] + MUX_OFFSET);
|
||
|
regval &= ~AUTO_CLK_SEL_ALWAYS_ON_MASK;
|
||
|
regval |= AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL;
|
||
|
writel_relaxed(regval, vbases[CBF_BASE] + MUX_OFFSET);
|
||
|
|
||
|
/* == Setup PLLs in FSM mode == */
|
||
|
|
||
|
/* Disable all PLLs (we're already on GPLL0 for both clusters) */
|
||
|
perfcl_alt_pll.c.ops->disable(&perfcl_alt_pll.c);
|
||
|
pwrcl_alt_pll.c.ops->disable(&pwrcl_alt_pll.c);
|
||
|
writel_relaxed(0x0, vbases[APC0_BASE] +
|
||
|
(unsigned long)pwrcl_pll.mode_reg);
|
||
|
writel_relaxed(0x0, vbases[APC1_BASE] +
|
||
|
(unsigned long)perfcl_pll.mode_reg);
|
||
|
writel_relaxed(0x0, vbases[CBF_PLL_BASE] +
|
||
|
(unsigned long)cbf_pll.mode_reg);
|
||
|
|
||
|
/* Let PLLs disable before re-init'ing them */
|
||
|
mb();
|
||
|
|
||
|
/* Initialize all the PLLs */
|
||
|
__variable_rate_pll_init(&perfcl_pll.c);
|
||
|
__variable_rate_pll_init(&pwrcl_pll.c);
|
||
|
__variable_rate_pll_init(&cbf_pll.c);
|
||
|
__init_alpha_pll(&perfcl_alt_pll.c);
|
||
|
__init_alpha_pll(&pwrcl_alt_pll.c);
|
||
|
|
||
|
/* Set an appropriate rate on the perf clusters PLLs */
|
||
|
perfcl_pll.c.ops->set_rate(&perfcl_pll.c, perfcl_early_boot_rate);
|
||
|
perfcl_alt_pll.c.ops->set_rate(&perfcl_alt_pll.c,
|
||
|
alt_pll_early_boot_rate);
|
||
|
pwrcl_pll.c.ops->set_rate(&pwrcl_pll.c, pwrcl_early_boot_rate);
|
||
|
pwrcl_alt_pll.c.ops->set_rate(&pwrcl_alt_pll.c,
|
||
|
alt_pll_early_boot_rate);
|
||
|
|
||
|
/* Set an appropriate rate on the CBF PLL */
|
||
|
cbf_pll.c.ops->set_rate(&cbf_pll.c, cbf_early_boot_rate);
|
||
|
|
||
|
/*
|
||
|
* Enable FSM mode on the primary PLLs.
|
||
|
* This should turn on the PLLs as well.
|
||
|
*/
|
||
|
writel_relaxed(0x00118000, vbases[APC0_BASE] +
|
||
|
(unsigned long)pwrcl_pll.mode_reg);
|
||
|
writel_relaxed(0x00118000, vbases[APC1_BASE] +
|
||
|
(unsigned long)perfcl_pll.mode_reg);
|
||
|
/*
|
||
|
* Enable FSM mode on the alternate PLLs.
|
||
|
* This should turn on the PLLs as well.
|
||
|
*/
|
||
|
writel_relaxed(0x00118000, vbases[APC0_BASE] +
|
||
|
(unsigned long)pwrcl_alt_pll.offset);
|
||
|
writel_relaxed(0x00118000, vbases[APC1_BASE] +
|
||
|
(unsigned long)perfcl_alt_pll.offset);
|
||
|
|
||
|
/*
|
||
|
* Enable FSM mode on the CBF PLL.
|
||
|
* This should turn on the PLL as well.
|
||
|
*/
|
||
|
writel_relaxed(0x00118000, vbases[CBF_PLL_BASE] +
|
||
|
(unsigned long)cbf_pll.mode_reg);
|
||
|
|
||
|
/*
|
||
|
* If we're on MSM8996 V1, the CBF FSM bits are not present, and
|
||
|
* the mode register will read zero at this point. In that case,
|
||
|
* just enable the CBF PLL to "simulate" FSM mode.
|
||
|
*/
|
||
|
if (!readl_relaxed(vbases[CBF_PLL_BASE] +
|
||
|
(unsigned long)cbf_pll.mode_reg))
|
||
|
clk_ops_variable_rate_pll.enable(&cbf_pll.c);
|
||
|
|
||
|
/* Ensure write goes through before auto clock selection is enabled */
|
||
|
mb();
|
||
|
|
||
|
/* Wait for PLL(s) to lock */
|
||
|
udelay(50);
|
||
|
|
||
|
/* Enable auto clock selection for both clusters and the CBF */
|
||
|
regval = readl_relaxed(vbases[APC0_BASE] + CLK_CTL_OFFSET);
|
||
|
regval |= AUTO_CLK_SEL_BIT;
|
||
|
writel_relaxed(regval, vbases[APC0_BASE] + CLK_CTL_OFFSET);
|
||
|
|
||
|
regval = readl_relaxed(vbases[APC1_BASE] + CLK_CTL_OFFSET);
|
||
|
regval |= AUTO_CLK_SEL_BIT;
|
||
|
writel_relaxed(regval, vbases[APC1_BASE] + CLK_CTL_OFFSET);
|
||
|
|
||
|
regval = readl_relaxed(vbases[CBF_BASE] + CBF_MUX_OFFSET);
|
||
|
regval |= CBF_AUTO_CLK_SEL_BIT;
|
||
|
writel_relaxed(regval, vbases[CBF_BASE] + CBF_MUX_OFFSET);
|
||
|
|
||
|
/* Ensure write goes through before muxes are switched */
|
||
|
mb();
|
||
|
udelay(5);
|
||
|
|
||
|
if (use_alt_pll) {
|
||
|
perfcl_hf_mux.safe_parent = &perfcl_lf_mux.c;
|
||
|
pwrcl_hf_mux.safe_parent = &pwrcl_lf_mux.c;
|
||
|
pwrcl_clk.alt_pll = NULL;
|
||
|
perfcl_clk.alt_pll = NULL;
|
||
|
pwrcl_clk.do_half_rate = false;
|
||
|
perfcl_clk.do_half_rate = false;
|
||
|
|
||
|
perfcl_hf_mux.parents = (struct clk_src *)
|
||
|
&clk_src_perfcl_hf_mux_alt;
|
||
|
perfcl_hf_mux.num_parents =
|
||
|
ARRAY_SIZE(clk_src_perfcl_hf_mux_alt);
|
||
|
pwrcl_hf_mux.parents = (struct clk_src *)
|
||
|
&clk_src_pwrcl_hf_mux_alt;
|
||
|
pwrcl_hf_mux.num_parents =
|
||
|
ARRAY_SIZE(clk_src_pwrcl_hf_mux_alt);
|
||
|
|
||
|
perfcl_lf_mux.parents = (struct clk_src *)
|
||
|
&clk_src_perfcl_lf_mux_alt;
|
||
|
perfcl_lf_mux.num_parents =
|
||
|
ARRAY_SIZE(clk_src_perfcl_lf_mux_alt);
|
||
|
pwrcl_lf_mux.parents = (struct clk_src *)
|
||
|
&clk_src_pwrcl_lf_mux_alt;
|
||
|
pwrcl_lf_mux.num_parents =
|
||
|
ARRAY_SIZE(clk_src_pwrcl_lf_mux_alt);
|
||
|
|
||
|
/* Switch the clusters to use the alternate PLLs */
|
||
|
writel_relaxed(0x33, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
writel_relaxed(0x33, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
}
|
||
|
|
||
|
/* Switch the CBF to use the primary PLL */
|
||
|
regval = readl_relaxed(vbases[CBF_BASE] + CBF_MUX_OFFSET);
|
||
|
regval &= ~BM(1, 0);
|
||
|
regval |= 0x1;
|
||
|
writel_relaxed(regval, vbases[CBF_BASE] + CBF_MUX_OFFSET);
|
||
|
|
||
|
if (!cpu_clocks_v3)
|
||
|
enable_acd = 0;
|
||
|
|
||
|
if (enable_acd) {
|
||
|
int i;
|
||
|
|
||
|
if (use_alt_pll)
|
||
|
panic("Can't enable ACD on the the alternate PLL\n");
|
||
|
|
||
|
perfcl_clk.has_acd = true;
|
||
|
pwrcl_clk.has_acd = true;
|
||
|
|
||
|
/* Enable ACD on this cluster if necessary */
|
||
|
cpu_clock_8996_acd_init();
|
||
|
|
||
|
/* Ensure we never use the non-ACD leg of the GFMUX */
|
||
|
for (i = 0; i < pwrcl_hf_mux.num_parents; i++)
|
||
|
if (pwrcl_hf_mux.parents[i].src == &pwrcl_pll.c)
|
||
|
pwrcl_hf_mux.parents[i].sel = 2;
|
||
|
|
||
|
for (i = 0; i < perfcl_hf_mux.num_parents; i++)
|
||
|
if (perfcl_hf_mux.parents[i].src == &perfcl_pll.c)
|
||
|
perfcl_hf_mux.parents[i].sel = 2;
|
||
|
|
||
|
BUG_ON(register_hotcpu_notifier(&clock_cpu_8996_cpu_notifier));
|
||
|
|
||
|
/* Pulse swallower and soft-start settings */
|
||
|
writel_relaxed(0x00030005, vbases[APC0_BASE] + PSCTL_OFFSET);
|
||
|
writel_relaxed(0x00030005, vbases[APC1_BASE] + PSCTL_OFFSET);
|
||
|
|
||
|
/* Ensure all config above goes through before the ACD switch */
|
||
|
mb();
|
||
|
|
||
|
/* Switch the clusters to use the ACD leg */
|
||
|
writel_relaxed(0x32, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
writel_relaxed(0x32, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
} else {
|
||
|
/* Switch the clusters to use the primary PLLs */
|
||
|
writel_relaxed(0x31, vbases[APC0_BASE] + MUX_OFFSET);
|
||
|
writel_relaxed(0x31, vbases[APC1_BASE] + MUX_OFFSET);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* One time print during boot - this is the earliest time
|
||
|
* that Linux configures the CPU clocks. It's critical for
|
||
|
* debugging that we know that this configuration completed,
|
||
|
* especially when debugging CPU hangs.
|
||
|
*/
|
||
|
pr_info("%s: finished CPU clock configuration\n", __func__);
|
||
|
|
||
|
iounmap(auxbase);
|
||
|
auxbase_fail:
|
||
|
iounmap(vbases[CBF_PLL_BASE]);
|
||
|
cbf_pll_map_fail:
|
||
|
iounmap(vbases[CBF_BASE]);
|
||
|
cbf_map_fail:
|
||
|
if (ret) {
|
||
|
iounmap(vbases[APC1_BASE]);
|
||
|
vbases[APC1_BASE] = NULL;
|
||
|
}
|
||
|
apc1_fail:
|
||
|
if (ret) {
|
||
|
iounmap(vbases[APC0_BASE]);
|
||
|
vbases[APC0_BASE] = NULL;
|
||
|
}
|
||
|
fail:
|
||
|
return ret;
|
||
|
}
|
||
|
early_initcall(cpu_clock_8996_early_init);
|
||
|
|
||
|
MODULE_DESCRIPTION("CPU clock driver for msm8996");
|
||
|
MODULE_LICENSE("GPL v2");
|