1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * StarFive JH7110 Image-Signal-Process 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_ISPCLK_ISP_TOP_CORE (JH7110_ISPCLK_END + 0) |
21 | #define JH7110_ISPCLK_ISP_TOP_AXI (JH7110_ISPCLK_END + 1) |
22 | #define JH7110_ISPCLK_NOC_BUS_ISP_AXI (JH7110_ISPCLK_END + 2) |
23 | #define JH7110_ISPCLK_DVP_CLK (JH7110_ISPCLK_END + 3) |
24 | #define JH7110_ISPCLK_EXT_END (JH7110_ISPCLK_END + 4) |
25 | |
26 | static struct clk_bulk_data jh7110_isp_top_clks[] = { |
27 | { .id = "isp_top_core" }, |
28 | { .id = "isp_top_axi" } |
29 | }; |
30 | |
31 | static const struct jh71x0_clk_data jh7110_ispclk_data[] = { |
32 | /* syscon */ |
33 | JH71X0__DIV(JH7110_ISPCLK_DOM4_APB_FUNC, "dom4_apb_func" , 15, |
34 | JH7110_ISPCLK_ISP_TOP_AXI), |
35 | JH71X0__DIV(JH7110_ISPCLK_MIPI_RX0_PXL, "mipi_rx0_pxl" , 8, |
36 | JH7110_ISPCLK_ISP_TOP_CORE), |
37 | JH71X0__INV(JH7110_ISPCLK_DVP_INV, "dvp_inv" , JH7110_ISPCLK_DVP_CLK), |
38 | /* vin */ |
39 | JH71X0__DIV(JH7110_ISPCLK_M31DPHY_CFG_IN, "m31dphy_cfg_in" , 16, |
40 | JH7110_ISPCLK_ISP_TOP_CORE), |
41 | JH71X0__DIV(JH7110_ISPCLK_M31DPHY_REF_IN, "m31dphy_ref_in" , 16, |
42 | JH7110_ISPCLK_ISP_TOP_CORE), |
43 | JH71X0__DIV(JH7110_ISPCLK_M31DPHY_TX_ESC_LAN0, "m31dphy_tx_esc_lan0" , 60, |
44 | JH7110_ISPCLK_ISP_TOP_CORE), |
45 | JH71X0_GATE(JH7110_ISPCLK_VIN_APB, "vin_apb" , 0, |
46 | JH7110_ISPCLK_DOM4_APB_FUNC), |
47 | JH71X0__DIV(JH7110_ISPCLK_VIN_SYS, "vin_sys" , 8, JH7110_ISPCLK_ISP_TOP_CORE), |
48 | JH71X0_GATE(JH7110_ISPCLK_VIN_PIXEL_IF0, "vin_pixel_if0" , 0, |
49 | JH7110_ISPCLK_MIPI_RX0_PXL), |
50 | JH71X0_GATE(JH7110_ISPCLK_VIN_PIXEL_IF1, "vin_pixel_if1" , 0, |
51 | JH7110_ISPCLK_MIPI_RX0_PXL), |
52 | JH71X0_GATE(JH7110_ISPCLK_VIN_PIXEL_IF2, "vin_pixel_if2" , 0, |
53 | JH7110_ISPCLK_MIPI_RX0_PXL), |
54 | JH71X0_GATE(JH7110_ISPCLK_VIN_PIXEL_IF3, "vin_pixel_if3" , 0, |
55 | JH7110_ISPCLK_MIPI_RX0_PXL), |
56 | JH71X0__MUX(JH7110_ISPCLK_VIN_P_AXI_WR, "vin_p_axi_wr" , 0, 2, |
57 | JH7110_ISPCLK_MIPI_RX0_PXL, |
58 | JH7110_ISPCLK_DVP_INV), |
59 | /* ispv2_top_wrapper */ |
60 | JH71X0_GMUX(JH7110_ISPCLK_ISPV2_TOP_WRAPPER_C, "ispv2_top_wrapper_c" , 0, 2, |
61 | JH7110_ISPCLK_MIPI_RX0_PXL, |
62 | JH7110_ISPCLK_DVP_INV), |
63 | }; |
64 | |
65 | static inline int jh7110_isp_top_rst_init(struct jh71x0_clk_priv *priv) |
66 | { |
67 | struct reset_control *top_rsts; |
68 | |
69 | /* The resets should be shared and other ISP modules will use its. */ |
70 | top_rsts = devm_reset_control_array_get_shared(dev: priv->dev); |
71 | if (IS_ERR(ptr: top_rsts)) |
72 | return dev_err_probe(dev: priv->dev, err: PTR_ERR(ptr: top_rsts), |
73 | fmt: "failed to get top resets\n" ); |
74 | |
75 | return reset_control_deassert(rstc: top_rsts); |
76 | } |
77 | |
78 | static struct clk_hw *jh7110_ispclk_get(struct of_phandle_args *clkspec, void *data) |
79 | { |
80 | struct jh71x0_clk_priv *priv = data; |
81 | unsigned int idx = clkspec->args[0]; |
82 | |
83 | if (idx < JH7110_ISPCLK_END) |
84 | return &priv->reg[idx].hw; |
85 | |
86 | return ERR_PTR(error: -EINVAL); |
87 | } |
88 | |
89 | #ifdef CONFIG_PM |
90 | static int jh7110_ispcrg_suspend(struct device *dev) |
91 | { |
92 | struct jh7110_top_sysclk *top = dev_get_drvdata(dev); |
93 | |
94 | clk_bulk_disable_unprepare(num_clks: top->top_clks_num, clks: top->top_clks); |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int jh7110_ispcrg_resume(struct device *dev) |
100 | { |
101 | struct jh7110_top_sysclk *top = dev_get_drvdata(dev); |
102 | |
103 | return clk_bulk_prepare_enable(num_clks: top->top_clks_num, clks: top->top_clks); |
104 | } |
105 | |
106 | static const struct dev_pm_ops jh7110_ispcrg_pm_ops = { |
107 | RUNTIME_PM_OPS(jh7110_ispcrg_suspend, jh7110_ispcrg_resume, NULL) |
108 | }; |
109 | #endif |
110 | |
111 | static int jh7110_ispcrg_probe(struct platform_device *pdev) |
112 | { |
113 | struct jh71x0_clk_priv *priv; |
114 | struct jh7110_top_sysclk *top; |
115 | unsigned int idx; |
116 | int ret; |
117 | |
118 | priv = devm_kzalloc(dev: &pdev->dev, |
119 | struct_size(priv, reg, JH7110_ISPCLK_END), |
120 | GFP_KERNEL); |
121 | if (!priv) |
122 | return -ENOMEM; |
123 | |
124 | top = devm_kzalloc(dev: &pdev->dev, size: sizeof(*top), GFP_KERNEL); |
125 | if (!top) |
126 | return -ENOMEM; |
127 | |
128 | spin_lock_init(&priv->rmw_lock); |
129 | priv->dev = &pdev->dev; |
130 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
131 | if (IS_ERR(ptr: priv->base)) |
132 | return PTR_ERR(ptr: priv->base); |
133 | |
134 | top->top_clks = jh7110_isp_top_clks; |
135 | top->top_clks_num = ARRAY_SIZE(jh7110_isp_top_clks); |
136 | ret = devm_clk_bulk_get(dev: priv->dev, num_clks: top->top_clks_num, clks: top->top_clks); |
137 | if (ret) |
138 | return dev_err_probe(dev: priv->dev, err: ret, fmt: "failed to get main clocks\n" ); |
139 | dev_set_drvdata(dev: priv->dev, data: top); |
140 | |
141 | /* enable power domain and clocks */ |
142 | pm_runtime_enable(dev: priv->dev); |
143 | ret = pm_runtime_get_sync(dev: priv->dev); |
144 | if (ret < 0) |
145 | return dev_err_probe(dev: priv->dev, err: ret, fmt: "failed to turn on power\n" ); |
146 | |
147 | ret = jh7110_isp_top_rst_init(priv); |
148 | if (ret) |
149 | goto err_exit; |
150 | |
151 | for (idx = 0; idx < JH7110_ISPCLK_END; idx++) { |
152 | u32 max = jh7110_ispclk_data[idx].max; |
153 | struct clk_parent_data parents[4] = {}; |
154 | struct clk_init_data init = { |
155 | .name = jh7110_ispclk_data[idx].name, |
156 | .ops = starfive_jh71x0_clk_ops(max), |
157 | .parent_data = parents, |
158 | .num_parents = |
159 | ((max & JH71X0_CLK_MUX_MASK) >> JH71X0_CLK_MUX_SHIFT) + 1, |
160 | .flags = jh7110_ispclk_data[idx].flags, |
161 | }; |
162 | struct jh71x0_clk *clk = &priv->reg[idx]; |
163 | unsigned int i; |
164 | const char *fw_name[JH7110_ISPCLK_EXT_END - JH7110_ISPCLK_END] = { |
165 | "isp_top_core" , |
166 | "isp_top_axi" , |
167 | "noc_bus_isp_axi" , |
168 | "dvp_clk" |
169 | }; |
170 | |
171 | for (i = 0; i < init.num_parents; i++) { |
172 | unsigned int pidx = jh7110_ispclk_data[idx].parents[i]; |
173 | |
174 | if (pidx < JH7110_ISPCLK_END) |
175 | parents[i].hw = &priv->reg[pidx].hw; |
176 | else |
177 | parents[i].fw_name = fw_name[pidx - JH7110_ISPCLK_END]; |
178 | } |
179 | |
180 | clk->hw.init = &init; |
181 | clk->idx = idx; |
182 | clk->max_div = max & JH71X0_CLK_DIV_MASK; |
183 | |
184 | ret = devm_clk_hw_register(dev: &pdev->dev, hw: &clk->hw); |
185 | if (ret) |
186 | goto err_exit; |
187 | } |
188 | |
189 | ret = devm_of_clk_add_hw_provider(dev: &pdev->dev, get: jh7110_ispclk_get, data: priv); |
190 | if (ret) |
191 | goto err_exit; |
192 | |
193 | ret = jh7110_reset_controller_register(priv, adev_name: "rst-isp" , adev_id: 3); |
194 | if (ret) |
195 | goto err_exit; |
196 | |
197 | return 0; |
198 | |
199 | err_exit: |
200 | pm_runtime_put_sync(dev: priv->dev); |
201 | pm_runtime_disable(dev: priv->dev); |
202 | return ret; |
203 | } |
204 | |
205 | static void jh7110_ispcrg_remove(struct platform_device *pdev) |
206 | { |
207 | pm_runtime_put_sync(dev: &pdev->dev); |
208 | pm_runtime_disable(dev: &pdev->dev); |
209 | } |
210 | |
211 | static const struct of_device_id jh7110_ispcrg_match[] = { |
212 | { .compatible = "starfive,jh7110-ispcrg" }, |
213 | { /* sentinel */ } |
214 | }; |
215 | MODULE_DEVICE_TABLE(of, jh7110_ispcrg_match); |
216 | |
217 | static struct platform_driver jh7110_ispcrg_driver = { |
218 | .probe = jh7110_ispcrg_probe, |
219 | .remove_new = jh7110_ispcrg_remove, |
220 | .driver = { |
221 | .name = "clk-starfive-jh7110-isp" , |
222 | .of_match_table = jh7110_ispcrg_match, |
223 | .pm = pm_ptr(&jh7110_ispcrg_pm_ops), |
224 | }, |
225 | }; |
226 | module_platform_driver(jh7110_ispcrg_driver); |
227 | |
228 | MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>" ); |
229 | MODULE_DESCRIPTION("StarFive JH7110 Image-Signal-Process clock driver" ); |
230 | MODULE_LICENSE("GPL" ); |
231 | |