1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2024 Neil Armstrong <neil.armstrong@linaro.org> |
4 | */ |
5 | |
6 | #include <linux/module.h> |
7 | #include "vclk.h" |
8 | |
9 | /* The VCLK gate has a supplementary reset bit to pulse after ungating */ |
10 | |
11 | static inline struct meson_vclk_gate_data * |
12 | clk_get_meson_vclk_gate_data(struct clk_regmap *clk) |
13 | { |
14 | return (struct meson_vclk_gate_data *)clk->data; |
15 | } |
16 | |
17 | static int meson_vclk_gate_enable(struct clk_hw *hw) |
18 | { |
19 | struct clk_regmap *clk = to_clk_regmap(hw); |
20 | struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); |
21 | |
22 | meson_parm_write(map: clk->map, p: &vclk->enable, val: 1); |
23 | |
24 | /* Do a reset pulse */ |
25 | meson_parm_write(map: clk->map, p: &vclk->reset, val: 1); |
26 | meson_parm_write(map: clk->map, p: &vclk->reset, val: 0); |
27 | |
28 | return 0; |
29 | } |
30 | |
31 | static void meson_vclk_gate_disable(struct clk_hw *hw) |
32 | { |
33 | struct clk_regmap *clk = to_clk_regmap(hw); |
34 | struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); |
35 | |
36 | meson_parm_write(map: clk->map, p: &vclk->enable, val: 0); |
37 | } |
38 | |
39 | static int meson_vclk_gate_is_enabled(struct clk_hw *hw) |
40 | { |
41 | struct clk_regmap *clk = to_clk_regmap(hw); |
42 | struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk); |
43 | |
44 | return meson_parm_read(map: clk->map, p: &vclk->enable); |
45 | } |
46 | |
47 | const struct clk_ops meson_vclk_gate_ops = { |
48 | .enable = meson_vclk_gate_enable, |
49 | .disable = meson_vclk_gate_disable, |
50 | .is_enabled = meson_vclk_gate_is_enabled, |
51 | }; |
52 | EXPORT_SYMBOL_NS_GPL(meson_vclk_gate_ops, "CLK_MESON" ); |
53 | |
54 | /* The VCLK Divider has supplementary reset & enable bits */ |
55 | |
56 | static inline struct meson_vclk_div_data * |
57 | clk_get_meson_vclk_div_data(struct clk_regmap *clk) |
58 | { |
59 | return (struct meson_vclk_div_data *)clk->data; |
60 | } |
61 | |
62 | static unsigned long meson_vclk_div_recalc_rate(struct clk_hw *hw, |
63 | unsigned long prate) |
64 | { |
65 | struct clk_regmap *clk = to_clk_regmap(hw); |
66 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
67 | |
68 | return divider_recalc_rate(hw, parent_rate: prate, val: meson_parm_read(map: clk->map, p: &vclk->div), |
69 | table: vclk->table, flags: vclk->flags, width: vclk->div.width); |
70 | } |
71 | |
72 | static int meson_vclk_div_determine_rate(struct clk_hw *hw, |
73 | struct clk_rate_request *req) |
74 | { |
75 | struct clk_regmap *clk = to_clk_regmap(hw); |
76 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
77 | |
78 | return divider_determine_rate(hw, req, table: vclk->table, width: vclk->div.width, |
79 | flags: vclk->flags); |
80 | } |
81 | |
82 | static int meson_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate, |
83 | unsigned long parent_rate) |
84 | { |
85 | struct clk_regmap *clk = to_clk_regmap(hw); |
86 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
87 | int ret; |
88 | |
89 | ret = divider_get_val(rate, parent_rate, table: vclk->table, width: vclk->div.width, |
90 | flags: vclk->flags); |
91 | if (ret < 0) |
92 | return ret; |
93 | |
94 | meson_parm_write(map: clk->map, p: &vclk->div, val: ret); |
95 | |
96 | return 0; |
97 | }; |
98 | |
99 | static int meson_vclk_div_enable(struct clk_hw *hw) |
100 | { |
101 | struct clk_regmap *clk = to_clk_regmap(hw); |
102 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
103 | |
104 | /* Unreset the divider when ungating */ |
105 | meson_parm_write(map: clk->map, p: &vclk->reset, val: 0); |
106 | meson_parm_write(map: clk->map, p: &vclk->enable, val: 1); |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static void meson_vclk_div_disable(struct clk_hw *hw) |
112 | { |
113 | struct clk_regmap *clk = to_clk_regmap(hw); |
114 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
115 | |
116 | /* Reset the divider when gating */ |
117 | meson_parm_write(map: clk->map, p: &vclk->enable, val: 0); |
118 | meson_parm_write(map: clk->map, p: &vclk->reset, val: 1); |
119 | } |
120 | |
121 | static int meson_vclk_div_is_enabled(struct clk_hw *hw) |
122 | { |
123 | struct clk_regmap *clk = to_clk_regmap(hw); |
124 | struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk); |
125 | |
126 | return meson_parm_read(map: clk->map, p: &vclk->enable); |
127 | } |
128 | |
129 | const struct clk_ops meson_vclk_div_ops = { |
130 | .recalc_rate = meson_vclk_div_recalc_rate, |
131 | .determine_rate = meson_vclk_div_determine_rate, |
132 | .set_rate = meson_vclk_div_set_rate, |
133 | .enable = meson_vclk_div_enable, |
134 | .disable = meson_vclk_div_disable, |
135 | .is_enabled = meson_vclk_div_is_enabled, |
136 | }; |
137 | EXPORT_SYMBOL_NS_GPL(meson_vclk_div_ops, "CLK_MESON" ); |
138 | |
139 | MODULE_DESCRIPTION("Amlogic vclk clock driver" ); |
140 | MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>" ); |
141 | MODULE_LICENSE("GPL" ); |
142 | MODULE_IMPORT_NS("CLK_MESON" ); |
143 | |