From 10047e6b2a4ed2a740331595bf6b05d92345506c Mon Sep 17 00:00:00 2001 From: "hong.guo" Date: Fri, 2 Mar 2018 15:46:13 +0800 Subject: [PATCH] CPUFREQ: add cpufreq driver. PD#156734: cpufreq: add cpufreq driver[2/2]. Change-Id: If38dba0973aa7c0d4e9bbba1bee1af81e84ba6b8 Signed-off-by: hong.guo --- MAINTAINERS | 5 + arch/arm64/boot/dts/amlogic/mesong12a.dtsi | 2 +- arch/arm64/configs/meson64_defconfig | 2 +- drivers/amlogic/clk/Makefile | 2 +- drivers/amlogic/clk/clk-cpu-fclk-composite.c | 314 ++++++++ drivers/amlogic/clk/clk-cpu.c | 74 +- drivers/amlogic/clk/clkc.h | 40 + drivers/amlogic/clk/g12a/g12a.c | 61 +- drivers/amlogic/clk/g12a/g12a.h | 15 +- drivers/amlogic/clk/g12a/g12a_clk-pll.c | 4 +- drivers/cpufreq/Kconfig.arm | 10 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/meson-cpufreq.c | 1081 ++++++++++++++++++++++++++ 13 files changed, 1584 insertions(+), 27 deletions(-) create mode 100644 drivers/amlogic/clk/clk-cpu-fclk-composite.c create mode 100644 drivers/cpufreq/meson-cpufreq.c diff --git a/MAINTAINERS b/MAINTAINERS index 704579b..86c0636 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14336,3 +14336,8 @@ AMLOGIC Audio codec AD82584F driver M: Peipeng Zhao F: sound/soc/codecs/amlogic/ad82584f.c F: sound/soc/codecs/amlogic/ad82584f.h + +AMLOGIC CPUFREQS DRIVER +M: hong guo +F: drivers/cpufreq/meson-cpufreq.c +F: drivers/amlogic/clk/clk-cpu-fclk-composite.c diff --git a/arch/arm64/boot/dts/amlogic/mesong12a.dtsi b/arch/arm64/boot/dts/amlogic/mesong12a.dtsi index 4492923..5ece33d 100644 --- a/arch/arm64/boot/dts/amlogic/mesong12a.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesong12a.dtsi @@ -183,7 +183,7 @@ }; opp09 { - opp-hz = /bits/ 64 <1992000000>; + opp-hz = /bits/ 64 <2016000000>; opp-microvolt = <1011000>; clock-latency-ns = <2000000>; }; diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index b1c1445..74fb82f 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -57,7 +57,7 @@ CONFIG_CPU_FREQ_STAT=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y CONFIG_ARM_BIG_LITTLE_CPUFREQ=y CONFIG_ARM_SCPI_CPUFREQ=y -CONFIG_ARM_MESON_CPUFREQ=y +CONFIG_AMLOGIC_MESON_CPUFREQ=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_PACKET_DIAG=y diff --git a/drivers/amlogic/clk/Makefile b/drivers/amlogic/clk/Makefile index 66aad27..28a1fd9 100644 --- a/drivers/amlogic/clk/Makefile +++ b/drivers/amlogic/clk/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_AMLOGIC_CLK) += clk-mux.o obj-$(CONFIG_AMLOGIC_CLK) += clk_measure.o obj-$(CONFIG_AMLOGIC_CLK) += clk-meson-register.o -obj-$(CONFIG_AMLOGIC_GX_CLK) += clk-cpu.o clk-mpll.o clk_test.o +obj-$(CONFIG_AMLOGIC_GX_CLK) += clk-cpu.o clk-mpll.o clk_test.o clk-cpu-fclk-composite.o obj-$(CONFIG_AMLOGIC_GX_CLK) += gxl/ obj-$(CONFIG_AMLOGIC_GX_CLK) += axg/ obj-$(CONFIG_AMLOGIC_GX_CLK) += txlx/ diff --git a/drivers/amlogic/clk/clk-cpu-fclk-composite.c b/drivers/amlogic/clk/clk-cpu-fclk-composite.c new file mode 100644 index 0000000..be01f3e --- /dev/null +++ b/drivers/amlogic/clk/clk-cpu-fclk-composite.c @@ -0,0 +1,314 @@ +/* + * drivers/amlogic/clk/clk-cpu-fclk-composite.c + * + * Copyright (C) 2017 Amlogic, Inc. 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + */ + +/* + * CPU clock path: + * + * +-[/N]-----|3| + * MUX2 +--[/3]-+----------|2| MUX1 + * [sys_pll]---|1| |--[/2]------------|1|-|1| + * | |---+------------------|0| | |----- [a5_clk] + * +--|0| | | + * [xtal]---+-------------------------------|0| + * + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MESON_CPU_CLK_CNTL 0x00 +#define MESON_CPU_CLK_CNTL1 0x40 + +#define MESON_POST_MUX0 BIT(2) +#define MESON_DYN_MUX BIT(10) +#define MESON_FINAL_MUX BIT(11) +#define MESON_POST_MUX1 BIT(18) +#define MESON_DYN_ENABLE BIT(26) + +#define MESON_N_WIDTH 9 +#define MESON_N_SHIFT 20 +#define MESON_SEL_WIDTH 2 +#define MESON_SEL_SHIFT 2 + +#include "clkc.h" + +#define to_clk_mux_divider(_hw) \ + container_of(_hw, struct meson_cpu_mux_divider, hw) +static unsigned int gap_rate = (10*1000*1000); + +/* GX series, control cpu clk in firmware, + * kernel do not know when freq will change + */ + +static const struct fclk_rate_table *meson_fclk_get_pll_settings + (struct meson_cpu_mux_divider *pll, unsigned long rate) +{ + const struct fclk_rate_table *rate_table = pll->rate_table; + int i; + + for (i = 0; i < pll->rate_count; i++) { + if (abs(rate-rate_table[i].rate) < gap_rate) + return &rate_table[i]; + } + return NULL; +} + +static u8 meson_fclk_cpu_get_parent(struct clk_hw *hw) +{ + struct meson_cpu_mux_divider *mux_divider = + to_clk_mux_divider(hw); + int num_parents = clk_hw_get_num_parents(hw); + u32 val, final_dyn_mask, premux_mask; + u8 final_dyn_shift, premux_shift; + + final_dyn_mask = mux_divider->cpu_fclk_p.mask; + final_dyn_shift = mux_divider->cpu_fclk_p.shift; + val = clk_readl(mux_divider->reg); + + if ((val >> final_dyn_shift) & final_dyn_mask) { + premux_mask = mux_divider->cpu_fclk_p10.mask; + premux_shift = mux_divider->cpu_fclk_p10.shift; + } else { + premux_mask = mux_divider->cpu_fclk_p00.mask; + premux_shift = mux_divider->cpu_fclk_p00.shift; + } + + val = clk_readl(mux_divider->reg) >> premux_shift; + val &= premux_mask; + + + if (val >= num_parents) + return -EINVAL; + + if (mux_divider->table) { + int i; + + for (i = 0; i < num_parents; i++) + if (mux_divider->table[i] == val) + return i; + } + return val; + +} + +static int meson_fclk_cpu_set_parent(struct clk_hw *hw, u8 index) +{ + struct meson_cpu_mux_divider *mux_divider = + to_clk_mux_divider(hw); + u32 val, final_dyn_mask, premux_mask; + u8 final_dyn_shift, premux_shift; + unsigned long flags = 0; + + final_dyn_mask = mux_divider->cpu_fclk_p.mask; + final_dyn_shift = mux_divider->cpu_fclk_p.shift; + val = clk_readl(mux_divider->reg); + if ((val >> final_dyn_shift) & final_dyn_mask) { + premux_mask = mux_divider->cpu_fclk_p00.mask; + premux_shift = mux_divider->cpu_fclk_p00.shift; + } else { + premux_mask = mux_divider->cpu_fclk_p10.mask; + premux_shift = mux_divider->cpu_fclk_p10.shift; + } + + if (mux_divider->table) { + index = mux_divider->table[index]; + } else { + if (mux_divider->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + + if (mux_divider->flags & CLK_MUX_INDEX_ONE) + index++; + } + + if (mux_divider->lock) + spin_lock_irqsave(mux_divider->lock, flags); + else + __acquire(mux_divider->lock); + + if (mux_divider->flags & CLK_MUX_HIWORD_MASK) { + val = premux_mask << (premux_shift + 16); + } else { + val = clk_readl(mux_divider->reg); + val &= ~(premux_mask << premux_shift); + } + + val |= index << premux_shift; + clk_writel(val, mux_divider->reg); + + if (mux_divider->lock) + spin_unlock_irqrestore(mux_divider->lock, flags); + else + __release(mux_divider->lock); + + return 0; +} + +static unsigned long meson_fclk_cpu_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct meson_cpu_mux_divider *mux_divider = + to_clk_mux_divider(hw); + struct clk_hw *parent_hw; + struct parm_fclk *p_premux, *p_postmux, *p_div; + unsigned long rate, new_parent_rate; + u32 val, final_dyn_mask, div; + u8 final_dyn_shift, index; + + final_dyn_mask = mux_divider->cpu_fclk_p.mask; + final_dyn_shift = mux_divider->cpu_fclk_p.shift; + val = readl(mux_divider->reg); + + if ((val >> final_dyn_shift) & final_dyn_mask) { + p_premux = &mux_divider->cpu_fclk_p10; + p_postmux = &mux_divider->cpu_fclk_p1; + p_div = &mux_divider->cpu_fclk_p11; + } else { + p_premux = &mux_divider->cpu_fclk_p00; + p_postmux = &mux_divider->cpu_fclk_p0; + p_div = &mux_divider->cpu_fclk_p01; + } + + index = meson_fclk_cpu_get_parent(hw); + parent_hw = clk_hw_get_parent_by_index(hw, index); + new_parent_rate = clk_hw_get_rate(parent_hw); + if (new_parent_rate != parent_rate) + clk_set_parent(hw->clk, parent_hw->clk); + div = PARM_GET(p_div->width, p_div->shift, val); + rate = parent_rate / (div + 1); + + return rate; +} + +static int meson_fclk_divider_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct meson_cpu_mux_divider *mux_divider = + to_clk_mux_divider(hw); + const struct fclk_rate_table *rate_set; + struct parm_fclk *p_premux, *p_postmux, *p_div; + u32 val, final_dyn_mask; + u8 final_dyn_shift; + unsigned long old_rate; + unsigned long flags = 0; + + if (parent_rate == 0 || rate == 0) + return -EINVAL; + + final_dyn_mask = mux_divider->cpu_fclk_p.mask; + final_dyn_shift = mux_divider->cpu_fclk_p.shift; + val = readl(mux_divider->reg); + + if ((val >> final_dyn_shift) & final_dyn_mask) { + p_premux = &mux_divider->cpu_fclk_p00; + p_postmux = &mux_divider->cpu_fclk_p0; + p_div = &mux_divider->cpu_fclk_p01; + } else { + p_premux = &mux_divider->cpu_fclk_p10; + p_postmux = &mux_divider->cpu_fclk_p1; + p_div = &mux_divider->cpu_fclk_p11; + } + + old_rate = rate; + rate_set = meson_fclk_get_pll_settings(mux_divider, rate); + if (!rate_set) + return -EINVAL; + + if (mux_divider->lock) + spin_lock_irqsave(mux_divider->lock, flags); + else + __acquire(mux_divider->lock); + writel((val | MESON_DYN_ENABLE), mux_divider->reg); + /*set mux_divider clk divider*/ + val = PARM_SET(p_div->width, p_div->shift, val, rate_set->mux_div); + writel(val, mux_divider->reg); + /*set mux_divider postmux*/ + val = PARM_SET(p_postmux->width, p_postmux->shift, val, + rate_set->postmux); + writel(val, mux_divider->reg); + /*set mux_divider final dyn*/ + val = readl(mux_divider->reg); + if ((val >> final_dyn_shift) & final_dyn_mask) + val &= ~(1 << final_dyn_shift); + else + val |= (1 << final_dyn_shift); + + writel(val, mux_divider->reg); + if (mux_divider->lock) + spin_unlock_irqrestore(mux_divider->lock, flags); + else + __release(mux_divider->lock); + + return 0; +} + +int meson_fclk_mux_divider_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *best_parent = NULL; + int ret; + unsigned long best = 0; + struct clk_rate_request parent_req = *req; + struct meson_cpu_mux_divider *mux_divider = + to_clk_mux_divider(hw); + const struct fclk_rate_table *rate_set; + u32 premux; + + rate_set = meson_fclk_get_pll_settings(mux_divider, req->rate); + if (!rate_set) + return -EINVAL; + + premux = rate_set->premux; + best_parent = clk_hw_get_parent_by_index(hw, premux); + best = clk_hw_get_rate(best_parent); + + if (best != parent_req.rate) { + ret = clk_set_rate(best_parent->clk, parent_req.rate); + if (ret) + pr_err("Fail! Can not set to %lu, cur rate: %lu\n", + parent_req.rate, best); + pr_debug("success set parent %s rate to %lu\n", + clk_hw_get_name(best_parent), + clk_hw_get_rate(best_parent)); + } + + if (!best_parent) + return -EINVAL; + + if (best_parent) + req->best_parent_hw = best_parent; + + req->best_parent_rate = best; + return 0; + +} + +const struct clk_ops meson_fclk_cpu_ops = { + .determine_rate = meson_fclk_mux_divider_determine_rate, + .recalc_rate = meson_fclk_cpu_recalc_rate, + .get_parent = meson_fclk_cpu_get_parent, + .set_parent = meson_fclk_cpu_set_parent, + .set_rate = meson_fclk_divider_set_rate, +}; + diff --git a/drivers/amlogic/clk/clk-cpu.c b/drivers/amlogic/clk/clk-cpu.c index 22937ed..41d8048 100644 --- a/drivers/amlogic/clk/clk-cpu.c +++ b/drivers/amlogic/clk/clk-cpu.c @@ -41,14 +41,16 @@ #define MESON_CPU_CLK_CNTL1 0x00 #define MESON_CPU_CLK_CNTL 0x40 -#define MESON_CPU_CLK_MUX1 BIT(7) -#define MESON_CPU_CLK_MUX2 BIT(0) +#define MESON_POST_MUX0 BIT(2) +#define MESON_DYN_MUX BIT(10) +#define MESON_FINAL_MUX BIT(11) +#define MESON_POST_MUX1 BIT(18) #define MESON_N_WIDTH 9 #define MESON_N_SHIFT 20 #define MESON_SEL_WIDTH 2 #define MESON_SEL_SHIFT 2 - +#define MID_RATE (1000*1000*1000) #include "clkc.h" #define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb) @@ -84,6 +86,40 @@ static u8 meson_clk_cpu_get_parent(struct clk_hw *hw) static int meson_clk_cpu_set_parent(struct clk_hw *hw, u8 index) { /* To Do*/ + struct clk_mux *mux = to_clk_mux(hw); + u32 val; + unsigned long flags = 0; + + if (mux->table) { + index = mux->table[index]; + } else { + if (mux->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; + } + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + else + __acquire(mux->lock); + + if (mux->flags & CLK_MUX_HIWORD_MASK) { + val = mux->mask << (mux->shift + 16); + } else { + val = clk_readl(mux->reg); + val &= ~(mux->mask << mux->shift); + } + + val |= index << mux->shift; + clk_writel(val, mux->reg); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + else + __release(mux->lock); + return 0; } @@ -111,19 +147,15 @@ static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu *clk_cpu, { u32 cpu_clk_cntl; - /* switch MUX1 to xtal */ - cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off + if (ndata->new_rate > MID_RATE) { + /* switch final mux to fix pll */ + cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL); - cpu_clk_cntl &= ~MESON_CPU_CLK_MUX1; - writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off + cpu_clk_cntl &= ~MESON_FINAL_MUX; + writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL); - udelay(100); - - /* switch MUX2 to sys-pll */ - cpu_clk_cntl |= MESON_CPU_CLK_MUX2; - writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off - + MESON_CPU_CLK_CNTL); - + udelay(100); + } return 0; } @@ -133,13 +165,15 @@ static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu *clk_cpu, { u32 cpu_clk_cntl; - /* switch MUX1 to divisors' output */ - cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off + if (ndata->new_rate > MID_RATE) { + /* switch final mux to sys pll */ + cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL); - cpu_clk_cntl |= MESON_CPU_CLK_MUX1; - writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off + cpu_clk_cntl |= MESON_FINAL_MUX; + writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL); - udelay(100); + udelay(100); + } return 0; } @@ -165,7 +199,7 @@ int meson_clk_cpu_notifier_cb(struct notifier_block *nb, } const struct clk_ops meson_clk_cpu_ops = { - .recalc_rate = meson_clk_cpu_recalc_rate, + .recalc_rate = meson_clk_cpu_recalc_rate, .get_parent = meson_clk_cpu_get_parent, .set_parent = meson_clk_cpu_set_parent, }; diff --git a/drivers/amlogic/clk/clkc.h b/drivers/amlogic/clk/clkc.h index 4580419..72dec81 100644 --- a/drivers/amlogic/clk/clkc.h +++ b/drivers/amlogic/clk/clkc.h @@ -41,6 +41,12 @@ struct parm { u8 width; }; +struct parm_fclk { + u8 shift; + u8 width; + u32 mask; +}; + struct pll_rate_table { unsigned long rate; u16 m; @@ -50,6 +56,13 @@ struct pll_rate_table { u16 frac; }; +struct fclk_rate_table { + unsigned long rate; + u16 premux; + u16 postmux; + u16 mux_div; +}; + #define PLL_RATE(_r, _m, _n, _od) \ { \ .rate = (_r), \ @@ -68,6 +81,14 @@ struct pll_rate_table { .frac = (_frac), \ } \ +#define FCLK_PLL_RATE(_r, _premux, _postmux, _mux_div) \ + { \ + .rate = (_r), \ + .premux = (_premux), \ + .postmux = (_postmux), \ + .mux_div = (_mux_div), \ + } + struct meson_clk_pll { struct clk_hw hw; void __iomem *base; @@ -197,6 +218,24 @@ struct meson_composite { unsigned long flags; }; +/*cpu mux and divider*/ +struct meson_cpu_mux_divider { + struct clk_hw hw; + void __iomem *reg; + u32 *table; + struct parm_fclk cpu_fclk_p00; + struct parm_fclk cpu_fclk_p0; + struct parm_fclk cpu_fclk_p10; + struct parm_fclk cpu_fclk_p1; + struct parm_fclk cpu_fclk_p; + struct parm_fclk cpu_fclk_p01; + struct parm_fclk cpu_fclk_p11; + const struct fclk_rate_table *rate_table; + unsigned int rate_count; + spinlock_t *lock; + unsigned long flags; +}; + /*single clock,mux/div/gate*/ struct meson_hw { unsigned int hw_id; @@ -207,6 +246,7 @@ struct meson_hw { extern const struct clk_ops meson_clk_pll_ro_ops; extern const struct clk_ops meson_clk_pll_ops; extern const struct clk_ops meson_clk_cpu_ops; +extern const struct clk_ops meson_fclk_cpu_ops; extern const struct clk_ops meson_clk_mpll_ro_ops; extern const struct clk_ops meson_clk_mpll_ops; extern const struct clk_ops meson_clk_mux_ops; diff --git a/drivers/amlogic/clk/g12a/g12a.c b/drivers/amlogic/clk/g12a/g12a.c index 59be554..c49aa5bb 100644 --- a/drivers/amlogic/clk/g12a/g12a.c +++ b/drivers/amlogic/clk/g12a/g12a.c @@ -439,6 +439,7 @@ static struct clk_gate g12a_mipi_bandgap_gate = { * post-dividers and should be modelled with their respective PLLs via the * forthcoming coordinated clock rates feature */ + #if 0 static u32 mux_table_cpu_px0[] = { 0, 1, 2 }; static u32 mux_table_cpu_px[] = { 0, 1 }; @@ -549,6 +550,57 @@ static struct clk_mux g12a_cpu_fixedpll_p = { .flags = CLK_GET_RATE_NOCACHE, }, }; +#endif +static u32 mux_table_cpu_p[] = { 0, 1, 2 }; +static u32 mux_table_cpu_px[] = { 0, 1 }; +static struct meson_cpu_mux_divider g12a_cpu_fclk_p = { + .reg = (void *)HHI_SYS_CPU_CLK_CNTL0, + .cpu_fclk_p00 = { + .mask = 0x3, + .shift = 0, + .width = 2, + }, + .cpu_fclk_p0 = { + .mask = 0x1, + .shift = 2, + .width = 1, + }, + .cpu_fclk_p10 = { + .mask = 0x3, + .shift = 16, + .width = 2, + }, + .cpu_fclk_p1 = { + .mask = 0x1, + .shift = 18, + .width = 1, + }, + .cpu_fclk_p = { + .mask = 0x1, + .shift = 10, + .width = 1, + }, + .cpu_fclk_p01 = { + .shift = 4, + .width = 6, + }, + .cpu_fclk_p11 = { + .shift = 20, + .width = 6, + }, + .table = mux_table_cpu_p, + .rate_table = fclk_pll_rate_table, + .rate_count = ARRAY_SIZE(fclk_pll_rate_table), + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cpu_fixedpll_p", + .ops = &meson_fclk_cpu_ops, + .parent_names = (const char *[]){ "xtal", "fclk_div2", + "fclk_div3"}, + .num_parents = 3, + .flags = (CLK_GET_RATE_NOCACHE | CLK_IGNORE_UNUSED), + }, +}; static struct meson_clk_cpu g12a_cpu_clk = { .reg_off = HHI_SYS_CPU_CLK_CNTL0, @@ -791,13 +843,15 @@ static struct clk_hw *g12a_clk_hws[] = { [CLKID_AO_IFACE] = &g12a_ao_iface.hw, [CLKID_AO_I2C] = &g12a_ao_i2c.hw, #endif +#if 0 [CLKID_CPU_FCLK_P00] = &g12a_cpu_fixedpll_p00.hw, [CLKID_CPU_FCLK_P01] = &g12a_cpu_fixedpll_p01.hw, [CLKID_CPU_FCLK_P0] = &g12a_cpu_fixedpll_p0.hw, [CLKID_CPU_FCLK_P10] = &g12a_cpu_fixedpll_p10.hw, [CLKID_CPU_FCLK_P11] = &g12a_cpu_fixedpll_p11.hw, [CLKID_CPU_FCLK_P1] = &g12a_cpu_fixedpll_p1.hw, - [CLKID_CPU_FCLK_P] = &g12a_cpu_fixedpll_p.hw, +#endif + [CLKID_CPU_FCLK_P] = &g12a_cpu_fclk_p.hw, [CLKID_CPU_CLK] = &g12a_cpu_clk.mux.hw, [CLKID_PCIE_PLL] = &g12a_pcie_pll.hw, @@ -919,14 +973,17 @@ static void __init g12a_clkc_init(struct device_node *np) g12a_clk_mplls[i]->base = clk_base; /* Populate the base address for CPU clk */ + g12a_cpu_clk.base = clk_base; g12a_cpu_clk.mux.reg = clk_base + (u64)g12a_cpu_clk.mux.reg; +#if 0 g12a_cpu_fixedpll_p00.reg = clk_base + (u64)g12a_cpu_fixedpll_p00.reg; g12a_cpu_fixedpll_p01.reg = clk_base + (u64)g12a_cpu_fixedpll_p01.reg; g12a_cpu_fixedpll_p10.reg = clk_base + (u64)g12a_cpu_fixedpll_p10.reg; g12a_cpu_fixedpll_p11.reg = clk_base + (u64)g12a_cpu_fixedpll_p11.reg; g12a_cpu_fixedpll_p0.reg = clk_base + (u64)g12a_cpu_fixedpll_p0.reg; g12a_cpu_fixedpll_p1.reg = clk_base + (u64)g12a_cpu_fixedpll_p1.reg; - g12a_cpu_fixedpll_p.reg = clk_base + (u64)g12a_cpu_fixedpll_p.reg; +#endif + g12a_cpu_fclk_p.reg = clk_base + (u64)g12a_cpu_fclk_p.reg; /* Populate the base address for the MPEG clks */ g12a_mpeg_clk_sel.reg = clk_base + (u64)g12a_mpeg_clk_sel.reg; diff --git a/drivers/amlogic/clk/g12a/g12a.h b/drivers/amlogic/clk/g12a/g12a.h index a5bcc63..1738191 100644 --- a/drivers/amlogic/clk/g12a/g12a.h +++ b/drivers/amlogic/clk/g12a/g12a.h @@ -134,7 +134,7 @@ static const struct pll_rate_table g12a_pll_rate_table[] = { PLL_RATE(1704000000, 142, 1, 1), /*DCO=3408M*/ PLL_RATE(1800000000, 150, 1, 1), /*DCO=3600M*/ PLL_RATE(1896000000, 158, 1, 1), /*DCO=3792M*/ - PLL_RATE(1992000000, 166, 1, 1), /*DCO=3984M*/ + PLL_RATE(2016000000, 168, 1, 1), /*DCO=4032M*/ PLL_RATE(2100000000, 175, 1, 1), /*DCO=4200M*/ PLL_RATE(2196000000, 183, 1, 1), /*DCO=4392M*/ PLL_RATE(2292000000, 191, 1, 1), /*DCO=4584M*/ @@ -179,6 +179,19 @@ static const struct pll_rate_table g12a_pll_rate_table[] = { { /* sentinel */ }, }; +/*fix pll rate table*/ +static const struct fclk_rate_table fclk_pll_rate_table[] = { + FCLK_PLL_RATE(50000000, 1, 1, 19), + FCLK_PLL_RATE(100000000, 1, 1, 9), + FCLK_PLL_RATE(167000000, 2, 1, 3), + FCLK_PLL_RATE(200000000, 1, 1, 4), + FCLK_PLL_RATE(250000000, 1, 1, 3), + FCLK_PLL_RATE(333000000, 2, 1, 1), + FCLK_PLL_RATE(500000000, 1, 1, 1), + FCLK_PLL_RATE(667000000, 2, 0, 0), + FCLK_PLL_RATE(1000000000, 1, 0, 0), +}; + /*PCIE clk_out = 24M*m/2/2/OD*/ static const struct pll_rate_table g12a_pcie_pll_rate_table[] = { PLL_RATE(100000000, 150, 0, 9), diff --git a/drivers/amlogic/clk/g12a/g12a_clk-pll.c b/drivers/amlogic/clk/g12a/g12a_clk-pll.c index c34a4c9..a370bc3 100644 --- a/drivers/amlogic/clk/g12a/g12a_clk-pll.c +++ b/drivers/amlogic/clk/g12a/g12a_clk-pll.c @@ -294,7 +294,7 @@ static int meson_g12a_pll_set_rate(struct clk_hw *hw, unsigned long rate, /* PLL reset */ writel(readl(pll->base + p->reg_off) | MESON_PLL_ENABLE, pll->base + p->reg_off); - udelay(10); + udelay(50); writel(readl(pll->base + p->reg_off) & (~MESON_PLL_RESET), pll->base + p->reg_off); ret = meson_g12a_pll_wait_lock(pll, p); @@ -375,6 +375,8 @@ static void meson_g12a_pll_disable(struct clk_hw *hw) if (pll->lock) spin_lock_irqsave(pll->lock, flags); + writel(readl(pll->base + p->reg_off) | (MESON_PLL_RESET), + pll->base + p->reg_off); writel(readl(pll->base + p->reg_off) & (~MESON_PLL_ENABLE), pll->base + p->reg_off); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 7de66df..f393d97 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -203,6 +203,16 @@ config ARM_SCPI_CPUFREQ This driver uses SCPI Message Protocol driver to interact with the firmware providing the CPU DVFS functionality. +config AMLOGIC_MESON_CPUFREQ + tristate "MESON based CPUfreq driver" + depends on ARM_BIG_LITTLE_CPUFREQ + help + This adds the CPUfreq driver support for ARM big.LITTLE platforms + using MESON dt for CPU power management. + + This driver uses meson cpufreq driver to interact with the + firmware providing the CPU DVFS functionality. + config ARM_SPEAR_CPUFREQ bool "SPEAr CPUFreq support" depends on PLAT_SPEAR diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index f0c9905..56b7b29 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o +obj-$(CONFIG_AMLOGIC_MESON_CPUFREQ) += meson-cpufreq.o obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o obj-$(CONFIG_ARM_TEGRA20_CPUFREQ) += tegra20-cpufreq.o diff --git a/drivers/cpufreq/meson-cpufreq.c b/drivers/cpufreq/meson-cpufreq.c new file mode 100644 index 0000000..8b2619e --- /dev/null +++ b/drivers/cpufreq/meson-cpufreq.c @@ -0,0 +1,1081 @@ +/* + * Generic big.LITTLE CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver and gets + * Frequency information from Device Tree. Freq table in DT must be in KHz. + * + * Copyright (C) 2013 Linaro. + * Viresh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#define DEBUG 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm_big_little.h" +#include "../regulator/internal.h" + +/* Currently we support only two clusters */ +#define A15_CLUSTER 0 +#define A7_CLUSTER 1 +#define MAX_CLUSTERS 2 + +#ifdef CONFIG_BL_SWITCHER +#include +static bool bL_switching_enabled; +#define is_bL_switching_enabled() bL_switching_enabled +#define set_switching_enabled(x) (bL_switching_enabled = (x)) +#else +#define is_bL_switching_enabled() false +#define set_switching_enabled(x) do { } while (0) +#define bL_switch_request(...) do { } while (0) +#define bL_switcher_put_enabled() do { } while (0) +#define bL_switcher_get_enabled() do { } while (0) +#endif + +#define ACTUAL_FREQ(cluster, freq) ((cluster == A7_CLUSTER) ? freq << 1 : freq) +#define VIRT_FREQ(cluster, freq) ((cluster == A7_CLUSTER) ? freq >> 1 : freq) + +/*core power supply*/ +#define CORE_SUPPLY "cpu" + +/* Core Clocks */ +#define CORE_CLK "core_clk" +#define LOW_FREQ_CLK_PARENT "low_freq_clk_parent" +#define HIGH_FREQ_CLK_PARENT "high_freq_clk_parent" + +static struct thermal_cooling_device *cdev[MAX_CLUSTERS]; +static struct cpufreq_arm_bL_ops *arm_bL_ops; +static struct clk *clk[MAX_CLUSTERS]; +static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS + 1]; +static atomic_t cluster_usage[MAX_CLUSTERS + 1]; + +static unsigned int clk_big_min; /* (Big) clock frequencies */ +static unsigned int clk_little_max; /* Maximum clock frequency (Little) */ + +/* Default voltage_tolerance */ +#define DEF_VOLT_TOL 0 + +/*mid rate for set parent,Khz*/ +static unsigned int mid_rate = (1000*1000); +static unsigned int gap_rate = (10*1000*1000); + +struct meson_cpufreq_driver_data { + struct device *cpu_dev; + struct regulator *reg; + /* voltage tolerance in percentage */ + unsigned int volt_tol; + struct clk *high_freq_clk_p; + struct clk *low_freq_clk_p; +}; + + +static DEFINE_PER_CPU(unsigned int, physical_cluster); +static DEFINE_PER_CPU(unsigned int, cpu_last_req_freq); + +static struct mutex cluster_lock[MAX_CLUSTERS]; + +static inline int raw_cpu_to_cluster(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static inline int cpu_to_cluster(int cpu) +{ + return is_bL_switching_enabled() ? + MAX_CLUSTERS : raw_cpu_to_cluster(cpu); +} + +static int dt_get_transition_latency(struct device *cpu_dev) +{ + struct device_node *np; + u32 transition_latency = CPUFREQ_ETERNAL; + + np = of_node_get(cpu_dev->of_node); + if (!np) { + pr_info("Failed to find cpu node. Use CPUFREQ_ETERNAL transition latency\n"); + return CPUFREQ_ETERNAL; + } + + of_property_read_u32(np, "clock-latency", &transition_latency); + of_node_put(np); + + pr_debug("%s: clock-latency: %d\n", __func__, transition_latency); + return transition_latency; +} + +static unsigned int find_cluster_maxfreq(int cluster) +{ + int j; + u32 max_freq = 0, cpu_freq; + + for_each_online_cpu(j) { + cpu_freq = per_cpu(cpu_last_req_freq, j); + + if ((cluster == per_cpu(physical_cluster, j)) && + (max_freq < cpu_freq)) + max_freq = cpu_freq; + } + + pr_debug("%s: cluster: %d, max freq: %d\n", __func__, cluster, + max_freq); + + return max_freq; +} + +static unsigned int clk_get_cpu_rate(unsigned int cpu) +{ + u32 cur_cluster = per_cpu(physical_cluster, cpu); + u32 rate = clk_get_rate(clk[cur_cluster]) / 1000; + + /* For switcher we use virtual A7 clock rates */ + if (is_bL_switching_enabled()) + rate = VIRT_FREQ(cur_cluster, rate); + + pr_debug("%s: cpu: %d, cluster: %d, freq: %u\n", __func__, cpu, + cur_cluster, rate); + + return rate; +} + +static unsigned int meson_bL_cpufreq_get_rate(unsigned int cpu) +{ + if (is_bL_switching_enabled()) { + pr_debug("%s: freq: %d\n", __func__, per_cpu(cpu_last_req_freq, + cpu)); + + return per_cpu(cpu_last_req_freq, cpu); + } else { + return clk_get_cpu_rate(cpu); + } +} + +static unsigned int meson_bL_cpufreq_set_rate(struct cpufreq_policy *policy, + u32 old_cluster, u32 new_cluster, u32 rate) +{ + struct clk *low_freq_clk_p, *high_freq_clk_p; + struct meson_cpufreq_driver_data *cpufreq_data; + u32 new_rate, prev_rate; + int ret, cpu = 0; + bool bLs = is_bL_switching_enabled(); + + cpu = policy->cpu; + cpufreq_data = policy->driver_data; + high_freq_clk_p = cpufreq_data->high_freq_clk_p; + low_freq_clk_p = cpufreq_data->low_freq_clk_p; + +#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI + /* MARK: cluster0 and cluster share the same scpi lock, + * and don't send scpi command at the same time + */ + mutex_lock(&cluster_lock[0]); +#else + mutex_lock(&cluster_lock[new_cluster]); +#endif + + if (bLs) { + prev_rate = per_cpu(cpu_last_req_freq, cpu); + per_cpu(cpu_last_req_freq, cpu) = rate; + per_cpu(physical_cluster, cpu) = new_cluster; + + new_rate = find_cluster_maxfreq(new_cluster); + new_rate = ACTUAL_FREQ(new_cluster, new_rate); + } else { + new_rate = rate; + } + + pr_debug("%s: cpu: %d, old cluster: %d, new cluster: %d, freq: %d\n", + __func__, cpu, old_cluster, new_cluster, new_rate); + + if (new_rate > mid_rate) { + if (__clk_get_enable_count(high_freq_clk_p) == 0) { + ret = clk_prepare_enable(high_freq_clk_p); + if (ret) { + pr_err("%s: CPU%d clk_prepare_enable failed\n", + __func__, policy->cpu); + return ret; + } + } + + ret = clk_set_parent(clk[new_cluster], low_freq_clk_p); + if (ret) { + pr_err("%s: error in setting low_freq_clk_p as parent\n", + __func__); + return ret; + } + + ret = clk_set_rate(high_freq_clk_p, new_rate * 1000); + if (ret) { + pr_err("%s: error in setting low_freq_clk_p rate!\n", + __func__); + return ret; + } + + ret = clk_set_parent(clk[new_cluster], high_freq_clk_p); + if (ret) { + pr_err("%s: error in setting high_freq_clk_p as parent\n", + __func__); + return ret; + } + } else { + ret = clk_set_rate(low_freq_clk_p, new_rate * 1000); + if (ret) { + pr_err("%s: error in setting low_freq_clk_p rate!\n", + __func__); + return ret; + } + + ret = clk_set_parent(clk[new_cluster], low_freq_clk_p); + if (ret) { + pr_err("%s: error in setting low_freq_clk_p rate!\n", + __func__); + return ret; + } + + if (__clk_get_enable_count(high_freq_clk_p) >= 1) + clk_disable_unprepare(high_freq_clk_p); + } + + if (!ret) { + /* + * FIXME: clk_set_rate hasn't returned an error here however it + * may be that clk_change_rate failed due to hardware or + * firmware issues and wasn't able to report that due to the + * current design of the clk core layer. To work around this + * problem we will read back the clock rate and check it is + * correct. This needs to be removed once clk core is fixed. + */ + if (abs(clk_get_rate(clk[new_cluster]) - new_rate * 1000) + > gap_rate) + ret = -EIO; + } + + if (WARN_ON(ret)) { + pr_err("clk_set_rate failed: %d, new cluster: %d\n", ret, + new_cluster); + if (bLs) { + per_cpu(cpu_last_req_freq, cpu) = prev_rate; + per_cpu(physical_cluster, cpu) = old_cluster; + } + +#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI + mutex_unlock(&cluster_lock[0]); +#else + mutex_unlock(&cluster_lock[new_cluster]); +#endif + + return ret; + } + +#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI + mutex_unlock(&cluster_lock[0]); +#else + mutex_unlock(&cluster_lock[new_cluster]); +#endif + + /* Recalc freq for old cluster when switching clusters */ + if (old_cluster != new_cluster) { + pr_debug("%s: cpu: %d, old cluster: %d, new cluster: %d\n", + __func__, cpu, old_cluster, new_cluster); + + /* Switch cluster */ + bL_switch_request(cpu, new_cluster); + +#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI + mutex_lock(&cluster_lock[0]); +#else + mutex_lock(&cluster_lock[new_cluster]); +#endif + + /* Set freq of old cluster if there are cpus left on it */ + new_rate = find_cluster_maxfreq(old_cluster); + new_rate = ACTUAL_FREQ(old_cluster, new_rate); + + if (new_rate) { + pr_debug("%s: Updating rate of old cluster: %d, to freq: %d\n", + __func__, old_cluster, new_rate); + + if (clk_set_rate(clk[old_cluster], new_rate * 1000)) + pr_err("%s: clk_set_rate failed: %d, old cluster: %d\n", + __func__, ret, old_cluster); + } +#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI + mutex_unlock(&cluster_lock[0]); +#else + mutex_unlock(&cluster_lock[new_cluster]); +#endif + } + + return 0; +} + +static int meson_regulator_set_volate(struct regulator *regulator, int old_uv, + int new_uv, int tol_uv) +{ + int cur, to, vol_cnt = 0; + int ret = 0; + int temp_uv = 0; + struct regulator_dev *rdev = regulator->rdev; + + cur = regulator_map_voltage_iterate(rdev, old_uv, old_uv + tol_uv); + to = regulator_map_voltage_iterate(rdev, new_uv, new_uv + tol_uv); + vol_cnt = regulator_count_voltages(regulator); + pr_debug("%s:old_uv:%d,cur:%d----->new_uv:%d,to:%d,vol_cnt=%d\n", + __func__, old_uv, cur, new_uv, to, vol_cnt); + + if (to >= vol_cnt) + to = vol_cnt - 1; + + if (cur < 0 || cur >= vol_cnt) { + temp_uv = regulator_list_voltage(regulator, to); + ret = regulator_set_voltage_tol(regulator, temp_uv, temp_uv + + tol_uv); + udelay(200); + return ret; + } + + while (cur != to) { + /* adjust to target voltage step by step */ + if (cur < to) { + if (cur < to - 3) + cur += 3; + else + cur = to; + } else { + if (cur > to + 3) + cur -= 3; + else + cur = to; + } + temp_uv = regulator_list_voltage(regulator, cur); + ret = regulator_set_voltage_tol(regulator, temp_uv, + temp_uv + tol_uv); + + pr_debug("%s:temp_uv:%d, cur:%d, change_cur_uv:%d\n", __func__, + temp_uv, cur, regulator_get_voltage(regulator)); + udelay(200); + } + return ret; + +} + +/* Set clock frequency */ +static int meson_bL_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int index) +{ + struct dev_pm_opp *opp; + u32 cpu = policy->cpu, cur_cluster, new_cluster, actual_cluster; + unsigned long int freq_new, freq_old; + unsigned int volt_new = 0, volt_old = 0, volt_tol = 0; + struct meson_cpufreq_driver_data *cpufreq_data; + struct device *cpu_dev; + struct regulator *cpu_reg; + int ret = 0; + + if (!policy) { + pr_err("invalid policy, returning\n"); + return -ENODEV; + } + cpufreq_data = policy->driver_data; + cpu_dev = cpufreq_data->cpu_dev; + cpu_reg = cpufreq_data->reg; + cur_cluster = cpu_to_cluster(cpu); + new_cluster = actual_cluster = per_cpu(physical_cluster, cpu); + + pr_debug("setting target for cpu %d, index =%d\n", policy->cpu, index); + + freq_new = freq_table[cur_cluster][index].frequency*1000; + + if (!IS_ERR(cpu_reg)) { + rcu_read_lock(); + opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_new); + if (IS_ERR(opp)) { + rcu_read_unlock(); + pr_err("failed to find OPP for %lu Khz\n", + freq_new / 1000); + return PTR_ERR(opp); + } + volt_new = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + volt_old = regulator_get_voltage(cpu_reg); + volt_tol = volt_new * cpufreq_data->volt_tol / 100; + pr_debug("Found OPP: %lu kHz, %u, tolerance: %u\n", + freq_new / 1000, volt_new, volt_tol); + } + + if (is_bL_switching_enabled()) { + per_cpu(cpu_last_req_freq, policy->cpu) = + clk_get_cpu_rate(policy->cpu); + if ((actual_cluster == A15_CLUSTER) && + (freq_new < clk_big_min)) { + new_cluster = A7_CLUSTER; + } else if ((actual_cluster == A7_CLUSTER) && + (freq_new > clk_little_max)) { + new_cluster = A15_CLUSTER; + } + } else + freq_old = clk_get_rate(clk[cur_cluster]); + + pr_debug("Scalling from %lu MHz, %u mV,cur_cluster_id:%u, --> %lu MHz, %u mV,new_cluster_id:%u\n", + freq_old / 1000000, (volt_old > 0) ? volt_old / 1000 : -1, + cur_cluster, + freq_new / 1000000, volt_new ? volt_new / 1000 : -1, + new_cluster); + + /*cpufreq up,change voltage before frequency*/ + if (freq_new > freq_old) { + ret = meson_regulator_set_volate(cpu_reg, volt_old, + volt_new, volt_tol); + if (ret) { + pr_err("failed to scale voltage %u %u up: %d\n", + volt_new, volt_tol, ret); + return ret; + } + } + + /*scale clock frequency*/ + ret = meson_bL_cpufreq_set_rate(policy, actual_cluster, new_cluster, + freq_new / 1000); + if (ret) { + pr_err("failed to set clock %luMhz rate: %d\n", + freq_new / 1000000, ret); + if ((volt_old > 0) && (freq_new > freq_old)) { + pr_debug("scaling to old voltage %u\n", volt_old); + meson_regulator_set_volate(cpu_reg, volt_old, volt_old, + volt_tol); + } + return ret; + } + /*cpufreq down,change voltage after frequency*/ + if (freq_new < freq_old) { + ret = meson_regulator_set_volate(cpu_reg, volt_old, + volt_new, volt_tol); + if (ret) { + pr_err("failed to scale volt %u %u down: %d\n", + volt_new, volt_tol, ret); + meson_bL_cpufreq_set_rate(policy, actual_cluster, + actual_cluster, freq_old / 1000); + } + } + + pr_debug("After transition, new lk rate %luMhz, volt %dmV\n", + clk_get_rate(clk[cur_cluster]) / 1000000, + regulator_get_voltage(cpu_reg) / 1000); + return ret; +} + + +static inline u32 get_table_count(struct cpufreq_frequency_table *table) +{ + int count; + + for (count = 0; table[count].frequency != CPUFREQ_TABLE_END; count++) + ; + + return count; +} + +/* get the minimum frequency in the cpufreq_frequency_table */ +static inline u32 get_table_min(struct cpufreq_frequency_table *table) +{ + struct cpufreq_frequency_table *pos; + uint32_t min_freq = ~0; + + cpufreq_for_each_entry(pos, table) + if (pos->frequency < min_freq) + min_freq = pos->frequency; + return min_freq; +} + +/* get the maximum frequency in the cpufreq_frequency_table */ +static inline u32 get_table_max(struct cpufreq_frequency_table *table) +{ + struct cpufreq_frequency_table *pos; + uint32_t max_freq = 0; + + cpufreq_for_each_entry(pos, table) + if (pos->frequency > max_freq) + max_freq = pos->frequency; + return max_freq; +} + +static int merge_cluster_tables(void) +{ + int i, j, k = 0, count = 1; + struct cpufreq_frequency_table *table; + + for (i = 0; i < MAX_CLUSTERS; i++) + count += get_table_count(freq_table[i]); + + table = kcalloc(count, sizeof(*table), GFP_KERNEL); + if (!table) + return -ENOMEM; + + freq_table[MAX_CLUSTERS] = table; + + /* Add in reverse order to get freqs in increasing order */ + for (i = MAX_CLUSTERS - 1; i >= 0; i--) { + for (j = 0; freq_table[i][j].frequency != CPUFREQ_TABLE_END; + j++) { + table[k].frequency = VIRT_FREQ(i, + freq_table[i][j].frequency); + pr_debug("%s: index: %d, freq: %d\n", __func__, k, + table[k].frequency); + k++; + } + } + + table[k].driver_data = k; + table[k].frequency = CPUFREQ_TABLE_END; + + pr_debug("%s: End, table: %p, count: %d\n", __func__, table, k); + + return 0; +} + +static void _put_cluster_clk_and_freq_table(struct device *cpu_dev, + const struct cpumask *cpumask) +{ + u32 cluster = raw_cpu_to_cluster(cpu_dev->id); + + if (!freq_table[cluster]) + return; + + clk_put(clk[cluster]); + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]); + if (arm_bL_ops->free_opp_table) + arm_bL_ops->free_opp_table(cpumask); + dev_dbg(cpu_dev, "%s: cluster: %d\n", __func__, cluster); +} + +static void put_cluster_clk_and_freq_table(struct device *cpu_dev, + const struct cpumask *cpumask) +{ + u32 cluster = cpu_to_cluster(cpu_dev->id); + int i; + + if (atomic_dec_return(&cluster_usage[cluster])) + return; + + if (cluster < MAX_CLUSTERS) + return _put_cluster_clk_and_freq_table(cpu_dev, cpumask); + + for_each_present_cpu(i) { + struct device *cdev = get_cpu_device(i); + + if (!cdev) { + pr_err("%s: failed to get cpu%d device\n", __func__, i); + return; + } + + _put_cluster_clk_and_freq_table(cdev, cpumask); + } + + /* free virtual table */ + kfree(freq_table[cluster]); +} + + +static int _get_cluster_clk_and_freq_table(struct device *cpu_dev, + const struct cpumask *cpumask) +{ + u32 cluster = raw_cpu_to_cluster(cpu_dev->id); + int ret; + + if (freq_table[cluster]) + return 0; + + ret = arm_bL_ops->init_opp_table(cpumask); + if (ret) { + dev_err(cpu_dev, "%s: init_opp_table failed, cpu: %d, err: %d\n", + __func__, cpu_dev->id, ret); + goto out; + } + + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster]); + if (ret) { + dev_err(cpu_dev, "%s: failed to init cpufreq table, cpu: %d, err: %d\n", + __func__, cpu_dev->id, ret); + goto free_opp_table; + } + + clk[cluster] = of_clk_get_by_name(of_node_get(cpu_dev->of_node), + CORE_CLK); + if (!IS_ERR(clk[cluster])) { + dev_dbg(cpu_dev, "%s: clk: %p & freq table: %p, cluster: %d\n", + __func__, clk[cluster], freq_table[cluster], + cluster); + return 0; + } + + dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d, cluster: %d\n", + __func__, cpu_dev->id, cluster); + + ret = PTR_ERR(clk[cluster]); + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]); + +free_opp_table: + if (arm_bL_ops->free_opp_table) + arm_bL_ops->free_opp_table(cpumask); +out: + dev_err(cpu_dev, "%s: Failed to get data for cluster: %d\n", __func__, + cluster); + return ret; +} + +static int get_cluster_clk_and_freq_table(struct device *cpu_dev, + const struct cpumask *cpumask) +{ + u32 cluster = cpu_to_cluster(cpu_dev->id); + int i, ret; + + if (atomic_inc_return(&cluster_usage[cluster]) != 1) + return 0; + + if (cluster < MAX_CLUSTERS) { + ret = _get_cluster_clk_and_freq_table(cpu_dev, cpumask); + if (ret) + atomic_dec(&cluster_usage[cluster]); + return ret; + } + + /* + * Get data for all clusters and fill virtual cluster with a merge of + * both + */ + + for_each_present_cpu(i) { + struct device *cdev = get_cpu_device(i); + + if (!cdev) { + pr_err("%s: failed to get cpu%d device\n", __func__, i); + return -ENODEV; + } + + ret = _get_cluster_clk_and_freq_table(cdev, cpumask); + if (ret) + goto put_clusters; + } + + ret = merge_cluster_tables(); + if (ret) + goto put_clusters; + + /* Assuming 2 cluster, set clk_big_min and clk_little_max */ + clk_big_min = get_table_min(freq_table[0]); + clk_little_max = VIRT_FREQ(1, get_table_max(freq_table[1])); + + pr_debug("%s: cluster: %d, clk_big_min: %d, clk_little_max: %d\n", + __func__, cluster, clk_big_min, clk_little_max); + + return 0; + +put_clusters: + for_each_present_cpu(i) { + struct device *cdev = get_cpu_device(i); + + if (!cdev) { + pr_err("%s: failed to get cpu%d device\n", __func__, i); + return -ENODEV; + } + + _put_cluster_clk_and_freq_table(cdev, cpumask); + } + + atomic_dec(&cluster_usage[cluster]); + + return ret; +} + + +/* Per-CPU initialization */ +static int meson_bL_cpufreq_init(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + struct dev_pm_opp *opp; + struct device *cpu_dev; + struct device_node *np; + struct regulator *cpu_reg = NULL; + struct meson_cpufreq_driver_data *cpufreq_data; + struct clk *low_freq_clk_p, *high_freq_clk_p = NULL; + unsigned int volt_new = 0, volt_old = 0, volt_tol = 0; + unsigned long freq_hz = 0; + int cpu = 0; + int ret = 0; + + if (!policy) { + pr_err("invalid cpufreq_policy\n"); + return -ENODEV; + } + + cpu = policy->cpu; + cpu_dev = get_cpu_device(cpu); + if (IS_ERR(cpu_dev)) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); + return -ENODEV; + } + + np = of_node_get(cpu_dev->of_node); + if (!np) { + pr_err("ERROR failed to find cpu%d node\n", cpu); + return -ENOENT; + } + + cpufreq_data = kzalloc(sizeof(*cpufreq_data), GFP_KERNEL); + if (IS_ERR(cpufreq_data)) { + pr_err("%s: failed to alloc cpufreq data!\n", __func__); + return -ENOMEM; + goto free_np; + } + + low_freq_clk_p = of_clk_get_by_name(np, LOW_FREQ_CLK_PARENT); + if (IS_ERR(low_freq_clk_p)) { + pr_err("%s: Failed to get low parent for cpu: %d, cluster: %d\n", + __func__, cpu_dev->id, cur_cluster); + goto free_clk; + } + + high_freq_clk_p = of_clk_get_by_name(np, HIGH_FREQ_CLK_PARENT); + if (IS_ERR(high_freq_clk_p)) { + pr_err("%s: Failed to get high parent for cpu: %d,cluster: %d\n", + __func__, cpu_dev->id, cur_cluster); + goto free_mem; + } + + cpu_reg = devm_regulator_get(cpu_dev, CORE_SUPPLY); + if (IS_ERR(cpu_reg)) { + pr_err("%s:failed to get regulator, %ld\n", __func__, + PTR_ERR(cpu_reg)); + ret = PTR_ERR(cpu_reg); + goto free_clk; + } + + if (of_property_read_u32(np, "voltage-tolerance", &volt_tol)) + volt_tol = DEF_VOLT_TOL; + pr_info("value of voltage_tolerance %u\n", volt_tol); + + if (cur_cluster < MAX_CLUSTERS) { + int cpu; + + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); + for_each_cpu(cpu, policy->cpus) + per_cpu(physical_cluster, cpu) = cur_cluster; + } else { + /* Assumption: during init, we are always running on A15 */ + per_cpu(physical_cluster, policy->cpu) = A15_CLUSTER; + } + + ret = get_cluster_clk_and_freq_table(cpu_dev, policy->cpus); + + if (ret) + return ret; + + ret = cpufreq_table_validate_and_show(policy, freq_table[cur_cluster]); + if (ret) { + dev_err(cpu_dev, "CPU %d, cluster: %d invalid freq table\n", + policy->cpu, cur_cluster); + put_cluster_clk_and_freq_table(cpu_dev, policy->cpus); + return ret; + } + + if (arm_bL_ops->get_transition_latency) + policy->cpuinfo.transition_latency = + arm_bL_ops->get_transition_latency(cpu_dev); + else + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + + cpufreq_data->cpu_dev = cpu_dev; + cpufreq_data->low_freq_clk_p = low_freq_clk_p; + cpufreq_data->high_freq_clk_p = high_freq_clk_p; + cpufreq_data->reg = cpu_reg; + cpufreq_data->volt_tol = volt_tol; + policy->driver_data = cpufreq_data; + policy->suspend_freq = get_table_max(freq_table[0]); + + if (is_bL_switching_enabled()) + per_cpu(cpu_last_req_freq, policy->cpu) = + clk_get_cpu_rate(policy->cpu); + else + policy->cur = clk_get_rate(clk[cur_cluster]) / 1000; + + freq_hz = policy->cur*1000; + opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz); + volt_new = dev_pm_opp_get_voltage(opp); + volt_old = regulator_get_voltage(cpu_reg); + volt_tol = volt_new * cpufreq_data->volt_tol / 100; + ret = meson_regulator_set_volate(cpu_reg, volt_old, volt_new, volt_tol); + + dev_info(cpu_dev, "%s: CPU %d initialized\n", __func__, policy->cpu); + + goto free_np; + +free_clk: + if (!IS_ERR(low_freq_clk_p)) + clk_put(low_freq_clk_p); + if (!IS_ERR(high_freq_clk_p)) + clk_put(high_freq_clk_p); +free_mem: + kfree(cpufreq_data); +free_np: + if (!np) + of_node_put(np); + return ret; +} + +static int meson_bL_cpufreq_exit(struct cpufreq_policy *policy) +{ + struct device *cpu_dev; + struct sprd_cpufreq_driver_data *cpufreq_data; + int cur_cluster = cpu_to_cluster(policy->cpu); + + cpufreq_data = policy->driver_data; + if (cpufreq_data == NULL) + return 0; + + if (cur_cluster < MAX_CLUSTERS) { + cpufreq_cooling_unregister(cdev[cur_cluster]); + cdev[cur_cluster] = NULL; + } + + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); + return -ENODEV; + } + + put_cluster_clk_and_freq_table(cpu_dev, policy->related_cpus); + dev_dbg(cpu_dev, "%s: Exited, cpu: %d\n", __func__, policy->cpu); + kfree(cpufreq_data); + + return 0; +} + +static int meson_cpufreq_suspend(struct cpufreq_policy *policy) +{ + + return cpufreq_generic_suspend(policy); +} + +static int meson_cpufreq_resume(struct cpufreq_policy *policy) +{ + return cpufreq_generic_suspend(policy); + +} + +static struct cpufreq_driver meson_cpufreq_driver = { + .name = "arm-big-little", + .flags = CPUFREQ_STICKY | + CPUFREQ_HAVE_GOVERNOR_PER_POLICY | + CPUFREQ_NEED_INITIAL_FREQ_CHECK, + .verify = cpufreq_generic_frequency_table_verify, + .target_index = meson_bL_cpufreq_set_target, + .get = meson_bL_cpufreq_get_rate, + .init = meson_bL_cpufreq_init, + .exit = meson_bL_cpufreq_exit, + .attr = cpufreq_generic_attr, + .suspend = meson_cpufreq_suspend, + .resume = meson_cpufreq_resume, +}; + +#ifdef CONFIG_BL_SWITCHER +static int bL_cpufreq_switcher_notifier(struct notifier_block *nfb, + unsigned long action, void *_arg) +{ + pr_debug("%s: action: %ld\n", __func__, action); + + switch (action) { + case BL_NOTIFY_PRE_ENABLE: + case BL_NOTIFY_PRE_DISABLE: + cpufreq_unregister_driver(&bL_cpufreq_driver); + break; + + case BL_NOTIFY_POST_ENABLE: + set_switching_enabled(true); + cpufreq_register_driver(&bL_cpufreq_driver); + break; + + case BL_NOTIFY_POST_DISABLE: + set_switching_enabled(false); + cpufreq_register_driver(&bL_cpufreq_driver); + break; + + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static struct notifier_block bL_switcher_notifier = { + .notifier_call = bL_cpufreq_switcher_notifier, +}; + +static int meson_cpufreq_register_notifier(void) +{ + return bL_switcher_register_notifier(&bL_switcher_notifier); +} + +static int meson_cpufreq_unregister_notifier(void) +{ + return bL_switcher_unregister_notifier(&bL_switcher_notifier); +} +#else +static int meson_cpufreq_register_notifier(void) { return 0; } +static int meson_cpufreq_unregister_notifier(void) { return 0; } +#endif + +int meson_cpufreq_register(struct cpufreq_arm_bL_ops *ops) +{ + int ret, i; + + if (arm_bL_ops) { + pr_debug("%s: Already registered: %s, exiting\n", __func__, + arm_bL_ops->name); + return -EBUSY; + } + + if (!ops || !strlen(ops->name) || !ops->init_opp_table) { + pr_err("%s: Invalid arm_bL_ops, exiting\n", __func__); + return -ENODEV; + } + + arm_bL_ops = ops; + set_switching_enabled(bL_switcher_get_enabled()); + + for (i = 0; i < MAX_CLUSTERS; i++) + mutex_init(&cluster_lock[i]); + + ret = cpufreq_register_driver(&meson_cpufreq_driver); + if (ret) { + pr_err("%s: Failed registering platform driver: %s, err: %d\n", + __func__, ops->name, ret); + arm_bL_ops = NULL; + } else { + ret = meson_cpufreq_register_notifier(); + if (ret) { + cpufreq_unregister_driver(&meson_cpufreq_driver); + arm_bL_ops = NULL; + } else { + pr_err("%s: Registered platform driver: %s\n", + __func__, ops->name); + } + } + + bL_switcher_put_enabled(); + return ret; +} +EXPORT_SYMBOL_GPL(meson_cpufreq_register); + +void meson_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops) +{ + if (arm_bL_ops != ops) { + pr_err("%s: Registered with: %s, can't unregister, exiting\n", + __func__, arm_bL_ops->name); + return; + } + + bL_switcher_get_enabled(); + meson_cpufreq_unregister_notifier(); + cpufreq_unregister_driver(&meson_cpufreq_driver); + bL_switcher_put_enabled(); + pr_info("%s: Un-registered platform driver: %s\n", __func__, + arm_bL_ops->name); + arm_bL_ops = NULL; +} +EXPORT_SYMBOL_GPL(meson_cpufreq_unregister); + + +static struct cpufreq_arm_bL_ops meson_dt_bL_ops = { + .name = "meson_dt-bl", + .get_transition_latency = dt_get_transition_latency, + .init_opp_table = dev_pm_opp_of_cpumask_add_table, + .free_opp_table = dev_pm_opp_of_cpumask_remove_table, +}; + +static int meson_cpufreq_probe(struct platform_device *pdev) +{ + struct device *cpu_dev; + struct device_node *np; + struct regulator *cpu_reg = NULL; + unsigned int cpu = 0; + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_err("failed to get cpu%d device\n", cpu); + return -ENODEV; + } + + np = of_node_get(cpu_dev->of_node); + if (!np) { + pr_err("failed to find cpu node\n"); + of_node_put(np); + return -ENODEV; + } + + cpu_reg = devm_regulator_get(cpu_dev, CORE_SUPPLY); + if (IS_ERR(cpu_reg)) { + pr_err("failed in regulator getting %ld\n", + PTR_ERR(cpu_reg)); + devm_regulator_put(cpu_reg); + return PTR_ERR(cpu_reg); + } + + return meson_cpufreq_register(&meson_dt_bL_ops); +} + +static int meson_cpufreq_remove(struct platform_device *pdev) +{ + meson_cpufreq_unregister(&meson_dt_bL_ops); + return 0; +} + +static const struct of_device_id amlogic_cpufreq_meson_dt_match[] = { + { .compatible = "amlogic, cpufreq-meson", + }, + {}, +}; + +static struct platform_driver meson_cpufreq_platdrv = { + .driver = { + .name = "cpufreq-meson", + .owner = THIS_MODULE, + .of_match_table = amlogic_cpufreq_meson_dt_match, + }, + .probe = meson_cpufreq_probe, + .remove = meson_cpufreq_remove, +}; +module_platform_driver(meson_cpufreq_platdrv); + +MODULE_AUTHOR("Amlogic cpufreq driver owner"); +MODULE_DESCRIPTION("Generic ARM big LITTLE cpufreq driver via DT"); +MODULE_LICENSE("GPL v2"); -- 2.7.4