Commit | Line | Data |
---|---|---|
9026e0d1 MR |
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 | #include <linux/regmap.h> | |
15 | ||
16 | #include "sun4i_tcon.h" | |
17 | ||
18 | struct sun4i_dclk { | |
19 | struct clk_hw hw; | |
20 | struct regmap *regmap; | |
21 | }; | |
22 | ||
23 | static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) | |
24 | { | |
25 | return container_of(hw, struct sun4i_dclk, hw); | |
26 | } | |
27 | ||
28 | static void sun4i_dclk_disable(struct clk_hw *hw) | |
29 | { | |
30 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
31 | ||
32 | regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
33 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0); | |
34 | } | |
35 | ||
36 | static int sun4i_dclk_enable(struct clk_hw *hw) | |
37 | { | |
38 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
39 | ||
40 | return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
41 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), | |
42 | BIT(SUN4I_TCON0_DCLK_GATE_BIT)); | |
43 | } | |
44 | ||
45 | static int sun4i_dclk_is_enabled(struct clk_hw *hw) | |
46 | { | |
47 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
48 | u32 val; | |
49 | ||
50 | regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); | |
51 | ||
52 | return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); | |
53 | } | |
54 | ||
55 | static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, | |
56 | unsigned long parent_rate) | |
57 | { | |
58 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
59 | u32 val; | |
60 | ||
61 | regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); | |
62 | ||
63 | val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; | |
64 | val &= SUN4I_TCON0_DCLK_DIV_WIDTH; | |
65 | ||
66 | if (!val) | |
67 | val = 1; | |
68 | ||
69 | return parent_rate / val; | |
70 | } | |
71 | ||
72 | static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, | |
73 | unsigned long *parent_rate) | |
74 | { | |
75 | return *parent_rate / DIV_ROUND_CLOSEST(*parent_rate, rate); | |
76 | } | |
77 | ||
78 | static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, | |
79 | unsigned long parent_rate) | |
80 | { | |
81 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
82 | int div = DIV_ROUND_CLOSEST(parent_rate, rate); | |
83 | ||
84 | return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
85 | GENMASK(6, 0), div); | |
86 | } | |
87 | ||
88 | static int sun4i_dclk_get_phase(struct clk_hw *hw) | |
89 | { | |
90 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
91 | u32 val; | |
92 | ||
93 | regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val); | |
94 | ||
95 | val >>= 28; | |
96 | val &= 3; | |
97 | ||
98 | return val * 120; | |
99 | } | |
100 | ||
101 | static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) | |
102 | { | |
103 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
104 | ||
105 | regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG, | |
106 | GENMASK(29, 28), | |
107 | degrees / 120); | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | static const struct clk_ops sun4i_dclk_ops = { | |
113 | .disable = sun4i_dclk_disable, | |
114 | .enable = sun4i_dclk_enable, | |
115 | .is_enabled = sun4i_dclk_is_enabled, | |
116 | ||
117 | .recalc_rate = sun4i_dclk_recalc_rate, | |
118 | .round_rate = sun4i_dclk_round_rate, | |
119 | .set_rate = sun4i_dclk_set_rate, | |
120 | ||
121 | .get_phase = sun4i_dclk_get_phase, | |
122 | .set_phase = sun4i_dclk_set_phase, | |
123 | }; | |
124 | ||
125 | int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) | |
126 | { | |
127 | const char *clk_name, *parent_name; | |
128 | struct clk_init_data init; | |
129 | struct sun4i_dclk *dclk; | |
130 | ||
131 | parent_name = __clk_get_name(tcon->sclk0); | |
132 | of_property_read_string_index(dev->of_node, "clock-output-names", 0, | |
133 | &clk_name); | |
134 | ||
135 | dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); | |
136 | if (!dclk) | |
137 | return -ENOMEM; | |
138 | ||
139 | init.name = clk_name; | |
140 | init.ops = &sun4i_dclk_ops; | |
141 | init.parent_names = &parent_name; | |
142 | init.num_parents = 1; | |
143 | ||
144 | dclk->regmap = tcon->regs; | |
145 | dclk->hw.init = &init; | |
146 | ||
147 | tcon->dclk = clk_register(dev, &dclk->hw); | |
148 | if (IS_ERR(tcon->dclk)) | |
149 | return PTR_ERR(tcon->dclk); | |
150 | ||
151 | return 0; | |
152 | } | |
153 | EXPORT_SYMBOL(sun4i_dclk_create); | |
154 | ||
155 | int sun4i_dclk_free(struct sun4i_tcon *tcon) | |
156 | { | |
157 | clk_unregister(tcon->dclk); | |
158 | return 0; | |
159 | } | |
160 | EXPORT_SYMBOL(sun4i_dclk_free); |