GNU Linux-libre 4.14.266-gnu1
[releases.git] / drivers / gpu / drm / sun4i / sun4i_hdmi_ddc_clk.c
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 < 16; _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 }