clk: Remove io.h from clk-provider.h
[linux-block.git] / drivers / clk / sunxi-ng / ccu_phase.c
CommitLineData
6f9f7f87
MR
1/*
2 * Copyright (C) 2016 Maxime Ripard
3 * Maxime Ripard <maxime.ripard@free-electrons.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 */
10
11#include <linux/clk-provider.h>
62e59c4e 12#include <linux/io.h>
6f9f7f87
MR
13#include <linux/spinlock.h>
14
15#include "ccu_phase.h"
16
17static int ccu_phase_get_phase(struct clk_hw *hw)
18{
19 struct ccu_phase *phase = hw_to_ccu_phase(hw);
20 struct clk_hw *parent, *grandparent;
21 unsigned int parent_rate, grandparent_rate;
22 u16 step, parent_div;
23 u32 reg;
24 u8 delay;
25
26 reg = readl(phase->common.base + phase->common.reg);
27 delay = (reg >> phase->shift);
28 delay &= (1 << phase->width) - 1;
29
30 if (!delay)
31 return 180;
32
33 /* Get our parent clock, it's the one that can adjust its rate */
34 parent = clk_hw_get_parent(hw);
35 if (!parent)
36 return -EINVAL;
37
38 /* And its rate */
39 parent_rate = clk_hw_get_rate(parent);
40 if (!parent_rate)
41 return -EINVAL;
42
43 /* Now, get our parent's parent (most likely some PLL) */
44 grandparent = clk_hw_get_parent(parent);
45 if (!grandparent)
46 return -EINVAL;
47
48 /* And its rate */
49 grandparent_rate = clk_hw_get_rate(grandparent);
50 if (!grandparent_rate)
51 return -EINVAL;
52
53 /* Get our parent clock divider */
54 parent_div = grandparent_rate / parent_rate;
55
56 step = DIV_ROUND_CLOSEST(360, parent_div);
57 return delay * step;
58}
59
60static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
61{
62 struct ccu_phase *phase = hw_to_ccu_phase(hw);
63 struct clk_hw *parent, *grandparent;
64 unsigned int parent_rate, grandparent_rate;
65 unsigned long flags;
66 u32 reg;
67 u8 delay;
68
69 /* Get our parent clock, it's the one that can adjust its rate */
70 parent = clk_hw_get_parent(hw);
71 if (!parent)
72 return -EINVAL;
73
74 /* And its rate */
75 parent_rate = clk_hw_get_rate(parent);
76 if (!parent_rate)
77 return -EINVAL;
78
79 /* Now, get our parent's parent (most likely some PLL) */
80 grandparent = clk_hw_get_parent(parent);
81 if (!grandparent)
82 return -EINVAL;
83
84 /* And its rate */
85 grandparent_rate = clk_hw_get_rate(grandparent);
86 if (!grandparent_rate)
87 return -EINVAL;
88
89 if (degrees != 180) {
90 u16 step, parent_div;
91
92 /* Get our parent divider */
93 parent_div = grandparent_rate / parent_rate;
94
95 /*
96 * We can only outphase the clocks by multiple of the
97 * PLL's period.
98 *
99 * Since our parent clock is only a divider, and the
100 * formula to get the outphasing in degrees is deg =
101 * 360 * delta / period
102 *
103 * If we simplify this formula, we can see that the
104 * only thing that we're concerned about is the number
105 * of period we want to outphase our clock from, and
106 * the divider set by our parent clock.
107 */
108 step = DIV_ROUND_CLOSEST(360, parent_div);
109 delay = DIV_ROUND_CLOSEST(degrees, step);
110 } else {
111 delay = 0;
112 }
113
114 spin_lock_irqsave(phase->common.lock, flags);
115 reg = readl(phase->common.base + phase->common.reg);
116 reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);
117 writel(reg | (delay << phase->shift),
118 phase->common.base + phase->common.reg);
119 spin_unlock_irqrestore(phase->common.lock, flags);
120
121 return 0;
122}
123
124const struct clk_ops ccu_phase_ops = {
125 .get_phase = ccu_phase_get_phase,
126 .set_phase = ccu_phase_set_phase,
127};