From patchwork Mon Aug 24 09:40:49 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gabriel Fernandez X-Patchwork-Id: 7063031 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id F3C5AC05AC for ; Mon, 24 Aug 2015 09:44:43 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id BD6DC20622 for ; Mon, 24 Aug 2015 09:44:42 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7BC772061E for ; Mon, 24 Aug 2015 09:44:41 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1ZToHB-0007QH-6J; Mon, 24 Aug 2015 09:42:49 +0000 Received: from mail-wi0-f181.google.com ([209.85.212.181]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1ZToGx-00077R-87 for linux-arm-kernel@lists.infradead.org; Mon, 24 Aug 2015 09:42:36 +0000 Received: by wijp15 with SMTP id p15so71822584wij.0 for ; Mon, 24 Aug 2015 02:42:13 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=CCUiXb+0XigItEVe4oij3/HCEUjpy2nuHgsN4u5IZGQ=; b=bfYN9FMLFtR033QS0S7uwWdrdc3/DaRdslzT+t9FCjxzW3Vqre5VDrrmQPEYVY2AvN nt3Ar9mnygzZgJrmnylT6P3+UM5x2lbXXyaclvBRbSznwT4TO9WTzXbfedcEvniUSpPu /X7xu6aOwGuM7W18ApqdnRApzYehMHPLxVVZA13n+ZQxa5fXlfStcwONJCg2eXFiKBba 0/hEUo75otb3Yj1dS6roeAjbcw3QX2nTj8TNMiUAofq4YPcEfjjNNqUDAA72wFBW9HDq YlmKBD230meBM1jJQh+yo0/PL5mareTj7vXqUdVZuQV/faDNvYNf7g9UAoF72dPKvwuS v56Q== X-Gm-Message-State: ALoCoQl/W1QIz70MIEhz14XoAqPCnwa6NiLWd8/wZ2tm8Wfu7kOKPkbLZiisjvws9qxECwPj43wy X-Received: by 10.180.211.82 with SMTP id na18mr28136042wic.76.1440409333648; Mon, 24 Aug 2015 02:42:13 -0700 (PDT) Received: from lmenx315.st.com. ([80.12.55.183]) by smtp.gmail.com with ESMTPSA id ub19sm15309920wib.8.2015.08.24.02.42.10 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 24 Aug 2015 02:42:12 -0700 (PDT) From: Gabriel Fernandez To: Rob Herring , Pawel Moll , Mark Rutland , Ian Campbell , Kumar Gala , Srinivas Kandagatla , Maxime Coquelin , Patrice Chotard , Russell King , Michael Turquette , Stephen Boyd , Gabriel Fernandez , Peter Griffin , Pankaj Dev , Olivier Bideau , Geert Uytterhoeven , Fabian Frederick Subject: [PATCH v2 2/4] drivers: clk: st: PLL rate change implementation for DVFS Date: Mon, 24 Aug 2015 11:40:49 +0200 Message-Id: <1440409251-11166-3-git-send-email-gabriel.fernandez@linaro.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1440409251-11166-1-git-send-email-gabriel.fernandez@linaro.org> References: <1440409251-11166-1-git-send-email-gabriel.fernandez@linaro.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150824_024235_612259_5E12720A X-CRM114-Status: GOOD ( 23.25 ) X-Spam-Score: -2.6 (--) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, linux-clk@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, kernel@stlinux.com MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-5.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Change A9 PLL rate, as per requirement from the cpufreq framework, for DVFS. For rate change, the A9 clock needs to be temporarily sourced from PLL external to A9 and then sourced back to A9-PLL Signed-off-by: Pankaj Dev Signed-off-by: Gabriel Fernandez --- drivers/clk/st/clkgen-mux.c | 3 + drivers/clk/st/clkgen-pll.c | 185 ++++++++++++++++++++++++++++++++++++++++++-- drivers/clk/st/clkgen.h | 2 + 3 files changed, 182 insertions(+), 8 deletions(-) diff --git a/drivers/clk/st/clkgen-mux.c b/drivers/clk/st/clkgen-mux.c index 4f7f6c0..5dc5ce2 100644 --- a/drivers/clk/st/clkgen-mux.c +++ b/drivers/clk/st/clkgen-mux.c @@ -17,6 +17,7 @@ #include #include #include +#include "clkgen.h" static DEFINE_SPINLOCK(clkgena_divmux_lock); static DEFINE_SPINLOCK(clkgenf_lock); @@ -576,6 +577,7 @@ static struct clkgen_mux_data stih415_a9_mux_data = { .offset = 0, .shift = 1, .width = 2, + .lock = &clkgen_a9_lock, }; static struct clkgen_mux_data stih416_a9_mux_data = { .offset = 0, @@ -586,6 +588,7 @@ static struct clkgen_mux_data stih407_a9_mux_data = { .offset = 0x1a4, .shift = 0, .width = 2, + .lock = &clkgen_a9_lock, }; static const struct of_device_id mux_of_match[] = { diff --git a/drivers/clk/st/clkgen-pll.c b/drivers/clk/st/clkgen-pll.c index 7ee485d..94e5a0b 100644 --- a/drivers/clk/st/clkgen-pll.c +++ b/drivers/clk/st/clkgen-pll.c @@ -23,6 +23,7 @@ #include "clkgen.h" static DEFINE_SPINLOCK(clkgena_c32_odf_lock); +DEFINE_SPINLOCK(clkgen_a9_lock); /* * Common PLL configuration register bits for PLL800 and PLL1600 C65 @@ -39,6 +40,7 @@ static DEFINE_SPINLOCK(clkgena_c32_odf_lock); #define C32_IDF_MASK (0x7) #define C32_ODF_MASK (0x3f) #define C32_LDF_MASK (0x7f) +#define C32_CP_MASK (0x1f) #define C32_MAX_ODFS (4) @@ -51,15 +53,20 @@ struct clkgen_pll_data { struct clkgen_field pdiv; struct clkgen_field idf; struct clkgen_field ldf; + struct clkgen_field cp; unsigned int num_odfs; struct clkgen_field odf[C32_MAX_ODFS]; struct clkgen_field odf_gate[C32_MAX_ODFS]; + bool switch2pll_en; + struct clkgen_field switch2pll; + spinlock_t *lock; const struct clk_ops *ops; }; static const struct clk_ops st_pll1600c65_ops; static const struct clk_ops st_pll800c65_ops; static const struct clk_ops stm_pll3200c32_ops; +static const struct clk_ops stm_pll3200c32_a9_ops; static const struct clk_ops st_pll1200c32_ops; static const struct clkgen_pll_data st_pll1600c65_ax = { @@ -242,7 +249,11 @@ static const struct clkgen_pll_data st_pll3200c32_407_a9 = { .num_odfs = 1, .odf = { CLKGEN_FIELD(0x1b0, C32_ODF_MASK, 8) }, .odf_gate = { CLKGEN_FIELD(0x1ac, 0x1, 28) }, - .ops = &stm_pll3200c32_ops, + .switch2pll_en = true, + .cp = CLKGEN_FIELD(0x1a8, C32_CP_MASK, 1), + .switch2pll = CLKGEN_FIELD(0x1a4, 0x1, 1), + .lock = &clkgen_a9_lock, + .ops = &stm_pll3200c32_a9_ops, }; /** @@ -268,10 +279,26 @@ struct clkgen_pll { struct clk_hw hw; struct clkgen_pll_data *data; void __iomem *regs_base; + spinlock_t *lock; + + u32 ndiv; + u32 idf; + u32 odf; + u32 cp; }; #define to_clkgen_pll(_hw) container_of(_hw, struct clkgen_pll, hw) +struct stm_pll { + unsigned long mdiv; + unsigned long ndiv; + unsigned long pdiv; + unsigned long odf; + unsigned long idf; + unsigned long ldf; + unsigned long cp; +}; + static int clkgen_pll_is_locked(struct clk_hw *hw) { struct clkgen_pll *pll = to_clkgen_pll(hw); @@ -303,8 +330,12 @@ static int __clkgen_pll_enable(struct clk_hw *hw) ret = readl_relaxed_poll_timeout(base + field->offset, reg, !!((reg >> field->shift) & field->mask), 0, 10000); - if (!ret) + if (!ret) { + if (pll->data->switch2pll_en) + CLKGEN_WRITE(pll, switch2pll, 0); + pr_debug("%s:%s enabled\n", __clk_get_name(hw->clk), __func__); + } return ret; } @@ -333,6 +364,9 @@ static void __clkgen_pll_disable(struct clk_hw *hw) if (!clkgen_pll_is_enabled(hw)) return; + if (pll->data->switch2pll_en) + CLKGEN_WRITE(pll, switch2pll, 1); + CLKGEN_WRITE(pll, pdn_ctrl, 1); pr_debug("%s:%s disabled\n", __clk_get_name(hw->clk), __func__); @@ -403,6 +437,67 @@ static unsigned long recalc_stm_pll1600c65(struct clk_hw *hw, return rate; } +static int clk_pll3200c32_get_params(unsigned long input, unsigned long output, + struct stm_pll *pll) +{ + unsigned long i, n; + unsigned long deviation = ~0; + unsigned long new_freq; + long new_deviation; + /* Charge pump table: highest ndiv value for cp=6 to 25 */ + static const unsigned char cp_table[] = { + 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, + 128, 136, 144, 152, 160, 168, 176, 184, 192 + }; + + /* Output clock range: 800Mhz to 1600Mhz */ + if (output < 800000000 || output > 1600000000) + return -EINVAL; + + input /= 1000; + output /= 1000; + + for (i = 1; i <= 7 && deviation; i++) { + n = i * output / (2 * input); + + /* Checks */ + if (n < 8) + continue; + if (n > 200) + break; + + new_freq = (input * 2 * n) / i; + + new_deviation = abs(new_freq - output); + + if (!new_deviation || new_deviation < deviation) { + pll->idf = i; + pll->ndiv = n; + deviation = new_deviation; + } + } + + if (deviation == ~0) /* No solution found */ + return -EINVAL; + + /* Computing recommended charge pump value */ + for (pll->cp = 6; pll->ndiv > cp_table[pll->cp-6]; (pll->cp)++) + ; + + return 0; +} + +static int clk_pll3200c32_get_rate(unsigned long input, struct stm_pll *pll, + unsigned long *rate) +{ + if (!pll->idf) + pll->idf = 1; + + *rate = ((2 * (input / 1000) * pll->ndiv) / pll->idf) * 1000; + + return 0; +} + static unsigned long recalc_stm_pll3200c32(struct clk_hw *hw, unsigned long parent_rate) { @@ -425,6 +520,70 @@ static unsigned long recalc_stm_pll3200c32(struct clk_hw *hw, return rate; } +static long round_rate_stm_pll3200c32(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct stm_pll params; + + if (!clk_pll3200c32_get_params(*prate, rate, ¶ms)) + clk_pll3200c32_get_rate(*prate, ¶ms, &rate); + else { + pr_debug("%s: %s rate %ld Invalid\n", __func__, + __clk_get_name(hw->clk), rate); + return 0; + } + + pr_debug("%s: %s new rate %ld [ndiv=%u] [idf=%u]\n", + __func__, __clk_get_name(hw->clk), + rate, (unsigned int)params.ndiv, + (unsigned int)params.idf); + + return rate; +} + +static int set_rate_stm_pll3200c32(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clkgen_pll *pll = to_clkgen_pll(hw); + struct stm_pll params; + long hwrate = 0; + unsigned long flags = 0; + + if (!rate || !parent_rate) + return -EINVAL; + + if (!clk_pll3200c32_get_params(parent_rate, rate, ¶ms)) + clk_pll3200c32_get_rate(parent_rate, ¶ms, &hwrate); + + pr_debug("%s: %s new rate %ld [ndiv=0x%x] [idf=0x%x]\n", + __func__, __clk_get_name(hw->clk), + hwrate, (unsigned int)params.ndiv, + (unsigned int)params.idf); + + if (!hwrate) + return -EINVAL; + + pll->ndiv = params.ndiv; + pll->idf = params.idf; + pll->cp = params.cp; + + clkgen_pll_disable(hw); + + if (pll->lock) + spin_lock_irqsave(pll->lock, flags); + + CLKGEN_WRITE(pll, ndiv, pll->ndiv); + CLKGEN_WRITE(pll, idf, pll->idf); + CLKGEN_WRITE(pll, cp, pll->cp); + + if (pll->lock) + spin_unlock_irqrestore(pll->lock, flags); + + clkgen_pll_enable(hw); + + return 0; +} + static unsigned long recalc_stm_pll1200c32(struct clk_hw *hw, unsigned long parent_rate) { @@ -473,6 +632,15 @@ static const struct clk_ops stm_pll3200c32_ops = { .recalc_rate = recalc_stm_pll3200c32, }; +static const struct clk_ops stm_pll3200c32_a9_ops = { + .enable = clkgen_pll_enable, + .disable = clkgen_pll_disable, + .is_enabled = clkgen_pll_is_enabled, + .recalc_rate = recalc_stm_pll3200c32, + .round_rate = round_rate_stm_pll3200c32, + .set_rate = set_rate_stm_pll3200c32, +}; + static const struct clk_ops st_pll1200c32_ops = { .enable = clkgen_pll_enable, .disable = clkgen_pll_disable, @@ -483,7 +651,7 @@ static const struct clk_ops st_pll1200c32_ops = { static struct clk * __init clkgen_pll_register(const char *parent_name, struct clkgen_pll_data *pll_data, void __iomem *reg, - const char *clk_name) + const char *clk_name, spinlock_t *lock) { struct clkgen_pll *pll; struct clk *clk; @@ -590,7 +758,7 @@ static void __init clkgena_c65_pll_setup(struct device_node *np) */ clk_data->clks[0] = clkgen_pll_register(parent_name, (struct clkgen_pll_data *) &st_pll1600c65_ax, - reg + CLKGENAx_PLL0_OFFSET, clk_name); + reg + CLKGENAx_PLL0_OFFSET, clk_name, NULL); if (IS_ERR(clk_data->clks[0])) goto err; @@ -619,7 +787,7 @@ static void __init clkgena_c65_pll_setup(struct device_node *np) */ clk_data->clks[2] = clkgen_pll_register(parent_name, (struct clkgen_pll_data *) &st_pll800c65_ax, - reg + CLKGENAx_PLL1_OFFSET, clk_name); + reg + CLKGENAx_PLL1_OFFSET, clk_name, NULL); if (IS_ERR(clk_data->clks[2])) goto err; @@ -646,7 +814,7 @@ static struct clk * __init clkgen_odf_register(const char *parent_name, struct clk_gate *gate; struct clk_divider *div; - flags = CLK_GET_RATE_NOCACHE | CLK_SET_RATE_GATE; + flags = CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT; gate = kzalloc(sizeof(*gate), GFP_KERNEL); if (!gate) @@ -754,7 +922,8 @@ static void __init clkgen_c32_pll_setup(struct device_node *np) if (!pll_base) return; - clk = clkgen_pll_register(parent_name, data, pll_base, np->name); + clk = clkgen_pll_register(parent_name, data, pll_base, np->name, + data->lock); if (IS_ERR(clk)) return; @@ -843,7 +1012,7 @@ static void __init clkgengpu_c32_pll_setup(struct device_node *np) /* * PLL 1200MHz output */ - clk = clkgen_pll_register(parent_name, data, reg, clk_name); + clk = clkgen_pll_register(parent_name, data, reg, clk_name, data->lock); if (!IS_ERR(clk)) of_clk_add_provider(np, of_clk_src_simple_get, clk); diff --git a/drivers/clk/st/clkgen.h b/drivers/clk/st/clkgen.h index 35c8632..f7ec2d9 100644 --- a/drivers/clk/st/clkgen.h +++ b/drivers/clk/st/clkgen.h @@ -9,6 +9,8 @@ Copyright (C) 2014 STMicroelectronics #ifndef __CLKGEN_INFO_H #define __CLKGEN_INFO_H +extern spinlock_t clkgen_a9_lock; + struct clkgen_field { unsigned int offset; unsigned int mask;