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#include <linux/spinlock.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "ccu_phase.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic int ccu_phase_get_phase(struct clk_hw *hw) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci struct ccu_phase *phase = hw_to_ccu_phase(hw); 1662306a36Sopenharmony_ci struct clk_hw *parent, *grandparent; 1762306a36Sopenharmony_ci unsigned int parent_rate, grandparent_rate; 1862306a36Sopenharmony_ci u16 step, parent_div; 1962306a36Sopenharmony_ci u32 reg; 2062306a36Sopenharmony_ci u8 delay; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci reg = readl(phase->common.base + phase->common.reg); 2362306a36Sopenharmony_ci delay = (reg >> phase->shift); 2462306a36Sopenharmony_ci delay &= (1 << phase->width) - 1; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci if (!delay) 2762306a36Sopenharmony_ci return 180; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci /* Get our parent clock, it's the one that can adjust its rate */ 3062306a36Sopenharmony_ci parent = clk_hw_get_parent(hw); 3162306a36Sopenharmony_ci if (!parent) 3262306a36Sopenharmony_ci return -EINVAL; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* And its rate */ 3562306a36Sopenharmony_ci parent_rate = clk_hw_get_rate(parent); 3662306a36Sopenharmony_ci if (!parent_rate) 3762306a36Sopenharmony_ci return -EINVAL; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* Now, get our parent's parent (most likely some PLL) */ 4062306a36Sopenharmony_ci grandparent = clk_hw_get_parent(parent); 4162306a36Sopenharmony_ci if (!grandparent) 4262306a36Sopenharmony_ci return -EINVAL; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* And its rate */ 4562306a36Sopenharmony_ci grandparent_rate = clk_hw_get_rate(grandparent); 4662306a36Sopenharmony_ci if (!grandparent_rate) 4762306a36Sopenharmony_ci return -EINVAL; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* Get our parent clock divider */ 5062306a36Sopenharmony_ci parent_div = grandparent_rate / parent_rate; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci step = DIV_ROUND_CLOSEST(360, parent_div); 5362306a36Sopenharmony_ci return delay * step; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int ccu_phase_set_phase(struct clk_hw *hw, int degrees) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct ccu_phase *phase = hw_to_ccu_phase(hw); 5962306a36Sopenharmony_ci struct clk_hw *parent, *grandparent; 6062306a36Sopenharmony_ci unsigned int parent_rate, grandparent_rate; 6162306a36Sopenharmony_ci unsigned long flags; 6262306a36Sopenharmony_ci u32 reg; 6362306a36Sopenharmony_ci u8 delay; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* Get our parent clock, it's the one that can adjust its rate */ 6662306a36Sopenharmony_ci parent = clk_hw_get_parent(hw); 6762306a36Sopenharmony_ci if (!parent) 6862306a36Sopenharmony_ci return -EINVAL; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* And its rate */ 7162306a36Sopenharmony_ci parent_rate = clk_hw_get_rate(parent); 7262306a36Sopenharmony_ci if (!parent_rate) 7362306a36Sopenharmony_ci return -EINVAL; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* Now, get our parent's parent (most likely some PLL) */ 7662306a36Sopenharmony_ci grandparent = clk_hw_get_parent(parent); 7762306a36Sopenharmony_ci if (!grandparent) 7862306a36Sopenharmony_ci return -EINVAL; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* And its rate */ 8162306a36Sopenharmony_ci grandparent_rate = clk_hw_get_rate(grandparent); 8262306a36Sopenharmony_ci if (!grandparent_rate) 8362306a36Sopenharmony_ci return -EINVAL; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (degrees != 180) { 8662306a36Sopenharmony_ci u16 step, parent_div; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* Get our parent divider */ 8962306a36Sopenharmony_ci parent_div = grandparent_rate / parent_rate; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci /* 9262306a36Sopenharmony_ci * We can only outphase the clocks by multiple of the 9362306a36Sopenharmony_ci * PLL's period. 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * Since our parent clock is only a divider, and the 9662306a36Sopenharmony_ci * formula to get the outphasing in degrees is deg = 9762306a36Sopenharmony_ci * 360 * delta / period 9862306a36Sopenharmony_ci * 9962306a36Sopenharmony_ci * If we simplify this formula, we can see that the 10062306a36Sopenharmony_ci * only thing that we're concerned about is the number 10162306a36Sopenharmony_ci * of period we want to outphase our clock from, and 10262306a36Sopenharmony_ci * the divider set by our parent clock. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_ci step = DIV_ROUND_CLOSEST(360, parent_div); 10562306a36Sopenharmony_ci delay = DIV_ROUND_CLOSEST(degrees, step); 10662306a36Sopenharmony_ci } else { 10762306a36Sopenharmony_ci delay = 0; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci spin_lock_irqsave(phase->common.lock, flags); 11162306a36Sopenharmony_ci reg = readl(phase->common.base + phase->common.reg); 11262306a36Sopenharmony_ci reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift); 11362306a36Sopenharmony_ci writel(reg | (delay << phase->shift), 11462306a36Sopenharmony_ci phase->common.base + phase->common.reg); 11562306a36Sopenharmony_ci spin_unlock_irqrestore(phase->common.lock, flags); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ciconst struct clk_ops ccu_phase_ops = { 12162306a36Sopenharmony_ci .get_phase = ccu_phase_get_phase, 12262306a36Sopenharmony_ci .set_phase = ccu_phase_set_phase, 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU); 125