brintos

brintos / linux-shallow public Read only

0
0
Text · 3.1 KiB · e4cae2a Raw
125 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2016 Maxime Ripard4 * Maxime Ripard <maxime.ripard@free-electrons.com>5 */6 7#include <linux/clk-provider.h>8#include <linux/io.h>9#include <linux/spinlock.h>10 11#include "ccu_phase.h"12 13static int ccu_phase_get_phase(struct clk_hw *hw)14{15	struct ccu_phase *phase = hw_to_ccu_phase(hw);16	struct clk_hw *parent, *grandparent;17	unsigned int parent_rate, grandparent_rate;18	u16 step, parent_div;19	u32 reg;20	u8 delay;21 22	reg = readl(phase->common.base + phase->common.reg);23	delay = (reg >> phase->shift);24	delay &= (1 << phase->width) - 1;25 26	if (!delay)27		return 180;28 29	/* Get our parent clock, it's the one that can adjust its rate */30	parent = clk_hw_get_parent(hw);31	if (!parent)32		return -EINVAL;33 34	/* And its rate */35	parent_rate = clk_hw_get_rate(parent);36	if (!parent_rate)37		return -EINVAL;38 39	/* Now, get our parent's parent (most likely some PLL) */40	grandparent = clk_hw_get_parent(parent);41	if (!grandparent)42		return -EINVAL;43 44	/* And its rate */45	grandparent_rate = clk_hw_get_rate(grandparent);46	if (!grandparent_rate)47		return -EINVAL;48 49	/* Get our parent clock divider */50	parent_div = grandparent_rate / parent_rate;51 52	step = DIV_ROUND_CLOSEST(360, parent_div);53	return delay * step;54}55 56static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)57{58	struct ccu_phase *phase = hw_to_ccu_phase(hw);59	struct clk_hw *parent, *grandparent;60	unsigned int parent_rate, grandparent_rate;61	unsigned long flags;62	u32 reg;63	u8 delay;64 65	/* Get our parent clock, it's the one that can adjust its rate */66	parent = clk_hw_get_parent(hw);67	if (!parent)68		return -EINVAL;69 70	/* And its rate */71	parent_rate = clk_hw_get_rate(parent);72	if (!parent_rate)73		return -EINVAL;74 75	/* Now, get our parent's parent (most likely some PLL) */76	grandparent = clk_hw_get_parent(parent);77	if (!grandparent)78		return -EINVAL;79 80	/* And its rate */81	grandparent_rate = clk_hw_get_rate(grandparent);82	if (!grandparent_rate)83		return -EINVAL;84 85	if (degrees != 180) {86		u16 step, parent_div;87 88		/* Get our parent divider */89		parent_div = grandparent_rate / parent_rate;90 91		/*92		 * We can only outphase the clocks by multiple of the93		 * PLL's period.94		 *95		 * Since our parent clock is only a divider, and the96		 * formula to get the outphasing in degrees is deg =97		 * 360 * delta / period98		 *99		 * If we simplify this formula, we can see that the100		 * only thing that we're concerned about is the number101		 * of period we want to outphase our clock from, and102		 * the divider set by our parent clock.103		 */104		step = DIV_ROUND_CLOSEST(360, parent_div);105		delay = DIV_ROUND_CLOSEST(degrees, step);106	} else {107		delay = 0;108	}109 110	spin_lock_irqsave(phase->common.lock, flags);111	reg = readl(phase->common.base + phase->common.reg);112	reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);113	writel(reg | (delay << phase->shift),114	       phase->common.base + phase->common.reg);115	spin_unlock_irqrestore(phase->common.lock, flags);116 117	return 0;118}119 120const struct clk_ops ccu_phase_ops = {121	.get_phase	= ccu_phase_get_phase,122	.set_phase	= ccu_phase_set_phase,123};124EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);125