Merge branches 'work.misc' and 'work.dcache' of git://git.kernel.org/pub/scm/linux...
[sfrench/cifs-2.6.git] / drivers / clk / meson / clk-audio-divider.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2017 AmLogic, Inc.
4  * Author: Jerome Brunet <jbrunet@baylibre.com>
5  */
6
7 /*
8  * i2s master clock divider: The algorithm of the generic clk-divider used with
9  * a very precise clock parent such as the mpll tends to select a low divider
10  * factor. This gives poor results with this particular divider, especially with
11  * high frequencies (> 100 MHz)
12  *
13  * This driver try to select the maximum possible divider with the rate the
14  * upstream clock can provide.
15  */
16
17 #include <linux/clk-provider.h>
18 #include "clkc.h"
19
20 static inline struct meson_clk_audio_div_data *
21 meson_clk_audio_div_data(struct clk_regmap *clk)
22 {
23         return (struct meson_clk_audio_div_data *)clk->data;
24 }
25
26 static int _div_round(unsigned long parent_rate, unsigned long rate,
27                       unsigned long flags)
28 {
29         if (flags & CLK_DIVIDER_ROUND_CLOSEST)
30                 return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, rate);
31
32         return DIV_ROUND_UP_ULL((u64)parent_rate, rate);
33 }
34
35 static int _get_val(unsigned long parent_rate, unsigned long rate)
36 {
37         return DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1;
38 }
39
40 static int _valid_divider(unsigned int width, int divider)
41 {
42         int max_divider = 1 << width;
43
44         return clamp(divider, 1, max_divider);
45 }
46
47 static unsigned long audio_divider_recalc_rate(struct clk_hw *hw,
48                                                unsigned long parent_rate)
49 {
50         struct clk_regmap *clk = to_clk_regmap(hw);
51         struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
52         unsigned long divider;
53
54         divider = meson_parm_read(clk->map, &adiv->div) + 1;
55
56         return DIV_ROUND_UP_ULL((u64)parent_rate, divider);
57 }
58
59 static long audio_divider_round_rate(struct clk_hw *hw,
60                                      unsigned long rate,
61                                      unsigned long *parent_rate)
62 {
63         struct clk_regmap *clk = to_clk_regmap(hw);
64         struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
65         unsigned long max_prate;
66         int divider;
67
68         if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
69                 divider = _div_round(*parent_rate, rate, adiv->flags);
70                 divider = _valid_divider(adiv->div.width, divider);
71                 return DIV_ROUND_UP_ULL((u64)*parent_rate, divider);
72         }
73
74         /* Get the maximum parent rate */
75         max_prate = clk_hw_round_rate(clk_hw_get_parent(hw), ULONG_MAX);
76
77         /* Get the corresponding rounded down divider */
78         divider = max_prate / rate;
79         divider = _valid_divider(adiv->div.width, divider);
80
81         /* Get actual rate of the parent */
82         *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
83                                          divider * rate);
84
85         return DIV_ROUND_UP_ULL((u64)*parent_rate, divider);
86 }
87
88 static int audio_divider_set_rate(struct clk_hw *hw,
89                                   unsigned long rate,
90                                   unsigned long parent_rate)
91 {
92         struct clk_regmap *clk = to_clk_regmap(hw);
93         struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
94         int val = _get_val(parent_rate, rate);
95
96         meson_parm_write(clk->map, &adiv->div, val);
97
98         return 0;
99 }
100
101 const struct clk_ops meson_clk_audio_divider_ro_ops = {
102         .recalc_rate    = audio_divider_recalc_rate,
103         .round_rate     = audio_divider_round_rate,
104 };
105
106 const struct clk_ops meson_clk_audio_divider_ops = {
107         .recalc_rate    = audio_divider_recalc_rate,
108         .round_rate     = audio_divider_round_rate,
109         .set_rate       = audio_divider_set_rate,
110 };