1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 Samsung Electronics Co.Ltd |
4 | * Authors: |
5 | * Hyungwon Hwang <human.hwang@samsung.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/component.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/module.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_graph.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_runtime.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | #include <video/of_videomode.h> |
22 | #include <video/videomode.h> |
23 | |
24 | #include <drm/drm_bridge.h> |
25 | #include <drm/drm_encoder.h> |
26 | #include <drm/drm_print.h> |
27 | |
28 | #include "exynos_drm_drv.h" |
29 | #include "exynos_drm_crtc.h" |
30 | |
31 | /* Sysreg registers for MIC */ |
32 | #define DSD_CFG_MUX 0x1004 |
33 | #define MIC0_RGB_MUX (1 << 0) |
34 | #define MIC0_I80_MUX (1 << 1) |
35 | #define MIC0_ON_MUX (1 << 5) |
36 | |
37 | /* MIC registers */ |
38 | #define MIC_OP 0x0 |
39 | #define MIC_IP_VER 0x0004 |
40 | #define MIC_V_TIMING_0 0x0008 |
41 | #define MIC_V_TIMING_1 0x000C |
42 | #define MIC_IMG_SIZE 0x0010 |
43 | #define MIC_INPUT_TIMING_0 0x0014 |
44 | #define MIC_INPUT_TIMING_1 0x0018 |
45 | #define MIC_2D_OUTPUT_TIMING_0 0x001C |
46 | #define MIC_2D_OUTPUT_TIMING_1 0x0020 |
47 | #define MIC_2D_OUTPUT_TIMING_2 0x0024 |
48 | #define MIC_3D_OUTPUT_TIMING_0 0x0028 |
49 | #define MIC_3D_OUTPUT_TIMING_1 0x002C |
50 | #define MIC_3D_OUTPUT_TIMING_2 0x0030 |
51 | #define MIC_CORE_PARA_0 0x0034 |
52 | #define MIC_CORE_PARA_1 0x0038 |
53 | #define MIC_CTC_CTRL 0x0040 |
54 | #define MIC_RD_DATA 0x0044 |
55 | |
56 | #define MIC_UPD_REG (1 << 31) |
57 | #define MIC_ON_REG (1 << 30) |
58 | #define MIC_TD_ON_REG (1 << 29) |
59 | #define MIC_BS_CHG_OUT (1 << 16) |
60 | #define MIC_VIDEO_TYPE(x) (((x) & 0xf) << 12) |
61 | #define MIC_PSR_EN (1 << 5) |
62 | #define MIC_SW_RST (1 << 4) |
63 | #define MIC_ALL_RST (1 << 3) |
64 | #define MIC_CORE_VER_CONTROL (1 << 2) |
65 | #define MIC_MODE_SEL_COMMAND_MODE (1 << 1) |
66 | #define MIC_MODE_SEL_MASK (1 << 1) |
67 | #define MIC_CORE_EN (1 << 0) |
68 | |
69 | #define MIC_V_PULSE_WIDTH(x) (((x) & 0x3fff) << 16) |
70 | #define MIC_V_PERIOD_LINE(x) ((x) & 0x3fff) |
71 | |
72 | #define MIC_VBP_SIZE(x) (((x) & 0x3fff) << 16) |
73 | #define MIC_VFP_SIZE(x) ((x) & 0x3fff) |
74 | |
75 | #define MIC_IMG_V_SIZE(x) (((x) & 0x3fff) << 16) |
76 | #define MIC_IMG_H_SIZE(x) ((x) & 0x3fff) |
77 | |
78 | #define MIC_H_PULSE_WIDTH_IN(x) (((x) & 0x3fff) << 16) |
79 | #define MIC_H_PERIOD_PIXEL_IN(x) ((x) & 0x3fff) |
80 | |
81 | #define MIC_HBP_SIZE_IN(x) (((x) & 0x3fff) << 16) |
82 | #define MIC_HFP_SIZE_IN(x) ((x) & 0x3fff) |
83 | |
84 | #define MIC_H_PULSE_WIDTH_2D(x) (((x) & 0x3fff) << 16) |
85 | #define MIC_H_PERIOD_PIXEL_2D(x) ((x) & 0x3fff) |
86 | |
87 | #define MIC_HBP_SIZE_2D(x) (((x) & 0x3fff) << 16) |
88 | #define MIC_HFP_SIZE_2D(x) ((x) & 0x3fff) |
89 | |
90 | #define MIC_BS_SIZE_2D(x) ((x) & 0x3fff) |
91 | |
92 | static const char *const clk_names[] = { "pclk_mic0" , "sclk_rgb_vclk_to_mic0" }; |
93 | #define NUM_CLKS ARRAY_SIZE(clk_names) |
94 | static DEFINE_MUTEX(mic_mutex); |
95 | |
96 | struct exynos_mic { |
97 | struct device *dev; |
98 | void __iomem *reg; |
99 | struct regmap *sysreg; |
100 | struct clk *clks[NUM_CLKS]; |
101 | |
102 | bool i80_mode; |
103 | struct videomode vm; |
104 | struct drm_bridge bridge; |
105 | |
106 | bool enabled; |
107 | }; |
108 | |
109 | static void mic_set_path(struct exynos_mic *mic, bool enable) |
110 | { |
111 | int ret; |
112 | unsigned int val; |
113 | |
114 | ret = regmap_read(map: mic->sysreg, DSD_CFG_MUX, val: &val); |
115 | if (ret) { |
116 | DRM_DEV_ERROR(mic->dev, |
117 | "mic: Failed to read system register\n" ); |
118 | return; |
119 | } |
120 | |
121 | if (enable) { |
122 | if (mic->i80_mode) |
123 | val |= MIC0_I80_MUX; |
124 | else |
125 | val |= MIC0_RGB_MUX; |
126 | |
127 | val |= MIC0_ON_MUX; |
128 | } else |
129 | val &= ~(MIC0_RGB_MUX | MIC0_I80_MUX | MIC0_ON_MUX); |
130 | |
131 | ret = regmap_write(map: mic->sysreg, DSD_CFG_MUX, val); |
132 | if (ret) |
133 | DRM_DEV_ERROR(mic->dev, |
134 | "mic: Failed to read system register\n" ); |
135 | } |
136 | |
137 | static int mic_sw_reset(struct exynos_mic *mic) |
138 | { |
139 | unsigned int retry = 100; |
140 | int ret; |
141 | |
142 | writel(MIC_SW_RST, addr: mic->reg + MIC_OP); |
143 | |
144 | while (retry-- > 0) { |
145 | ret = readl(addr: mic->reg + MIC_OP); |
146 | if (!(ret & MIC_SW_RST)) |
147 | return 0; |
148 | |
149 | udelay(10); |
150 | } |
151 | |
152 | return -ETIMEDOUT; |
153 | } |
154 | |
155 | static void mic_set_porch_timing(struct exynos_mic *mic) |
156 | { |
157 | struct videomode vm = mic->vm; |
158 | u32 reg; |
159 | |
160 | reg = MIC_V_PULSE_WIDTH(vm.vsync_len) + |
161 | MIC_V_PERIOD_LINE(vm.vsync_len + vm.vactive + |
162 | vm.vback_porch + vm.vfront_porch); |
163 | writel(val: reg, addr: mic->reg + MIC_V_TIMING_0); |
164 | |
165 | reg = MIC_VBP_SIZE(vm.vback_porch) + |
166 | MIC_VFP_SIZE(vm.vfront_porch); |
167 | writel(val: reg, addr: mic->reg + MIC_V_TIMING_1); |
168 | |
169 | reg = MIC_V_PULSE_WIDTH(vm.hsync_len) + |
170 | MIC_V_PERIOD_LINE(vm.hsync_len + vm.hactive + |
171 | vm.hback_porch + vm.hfront_porch); |
172 | writel(val: reg, addr: mic->reg + MIC_INPUT_TIMING_0); |
173 | |
174 | reg = MIC_VBP_SIZE(vm.hback_porch) + |
175 | MIC_VFP_SIZE(vm.hfront_porch); |
176 | writel(val: reg, addr: mic->reg + MIC_INPUT_TIMING_1); |
177 | } |
178 | |
179 | static void mic_set_img_size(struct exynos_mic *mic) |
180 | { |
181 | struct videomode *vm = &mic->vm; |
182 | u32 reg; |
183 | |
184 | reg = MIC_IMG_H_SIZE(vm->hactive) + |
185 | MIC_IMG_V_SIZE(vm->vactive); |
186 | |
187 | writel(val: reg, addr: mic->reg + MIC_IMG_SIZE); |
188 | } |
189 | |
190 | static void mic_set_output_timing(struct exynos_mic *mic) |
191 | { |
192 | struct videomode vm = mic->vm; |
193 | u32 reg, bs_size_2d; |
194 | |
195 | DRM_DEV_DEBUG(mic->dev, "w: %u, h: %u\n" , vm.hactive, vm.vactive); |
196 | bs_size_2d = ((vm.hactive >> 2) << 1) + (vm.vactive % 4); |
197 | reg = MIC_BS_SIZE_2D(bs_size_2d); |
198 | writel(val: reg, addr: mic->reg + MIC_2D_OUTPUT_TIMING_2); |
199 | |
200 | if (!mic->i80_mode) { |
201 | reg = MIC_H_PULSE_WIDTH_2D(vm.hsync_len) + |
202 | MIC_H_PERIOD_PIXEL_2D(vm.hsync_len + bs_size_2d + |
203 | vm.hback_porch + vm.hfront_porch); |
204 | writel(val: reg, addr: mic->reg + MIC_2D_OUTPUT_TIMING_0); |
205 | |
206 | reg = MIC_HBP_SIZE_2D(vm.hback_porch) + |
207 | MIC_H_PERIOD_PIXEL_2D(vm.hfront_porch); |
208 | writel(val: reg, addr: mic->reg + MIC_2D_OUTPUT_TIMING_1); |
209 | } |
210 | } |
211 | |
212 | static void mic_set_reg_on(struct exynos_mic *mic, bool enable) |
213 | { |
214 | u32 reg = readl(addr: mic->reg + MIC_OP); |
215 | |
216 | if (enable) { |
217 | reg &= ~(MIC_MODE_SEL_MASK | MIC_CORE_VER_CONTROL | MIC_PSR_EN); |
218 | reg |= (MIC_CORE_EN | MIC_BS_CHG_OUT | MIC_ON_REG); |
219 | |
220 | reg &= ~MIC_MODE_SEL_COMMAND_MODE; |
221 | if (mic->i80_mode) |
222 | reg |= MIC_MODE_SEL_COMMAND_MODE; |
223 | } else { |
224 | reg &= ~MIC_CORE_EN; |
225 | } |
226 | |
227 | reg |= MIC_UPD_REG; |
228 | writel(val: reg, addr: mic->reg + MIC_OP); |
229 | } |
230 | |
231 | static void mic_post_disable(struct drm_bridge *bridge) |
232 | { |
233 | struct exynos_mic *mic = bridge->driver_private; |
234 | |
235 | mutex_lock(&mic_mutex); |
236 | if (!mic->enabled) |
237 | goto already_disabled; |
238 | |
239 | mic_set_path(mic, enable: 0); |
240 | |
241 | pm_runtime_put(dev: mic->dev); |
242 | mic->enabled = 0; |
243 | |
244 | already_disabled: |
245 | mutex_unlock(lock: &mic_mutex); |
246 | } |
247 | |
248 | static void mic_mode_set(struct drm_bridge *bridge, |
249 | const struct drm_display_mode *mode, |
250 | const struct drm_display_mode *adjusted_mode) |
251 | { |
252 | struct exynos_mic *mic = bridge->driver_private; |
253 | |
254 | mutex_lock(&mic_mutex); |
255 | drm_display_mode_to_videomode(dmode: mode, vm: &mic->vm); |
256 | mic->i80_mode = to_exynos_crtc(bridge->encoder->crtc)->i80_mode; |
257 | mutex_unlock(lock: &mic_mutex); |
258 | } |
259 | |
260 | static void mic_pre_enable(struct drm_bridge *bridge) |
261 | { |
262 | struct exynos_mic *mic = bridge->driver_private; |
263 | int ret; |
264 | |
265 | mutex_lock(&mic_mutex); |
266 | if (mic->enabled) |
267 | goto unlock; |
268 | |
269 | ret = pm_runtime_resume_and_get(dev: mic->dev); |
270 | if (ret < 0) |
271 | goto unlock; |
272 | |
273 | mic_set_path(mic, enable: 1); |
274 | |
275 | ret = mic_sw_reset(mic); |
276 | if (ret) { |
277 | DRM_DEV_ERROR(mic->dev, "Failed to reset\n" ); |
278 | goto turn_off; |
279 | } |
280 | |
281 | if (!mic->i80_mode) |
282 | mic_set_porch_timing(mic); |
283 | mic_set_img_size(mic); |
284 | mic_set_output_timing(mic); |
285 | mic_set_reg_on(mic, enable: 1); |
286 | mic->enabled = 1; |
287 | mutex_unlock(lock: &mic_mutex); |
288 | |
289 | return; |
290 | |
291 | turn_off: |
292 | pm_runtime_put(dev: mic->dev); |
293 | unlock: |
294 | mutex_unlock(lock: &mic_mutex); |
295 | } |
296 | |
297 | static const struct drm_bridge_funcs mic_bridge_funcs = { |
298 | .post_disable = mic_post_disable, |
299 | .mode_set = mic_mode_set, |
300 | .pre_enable = mic_pre_enable, |
301 | }; |
302 | |
303 | static int exynos_mic_bind(struct device *dev, struct device *master, |
304 | void *data) |
305 | { |
306 | struct exynos_mic *mic = dev_get_drvdata(dev); |
307 | struct drm_device *drm_dev = data; |
308 | struct exynos_drm_crtc *crtc = exynos_drm_crtc_get_by_type(drm_dev, |
309 | out_type: EXYNOS_DISPLAY_TYPE_LCD); |
310 | struct drm_encoder *e, *encoder = NULL; |
311 | |
312 | drm_for_each_encoder(e, drm_dev) |
313 | if (e->possible_crtcs == drm_crtc_mask(crtc: &crtc->base)) |
314 | encoder = e; |
315 | if (!encoder) |
316 | return -ENODEV; |
317 | |
318 | mic->bridge.driver_private = mic; |
319 | |
320 | return drm_bridge_attach(encoder, bridge: &mic->bridge, NULL, flags: 0); |
321 | } |
322 | |
323 | static void exynos_mic_unbind(struct device *dev, struct device *master, |
324 | void *data) |
325 | { |
326 | struct exynos_mic *mic = dev_get_drvdata(dev); |
327 | |
328 | mutex_lock(&mic_mutex); |
329 | if (!mic->enabled) |
330 | goto already_disabled; |
331 | |
332 | pm_runtime_put(dev: mic->dev); |
333 | |
334 | already_disabled: |
335 | mutex_unlock(lock: &mic_mutex); |
336 | } |
337 | |
338 | static const struct component_ops exynos_mic_component_ops = { |
339 | .bind = exynos_mic_bind, |
340 | .unbind = exynos_mic_unbind, |
341 | }; |
342 | |
343 | static int exynos_mic_suspend(struct device *dev) |
344 | { |
345 | struct exynos_mic *mic = dev_get_drvdata(dev); |
346 | int i; |
347 | |
348 | for (i = NUM_CLKS - 1; i > -1; i--) |
349 | clk_disable_unprepare(clk: mic->clks[i]); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | static int exynos_mic_resume(struct device *dev) |
355 | { |
356 | struct exynos_mic *mic = dev_get_drvdata(dev); |
357 | int ret, i; |
358 | |
359 | for (i = 0; i < NUM_CLKS; i++) { |
360 | ret = clk_prepare_enable(clk: mic->clks[i]); |
361 | if (ret < 0) { |
362 | DRM_DEV_ERROR(dev, "Failed to enable clock (%s)\n" , |
363 | clk_names[i]); |
364 | while (--i > -1) |
365 | clk_disable_unprepare(clk: mic->clks[i]); |
366 | return ret; |
367 | } |
368 | } |
369 | return 0; |
370 | } |
371 | |
372 | static DEFINE_RUNTIME_DEV_PM_OPS(exynos_mic_pm_ops, exynos_mic_suspend, |
373 | exynos_mic_resume, NULL); |
374 | |
375 | static int exynos_mic_probe(struct platform_device *pdev) |
376 | { |
377 | struct device *dev = &pdev->dev; |
378 | struct exynos_mic *mic; |
379 | struct resource res; |
380 | int ret, i; |
381 | |
382 | mic = devm_kzalloc(dev, size: sizeof(*mic), GFP_KERNEL); |
383 | if (!mic) { |
384 | DRM_DEV_ERROR(dev, |
385 | "mic: Failed to allocate memory for MIC object\n" ); |
386 | ret = -ENOMEM; |
387 | goto err; |
388 | } |
389 | |
390 | mic->dev = dev; |
391 | |
392 | ret = of_address_to_resource(dev: dev->of_node, index: 0, r: &res); |
393 | if (ret) { |
394 | DRM_DEV_ERROR(dev, "mic: Failed to get mem region for MIC\n" ); |
395 | goto err; |
396 | } |
397 | mic->reg = devm_ioremap(dev, offset: res.start, size: resource_size(res: &res)); |
398 | if (!mic->reg) { |
399 | DRM_DEV_ERROR(dev, "mic: Failed to remap for MIC\n" ); |
400 | ret = -ENOMEM; |
401 | goto err; |
402 | } |
403 | |
404 | mic->sysreg = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
405 | property: "samsung,disp-syscon" ); |
406 | if (IS_ERR(ptr: mic->sysreg)) { |
407 | DRM_DEV_ERROR(dev, "mic: Failed to get system register.\n" ); |
408 | ret = PTR_ERR(ptr: mic->sysreg); |
409 | goto err; |
410 | } |
411 | |
412 | for (i = 0; i < NUM_CLKS; i++) { |
413 | mic->clks[i] = devm_clk_get(dev, id: clk_names[i]); |
414 | if (IS_ERR(ptr: mic->clks[i])) { |
415 | DRM_DEV_ERROR(dev, "mic: Failed to get clock (%s)\n" , |
416 | clk_names[i]); |
417 | ret = PTR_ERR(ptr: mic->clks[i]); |
418 | goto err; |
419 | } |
420 | } |
421 | |
422 | platform_set_drvdata(pdev, data: mic); |
423 | |
424 | mic->bridge.funcs = &mic_bridge_funcs; |
425 | mic->bridge.of_node = dev->of_node; |
426 | |
427 | drm_bridge_add(bridge: &mic->bridge); |
428 | |
429 | pm_runtime_enable(dev); |
430 | |
431 | ret = component_add(dev, &exynos_mic_component_ops); |
432 | if (ret) |
433 | goto err_pm; |
434 | |
435 | DRM_DEV_DEBUG_KMS(dev, "MIC has been probed\n" ); |
436 | |
437 | return 0; |
438 | |
439 | err_pm: |
440 | pm_runtime_disable(dev); |
441 | err: |
442 | return ret; |
443 | } |
444 | |
445 | static void exynos_mic_remove(struct platform_device *pdev) |
446 | { |
447 | struct exynos_mic *mic = platform_get_drvdata(pdev); |
448 | |
449 | component_del(&pdev->dev, &exynos_mic_component_ops); |
450 | pm_runtime_disable(dev: &pdev->dev); |
451 | |
452 | drm_bridge_remove(bridge: &mic->bridge); |
453 | } |
454 | |
455 | static const struct of_device_id exynos_mic_of_match[] = { |
456 | { .compatible = "samsung,exynos5433-mic" }, |
457 | { } |
458 | }; |
459 | MODULE_DEVICE_TABLE(of, exynos_mic_of_match); |
460 | |
461 | struct platform_driver mic_driver = { |
462 | .probe = exynos_mic_probe, |
463 | .remove_new = exynos_mic_remove, |
464 | .driver = { |
465 | .name = "exynos-mic" , |
466 | .pm = pm_ptr(&exynos_mic_pm_ops), |
467 | .owner = THIS_MODULE, |
468 | .of_match_table = exynos_mic_of_match, |
469 | }, |
470 | }; |
471 | |