1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2021 Rockchip Electronics Co., Ltd
4 */
5
6 #include <linux/clk-provider.h>
7 #include <linux/io.h>
8 #include <linux/module.h>
9 #include <linux/of_address.h>
10 #include <linux/platform_device.h>
11 #include <linux/pm_clock.h>
12 #include <linux/pm_runtime.h>
13 #include <linux/slab.h>
14
15 struct rockchip_link_info {
16 u32 shift;
17 const char *name;
18 const char *pname;
19 };
20
21 struct rockchip_link {
22 int num;
23 const struct rockchip_link_info *info;
24 };
25
26 struct rockchip_link_clk {
27 void __iomem *base;
28 struct clk_gate *gate;
29 spinlock_t lock;
30 u32 shift;
31 u32 flag;
32 const char *name;
33 const char *pname;
34 const char *link_name;
35 const struct rockchip_link *link;
36 };
37
38 #define GFLAGS (CLK_GATE_HIWORD_MASK | CLK_GATE_SET_TO_DISABLE)
39
40 #define GATE_LINK(_name, _pname, _shift) \
41 { \
42 .name = _name, \
43 .pname = _pname, \
44 .shift = (_shift), \
45 }
46
register_clocks(struct rockchip_link_clk * priv,struct device * dev)47 static int register_clocks(struct rockchip_link_clk *priv, struct device *dev)
48 {
49 struct clk_gate *gate;
50 struct clk_init_data init = {};
51 struct clk *clk;
52
53 gate = devm_kzalloc(dev, sizeof(struct clk_gate), GFP_KERNEL);
54 if (!gate)
55 return -ENOMEM;
56
57 init.name = priv->name;
58 init.ops = &clk_gate_ops;
59 init.flags |= CLK_SET_RATE_PARENT;
60 init.parent_names = &priv->pname;
61 init.num_parents = 1;
62
63 /* struct clk_gate assignments */
64 gate->reg = priv->base;
65 gate->bit_idx = priv->shift;
66 gate->flags = GFLAGS;
67 gate->lock = &priv->lock;
68 gate->hw.init = &init;
69
70 clk = devm_clk_register(dev, &gate->hw);
71 if (IS_ERR(clk))
72 return -EINVAL;
73
74 return of_clk_add_provider(dev->of_node, of_clk_src_simple_get, clk);
75 }
76
77 static const struct rockchip_link_info rk3588_clk_gate_link_info[] = {
78 GATE_LINK("aclk_isp1_pre", "aclk_isp1_root", 6),
79 GATE_LINK("hclk_isp1_pre", "hclk_isp1_root", 8),
80 GATE_LINK("hclk_nvm", "hclk_nvm_root", 2),
81 GATE_LINK("aclk_usb", "aclk_usb_root", 2),
82 GATE_LINK("hclk_usb", "hclk_usb_root", 3),
83 GATE_LINK("aclk_jpeg_decoder_pre", "aclk_jpeg_decoder_root", 7),
84 GATE_LINK("aclk_vdpu_low_pre", "aclk_vdpu_low_root", 5),
85 GATE_LINK("aclk_rkvenc1_pre", "aclk_rkvenc1_root", 3),
86 GATE_LINK("hclk_rkvenc1_pre", "hclk_rkvenc1_root", 2),
87 GATE_LINK("hclk_rkvdec0_pre", "hclk_rkvdec0_root", 5),
88 GATE_LINK("aclk_rkvdec0_pre", "aclk_rkvdec0_root", 6),
89 GATE_LINK("hclk_rkvdec1_pre", "hclk_rkvdec1_root", 4),
90 GATE_LINK("aclk_rkvdec1_pre", "aclk_rkvdec1_root", 5),
91 GATE_LINK("aclk_hdcp0_pre", "aclk_vo0_root", 9),
92 GATE_LINK("hclk_vo0", "hclk_vo0_root", 5),
93 GATE_LINK("aclk_hdcp1_pre", "aclk_hdcp1_root", 6),
94 GATE_LINK("hclk_vo1", "hclk_vo1_root", 9),
95 GATE_LINK("aclk_av1_pre", "aclk_av1_root", 1),
96 GATE_LINK("pclk_av1_pre", "pclk_av1_root", 4),
97 GATE_LINK("hclk_sdio_pre", "hclk_sdio_root", 1),
98 };
99
100 static const struct rockchip_link rk3588_clk_gate_link = {
101 .num = ARRAY_SIZE(rk3588_clk_gate_link_info),
102 .info = rk3588_clk_gate_link_info,
103 };
104
105 static const struct of_device_id rockchip_clk_link_of_match[] = {
106 {
107 .compatible = "rockchip,rk3588-clock-gate-link",
108 .data = (void *)&rk3588_clk_gate_link,
109 },
110 {}
111 };
112 MODULE_DEVICE_TABLE(of, rockchip_clk_link_of_match);
113
114 static const struct rockchip_link_info *
rockchip_get_link_infos(const struct rockchip_link * link,const char * name)115 rockchip_get_link_infos(const struct rockchip_link *link, const char *name)
116 {
117 const struct rockchip_link_info *info = link->info;
118 int i = 0;
119
120 for (i = 0; i < link->num; i++) {
121 if (strcmp(info->name, name) == 0)
122 break;
123 info++;
124 }
125 return info;
126 }
127
rockchip_clk_link_probe(struct platform_device * pdev)128 static int rockchip_clk_link_probe(struct platform_device *pdev)
129 {
130 struct rockchip_link_clk *priv;
131 struct device_node *node = pdev->dev.of_node;
132 const struct of_device_id *match;
133 const char *clk_name;
134 const struct rockchip_link_info *link_info;
135 int ret;
136
137 match = of_match_node(rockchip_clk_link_of_match, node);
138 if (!match)
139 return -ENXIO;
140
141 priv = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_link_clk),
142 GFP_KERNEL);
143 if (!priv)
144 return -ENOMEM;
145
146 priv->link = match->data;
147
148 spin_lock_init(&priv->lock);
149 platform_set_drvdata(pdev, priv);
150
151 priv->base = of_iomap(node, 0);
152 if (IS_ERR(priv->base))
153 return PTR_ERR(priv->base);
154
155 if (of_property_read_string(node, "clock-output-names", &clk_name))
156 priv->name = node->name;
157 else
158 priv->name = clk_name;
159
160 link_info = rockchip_get_link_infos(priv->link, priv->name);
161 priv->shift = link_info->shift;
162 priv->pname = link_info->pname;
163
164 pm_runtime_enable(&pdev->dev);
165 ret = pm_clk_create(&pdev->dev);
166 if (ret)
167 goto disable_pm_runtime;
168
169 ret = pm_clk_add(&pdev->dev, "link");
170
171 if (ret)
172 goto destroy_pm_clk;
173
174 ret = register_clocks(priv, &pdev->dev);
175 if (ret)
176 goto destroy_pm_clk;
177
178 return 0;
179
180 destroy_pm_clk:
181 pm_clk_destroy(&pdev->dev);
182 disable_pm_runtime:
183 pm_runtime_disable(&pdev->dev);
184
185 return ret;
186 }
187
rockchip_clk_link_remove(struct platform_device * pdev)188 static int rockchip_clk_link_remove(struct platform_device *pdev)
189 {
190 pm_clk_destroy(&pdev->dev);
191 pm_runtime_disable(&pdev->dev);
192
193 return 0;
194 }
195
196 static const struct dev_pm_ops rockchip_clk_link_pm_ops = {
197 SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
198 };
199
200 static struct platform_driver rockchip_clk_link_driver = {
201 .driver = {
202 .name = "clock-link",
203 .of_match_table = of_match_ptr(rockchip_clk_link_of_match),
204 .pm = &rockchip_clk_link_pm_ops,
205 },
206 .probe = rockchip_clk_link_probe,
207 .remove = rockchip_clk_link_remove,
208 };
209
rockchip_clk_link_drv_register(void)210 static int __init rockchip_clk_link_drv_register(void)
211 {
212 return platform_driver_register(&rockchip_clk_link_driver);
213 }
214 postcore_initcall_sync(rockchip_clk_link_drv_register);
215
rockchip_clk_link_drv_unregister(void)216 static void __exit rockchip_clk_link_drv_unregister(void)
217 {
218 platform_driver_unregister(&rockchip_clk_link_driver);
219 }
220 module_exit(rockchip_clk_link_drv_unregister);
221
222 MODULE_AUTHOR("Elaine Zhang <zhangqing@rock-chips.com>");
223 MODULE_DESCRIPTION("Clock driver for Niu Dependencies");
224 MODULE_LICENSE("GPL");
225