]> Git Repo - linux.git/blob - drivers/gpu/drm/mcde/mcde_clk_div.c
Linux 6.14-rc3
[linux.git] / drivers / gpu / drm / mcde / mcde_clk_div.c
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 */
10 struct mcde_clk_div {
11         struct clk_hw hw;
12         struct mcde *mcde;
13         u32 cr;
14         u32 cr_div;
15 };
16
17 static 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(&mcde->fifo_crx1_lock);
24         val = readl(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, mcde->regs + cdiv->cr);
39         spin_unlock(&mcde->fifo_crx1_lock);
40
41         return 0;
42 }
43
44 static 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(parent, 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
74 static 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, true);
78
79         return DIV_ROUND_UP_ULL(*prate, div);
80 }
81
82 static 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(mcde->epod))
96                 return DIV_ROUND_UP_ULL(prate, 2);
97
98         cr = readl(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
109 static 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, 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
132 static 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
139 int 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(mcde->lcd_clk);
163
164         /* Allocate 2 clocks */
165         fifoa = devm_kzalloc(dev, sizeof(*fifoa), GFP_KERNEL);
166         if (!fifoa)
167                 return -ENOMEM;
168         fifob = devm_kzalloc(dev, 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, &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, &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 }
This page took 0.041448 seconds and 4 git commands to generate.