1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Samsung S5P/Exynos SoC series MIPI CSIS/DSIM DPHY driver |
4 | * |
5 | * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd. |
6 | * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/err.h> |
10 | #include <linux/io.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/phy/phy.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/spinlock.h> |
18 | #include <linux/soc/samsung/exynos-regs-pmu.h> |
19 | #include <linux/mfd/syscon.h> |
20 | |
21 | enum exynos_mipi_phy_id { |
22 | EXYNOS_MIPI_PHY_ID_NONE = -1, |
23 | EXYNOS_MIPI_PHY_ID_CSIS0, |
24 | EXYNOS_MIPI_PHY_ID_DSIM0, |
25 | EXYNOS_MIPI_PHY_ID_CSIS1, |
26 | EXYNOS_MIPI_PHY_ID_DSIM1, |
27 | EXYNOS_MIPI_PHY_ID_CSIS2, |
28 | EXYNOS_MIPI_PHYS_NUM |
29 | }; |
30 | |
31 | enum exynos_mipi_phy_regmap_id { |
32 | EXYNOS_MIPI_REGMAP_PMU, |
33 | EXYNOS_MIPI_REGMAP_DISP, |
34 | EXYNOS_MIPI_REGMAP_CAM0, |
35 | EXYNOS_MIPI_REGMAP_CAM1, |
36 | EXYNOS_MIPI_REGMAPS_NUM |
37 | }; |
38 | |
39 | struct mipi_phy_device_desc { |
40 | int num_phys; |
41 | int num_regmaps; |
42 | const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM]; |
43 | struct exynos_mipi_phy_desc { |
44 | enum exynos_mipi_phy_id coupled_phy_id; |
45 | u32 enable_val; |
46 | unsigned int enable_reg; |
47 | enum exynos_mipi_phy_regmap_id enable_map; |
48 | u32 resetn_val; |
49 | unsigned int resetn_reg; |
50 | enum exynos_mipi_phy_regmap_id resetn_map; |
51 | } phys[EXYNOS_MIPI_PHYS_NUM]; |
52 | }; |
53 | |
54 | static const struct mipi_phy_device_desc s5pv210_mipi_phy = { |
55 | .num_regmaps = 1, |
56 | .regmap_names = {"syscon" }, |
57 | .num_phys = 4, |
58 | .phys = { |
59 | { |
60 | /* EXYNOS_MIPI_PHY_ID_CSIS0 */ |
61 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, |
62 | .enable_val = EXYNOS4_PHY_ENABLE, |
63 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
64 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
65 | .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, |
66 | .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
67 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
68 | }, { |
69 | /* EXYNOS_MIPI_PHY_ID_DSIM0 */ |
70 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, |
71 | .enable_val = EXYNOS4_PHY_ENABLE, |
72 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
73 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
74 | .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, |
75 | .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
76 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
77 | }, { |
78 | /* EXYNOS_MIPI_PHY_ID_CSIS1 */ |
79 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, |
80 | .enable_val = EXYNOS4_PHY_ENABLE, |
81 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
82 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
83 | .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, |
84 | .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
85 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
86 | }, { |
87 | /* EXYNOS_MIPI_PHY_ID_DSIM1 */ |
88 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, |
89 | .enable_val = EXYNOS4_PHY_ENABLE, |
90 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
91 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
92 | .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, |
93 | .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
94 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
95 | }, |
96 | }, |
97 | }; |
98 | |
99 | static const struct mipi_phy_device_desc exynos5420_mipi_phy = { |
100 | .num_regmaps = 1, |
101 | .regmap_names = {"syscon" }, |
102 | .num_phys = 5, |
103 | .phys = { |
104 | { |
105 | /* EXYNOS_MIPI_PHY_ID_CSIS0 */ |
106 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, |
107 | .enable_val = EXYNOS4_PHY_ENABLE, |
108 | .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), |
109 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
110 | .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, |
111 | .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), |
112 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
113 | }, { |
114 | /* EXYNOS_MIPI_PHY_ID_DSIM0 */ |
115 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, |
116 | .enable_val = EXYNOS4_PHY_ENABLE, |
117 | .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), |
118 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
119 | .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, |
120 | .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), |
121 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
122 | }, { |
123 | /* EXYNOS_MIPI_PHY_ID_CSIS1 */ |
124 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, |
125 | .enable_val = EXYNOS4_PHY_ENABLE, |
126 | .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), |
127 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
128 | .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, |
129 | .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), |
130 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
131 | }, { |
132 | /* EXYNOS_MIPI_PHY_ID_DSIM1 */ |
133 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, |
134 | .enable_val = EXYNOS4_PHY_ENABLE, |
135 | .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), |
136 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
137 | .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, |
138 | .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), |
139 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
140 | }, { |
141 | /* EXYNOS_MIPI_PHY_ID_CSIS2 */ |
142 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, |
143 | .enable_val = EXYNOS4_PHY_ENABLE, |
144 | .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), |
145 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
146 | .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, |
147 | .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), |
148 | .resetn_map = EXYNOS_MIPI_REGMAP_PMU, |
149 | }, |
150 | }, |
151 | }; |
152 | |
153 | #define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C |
154 | #define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014 |
155 | #define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020 |
156 | |
157 | static const struct mipi_phy_device_desc exynos5433_mipi_phy = { |
158 | .num_regmaps = 4, |
159 | .regmap_names = { |
160 | "samsung,pmu-syscon" , |
161 | "samsung,disp-sysreg" , |
162 | "samsung,cam0-sysreg" , |
163 | "samsung,cam1-sysreg" |
164 | }, |
165 | .num_phys = 5, |
166 | .phys = { |
167 | { |
168 | /* EXYNOS_MIPI_PHY_ID_CSIS0 */ |
169 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, |
170 | .enable_val = EXYNOS4_PHY_ENABLE, |
171 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
172 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
173 | .resetn_val = BIT(0), |
174 | .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, |
175 | .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, |
176 | }, { |
177 | /* EXYNOS_MIPI_PHY_ID_DSIM0 */ |
178 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, |
179 | .enable_val = EXYNOS4_PHY_ENABLE, |
180 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), |
181 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
182 | .resetn_val = BIT(0), |
183 | .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, |
184 | .resetn_map = EXYNOS_MIPI_REGMAP_DISP, |
185 | }, { |
186 | /* EXYNOS_MIPI_PHY_ID_CSIS1 */ |
187 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, |
188 | .enable_val = EXYNOS4_PHY_ENABLE, |
189 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
190 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
191 | .resetn_val = BIT(1), |
192 | .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, |
193 | .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, |
194 | }, { |
195 | /* EXYNOS_MIPI_PHY_ID_DSIM1 */ |
196 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, |
197 | .enable_val = EXYNOS4_PHY_ENABLE, |
198 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), |
199 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
200 | .resetn_val = BIT(1), |
201 | .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, |
202 | .resetn_map = EXYNOS_MIPI_REGMAP_DISP, |
203 | }, { |
204 | /* EXYNOS_MIPI_PHY_ID_CSIS2 */ |
205 | .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, |
206 | .enable_val = EXYNOS4_PHY_ENABLE, |
207 | .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2), |
208 | .enable_map = EXYNOS_MIPI_REGMAP_PMU, |
209 | .resetn_val = BIT(0), |
210 | .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, |
211 | .resetn_map = EXYNOS_MIPI_REGMAP_CAM1, |
212 | }, |
213 | }, |
214 | }; |
215 | |
216 | struct exynos_mipi_video_phy { |
217 | struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM]; |
218 | int num_phys; |
219 | struct video_phy_desc { |
220 | struct phy *phy; |
221 | unsigned int index; |
222 | const struct exynos_mipi_phy_desc *data; |
223 | } phys[EXYNOS_MIPI_PHYS_NUM]; |
224 | spinlock_t slock; |
225 | }; |
226 | |
227 | static int __set_phy_state(const struct exynos_mipi_phy_desc *data, |
228 | struct exynos_mipi_video_phy *state, unsigned int on) |
229 | { |
230 | struct regmap *enable_map = state->regmaps[data->enable_map]; |
231 | struct regmap *resetn_map = state->regmaps[data->resetn_map]; |
232 | |
233 | spin_lock(lock: &state->slock); |
234 | |
235 | /* disable in PMU sysreg */ |
236 | if (!on && data->coupled_phy_id >= 0 && |
237 | state->phys[data->coupled_phy_id].phy->power_count == 0) |
238 | regmap_update_bits(map: enable_map, reg: data->enable_reg, |
239 | mask: data->enable_val, val: 0); |
240 | /* PHY reset */ |
241 | if (on) |
242 | regmap_update_bits(map: resetn_map, reg: data->resetn_reg, |
243 | mask: data->resetn_val, val: data->resetn_val); |
244 | else |
245 | regmap_update_bits(map: resetn_map, reg: data->resetn_reg, |
246 | mask: data->resetn_val, val: 0); |
247 | /* enable in PMU sysreg */ |
248 | if (on) |
249 | regmap_update_bits(map: enable_map, reg: data->enable_reg, |
250 | mask: data->enable_val, val: data->enable_val); |
251 | |
252 | spin_unlock(lock: &state->slock); |
253 | |
254 | return 0; |
255 | } |
256 | |
257 | #define to_mipi_video_phy(desc) \ |
258 | container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]) |
259 | |
260 | static int exynos_mipi_video_phy_power_on(struct phy *phy) |
261 | { |
262 | struct video_phy_desc *phy_desc = phy_get_drvdata(phy); |
263 | struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); |
264 | |
265 | return __set_phy_state(data: phy_desc->data, state, on: 1); |
266 | } |
267 | |
268 | static int exynos_mipi_video_phy_power_off(struct phy *phy) |
269 | { |
270 | struct video_phy_desc *phy_desc = phy_get_drvdata(phy); |
271 | struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); |
272 | |
273 | return __set_phy_state(data: phy_desc->data, state, on: 0); |
274 | } |
275 | |
276 | static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, |
277 | const struct of_phandle_args *args) |
278 | { |
279 | struct exynos_mipi_video_phy *state = dev_get_drvdata(dev); |
280 | |
281 | if (WARN_ON(args->args[0] >= state->num_phys)) |
282 | return ERR_PTR(error: -ENODEV); |
283 | |
284 | return state->phys[args->args[0]].phy; |
285 | } |
286 | |
287 | static const struct phy_ops exynos_mipi_video_phy_ops = { |
288 | .power_on = exynos_mipi_video_phy_power_on, |
289 | .power_off = exynos_mipi_video_phy_power_off, |
290 | .owner = THIS_MODULE, |
291 | }; |
292 | |
293 | static int exynos_mipi_video_phy_probe(struct platform_device *pdev) |
294 | { |
295 | const struct mipi_phy_device_desc *phy_dev; |
296 | struct exynos_mipi_video_phy *state; |
297 | struct device *dev = &pdev->dev; |
298 | struct device_node *np = dev->of_node; |
299 | struct phy_provider *phy_provider; |
300 | unsigned int i = 0; |
301 | |
302 | phy_dev = of_device_get_match_data(dev); |
303 | if (!phy_dev) |
304 | return -ENODEV; |
305 | |
306 | state = devm_kzalloc(dev, size: sizeof(*state), GFP_KERNEL); |
307 | if (!state) |
308 | return -ENOMEM; |
309 | |
310 | state->regmaps[i] = syscon_node_to_regmap(np: dev->parent->of_node); |
311 | if (!IS_ERR(ptr: state->regmaps[i])) |
312 | i++; |
313 | for (; i < phy_dev->num_regmaps; i++) { |
314 | state->regmaps[i] = syscon_regmap_lookup_by_phandle(np, |
315 | property: phy_dev->regmap_names[i]); |
316 | if (IS_ERR(ptr: state->regmaps[i])) |
317 | return PTR_ERR(ptr: state->regmaps[i]); |
318 | } |
319 | state->num_phys = phy_dev->num_phys; |
320 | spin_lock_init(&state->slock); |
321 | |
322 | dev_set_drvdata(dev, data: state); |
323 | |
324 | for (i = 0; i < state->num_phys; i++) { |
325 | struct phy *phy = devm_phy_create(dev, NULL, |
326 | ops: &exynos_mipi_video_phy_ops); |
327 | if (IS_ERR(ptr: phy)) { |
328 | dev_err(dev, "failed to create PHY %d\n" , i); |
329 | return PTR_ERR(ptr: phy); |
330 | } |
331 | |
332 | state->phys[i].phy = phy; |
333 | state->phys[i].index = i; |
334 | state->phys[i].data = &phy_dev->phys[i]; |
335 | phy_set_drvdata(phy, data: &state->phys[i]); |
336 | } |
337 | |
338 | phy_provider = devm_of_phy_provider_register(dev, |
339 | exynos_mipi_video_phy_xlate); |
340 | |
341 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
342 | } |
343 | |
344 | static const struct of_device_id exynos_mipi_video_phy_of_match[] = { |
345 | { |
346 | .compatible = "samsung,s5pv210-mipi-video-phy" , |
347 | .data = &s5pv210_mipi_phy, |
348 | }, { |
349 | .compatible = "samsung,exynos5420-mipi-video-phy" , |
350 | .data = &exynos5420_mipi_phy, |
351 | }, { |
352 | .compatible = "samsung,exynos5433-mipi-video-phy" , |
353 | .data = &exynos5433_mipi_phy, |
354 | }, |
355 | { /* sentinel */ }, |
356 | }; |
357 | MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match); |
358 | |
359 | static struct platform_driver exynos_mipi_video_phy_driver = { |
360 | .probe = exynos_mipi_video_phy_probe, |
361 | .driver = { |
362 | .of_match_table = exynos_mipi_video_phy_of_match, |
363 | .name = "exynos-mipi-video-phy" , |
364 | .suppress_bind_attrs = true, |
365 | } |
366 | }; |
367 | module_platform_driver(exynos_mipi_video_phy_driver); |
368 | |
369 | MODULE_DESCRIPTION("Samsung S5P/Exynos SoC MIPI CSI-2/DSI PHY driver" ); |
370 | MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>" ); |
371 | MODULE_LICENSE("GPL v2" ); |
372 | |