1 /* 2 * Copyright (C) 2016 Free Electrons 3 * Copyright (C) 2016 NextThing Co 4 * 5 * Maxime Ripard <maxime.ripard@free-electrons.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation; either version 2 of 10 * the License, or (at your option) any later version. 11 */ 12 13 #include <linux/clk-provider.h> 14 15 #include "sun4i_tcon.h" 16 #include "sun4i_hdmi.h" 17 18 struct sun4i_ddc { 19 struct clk_hw hw; 20 struct sun4i_hdmi *hdmi; 21 }; 22 23 static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) 24 { 25 return container_of(hw, struct sun4i_ddc, hw); 26 } 27 28 static unsigned long sun4i_ddc_calc_divider(unsigned long rate, 29 unsigned long parent_rate, 30 u8 *m, u8 *n) 31 { 32 unsigned long best_rate = 0; 33 u8 best_m = 0, best_n = 0, _m, _n; 34 35 for (_m = 0; _m < 8; _m++) { 36 for (_n = 0; _n < 8; _n++) { 37 unsigned long tmp_rate; 38 39 tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1); 40 41 if (tmp_rate > rate) 42 continue; 43 44 if (abs(rate - tmp_rate) < abs(rate - best_rate)) { 45 best_rate = tmp_rate; 46 best_m = _m; 47 best_n = _n; 48 } 49 } 50 } 51 52 if (m && n) { 53 *m = best_m; 54 *n = best_n; 55 } 56 57 return best_rate; 58 } 59 60 static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, 61 unsigned long *prate) 62 { 63 return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL); 64 } 65 66 static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, 67 unsigned long parent_rate) 68 { 69 struct sun4i_ddc *ddc = hw_to_ddc(hw); 70 u32 reg; 71 u8 m, n; 72 73 reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); 74 m = (reg >> 3) & 0x7; 75 n = reg & 0x7; 76 77 return (((parent_rate / 2) / 10) >> n) / (m + 1); 78 } 79 80 static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, 81 unsigned long parent_rate) 82 { 83 struct sun4i_ddc *ddc = hw_to_ddc(hw); 84 u8 div_m, div_n; 85 86 sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n); 87 88 writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n), 89 ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); 90 91 return 0; 92 } 93 94 static const struct clk_ops sun4i_ddc_ops = { 95 .recalc_rate = sun4i_ddc_recalc_rate, 96 .round_rate = sun4i_ddc_round_rate, 97 .set_rate = sun4i_ddc_set_rate, 98 }; 99 100 int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) 101 { 102 struct clk_init_data init; 103 struct sun4i_ddc *ddc; 104 const char *parent_name; 105 106 parent_name = __clk_get_name(parent); 107 if (!parent_name) 108 return -ENODEV; 109 110 ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); 111 if (!ddc) 112 return -ENOMEM; 113 114 init.name = "hdmi-ddc"; 115 init.ops = &sun4i_ddc_ops; 116 init.parent_names = &parent_name; 117 init.num_parents = 1; 118 119 ddc->hdmi = hdmi; 120 ddc->hw.init = &init; 121 122 hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); 123 if (IS_ERR(hdmi->ddc_clk)) 124 return PTR_ERR(hdmi->ddc_clk); 125 126 return 0; 127 } 128