| /* | 
 |  * This file is provided under a dual BSD/GPLv2 license.  When using or | 
 |  * redistributing this file, you may do so under either license. | 
 |  * | 
 |  * GPL LICENSE SUMMARY | 
 |  * | 
 |  * Copyright (c) 2016 AmLogic, Inc. | 
 |  * Author: Michael Turquette <mturquette@baylibre.com> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of version 2 of the GNU General Public License 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. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License | 
 |  * along with this program; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | 
 |  * The full GNU General Public License is included in this distribution | 
 |  * in the file called COPYING | 
 |  * | 
 |  * BSD LICENSE | 
 |  * | 
 |  * Copyright (c) 2016 AmLogic, Inc. | 
 |  * Author: Michael Turquette <mturquette@baylibre.com> | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * | 
 |  *   * Redistributions of source code must retain the above copyright | 
 |  *     notice, this list of conditions and the following disclaimer. | 
 |  *   * Redistributions in binary form must reproduce the above copyright | 
 |  *     notice, this list of conditions and the following disclaimer in | 
 |  *     the documentation and/or other materials provided with the | 
 |  *     distribution. | 
 |  *   * Neither the name of Intel Corporation nor the names of its | 
 |  *     contributors may be used to endorse or promote products derived | 
 |  *     from this software without specific prior written permission. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 |  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 |  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 |  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 |  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 |  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 |  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 |  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 |  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | /* | 
 |  * MultiPhase Locked Loops are outputs from a PLL with additional frequency | 
 |  * scaling capabilities. MPLL rates are calculated as: | 
 |  * | 
 |  * f(N2_integer, SDM_IN ) = 2.0G/(N2_integer + SDM_IN/16384) | 
 |  */ | 
 |  | 
 | #include <linux/clk-provider.h> | 
 | #include "clkc.h" | 
 |  | 
 | #define SDM_DEN 16384 | 
 | #define N2_MIN	4 | 
 | #define N2_MAX	511 | 
 |  | 
 | #define to_meson_clk_mpll(_hw) container_of(_hw, struct meson_clk_mpll, hw) | 
 |  | 
 | static long rate_from_params(unsigned long parent_rate, | 
 | 				      unsigned long sdm, | 
 | 				      unsigned long n2) | 
 | { | 
 | 	unsigned long divisor = (SDM_DEN * n2) + sdm; | 
 |  | 
 | 	if (n2 < N2_MIN) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return DIV_ROUND_UP_ULL((u64)parent_rate * SDM_DEN, divisor); | 
 | } | 
 |  | 
 | static void params_from_rate(unsigned long requested_rate, | 
 | 			     unsigned long parent_rate, | 
 | 			     unsigned long *sdm, | 
 | 			     unsigned long *n2) | 
 | { | 
 | 	uint64_t div = parent_rate; | 
 | 	unsigned long rem = do_div(div, requested_rate); | 
 |  | 
 | 	if (div < N2_MIN) { | 
 | 		*n2 = N2_MIN; | 
 | 		*sdm = 0; | 
 | 	} else if (div > N2_MAX) { | 
 | 		*n2 = N2_MAX; | 
 | 		*sdm = SDM_DEN - 1; | 
 | 	} else { | 
 | 		*n2 = div; | 
 | 		*sdm = DIV_ROUND_UP(rem * SDM_DEN, requested_rate); | 
 | 	} | 
 | } | 
 |  | 
 | static unsigned long mpll_recalc_rate(struct clk_hw *hw, | 
 | 		unsigned long parent_rate) | 
 | { | 
 | 	struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw); | 
 | 	struct parm *p; | 
 | 	unsigned long reg, sdm, n2; | 
 | 	long rate; | 
 |  | 
 | 	p = &mpll->sdm; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	sdm = PARM_GET(p->width, p->shift, reg); | 
 |  | 
 | 	p = &mpll->n2; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	n2 = PARM_GET(p->width, p->shift, reg); | 
 |  | 
 | 	rate = rate_from_params(parent_rate, sdm, n2); | 
 | 	if (rate < 0) | 
 | 		return 0; | 
 |  | 
 | 	return rate; | 
 | } | 
 |  | 
 | static long mpll_round_rate(struct clk_hw *hw, | 
 | 			    unsigned long rate, | 
 | 			    unsigned long *parent_rate) | 
 | { | 
 | 	unsigned long sdm, n2; | 
 |  | 
 | 	params_from_rate(rate, *parent_rate, &sdm, &n2); | 
 | 	return rate_from_params(*parent_rate, sdm, n2); | 
 | } | 
 |  | 
 | static int mpll_set_rate(struct clk_hw *hw, | 
 | 			 unsigned long rate, | 
 | 			 unsigned long parent_rate) | 
 | { | 
 | 	struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw); | 
 | 	struct parm *p; | 
 | 	unsigned long reg, sdm, n2; | 
 | 	unsigned long flags = 0; | 
 |  | 
 | 	params_from_rate(rate, parent_rate, &sdm, &n2); | 
 |  | 
 | 	if (mpll->lock) | 
 | 		spin_lock_irqsave(mpll->lock, flags); | 
 | 	else | 
 | 		__acquire(mpll->lock); | 
 |  | 
 | 	p = &mpll->sdm; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	reg = PARM_SET(p->width, p->shift, reg, sdm); | 
 | 	writel(reg, mpll->base + p->reg_off); | 
 |  | 
 | 	p = &mpll->sdm_en; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	reg = PARM_SET(p->width, p->shift, reg, 1); | 
 | 	writel(reg, mpll->base + p->reg_off); | 
 |  | 
 | 	p = &mpll->ssen; | 
 | 	if (p->width != 0) { | 
 | 		reg = readl(mpll->base + p->reg_off); | 
 | 		reg = PARM_SET(p->width, p->shift, reg, 1); | 
 | 		writel(reg, mpll->base + p->reg_off); | 
 | 	} | 
 |  | 
 | 	p = &mpll->n2; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	reg = PARM_SET(p->width, p->shift, reg, n2); | 
 | 	writel(reg, mpll->base + p->reg_off); | 
 |  | 
 | 	if (mpll->lock) | 
 | 		spin_unlock_irqrestore(mpll->lock, flags); | 
 | 	else | 
 | 		__release(mpll->lock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void mpll_enable_core(struct clk_hw *hw, int enable) | 
 | { | 
 | 	struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw); | 
 | 	struct parm *p; | 
 | 	unsigned long reg; | 
 | 	unsigned long flags = 0; | 
 |  | 
 | 	if (mpll->lock) | 
 | 		spin_lock_irqsave(mpll->lock, flags); | 
 | 	else | 
 | 		__acquire(mpll->lock); | 
 |  | 
 | 	p = &mpll->en; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	reg = PARM_SET(p->width, p->shift, reg, enable ? 1 : 0); | 
 | 	writel(reg, mpll->base + p->reg_off); | 
 |  | 
 | 	if (mpll->lock) | 
 | 		spin_unlock_irqrestore(mpll->lock, flags); | 
 | 	else | 
 | 		__release(mpll->lock); | 
 | } | 
 |  | 
 |  | 
 | static int mpll_enable(struct clk_hw *hw) | 
 | { | 
 | 	mpll_enable_core(hw, 1); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void mpll_disable(struct clk_hw *hw) | 
 | { | 
 | 	mpll_enable_core(hw, 0); | 
 | } | 
 |  | 
 | static int mpll_is_enabled(struct clk_hw *hw) | 
 | { | 
 | 	struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw); | 
 | 	struct parm *p; | 
 | 	unsigned long reg; | 
 | 	int en; | 
 |  | 
 | 	p = &mpll->en; | 
 | 	reg = readl(mpll->base + p->reg_off); | 
 | 	en = PARM_GET(p->width, p->shift, reg); | 
 |  | 
 | 	return en; | 
 | } | 
 |  | 
 | const struct clk_ops meson_clk_mpll_ro_ops = { | 
 | 	.recalc_rate	= mpll_recalc_rate, | 
 | 	.round_rate	= mpll_round_rate, | 
 | 	.is_enabled	= mpll_is_enabled, | 
 | }; | 
 |  | 
 | const struct clk_ops meson_clk_mpll_ops = { | 
 | 	.recalc_rate	= mpll_recalc_rate, | 
 | 	.round_rate	= mpll_round_rate, | 
 | 	.set_rate	= mpll_set_rate, | 
 | 	.enable		= mpll_enable, | 
 | 	.disable	= mpll_disable, | 
 | 	.is_enabled	= mpll_is_enabled, | 
 | }; |