1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Clkout driver for Rockchip RK808 |
4 | * |
5 | * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd |
6 | * |
7 | * Author:Chris Zhong <zyw@rock-chips.com> |
8 | */ |
9 | |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/mfd/rk808.h> |
15 | |
16 | struct rk808_clkout { |
17 | struct regmap *regmap; |
18 | struct clk_hw clkout1_hw; |
19 | struct clk_hw clkout2_hw; |
20 | }; |
21 | |
22 | static unsigned long rk808_clkout_recalc_rate(struct clk_hw *hw, |
23 | unsigned long parent_rate) |
24 | { |
25 | return 32768; |
26 | } |
27 | |
28 | static int rk808_clkout2_enable(struct clk_hw *hw, bool enable) |
29 | { |
30 | struct rk808_clkout *rk808_clkout = container_of(hw, |
31 | struct rk808_clkout, |
32 | clkout2_hw); |
33 | |
34 | return regmap_update_bits(map: rk808_clkout->regmap, RK808_CLK32OUT_REG, |
35 | CLK32KOUT2_EN, val: enable ? CLK32KOUT2_EN : 0); |
36 | } |
37 | |
38 | static int rk808_clkout2_prepare(struct clk_hw *hw) |
39 | { |
40 | return rk808_clkout2_enable(hw, enable: true); |
41 | } |
42 | |
43 | static void rk808_clkout2_unprepare(struct clk_hw *hw) |
44 | { |
45 | rk808_clkout2_enable(hw, enable: false); |
46 | } |
47 | |
48 | static int rk808_clkout2_is_prepared(struct clk_hw *hw) |
49 | { |
50 | struct rk808_clkout *rk808_clkout = container_of(hw, |
51 | struct rk808_clkout, |
52 | clkout2_hw); |
53 | uint32_t val; |
54 | |
55 | int ret = regmap_read(map: rk808_clkout->regmap, RK808_CLK32OUT_REG, val: &val); |
56 | |
57 | if (ret < 0) |
58 | return ret; |
59 | |
60 | return (val & CLK32KOUT2_EN) ? 1 : 0; |
61 | } |
62 | |
63 | static const struct clk_ops rk808_clkout1_ops = { |
64 | .recalc_rate = rk808_clkout_recalc_rate, |
65 | }; |
66 | |
67 | static const struct clk_ops rk808_clkout2_ops = { |
68 | .prepare = rk808_clkout2_prepare, |
69 | .unprepare = rk808_clkout2_unprepare, |
70 | .is_prepared = rk808_clkout2_is_prepared, |
71 | .recalc_rate = rk808_clkout_recalc_rate, |
72 | }; |
73 | |
74 | static struct clk_hw * |
75 | of_clk_rk808_get(struct of_phandle_args *clkspec, void *data) |
76 | { |
77 | struct rk808_clkout *rk808_clkout = data; |
78 | unsigned int idx = clkspec->args[0]; |
79 | |
80 | if (idx >= 2) { |
81 | pr_err("%s: invalid index %u\n" , __func__, idx); |
82 | return ERR_PTR(error: -EINVAL); |
83 | } |
84 | |
85 | return idx ? &rk808_clkout->clkout2_hw : &rk808_clkout->clkout1_hw; |
86 | } |
87 | |
88 | static int rk817_clkout2_enable(struct clk_hw *hw, bool enable) |
89 | { |
90 | struct rk808_clkout *rk808_clkout = container_of(hw, |
91 | struct rk808_clkout, |
92 | clkout2_hw); |
93 | |
94 | return regmap_update_bits(map: rk808_clkout->regmap, RK817_SYS_CFG(1), |
95 | RK817_CLK32KOUT2_EN, |
96 | val: enable ? RK817_CLK32KOUT2_EN : 0); |
97 | } |
98 | |
99 | static int rk817_clkout2_prepare(struct clk_hw *hw) |
100 | { |
101 | return rk817_clkout2_enable(hw, enable: true); |
102 | } |
103 | |
104 | static void rk817_clkout2_unprepare(struct clk_hw *hw) |
105 | { |
106 | rk817_clkout2_enable(hw, enable: false); |
107 | } |
108 | |
109 | static int rk817_clkout2_is_prepared(struct clk_hw *hw) |
110 | { |
111 | struct rk808_clkout *rk808_clkout = container_of(hw, |
112 | struct rk808_clkout, |
113 | clkout2_hw); |
114 | unsigned int val; |
115 | |
116 | int ret = regmap_read(map: rk808_clkout->regmap, RK817_SYS_CFG(1), val: &val); |
117 | |
118 | if (ret < 0) |
119 | return 0; |
120 | |
121 | return (val & RK817_CLK32KOUT2_EN) ? 1 : 0; |
122 | } |
123 | |
124 | static const struct clk_ops rk817_clkout2_ops = { |
125 | .prepare = rk817_clkout2_prepare, |
126 | .unprepare = rk817_clkout2_unprepare, |
127 | .is_prepared = rk817_clkout2_is_prepared, |
128 | .recalc_rate = rk808_clkout_recalc_rate, |
129 | }; |
130 | |
131 | static const struct clk_ops *rkpmic_get_ops(long variant) |
132 | { |
133 | switch (variant) { |
134 | case RK809_ID: |
135 | case RK817_ID: |
136 | return &rk817_clkout2_ops; |
137 | /* |
138 | * For the default case, it match the following PMIC type. |
139 | * RK805_ID |
140 | * RK808_ID |
141 | * RK818_ID |
142 | */ |
143 | default: |
144 | return &rk808_clkout2_ops; |
145 | } |
146 | } |
147 | |
148 | static int rk808_clkout_probe(struct platform_device *pdev) |
149 | { |
150 | struct rk808 *rk808 = dev_get_drvdata(dev: pdev->dev.parent); |
151 | struct device *dev = &pdev->dev; |
152 | struct clk_init_data init = {}; |
153 | struct rk808_clkout *rk808_clkout; |
154 | int ret; |
155 | |
156 | dev->of_node = pdev->dev.parent->of_node; |
157 | |
158 | rk808_clkout = devm_kzalloc(dev, |
159 | size: sizeof(*rk808_clkout), GFP_KERNEL); |
160 | if (!rk808_clkout) |
161 | return -ENOMEM; |
162 | |
163 | rk808_clkout->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
164 | if (!rk808_clkout->regmap) |
165 | return -ENODEV; |
166 | |
167 | init.parent_names = NULL; |
168 | init.num_parents = 0; |
169 | init.name = "rk808-clkout1" ; |
170 | init.ops = &rk808_clkout1_ops; |
171 | rk808_clkout->clkout1_hw.init = &init; |
172 | |
173 | /* optional override of the clockname */ |
174 | of_property_read_string_index(np: dev->of_node, propname: "clock-output-names" , |
175 | index: 0, output: &init.name); |
176 | |
177 | ret = devm_clk_hw_register(dev, hw: &rk808_clkout->clkout1_hw); |
178 | if (ret) |
179 | return ret; |
180 | |
181 | init.name = "rk808-clkout2" ; |
182 | init.ops = rkpmic_get_ops(variant: rk808->variant); |
183 | rk808_clkout->clkout2_hw.init = &init; |
184 | |
185 | /* optional override of the clockname */ |
186 | of_property_read_string_index(np: dev->of_node, propname: "clock-output-names" , |
187 | index: 1, output: &init.name); |
188 | |
189 | ret = devm_clk_hw_register(dev, hw: &rk808_clkout->clkout2_hw); |
190 | if (ret) |
191 | return ret; |
192 | |
193 | return devm_of_clk_add_hw_provider(dev: &pdev->dev, get: of_clk_rk808_get, |
194 | data: rk808_clkout); |
195 | } |
196 | |
197 | static struct platform_driver rk808_clkout_driver = { |
198 | .probe = rk808_clkout_probe, |
199 | .driver = { |
200 | .name = "rk808-clkout" , |
201 | }, |
202 | }; |
203 | |
204 | module_platform_driver(rk808_clkout_driver); |
205 | |
206 | MODULE_DESCRIPTION("Clkout driver for the rk808 series PMICs" ); |
207 | MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>" ); |
208 | MODULE_LICENSE("GPL" ); |
209 | MODULE_ALIAS("platform:rk808-clkout" ); |
210 | |