162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard 462306a36Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/clk-provider.h> 862306a36Sopenharmony_ci#include <linux/io.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "ccu_gate.h" 1162306a36Sopenharmony_ci#include "ccu_mult.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistruct _ccu_mult { 1462306a36Sopenharmony_ci unsigned long mult, min, max; 1562306a36Sopenharmony_ci}; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic void ccu_mult_find_best(unsigned long parent, unsigned long rate, 1862306a36Sopenharmony_ci struct _ccu_mult *mult) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci int _mult; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci _mult = rate / parent; 2362306a36Sopenharmony_ci if (_mult < mult->min) 2462306a36Sopenharmony_ci _mult = mult->min; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci if (_mult > mult->max) 2762306a36Sopenharmony_ci _mult = mult->max; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci mult->mult = _mult; 3062306a36Sopenharmony_ci} 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux, 3362306a36Sopenharmony_ci struct clk_hw *parent, 3462306a36Sopenharmony_ci unsigned long *parent_rate, 3562306a36Sopenharmony_ci unsigned long rate, 3662306a36Sopenharmony_ci void *data) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci struct ccu_mult *cm = data; 3962306a36Sopenharmony_ci struct _ccu_mult _cm; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci _cm.min = cm->mult.min; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci if (cm->mult.max) 4462306a36Sopenharmony_ci _cm.max = cm->mult.max; 4562306a36Sopenharmony_ci else 4662306a36Sopenharmony_ci _cm.max = (1 << cm->mult.width) + cm->mult.offset - 1; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci ccu_mult_find_best(*parent_rate, rate, &_cm); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci return *parent_rate * _cm.mult; 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic void ccu_mult_disable(struct clk_hw *hw) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return ccu_gate_helper_disable(&cm->common, cm->enable); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int ccu_mult_enable(struct clk_hw *hw) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return ccu_gate_helper_enable(&cm->common, cm->enable); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int ccu_mult_is_enabled(struct clk_hw *hw) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return ccu_gate_helper_is_enabled(&cm->common, cm->enable); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic unsigned long ccu_mult_recalc_rate(struct clk_hw *hw, 7562306a36Sopenharmony_ci unsigned long parent_rate) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 7862306a36Sopenharmony_ci unsigned long val; 7962306a36Sopenharmony_ci u32 reg; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (ccu_frac_helper_is_enabled(&cm->common, &cm->frac)) 8262306a36Sopenharmony_ci return ccu_frac_helper_read_rate(&cm->common, &cm->frac); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci reg = readl(cm->common.base + cm->common.reg); 8562306a36Sopenharmony_ci val = reg >> cm->mult.shift; 8662306a36Sopenharmony_ci val &= (1 << cm->mult.width) - 1; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1, 8962306a36Sopenharmony_ci parent_rate); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci return parent_rate * (val + cm->mult.offset); 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic int ccu_mult_determine_rate(struct clk_hw *hw, 9562306a36Sopenharmony_ci struct clk_rate_request *req) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci return ccu_mux_helper_determine_rate(&cm->common, &cm->mux, 10062306a36Sopenharmony_ci req, ccu_mult_round_rate, cm); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate, 10462306a36Sopenharmony_ci unsigned long parent_rate) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 10762306a36Sopenharmony_ci struct _ccu_mult _cm; 10862306a36Sopenharmony_ci unsigned long flags; 10962306a36Sopenharmony_ci u32 reg; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (ccu_frac_helper_has_rate(&cm->common, &cm->frac, rate)) { 11262306a36Sopenharmony_ci ccu_frac_helper_enable(&cm->common, &cm->frac); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci return ccu_frac_helper_set_rate(&cm->common, &cm->frac, 11562306a36Sopenharmony_ci rate, cm->lock); 11662306a36Sopenharmony_ci } else { 11762306a36Sopenharmony_ci ccu_frac_helper_disable(&cm->common, &cm->frac); 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1, 12162306a36Sopenharmony_ci parent_rate); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci _cm.min = cm->mult.min; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (cm->mult.max) 12662306a36Sopenharmony_ci _cm.max = cm->mult.max; 12762306a36Sopenharmony_ci else 12862306a36Sopenharmony_ci _cm.max = (1 << cm->mult.width) + cm->mult.offset - 1; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci ccu_mult_find_best(parent_rate, rate, &_cm); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci spin_lock_irqsave(cm->common.lock, flags); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci reg = readl(cm->common.base + cm->common.reg); 13562306a36Sopenharmony_ci reg &= ~GENMASK(cm->mult.width + cm->mult.shift - 1, cm->mult.shift); 13662306a36Sopenharmony_ci reg |= ((_cm.mult - cm->mult.offset) << cm->mult.shift); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci writel(reg, cm->common.base + cm->common.reg); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci spin_unlock_irqrestore(cm->common.lock, flags); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ccu_helper_wait_for_lock(&cm->common, cm->lock); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic u8 ccu_mult_get_parent(struct clk_hw *hw) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return ccu_mux_helper_get_parent(&cm->common, &cm->mux); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int ccu_mult_set_parent(struct clk_hw *hw, u8 index) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct ccu_mult *cm = hw_to_ccu_mult(hw); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ciconst struct clk_ops ccu_mult_ops = { 16262306a36Sopenharmony_ci .disable = ccu_mult_disable, 16362306a36Sopenharmony_ci .enable = ccu_mult_enable, 16462306a36Sopenharmony_ci .is_enabled = ccu_mult_is_enabled, 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci .get_parent = ccu_mult_get_parent, 16762306a36Sopenharmony_ci .set_parent = ccu_mult_set_parent, 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci .determine_rate = ccu_mult_determine_rate, 17062306a36Sopenharmony_ci .recalc_rate = ccu_mult_recalc_rate, 17162306a36Sopenharmony_ci .set_rate = ccu_mult_set_rate, 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mult_ops, SUNXI_CCU); 174