1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2017 AmLogic, Inc.
4 * Author: Jerome Brunet <jbrunet@baylibre.com>
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)
13 * This driver try to select the maximum possible divider with the rate the
14 * upstream clock can provide.
17 #include <linux/clk-provider.h>
20 static inline struct meson_clk_audio_div_data
*
21 meson_clk_audio_div_data(struct clk_regmap
*clk
)
23 return (struct meson_clk_audio_div_data
*)clk
->data
;
26 static int _div_round(unsigned long parent_rate
, unsigned long rate
,
29 if (flags
& CLK_DIVIDER_ROUND_CLOSEST
)
30 return DIV_ROUND_CLOSEST_ULL((u64
)parent_rate
, rate
);
32 return DIV_ROUND_UP_ULL((u64
)parent_rate
, rate
);
35 static int _get_val(unsigned long parent_rate
, unsigned long rate
)
37 return DIV_ROUND_UP_ULL((u64
)parent_rate
, rate
) - 1;
40 static int _valid_divider(unsigned int width
, int divider
)
42 int max_divider
= 1 << width
;
44 return clamp(divider
, 1, max_divider
);
47 static unsigned long audio_divider_recalc_rate(struct clk_hw
*hw
,
48 unsigned long parent_rate
)
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
;
54 divider
= meson_parm_read(clk
->map
, &adiv
->div
);
56 return DIV_ROUND_UP_ULL((u64
)parent_rate
, divider
);
59 static long audio_divider_round_rate(struct clk_hw
*hw
,
61 unsigned long *parent_rate
)
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
;
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
);
74 /* Get the maximum parent rate */
75 max_prate
= clk_hw_round_rate(clk_hw_get_parent(hw
), ULONG_MAX
);
77 /* Get the corresponding rounded down divider */
78 divider
= max_prate
/ rate
;
79 divider
= _valid_divider(adiv
->div
.width
, divider
);
81 /* Get actual rate of the parent */
82 *parent_rate
= clk_hw_round_rate(clk_hw_get_parent(hw
),
85 return DIV_ROUND_UP_ULL((u64
)*parent_rate
, divider
);
88 static int audio_divider_set_rate(struct clk_hw
*hw
,
90 unsigned long parent_rate
)
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
);
96 meson_parm_write(clk
->map
, &adiv
->div
, val
);
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
,
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
,