1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * StarFive JH7110 Video-Output Clock Driver |
4 | * |
5 | * Copyright (C) 2022-2023 StarFive Technology Co., Ltd. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/io.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/pm_runtime.h> |
13 | #include <linux/reset.h> |
14 | |
15 | #include <dt-bindings/clock/starfive,jh7110-crg.h> |
16 | |
17 | #include "clk-starfive-jh7110.h" |
18 | |
19 | /* external clocks */ |
20 | #define JH7110_VOUTCLK_VOUT_SRC (JH7110_VOUTCLK_END + 0) |
21 | #define JH7110_VOUTCLK_VOUT_TOP_AHB (JH7110_VOUTCLK_END + 1) |
22 | #define JH7110_VOUTCLK_VOUT_TOP_AXI (JH7110_VOUTCLK_END + 2) |
23 | #define JH7110_VOUTCLK_VOUT_TOP_HDMITX0_MCLK (JH7110_VOUTCLK_END + 3) |
24 | #define JH7110_VOUTCLK_I2STX0_BCLK (JH7110_VOUTCLK_END + 4) |
25 | #define JH7110_VOUTCLK_HDMITX0_PIXELCLK (JH7110_VOUTCLK_END + 5) |
26 | #define JH7110_VOUTCLK_EXT_END (JH7110_VOUTCLK_END + 6) |
27 | |
28 | static struct clk_bulk_data jh7110_vout_top_clks[] = { |
29 | { .id = "vout_src" }, |
30 | { .id = "vout_top_ahb" } |
31 | }; |
32 | |
33 | static const struct jh71x0_clk_data jh7110_voutclk_data[] = { |
34 | /* divider */ |
35 | JH71X0__DIV(JH7110_VOUTCLK_APB, "apb" , 8, JH7110_VOUTCLK_VOUT_TOP_AHB), |
36 | JH71X0__DIV(JH7110_VOUTCLK_DC8200_PIX, "dc8200_pix" , 63, JH7110_VOUTCLK_VOUT_SRC), |
37 | JH71X0__DIV(JH7110_VOUTCLK_DSI_SYS, "dsi_sys" , 31, JH7110_VOUTCLK_VOUT_SRC), |
38 | JH71X0__DIV(JH7110_VOUTCLK_TX_ESC, "tx_esc" , 31, JH7110_VOUTCLK_VOUT_TOP_AHB), |
39 | /* dc8200 */ |
40 | JH71X0_GATE(JH7110_VOUTCLK_DC8200_AXI, "dc8200_axi" , 0, JH7110_VOUTCLK_VOUT_TOP_AXI), |
41 | JH71X0_GATE(JH7110_VOUTCLK_DC8200_CORE, "dc8200_core" , 0, JH7110_VOUTCLK_VOUT_TOP_AXI), |
42 | JH71X0_GATE(JH7110_VOUTCLK_DC8200_AHB, "dc8200_ahb" , 0, JH7110_VOUTCLK_VOUT_TOP_AHB), |
43 | JH71X0_GMUX(JH7110_VOUTCLK_DC8200_PIX0, "dc8200_pix0" , 0, 2, |
44 | JH7110_VOUTCLK_DC8200_PIX, |
45 | JH7110_VOUTCLK_HDMITX0_PIXELCLK), |
46 | JH71X0_GMUX(JH7110_VOUTCLK_DC8200_PIX1, "dc8200_pix1" , 0, 2, |
47 | JH7110_VOUTCLK_DC8200_PIX, |
48 | JH7110_VOUTCLK_HDMITX0_PIXELCLK), |
49 | /* LCD */ |
50 | JH71X0_GMUX(JH7110_VOUTCLK_DOM_VOUT_TOP_LCD, "dom_vout_top_lcd" , 0, 2, |
51 | JH7110_VOUTCLK_DC8200_PIX0, |
52 | JH7110_VOUTCLK_DC8200_PIX1), |
53 | /* dsiTx */ |
54 | JH71X0_GATE(JH7110_VOUTCLK_DSITX_APB, "dsiTx_apb" , 0, JH7110_VOUTCLK_DSI_SYS), |
55 | JH71X0_GATE(JH7110_VOUTCLK_DSITX_SYS, "dsiTx_sys" , 0, JH7110_VOUTCLK_DSI_SYS), |
56 | JH71X0_GMUX(JH7110_VOUTCLK_DSITX_DPI, "dsiTx_dpi" , 0, 2, |
57 | JH7110_VOUTCLK_DC8200_PIX, |
58 | JH7110_VOUTCLK_HDMITX0_PIXELCLK), |
59 | JH71X0_GATE(JH7110_VOUTCLK_DSITX_TXESC, "dsiTx_txesc" , 0, JH7110_VOUTCLK_TX_ESC), |
60 | /* mipitx DPHY */ |
61 | JH71X0_GATE(JH7110_VOUTCLK_MIPITX_DPHY_TXESC, "mipitx_dphy_txesc" , 0, |
62 | JH7110_VOUTCLK_TX_ESC), |
63 | /* hdmi */ |
64 | JH71X0_GATE(JH7110_VOUTCLK_HDMI_TX_MCLK, "hdmi_tx_mclk" , 0, |
65 | JH7110_VOUTCLK_VOUT_TOP_HDMITX0_MCLK), |
66 | JH71X0_GATE(JH7110_VOUTCLK_HDMI_TX_BCLK, "hdmi_tx_bclk" , 0, |
67 | JH7110_VOUTCLK_I2STX0_BCLK), |
68 | JH71X0_GATE(JH7110_VOUTCLK_HDMI_TX_SYS, "hdmi_tx_sys" , 0, JH7110_VOUTCLK_APB), |
69 | }; |
70 | |
71 | static int jh7110_vout_top_rst_init(struct jh71x0_clk_priv *priv) |
72 | { |
73 | struct reset_control *top_rst; |
74 | |
75 | /* The reset should be shared and other Vout modules will use its. */ |
76 | top_rst = devm_reset_control_get_shared(dev: priv->dev, NULL); |
77 | if (IS_ERR(ptr: top_rst)) |
78 | return dev_err_probe(dev: priv->dev, err: PTR_ERR(ptr: top_rst), fmt: "failed to get top reset\n" ); |
79 | |
80 | return reset_control_deassert(rstc: top_rst); |
81 | } |
82 | |
83 | static struct clk_hw *jh7110_voutclk_get(struct of_phandle_args *clkspec, void *data) |
84 | { |
85 | struct jh71x0_clk_priv *priv = data; |
86 | unsigned int idx = clkspec->args[0]; |
87 | |
88 | if (idx < JH7110_VOUTCLK_END) |
89 | return &priv->reg[idx].hw; |
90 | |
91 | return ERR_PTR(error: -EINVAL); |
92 | } |
93 | |
94 | #ifdef CONFIG_PM |
95 | static int jh7110_voutcrg_suspend(struct device *dev) |
96 | { |
97 | struct jh7110_top_sysclk *top = dev_get_drvdata(dev); |
98 | |
99 | clk_bulk_disable_unprepare(num_clks: top->top_clks_num, clks: top->top_clks); |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int jh7110_voutcrg_resume(struct device *dev) |
105 | { |
106 | struct jh7110_top_sysclk *top = dev_get_drvdata(dev); |
107 | |
108 | return clk_bulk_prepare_enable(num_clks: top->top_clks_num, clks: top->top_clks); |
109 | } |
110 | |
111 | static const struct dev_pm_ops jh7110_voutcrg_pm_ops = { |
112 | RUNTIME_PM_OPS(jh7110_voutcrg_suspend, jh7110_voutcrg_resume, NULL) |
113 | }; |
114 | #endif |
115 | |
116 | static int jh7110_voutcrg_probe(struct platform_device *pdev) |
117 | { |
118 | struct jh71x0_clk_priv *priv; |
119 | struct jh7110_top_sysclk *top; |
120 | unsigned int idx; |
121 | int ret; |
122 | |
123 | priv = devm_kzalloc(dev: &pdev->dev, |
124 | struct_size(priv, reg, JH7110_VOUTCLK_END), |
125 | GFP_KERNEL); |
126 | if (!priv) |
127 | return -ENOMEM; |
128 | |
129 | top = devm_kzalloc(dev: &pdev->dev, size: sizeof(*top), GFP_KERNEL); |
130 | if (!top) |
131 | return -ENOMEM; |
132 | |
133 | spin_lock_init(&priv->rmw_lock); |
134 | priv->dev = &pdev->dev; |
135 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
136 | if (IS_ERR(ptr: priv->base)) |
137 | return PTR_ERR(ptr: priv->base); |
138 | |
139 | top->top_clks = jh7110_vout_top_clks; |
140 | top->top_clks_num = ARRAY_SIZE(jh7110_vout_top_clks); |
141 | ret = devm_clk_bulk_get(dev: priv->dev, num_clks: top->top_clks_num, clks: top->top_clks); |
142 | if (ret) |
143 | return dev_err_probe(dev: priv->dev, err: ret, fmt: "failed to get top clocks\n" ); |
144 | dev_set_drvdata(dev: priv->dev, data: top); |
145 | |
146 | /* enable power domain and clocks */ |
147 | pm_runtime_enable(dev: priv->dev); |
148 | ret = pm_runtime_get_sync(dev: priv->dev); |
149 | if (ret < 0) |
150 | return dev_err_probe(dev: priv->dev, err: ret, fmt: "failed to turn on power\n" ); |
151 | |
152 | ret = jh7110_vout_top_rst_init(priv); |
153 | if (ret) |
154 | goto err_exit; |
155 | |
156 | for (idx = 0; idx < JH7110_VOUTCLK_END; idx++) { |
157 | u32 max = jh7110_voutclk_data[idx].max; |
158 | struct clk_parent_data parents[4] = {}; |
159 | struct clk_init_data init = { |
160 | .name = jh7110_voutclk_data[idx].name, |
161 | .ops = starfive_jh71x0_clk_ops(max), |
162 | .parent_data = parents, |
163 | .num_parents = |
164 | ((max & JH71X0_CLK_MUX_MASK) >> JH71X0_CLK_MUX_SHIFT) + 1, |
165 | .flags = jh7110_voutclk_data[idx].flags, |
166 | }; |
167 | struct jh71x0_clk *clk = &priv->reg[idx]; |
168 | unsigned int i; |
169 | const char *fw_name[JH7110_VOUTCLK_EXT_END - JH7110_VOUTCLK_END] = { |
170 | "vout_src" , |
171 | "vout_top_ahb" , |
172 | "vout_top_axi" , |
173 | "vout_top_hdmitx0_mclk" , |
174 | "i2stx0_bclk" , |
175 | "hdmitx0_pixelclk" |
176 | }; |
177 | |
178 | for (i = 0; i < init.num_parents; i++) { |
179 | unsigned int pidx = jh7110_voutclk_data[idx].parents[i]; |
180 | |
181 | if (pidx < JH7110_VOUTCLK_END) |
182 | parents[i].hw = &priv->reg[pidx].hw; |
183 | else if (pidx < JH7110_VOUTCLK_EXT_END) |
184 | parents[i].fw_name = fw_name[pidx - JH7110_VOUTCLK_END]; |
185 | } |
186 | |
187 | clk->hw.init = &init; |
188 | clk->idx = idx; |
189 | clk->max_div = max & JH71X0_CLK_DIV_MASK; |
190 | |
191 | ret = devm_clk_hw_register(dev: &pdev->dev, hw: &clk->hw); |
192 | if (ret) |
193 | goto err_exit; |
194 | } |
195 | |
196 | ret = devm_of_clk_add_hw_provider(dev: &pdev->dev, get: jh7110_voutclk_get, data: priv); |
197 | if (ret) |
198 | goto err_exit; |
199 | |
200 | ret = jh7110_reset_controller_register(priv, adev_name: "rst-vo" , adev_id: 4); |
201 | if (ret) |
202 | goto err_exit; |
203 | |
204 | return 0; |
205 | |
206 | err_exit: |
207 | pm_runtime_put_sync(dev: priv->dev); |
208 | pm_runtime_disable(dev: priv->dev); |
209 | return ret; |
210 | } |
211 | |
212 | static void jh7110_voutcrg_remove(struct platform_device *pdev) |
213 | { |
214 | pm_runtime_put_sync(dev: &pdev->dev); |
215 | pm_runtime_disable(dev: &pdev->dev); |
216 | } |
217 | |
218 | static const struct of_device_id jh7110_voutcrg_match[] = { |
219 | { .compatible = "starfive,jh7110-voutcrg" }, |
220 | { /* sentinel */ } |
221 | }; |
222 | MODULE_DEVICE_TABLE(of, jh7110_voutcrg_match); |
223 | |
224 | static struct platform_driver jh7110_voutcrg_driver = { |
225 | .probe = jh7110_voutcrg_probe, |
226 | .remove_new = jh7110_voutcrg_remove, |
227 | .driver = { |
228 | .name = "clk-starfive-jh7110-vout" , |
229 | .of_match_table = jh7110_voutcrg_match, |
230 | .pm = pm_ptr(&jh7110_voutcrg_pm_ops), |
231 | }, |
232 | }; |
233 | module_platform_driver(jh7110_voutcrg_driver); |
234 | |
235 | MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>" ); |
236 | MODULE_DESCRIPTION("StarFive JH7110 Video-Output clock driver" ); |
237 | MODULE_LICENSE("GPL" ); |
238 | |