12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29c568101SMaxime Ripard /*
39c568101SMaxime Ripard  * Copyright (C) 2016 Free Electrons
49c568101SMaxime Ripard  * Copyright (C) 2016 NextThing Co
59c568101SMaxime Ripard  *
69c568101SMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
79c568101SMaxime Ripard  */
89c568101SMaxime Ripard 
99c568101SMaxime Ripard #include <linux/clk-provider.h>
10939d749aSChen-Yu Tsai #include <linux/regmap.h>
119c568101SMaxime Ripard 
129c568101SMaxime Ripard #include "sun4i_hdmi.h"
139c568101SMaxime Ripard 
149c568101SMaxime Ripard struct sun4i_ddc {
159c568101SMaxime Ripard 	struct clk_hw		hw;
169c568101SMaxime Ripard 	struct sun4i_hdmi	*hdmi;
17939d749aSChen-Yu Tsai 	struct regmap_field	*reg;
18939d749aSChen-Yu Tsai 	u8			pre_div;
19939d749aSChen-Yu Tsai 	u8			m_offset;
209c568101SMaxime Ripard };
219c568101SMaxime Ripard 
hw_to_ddc(struct clk_hw * hw)229c568101SMaxime Ripard static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
239c568101SMaxime Ripard {
249c568101SMaxime Ripard 	return container_of(hw, struct sun4i_ddc, hw);
259c568101SMaxime Ripard }
269c568101SMaxime Ripard 
sun4i_ddc_calc_divider(unsigned long rate,unsigned long parent_rate,const u8 pre_div,const u8 m_offset,u8 * m,u8 * n)279c568101SMaxime Ripard static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
289c568101SMaxime Ripard 					    unsigned long parent_rate,
29939d749aSChen-Yu Tsai 					    const u8 pre_div,
30939d749aSChen-Yu Tsai 					    const u8 m_offset,
319c568101SMaxime Ripard 					    u8 *m, u8 *n)
329c568101SMaxime Ripard {
339c568101SMaxime Ripard 	unsigned long best_rate = 0;
349c568101SMaxime Ripard 	u8 best_m = 0, best_n = 0, _m, _n;
359c568101SMaxime Ripard 
3654e1e06bSJernej Skrabec 	for (_m = 0; _m < 16; _m++) {
379c568101SMaxime Ripard 		for (_n = 0; _n < 8; _n++) {
389c568101SMaxime Ripard 			unsigned long tmp_rate;
399c568101SMaxime Ripard 
40939d749aSChen-Yu Tsai 			tmp_rate = (((parent_rate / pre_div) / 10) >> _n) /
41939d749aSChen-Yu Tsai 				(_m + m_offset);
429c568101SMaxime Ripard 
439c568101SMaxime Ripard 			if (tmp_rate > rate)
449c568101SMaxime Ripard 				continue;
459c568101SMaxime Ripard 
469c568101SMaxime Ripard 			if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
479c568101SMaxime Ripard 				best_rate = tmp_rate;
489c568101SMaxime Ripard 				best_m = _m;
499c568101SMaxime Ripard 				best_n = _n;
509c568101SMaxime Ripard 			}
519c568101SMaxime Ripard 		}
529c568101SMaxime Ripard 	}
539c568101SMaxime Ripard 
549c568101SMaxime Ripard 	if (m && n) {
559c568101SMaxime Ripard 		*m = best_m;
569c568101SMaxime Ripard 		*n = best_n;
579c568101SMaxime Ripard 	}
589c568101SMaxime Ripard 
599c568101SMaxime Ripard 	return best_rate;
609c568101SMaxime Ripard }
619c568101SMaxime Ripard 
sun4i_ddc_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)629c568101SMaxime Ripard static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
639c568101SMaxime Ripard 				 unsigned long *prate)
649c568101SMaxime Ripard {
65939d749aSChen-Yu Tsai 	struct sun4i_ddc *ddc = hw_to_ddc(hw);
66939d749aSChen-Yu Tsai 
67939d749aSChen-Yu Tsai 	return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div,
68939d749aSChen-Yu Tsai 				      ddc->m_offset, NULL, NULL);
699c568101SMaxime Ripard }
709c568101SMaxime Ripard 
sun4i_ddc_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)719c568101SMaxime Ripard static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
729c568101SMaxime Ripard 					    unsigned long parent_rate)
739c568101SMaxime Ripard {
749c568101SMaxime Ripard 	struct sun4i_ddc *ddc = hw_to_ddc(hw);
75939d749aSChen-Yu Tsai 	unsigned int reg;
769c568101SMaxime Ripard 	u8 m, n;
779c568101SMaxime Ripard 
78939d749aSChen-Yu Tsai 	regmap_field_read(ddc->reg, &reg);
79939d749aSChen-Yu Tsai 	m = (reg >> 3) & 0xf;
809c568101SMaxime Ripard 	n = reg & 0x7;
819c568101SMaxime Ripard 
82939d749aSChen-Yu Tsai 	return (((parent_rate / ddc->pre_div) / 10) >> n) /
83939d749aSChen-Yu Tsai 	       (m + ddc->m_offset);
849c568101SMaxime Ripard }
859c568101SMaxime Ripard 
sun4i_ddc_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)869c568101SMaxime Ripard static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
879c568101SMaxime Ripard 			      unsigned long parent_rate)
889c568101SMaxime Ripard {
899c568101SMaxime Ripard 	struct sun4i_ddc *ddc = hw_to_ddc(hw);
909c568101SMaxime Ripard 	u8 div_m, div_n;
919c568101SMaxime Ripard 
92939d749aSChen-Yu Tsai 	sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div,
93939d749aSChen-Yu Tsai 			       ddc->m_offset, &div_m, &div_n);
949c568101SMaxime Ripard 
95939d749aSChen-Yu Tsai 	regmap_field_write(ddc->reg,
96939d749aSChen-Yu Tsai 			   SUN4I_HDMI_DDC_CLK_M(div_m) |
97939d749aSChen-Yu Tsai 			   SUN4I_HDMI_DDC_CLK_N(div_n));
989c568101SMaxime Ripard 
999c568101SMaxime Ripard 	return 0;
1009c568101SMaxime Ripard }
1019c568101SMaxime Ripard 
1029c568101SMaxime Ripard static const struct clk_ops sun4i_ddc_ops = {
1039c568101SMaxime Ripard 	.recalc_rate	= sun4i_ddc_recalc_rate,
1049c568101SMaxime Ripard 	.round_rate	= sun4i_ddc_round_rate,
1059c568101SMaxime Ripard 	.set_rate	= sun4i_ddc_set_rate,
1069c568101SMaxime Ripard };
1079c568101SMaxime Ripard 
sun4i_ddc_create(struct sun4i_hdmi * hdmi,struct clk * parent)1089c568101SMaxime Ripard int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
1099c568101SMaxime Ripard {
1109c568101SMaxime Ripard 	struct clk_init_data init;
1119c568101SMaxime Ripard 	struct sun4i_ddc *ddc;
1129c568101SMaxime Ripard 	const char *parent_name;
1139c568101SMaxime Ripard 
1149c568101SMaxime Ripard 	parent_name = __clk_get_name(parent);
1159c568101SMaxime Ripard 	if (!parent_name)
1169c568101SMaxime Ripard 		return -ENODEV;
1179c568101SMaxime Ripard 
1189c568101SMaxime Ripard 	ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
1199c568101SMaxime Ripard 	if (!ddc)
1209c568101SMaxime Ripard 		return -ENOMEM;
1219c568101SMaxime Ripard 
122939d749aSChen-Yu Tsai 	ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
123939d749aSChen-Yu Tsai 					   hdmi->variant->ddc_clk_reg);
124939d749aSChen-Yu Tsai 	if (IS_ERR(ddc->reg))
125939d749aSChen-Yu Tsai 		return PTR_ERR(ddc->reg);
126939d749aSChen-Yu Tsai 
1279c568101SMaxime Ripard 	init.name = "hdmi-ddc";
1289c568101SMaxime Ripard 	init.ops = &sun4i_ddc_ops;
1299c568101SMaxime Ripard 	init.parent_names = &parent_name;
1309c568101SMaxime Ripard 	init.num_parents = 1;
1319c568101SMaxime Ripard 
1329c568101SMaxime Ripard 	ddc->hdmi = hdmi;
1339c568101SMaxime Ripard 	ddc->hw.init = &init;
134939d749aSChen-Yu Tsai 	ddc->pre_div = hdmi->variant->ddc_clk_pre_divider;
135939d749aSChen-Yu Tsai 	ddc->m_offset = hdmi->variant->ddc_clk_m_offset;
1369c568101SMaxime Ripard 
1379c568101SMaxime Ripard 	hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
1389c568101SMaxime Ripard 	if (IS_ERR(hdmi->ddc_clk))
1399c568101SMaxime Ripard 		return PTR_ERR(hdmi->ddc_clk);
1409c568101SMaxime Ripard 
1419c568101SMaxime Ripard 	return 0;
1429c568101SMaxime Ripard }
143