1// SPDX-License-Identifier: GPL-2.0
2#include <linux/clk-provider.h>
3#include <linux/io.h>
4#include <linux/regulator/consumer.h>
5
6#include "mcde_drm.h"
7#include "mcde_display_regs.h"
8
9/* The MCDE internal clock dividers for FIFO A and B */
10struct mcde_clk_div {
11 struct clk_hw hw;
12 struct mcde *mcde;
13 u32 cr;
14 u32 cr_div;
15};
16
17static int mcde_clk_div_enable(struct clk_hw *hw)
18{
19 struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
20 struct mcde *mcde = cdiv->mcde;
21 u32 val;
22
23 spin_lock(lock: &mcde->fifo_crx1_lock);
24 val = readl(addr: mcde->regs + cdiv->cr);
25 /*
26 * Select the PLL72 (LCD) clock as parent
27 * FIXME: implement other parents.
28 */
29 val &= ~MCDE_CRX1_CLKSEL_MASK;
30 val |= MCDE_CRX1_CLKSEL_CLKPLL72 << MCDE_CRX1_CLKSEL_SHIFT;
31 /* Internal clock */
32 val |= MCDE_CRA1_CLKTYPE_TVXCLKSEL1;
33
34 /* Clear then set the divider */
35 val &= ~(MCDE_CRX1_BCD | MCDE_CRX1_PCD_MASK);
36 val |= cdiv->cr_div;
37
38 writel(val, addr: mcde->regs + cdiv->cr);
39 spin_unlock(lock: &mcde->fifo_crx1_lock);
40
41 return 0;
42}
43
44static int mcde_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
45 unsigned long *prate, bool set_parent)
46{
47 int best_div = 1, div;
48 struct clk_hw *parent = clk_hw_get_parent(hw);
49 unsigned long best_prate = 0;
50 unsigned long best_diff = ~0ul;
51 int max_div = (1 << MCDE_CRX1_PCD_BITS) - 1;
52
53 for (div = 1; div < max_div; div++) {
54 unsigned long this_prate, div_rate, diff;
55
56 if (set_parent)
57 this_prate = clk_hw_round_rate(hw: parent, rate: rate * div);
58 else
59 this_prate = *prate;
60 div_rate = DIV_ROUND_UP_ULL(this_prate, div);
61 diff = abs(rate - div_rate);
62
63 if (diff < best_diff) {
64 best_div = div;
65 best_diff = diff;
66 best_prate = this_prate;
67 }
68 }
69
70 *prate = best_prate;
71 return best_div;
72}
73
74static long mcde_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
75 unsigned long *prate)
76{
77 int div = mcde_clk_div_choose_div(hw, rate, prate, set_parent: true);
78
79 return DIV_ROUND_UP_ULL(*prate, div);
80}
81
82static unsigned long mcde_clk_div_recalc_rate(struct clk_hw *hw,
83 unsigned long prate)
84{
85 struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
86 struct mcde *mcde = cdiv->mcde;
87 u32 cr;
88 int div;
89
90 /*
91 * If the MCDE is not powered we can't access registers.
92 * It will come up with 0 in the divider register bits, which
93 * means "divide by 2".
94 */
95 if (!regulator_is_enabled(regulator: mcde->epod))
96 return DIV_ROUND_UP_ULL(prate, 2);
97
98 cr = readl(addr: mcde->regs + cdiv->cr);
99 if (cr & MCDE_CRX1_BCD)
100 return prate;
101
102 /* 0 in the PCD means "divide by 2", 1 means "divide by 3" etc */
103 div = cr & MCDE_CRX1_PCD_MASK;
104 div += 2;
105
106 return DIV_ROUND_UP_ULL(prate, div);
107}
108
109static int mcde_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
110 unsigned long prate)
111{
112 struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
113 int div = mcde_clk_div_choose_div(hw, rate, prate: &prate, set_parent: false);
114 u32 cr = 0;
115
116 /*
117 * We cache the CR bits to set the divide in the state so that
118 * we can call this before we can even write to the hardware.
119 */
120 if (div == 1) {
121 /* Bypass clock divider */
122 cr |= MCDE_CRX1_BCD;
123 } else {
124 div -= 2;
125 cr |= div & MCDE_CRX1_PCD_MASK;
126 }
127 cdiv->cr_div = cr;
128
129 return 0;
130}
131
132static const struct clk_ops mcde_clk_div_ops = {
133 .enable = mcde_clk_div_enable,
134 .recalc_rate = mcde_clk_div_recalc_rate,
135 .round_rate = mcde_clk_div_round_rate,
136 .set_rate = mcde_clk_div_set_rate,
137};
138
139int mcde_init_clock_divider(struct mcde *mcde)
140{
141 struct device *dev = mcde->dev;
142 struct mcde_clk_div *fifoa;
143 struct mcde_clk_div *fifob;
144 const char *parent_name;
145 struct clk_init_data fifoa_init = {
146 .name = "fifoa",
147 .ops = &mcde_clk_div_ops,
148 .parent_names = &parent_name,
149 .num_parents = 1,
150 .flags = CLK_SET_RATE_PARENT,
151 };
152 struct clk_init_data fifob_init = {
153 .name = "fifob",
154 .ops = &mcde_clk_div_ops,
155 .parent_names = &parent_name,
156 .num_parents = 1,
157 .flags = CLK_SET_RATE_PARENT,
158 };
159 int ret;
160
161 spin_lock_init(&mcde->fifo_crx1_lock);
162 parent_name = __clk_get_name(clk: mcde->lcd_clk);
163
164 /* Allocate 2 clocks */
165 fifoa = devm_kzalloc(dev, size: sizeof(*fifoa), GFP_KERNEL);
166 if (!fifoa)
167 return -ENOMEM;
168 fifob = devm_kzalloc(dev, size: sizeof(*fifob), GFP_KERNEL);
169 if (!fifob)
170 return -ENOMEM;
171
172 fifoa->mcde = mcde;
173 fifoa->cr = MCDE_CRA1;
174 fifoa->hw.init = &fifoa_init;
175 ret = devm_clk_hw_register(dev, hw: &fifoa->hw);
176 if (ret) {
177 dev_err(dev, "error registering FIFO A clock divider\n");
178 return ret;
179 }
180 mcde->fifoa_clk = fifoa->hw.clk;
181
182 fifob->mcde = mcde;
183 fifob->cr = MCDE_CRB1;
184 fifob->hw.init = &fifob_init;
185 ret = devm_clk_hw_register(dev, hw: &fifob->hw);
186 if (ret) {
187 dev_err(dev, "error registering FIFO B clock divider\n");
188 return ret;
189 }
190 mcde->fifob_clk = fifob->hw.clk;
191
192 return 0;
193}
194

source code of linux/drivers/gpu/drm/mcde/mcde_clk_div.c