From patchwork Fri Feb 22 17:17:52 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 2176651 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id EA8B33FD4E for ; Fri, 22 Feb 2013 17:21:41 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1U8wH3-0007TC-2Z; Fri, 22 Feb 2013 17:19:05 +0000 Received: from moutng.kundenserver.de ([212.227.126.171]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1U8wGZ-0007On-Sv for linux-arm-kernel@lists.infradead.org; Fri, 22 Feb 2013 17:18:38 +0000 Received: from axis700.grange (dslb-178-001-229-135.pools.arcor-ip.net [178.1.229.135]) by mrelayeu.kundenserver.de (node=mrbap3) with ESMTP (Nemesis) id 0LylIV-1UtKge01Sh-016A3z; Fri, 22 Feb 2013 18:18:19 +0100 Received: from 6a.grange (6a.grange [192.168.1.11]) by axis700.grange (Postfix) with ESMTPS id 681FD40B98; Fri, 22 Feb 2013 18:18:18 +0100 (CET) Received: from lyakh by 6a.grange with local (Exim 4.72) (envelope-from ) id 1U8wGI-00072U-6d; Fri, 22 Feb 2013 18:18:18 +0100 From: Guennadi Liakhovetski To: linux-sh@vger.kernel.org Subject: [PATCH/RFC 2/4] ARM: shmobile: sh73a0: add support for adjusting CPU frequency Date: Fri, 22 Feb 2013 18:17:52 +0100 Message-Id: <1361553474-27022-3-git-send-email-g.liakhovetski@gmx.de> X-Mailer: git-send-email 1.7.2.5 In-Reply-To: <1361553474-27022-1-git-send-email-g.liakhovetski@gmx.de> References: <1361553474-27022-1-git-send-email-g.liakhovetski@gmx.de> X-Provags-ID: V02:K0:CfajS91Y/JaEsTYz1qZ0x0WXp93OSUTDTO203gu+Tvv E5aLLwXWBSmIRSJEj0M6gnF0VYY7Q94sup1q9+QkXw2bRJxAwt tgigaJ9gKL/Nb14cUfG3tHJxqvUsktLTAxgaR4yeKJY5v74sQJ YYTsC0kQGItQ2KD7yOGtepHUIP8Ufi9Dz75bCtyUjVC8R4Un1C bVasPEyF/r31X4ALoGctSI8rFtc3Y+uQ5zjHTnRMHAQG8wLQHH tXP9n6AiTvlsEcK86nlFWVHZ4zTlyPsHxhWdGTDtwg5R6vi8PW m5jIDLYj58AHJAanxPPSIk4ZERZU2cT5RwuI11fkWPBm/IUKPP bpczD6JdfemN6/6uqaabizgDtJh+oZPSOiRy16y+yHmSSzlFQF XPTfICKtUU8Hg== X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130222_121836_198280_0BE85EF1 X-CRM114-Status: GOOD ( 26.13 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [212.227.126.171 listed in list.dnswl.org] 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider (g.liakhovetski[at]gmx.de) -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record -0.7 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Magnus Damm , cpufreq@vger.kernel.org, "Rafael J. Wysocki" , Simon Horman , Guennadi Liakhovetski , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org On SH73A0 the output of PLL0 is supplied to two dividers, feeding clock to the CPU core and SGX. Lower CPU frequencies allow the use of lower supply voltages and thus reduce power consumption. Signed-off-by: Guennadi Liakhovetski --- No, I don't like the idea of changing the parent frequency in the child clock driver very much either. But having a clock, that acts like this, allows the use of the generic cpufreq-cpu0 driver, which handles exactly one clock and one regulator. Instead of changing a physical clock driver to modify its parent, we could add a virtual clock, that would adjust them both. Otherwise, of course, we could write our own cpufreq driver, that would explicitly modify the 2 clocks, but that would be too hardware- specific. I'm open for ideas. arch/arm/mach-shmobile/clock-sh73a0.c | 217 ++++++++++++++++++++++++++++++++- 1 files changed, 213 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-shmobile/clock-sh73a0.c b/arch/arm/mach-shmobile/clock-sh73a0.c index 71843dd..3170482 100644 --- a/arch/arm/mach-shmobile/clock-sh73a0.c +++ b/arch/arm/mach-shmobile/clock-sh73a0.c @@ -16,6 +16,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #include @@ -156,18 +157,97 @@ static unsigned long pll_recalc(struct clk *clk) return clk->parent->rate * mult; } -static struct sh_clk_ops pll_clk_ops = { +static int pll0_mult(struct clk *clk, unsigned long *rate) +{ + unsigned long mult, f_parent; + + if (!clk->parent || !__clk_get(clk->parent)) + return -ENODEV; + + f_parent = clk_get_rate(clk->parent); + __clk_put(clk->parent); + + if (WARN_ON(!f_parent)) + /* Should never happen */ + return -EINVAL; + + mult = (*rate + f_parent / 2) / f_parent; + + /* 27 <= multiplier <= 46 */ + switch (mult) { + case 0 ... 27 / 2: + /* 1:1 - rate doesn't change */ + return 1; + case 27 / 2 + 1 ... 27: + *rate *= 27; + return 27; + case 28 ... 45: + *rate *= mult; + return mult; + default: + *rate *= 46; + return 46; + } +} + +static int pll0_set_rate(struct clk *clk, unsigned long rate) +{ + int mult = pll0_mult(clk, &rate), i; + + if (mult < 0) + return mult; + + if (mult == 1) { + /* 1:1 - switch PLL off */ + __raw_writel(__raw_readl(PLLECR) & ~(1 << clk->enable_bit), + PLLECR); + return 0; + } + + i = __raw_readl(PLLECR) & (1 << (8 + clk->enable_bit)); + + __raw_writel((__raw_readl(clk->enable_reg) & ~(0x3f << 24)) | + ((mult - 1) << 24), clk->enable_reg); + + if (!i) + __raw_writel(__raw_readl(PLLECR) | (1 << clk->enable_bit), + PLLECR); + + for (i = 1000; i; i--) + if (__raw_readl(PLLECR) & (1 << (8 + clk->enable_bit))) + break; + else + cpu_relax(); + + return i ? 0 : -ETIMEDOUT; +} + +static long pll0_round_rate(struct clk *clk, unsigned long rate) +{ + int ret = pll0_mult(clk, &rate); + if (ret < 0) + return ret; + return rate; +} + +static struct sh_clk_ops pll0_clk_ops = { .recalc = pll_recalc, + .round_rate = pll0_round_rate, + .set_rate = pll0_set_rate, }; static struct clk pll0_clk = { - .ops = &pll_clk_ops, + .ops = &pll0_clk_ops, .flags = CLK_ENABLE_ON_INIT, .parent = &main_clk, .enable_reg = (void __iomem *)PLL0CR, .enable_bit = 0, }; +static struct sh_clk_ops pll_clk_ops = { + .recalc = pll_recalc, +}; + static struct clk pll1_clk = { .ops = &pll_clk_ops, .flags = CLK_ENABLE_ON_INIT, @@ -277,6 +357,126 @@ static struct clk div4_clks[DIV4_NR] = { [DIV4_HP] = DIV4(FRQCRB, 4, 0xdff, 0), }; +static int (*div4_set_rate)(struct clk *clk, unsigned long rate); +static unsigned long (*div4_recalc)(struct clk *clk); + +/* Supported system CPU (Z-clock) and PLL0 frequency combinations */ +static struct { + unsigned long zclk; + unsigned long pll0; +} zclk_rate[] = { + { + .zclk = 1196000000, + .pll0 = 1196000000, + }, { + .zclk = 806000000, + .pll0 = 806000000, + }, { + .zclk = 403000000, + .pll0 = 806000000, + }, +}; + +static int zclk_set_rate(struct clk *clk, unsigned long rate) +{ + int i, ret; + struct clk *pll0; + + /* We only support frequencies from the zclk_rate table above */ + for (i = 0; i < ARRAY_SIZE(zclk_rate); i++) + if (rate == zclk_rate[i].zclk) + break; + + if (i == ARRAY_SIZE(zclk_rate)) { + pr_warning("%s(): unsupported CPU clock frequency %lu\n", + __func__, rate); + return -EINVAL; + } + + /* We could just use ->parent, but it's good to refcount */ + pll0 = clk_get(NULL, "pll0_clk"); + if (IS_ERR(pll0)) + return PTR_ERR(pll0); + + if (zclk_rate[i].pll0 != clk_get_rate(pll0)) { + /* cannot call clk_set_rate() - would cause a nested spinlock */ + ret = pll0_set_rate(pll0, zclk_rate[i].pll0); + if (ret < 0) + goto esetrate; + pll0->rate = pll_recalc(pll0); + propagate_rate(pll0); + } + + if (zclk_rate[i].pll0 == zclk_rate[i].zclk) { + /* 1:1 - switch off divider */ + __raw_writel(__raw_readl(FRQCRB) & ~(1 << 28), FRQCRB); + ret = 0; + } else { + /* set the divider - call the DIV4 method */ + ret = div4_set_rate(clk, rate); + if (ret < 0) + goto esetrate; + + /* Enable the divider */ + __raw_writel(__raw_readl(FRQCRB) | (1 << 28), FRQCRB); + } + + /* + * Kick the clock - this is also done in sh_clk_div_set_rate(), but we + * want to check success + */ + __raw_writel(__raw_readl(FRQCRB) | (1 << 31), FRQCRB); + for (i = 1000; i; i--) + if (__raw_readl(FRQCRB) & (1 << 31)) + cpu_relax(); + else + break; + if (!i) + ret = -ETIMEDOUT; + +esetrate: + clk_put(pll0); + return ret; +} + +static long zclk_round_rate(struct clk *clk, unsigned long rate) +{ + int i; + + /* We only support frequencies from the zclk_rate table above */ + for (i = 0; i < ARRAY_SIZE(zclk_rate); i++) + if (rate == zclk_rate[i].zclk) + break; + + if (i == ARRAY_SIZE(zclk_rate)) { + pr_warning("%s(): unsupported CPU clock frequency %lu\n", + __func__, rate); + return -EINVAL; + } + + return rate; +} + +static unsigned long zclk_recalc(struct clk *clk) +{ + /* Must recalculate frequencies, even if the divisor is unused ATM! */ + unsigned long div_freq = div4_recalc(clk); + + if (__raw_readl(FRQCRB) & (1 << 28)) + return div_freq; + + return clk_get_rate(clk->parent); +} + +static void zclk_extend(void) +{ + div4_set_rate = div4_clks[DIV4_Z].ops->set_rate; + div4_recalc = div4_clks[DIV4_Z].ops->recalc; + div4_clks[DIV4_Z].ops->set_rate = zclk_set_rate; + div4_clks[DIV4_Z].ops->round_rate = zclk_round_rate; + div4_clks[DIV4_Z].ops->recalc = zclk_recalc; +} + enum { DIV6_VCK1, DIV6_VCK2, DIV6_VCK3, DIV6_ZB1, DIV6_FLCTL, DIV6_SDHI0, DIV6_SDHI1, DIV6_SDHI2, DIV6_FSIA, DIV6_FSIB, DIV6_SUB, @@ -474,7 +674,7 @@ static struct clk *late_main_clks[] = { }; enum { MSTP001, - MSTP129, MSTP128, MSTP127, MSTP126, MSTP125, MSTP118, MSTP116, MSTP100, + MSTP129, MSTP128, MSTP127, MSTP126, MSTP125, MSTP118, MSTP116, MSTP112, MSTP100, MSTP219, MSTP218, MSTP217, MSTP207, MSTP206, MSTP204, MSTP203, MSTP202, MSTP201, MSTP200, MSTP331, MSTP329, MSTP328, MSTP325, MSTP323, MSTP322, @@ -495,6 +695,7 @@ static struct clk mstp_clks[MSTP_NR] = { [MSTP125] = MSTP(&div6_clks[DIV6_SUB], SMSTPCR1, 25, 0), /* TMU0 */ [MSTP118] = MSTP(&div4_clks[DIV4_B], SMSTPCR1, 18, 0), /* DSITX0 */ [MSTP116] = MSTP(&div4_clks[DIV4_HP], SMSTPCR1, 16, 0), /* IIC0 */ + [MSTP112] = MSTP(&div4_clks[DIV4_ZG], SMSTPCR1, 12, 0), /* SGX */ [MSTP100] = MSTP(&div4_clks[DIV4_B], SMSTPCR1, 0, 0), /* LCDC0 */ [MSTP219] = MSTP(&div6_clks[DIV6_SUB], SMSTPCR2, 19, 0), /* SCIFA7 */ [MSTP218] = MSTP(&div4_clks[DIV4_HP], SMSTPCR2, 18, 0), /* SY-DMAC */ @@ -535,6 +736,10 @@ static struct clk mstp_clks[MSTP_NR] = { static struct clk_lookup lookups[] = { /* main clocks */ CLKDEV_CON_ID("r_clk", &r_clk), + CLKDEV_CON_ID("pll0_clk", &pll0_clk), + + /* DIV4 clocks */ + CLKDEV_DEV_ID("cpu0", &div4_clks[DIV4_Z]), /* cpufreq-cpu0 */ /* DIV6 clocks */ CLKDEV_CON_ID("vck1_clk", &div6_clks[DIV6_VCK1]), @@ -562,6 +767,7 @@ static struct clk_lookup lookups[] = { CLKDEV_DEV_ID("sh-mipi-dsi.0", &mstp_clks[MSTP118]), /* DSITX */ CLKDEV_DEV_ID("i2c-sh_mobile.0", &mstp_clks[MSTP116]), /* I2C0 */ CLKDEV_DEV_ID("e6820000.i2c", &mstp_clks[MSTP116]), /* I2C0 */ + CLKDEV_CON_ID("sgx_clk", &mstp_clks[MSTP112]), CLKDEV_DEV_ID("sh_mobile_lcdc_fb.0", &mstp_clks[MSTP100]), /* LCDC0 */ CLKDEV_DEV_ID("sh-sci.7", &mstp_clks[MSTP219]), /* SCIFA7 */ CLKDEV_DEV_ID("sh-dma-engine.0", &mstp_clks[MSTP218]), /* SY-DMAC */ @@ -627,8 +833,11 @@ void __init sh73a0_clock_init(void) for (k = 0; !ret && (k < ARRAY_SIZE(main_clks)); k++) ret = clk_register(main_clks[k]); - if (!ret) + if (!ret) { ret = sh_clk_div4_register(div4_clks, DIV4_NR, &div4_table); + if (!ret) + zclk_extend(); + } if (!ret) ret = sh_clk_div6_reparent_register(div6_clks, DIV6_NR);