2 * Copyright (c) 2017 AmLogic, Inc.
3 * Author: Jerome Brunet <jbrunet@baylibre.com>
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * You should have received a copy of the GNU General Public License along with
15 * this program. If not, see <http://www.gnu.org/licenses/>.
19 * i2s master clock divider: The algorithm of the generic clk-divider used with
20 * a very precise clock parent such as the mpll tends to select a low divider
21 * factor. This gives poor results with this particular divider, especially with
22 * high frequencies (> 100 MHz)
24 * This driver try to select the maximum possible divider with the rate the
25 * upstream clock can provide.
28 #include <linux/clk-provider.h>
31 #define to_meson_clk_audio_divider(_hw) container_of(_hw, \
32 struct meson_clk_audio_divider, hw)
34 static int _div_round(unsigned long parent_rate
, unsigned long rate
,
37 if (flags
& CLK_DIVIDER_ROUND_CLOSEST
)
38 return DIV_ROUND_CLOSEST_ULL((u64
)parent_rate
, rate
);
40 return DIV_ROUND_UP_ULL((u64
)parent_rate
, rate
);
43 static int _get_val(unsigned long parent_rate
, unsigned long rate
)
45 return DIV_ROUND_UP_ULL((u64
)parent_rate
, rate
) - 1;
48 static int _valid_divider(struct clk_hw
*hw
, int divider
)
50 struct meson_clk_audio_divider
*adiv
=
51 to_meson_clk_audio_divider(hw
);
55 width
= adiv
->div
.width
;
56 max_divider
= 1 << width
;
58 return clamp(divider
, 1, max_divider
);
61 static unsigned long audio_divider_recalc_rate(struct clk_hw
*hw
,
62 unsigned long parent_rate
)
64 struct meson_clk_audio_divider
*adiv
=
65 to_meson_clk_audio_divider(hw
);
67 unsigned long reg
, divider
;
70 reg
= readl(adiv
->base
+ p
->reg_off
);
71 divider
= PARM_GET(p
->width
, p
->shift
, reg
) + 1;
73 return DIV_ROUND_UP_ULL((u64
)parent_rate
, divider
);
76 static long audio_divider_round_rate(struct clk_hw
*hw
,
78 unsigned long *parent_rate
)
80 struct meson_clk_audio_divider
*adiv
=
81 to_meson_clk_audio_divider(hw
);
82 unsigned long max_prate
;
85 if (!(clk_hw_get_flags(hw
) & CLK_SET_RATE_PARENT
)) {
86 divider
= _div_round(*parent_rate
, rate
, adiv
->flags
);
87 divider
= _valid_divider(hw
, divider
);
88 return DIV_ROUND_UP_ULL((u64
)*parent_rate
, divider
);
91 /* Get the maximum parent rate */
92 max_prate
= clk_hw_round_rate(clk_hw_get_parent(hw
), ULONG_MAX
);
94 /* Get the corresponding rounded down divider */
95 divider
= max_prate
/ rate
;
96 divider
= _valid_divider(hw
, divider
);
98 /* Get actual rate of the parent */
99 *parent_rate
= clk_hw_round_rate(clk_hw_get_parent(hw
),
102 return DIV_ROUND_UP_ULL((u64
)*parent_rate
, divider
);
105 static int audio_divider_set_rate(struct clk_hw
*hw
,
107 unsigned long parent_rate
)
109 struct meson_clk_audio_divider
*adiv
=
110 to_meson_clk_audio_divider(hw
);
112 unsigned long reg
, flags
= 0;
115 val
= _get_val(parent_rate
, rate
);
118 spin_lock_irqsave(adiv
->lock
, flags
);
120 __acquire(adiv
->lock
);
123 reg
= readl(adiv
->base
+ p
->reg_off
);
124 reg
= PARM_SET(p
->width
, p
->shift
, reg
, val
);
125 writel(reg
, adiv
->base
+ p
->reg_off
);
128 spin_unlock_irqrestore(adiv
->lock
, flags
);
130 __release(adiv
->lock
);
135 const struct clk_ops meson_clk_audio_divider_ro_ops
= {
136 .recalc_rate
= audio_divider_recalc_rate
,
137 .round_rate
= audio_divider_round_rate
,
140 const struct clk_ops meson_clk_audio_divider_ops
= {
141 .recalc_rate
= audio_divider_recalc_rate
,
142 .round_rate
= audio_divider_round_rate
,
143 .set_rate
= audio_divider_set_rate
,