1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2016 Free Electrons 4 * Copyright (C) 2016 NextThing Co 5 * 6 * Maxime Ripard <maxime.ripard@free-electrons.com> 7 */ 8 9 #include <linux/clk-provider.h> 10 #include <linux/regmap.h> 11 12 #include "sun4i_hdmi.h" 13 14 struct sun4i_ddc { 15 struct clk_hw hw; 16 struct sun4i_hdmi *hdmi; 17 struct regmap_field *reg; 18 u8 pre_div; 19 u8 m_offset; 20 }; 21 22 static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) 23 { 24 return container_of(hw, struct sun4i_ddc, hw); 25 } 26 27 static unsigned long sun4i_ddc_calc_divider(unsigned long rate, 28 unsigned long parent_rate, 29 const u8 pre_div, 30 const u8 m_offset, 31 u8 *m, u8 *n) 32 { 33 unsigned long best_rate = 0; 34 u8 best_m = 0, best_n = 0, _m, _n; 35 36 for (_m = 0; _m < 16; _m++) { 37 for (_n = 0; _n < 8; _n++) { 38 unsigned long tmp_rate; 39 40 tmp_rate = (((parent_rate / pre_div) / 10) >> _n) / 41 (_m + m_offset); 42 43 if (tmp_rate > rate) 44 continue; 45 46 if (abs(rate - tmp_rate) < abs(rate - best_rate)) { 47 best_rate = tmp_rate; 48 best_m = _m; 49 best_n = _n; 50 } 51 } 52 } 53 54 if (m && n) { 55 *m = best_m; 56 *n = best_n; 57 } 58 59 return best_rate; 60 } 61 62 static int sun4i_ddc_determine_rate(struct clk_hw *hw, 63 struct clk_rate_request *req) 64 { 65 struct sun4i_ddc *ddc = hw_to_ddc(hw); 66 67 req->rate = sun4i_ddc_calc_divider(req->rate, req->best_parent_rate, 68 ddc->pre_div, ddc->m_offset, NULL, NULL); 69 70 return 0; 71 } 72 73 static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, 74 unsigned long parent_rate) 75 { 76 struct sun4i_ddc *ddc = hw_to_ddc(hw); 77 unsigned int reg; 78 u8 m, n; 79 80 regmap_field_read(ddc->reg, ®); 81 m = (reg >> 3) & 0xf; 82 n = reg & 0x7; 83 84 return (((parent_rate / ddc->pre_div) / 10) >> n) / 85 (m + ddc->m_offset); 86 } 87 88 static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, 89 unsigned long parent_rate) 90 { 91 struct sun4i_ddc *ddc = hw_to_ddc(hw); 92 u8 div_m, div_n; 93 94 sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div, 95 ddc->m_offset, &div_m, &div_n); 96 97 regmap_field_write(ddc->reg, 98 SUN4I_HDMI_DDC_CLK_M(div_m) | 99 SUN4I_HDMI_DDC_CLK_N(div_n)); 100 101 return 0; 102 } 103 104 static const struct clk_ops sun4i_ddc_ops = { 105 .recalc_rate = sun4i_ddc_recalc_rate, 106 .determine_rate = sun4i_ddc_determine_rate, 107 .set_rate = sun4i_ddc_set_rate, 108 }; 109 110 int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) 111 { 112 struct clk_init_data init; 113 struct sun4i_ddc *ddc; 114 const char *parent_name; 115 116 parent_name = __clk_get_name(parent); 117 if (!parent_name) 118 return -ENODEV; 119 120 ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); 121 if (!ddc) 122 return -ENOMEM; 123 124 ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, 125 hdmi->variant->ddc_clk_reg); 126 if (IS_ERR(ddc->reg)) 127 return PTR_ERR(ddc->reg); 128 129 init.name = "hdmi-ddc"; 130 init.ops = &sun4i_ddc_ops; 131 init.parent_names = &parent_name; 132 init.num_parents = 1; 133 134 ddc->hdmi = hdmi; 135 ddc->hw.init = &init; 136 ddc->pre_div = hdmi->variant->ddc_clk_pre_divider; 137 ddc->m_offset = hdmi->variant->ddc_clk_m_offset; 138 139 hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); 140 if (IS_ERR(hdmi->ddc_clk)) 141 return PTR_ERR(hdmi->ddc_clk); 142 143 return 0; 144 } 145