1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2012 Samsung Electronics Co.Ltd |
4 | * Authors: |
5 | * YoungJun Cho <yj44.cho@samsung.com> |
6 | * Eunchul Kim <chulspro.kim@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/component.h> |
11 | #include <linux/err.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/io.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm_runtime.h> |
18 | #include <linux/sizes.h> |
19 | |
20 | #include <drm/drm_fourcc.h> |
21 | #include <drm/exynos_drm.h> |
22 | |
23 | #include "exynos_drm_drv.h" |
24 | #include "exynos_drm_ipp.h" |
25 | #include "regs-rotator.h" |
26 | |
27 | /* |
28 | * Rotator supports image crop/rotator and input/output DMA operations. |
29 | * input DMA reads image data from the memory. |
30 | * output DMA writes image data to memory. |
31 | */ |
32 | |
33 | #define ROTATOR_AUTOSUSPEND_DELAY 2000 |
34 | |
35 | #define rot_read(offset) readl(rot->regs + (offset)) |
36 | #define rot_write(cfg, offset) writel(cfg, rot->regs + (offset)) |
37 | |
38 | enum rot_irq_status { |
39 | ROT_IRQ_STATUS_COMPLETE = 8, |
40 | ROT_IRQ_STATUS_ILLEGAL = 9, |
41 | }; |
42 | |
43 | struct rot_variant { |
44 | const struct exynos_drm_ipp_formats *formats; |
45 | unsigned int num_formats; |
46 | }; |
47 | |
48 | /* |
49 | * A structure of rotator context. |
50 | * @ippdrv: prepare initialization using ippdrv. |
51 | * @regs: memory mapped io registers. |
52 | * @clock: rotator gate clock. |
53 | * @limit_tbl: limitation of rotator. |
54 | * @irq: irq number. |
55 | */ |
56 | struct rot_context { |
57 | struct exynos_drm_ipp ipp; |
58 | struct drm_device *drm_dev; |
59 | void *dma_priv; |
60 | struct device *dev; |
61 | void __iomem *regs; |
62 | struct clk *clock; |
63 | const struct exynos_drm_ipp_formats *formats; |
64 | unsigned int num_formats; |
65 | struct exynos_drm_ipp_task *task; |
66 | }; |
67 | |
68 | static void rotator_reg_set_irq(struct rot_context *rot, bool enable) |
69 | { |
70 | u32 val = rot_read(ROT_CONFIG); |
71 | |
72 | if (enable == true) |
73 | val |= ROT_CONFIG_IRQ; |
74 | else |
75 | val &= ~ROT_CONFIG_IRQ; |
76 | |
77 | rot_write(val, ROT_CONFIG); |
78 | } |
79 | |
80 | static enum rot_irq_status rotator_reg_get_irq_status(struct rot_context *rot) |
81 | { |
82 | u32 val = rot_read(ROT_STATUS); |
83 | |
84 | val = ROT_STATUS_IRQ(val); |
85 | |
86 | if (val == ROT_STATUS_IRQ_VAL_COMPLETE) |
87 | return ROT_IRQ_STATUS_COMPLETE; |
88 | |
89 | return ROT_IRQ_STATUS_ILLEGAL; |
90 | } |
91 | |
92 | static irqreturn_t rotator_irq_handler(int irq, void *arg) |
93 | { |
94 | struct rot_context *rot = arg; |
95 | enum rot_irq_status irq_status; |
96 | u32 val; |
97 | |
98 | /* Get execution result */ |
99 | irq_status = rotator_reg_get_irq_status(rot); |
100 | |
101 | /* clear status */ |
102 | val = rot_read(ROT_STATUS); |
103 | val |= ROT_STATUS_IRQ_PENDING((u32)irq_status); |
104 | rot_write(val, ROT_STATUS); |
105 | |
106 | if (rot->task) { |
107 | struct exynos_drm_ipp_task *task = rot->task; |
108 | |
109 | rot->task = NULL; |
110 | pm_runtime_mark_last_busy(dev: rot->dev); |
111 | pm_runtime_put_autosuspend(dev: rot->dev); |
112 | exynos_drm_ipp_task_done(task, |
113 | ret: irq_status == ROT_IRQ_STATUS_COMPLETE ? 0 : -EINVAL); |
114 | } |
115 | |
116 | return IRQ_HANDLED; |
117 | } |
118 | |
119 | static void rotator_src_set_fmt(struct rot_context *rot, u32 fmt) |
120 | { |
121 | u32 val; |
122 | |
123 | val = rot_read(ROT_CONTROL); |
124 | val &= ~ROT_CONTROL_FMT_MASK; |
125 | |
126 | switch (fmt) { |
127 | case DRM_FORMAT_NV12: |
128 | val |= ROT_CONTROL_FMT_YCBCR420_2P; |
129 | break; |
130 | case DRM_FORMAT_XRGB8888: |
131 | val |= ROT_CONTROL_FMT_RGB888; |
132 | break; |
133 | } |
134 | |
135 | rot_write(val, ROT_CONTROL); |
136 | } |
137 | |
138 | static void rotator_src_set_buf(struct rot_context *rot, |
139 | struct exynos_drm_ipp_buffer *buf) |
140 | { |
141 | u32 val; |
142 | |
143 | /* Set buffer size configuration */ |
144 | val = ROT_SET_BUF_SIZE_H(buf->buf.height) | |
145 | ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]); |
146 | rot_write(val, ROT_SRC_BUF_SIZE); |
147 | |
148 | /* Set crop image position configuration */ |
149 | val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x); |
150 | rot_write(val, ROT_SRC_CROP_POS); |
151 | val = ROT_SRC_CROP_SIZE_H(buf->rect.h) | |
152 | ROT_SRC_CROP_SIZE_W(buf->rect.w); |
153 | rot_write(val, ROT_SRC_CROP_SIZE); |
154 | |
155 | /* Set buffer DMA address */ |
156 | rot_write(buf->dma_addr[0], ROT_SRC_BUF_ADDR(0)); |
157 | rot_write(buf->dma_addr[1], ROT_SRC_BUF_ADDR(1)); |
158 | } |
159 | |
160 | static void rotator_dst_set_transf(struct rot_context *rot, |
161 | unsigned int rotation) |
162 | { |
163 | u32 val; |
164 | |
165 | /* Set transform configuration */ |
166 | val = rot_read(ROT_CONTROL); |
167 | val &= ~ROT_CONTROL_FLIP_MASK; |
168 | |
169 | if (rotation & DRM_MODE_REFLECT_X) |
170 | val |= ROT_CONTROL_FLIP_VERTICAL; |
171 | if (rotation & DRM_MODE_REFLECT_Y) |
172 | val |= ROT_CONTROL_FLIP_HORIZONTAL; |
173 | |
174 | val &= ~ROT_CONTROL_ROT_MASK; |
175 | |
176 | if (rotation & DRM_MODE_ROTATE_90) |
177 | val |= ROT_CONTROL_ROT_90; |
178 | else if (rotation & DRM_MODE_ROTATE_180) |
179 | val |= ROT_CONTROL_ROT_180; |
180 | else if (rotation & DRM_MODE_ROTATE_270) |
181 | val |= ROT_CONTROL_ROT_270; |
182 | |
183 | rot_write(val, ROT_CONTROL); |
184 | } |
185 | |
186 | static void rotator_dst_set_buf(struct rot_context *rot, |
187 | struct exynos_drm_ipp_buffer *buf) |
188 | { |
189 | u32 val; |
190 | |
191 | /* Set buffer size configuration */ |
192 | val = ROT_SET_BUF_SIZE_H(buf->buf.height) | |
193 | ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]); |
194 | rot_write(val, ROT_DST_BUF_SIZE); |
195 | |
196 | /* Set crop image position configuration */ |
197 | val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x); |
198 | rot_write(val, ROT_DST_CROP_POS); |
199 | |
200 | /* Set buffer DMA address */ |
201 | rot_write(buf->dma_addr[0], ROT_DST_BUF_ADDR(0)); |
202 | rot_write(buf->dma_addr[1], ROT_DST_BUF_ADDR(1)); |
203 | } |
204 | |
205 | static void rotator_start(struct rot_context *rot) |
206 | { |
207 | u32 val; |
208 | |
209 | /* Set interrupt enable */ |
210 | rotator_reg_set_irq(rot, enable: true); |
211 | |
212 | val = rot_read(ROT_CONTROL); |
213 | val |= ROT_CONTROL_START; |
214 | rot_write(val, ROT_CONTROL); |
215 | } |
216 | |
217 | static int rotator_commit(struct exynos_drm_ipp *ipp, |
218 | struct exynos_drm_ipp_task *task) |
219 | { |
220 | struct rot_context *rot = |
221 | container_of(ipp, struct rot_context, ipp); |
222 | int ret; |
223 | |
224 | ret = pm_runtime_resume_and_get(dev: rot->dev); |
225 | if (ret < 0) { |
226 | dev_err(rot->dev, "failed to enable ROTATOR device.\n" ); |
227 | return ret; |
228 | } |
229 | rot->task = task; |
230 | |
231 | rotator_src_set_fmt(rot, fmt: task->src.buf.fourcc); |
232 | rotator_src_set_buf(rot, buf: &task->src); |
233 | rotator_dst_set_transf(rot, rotation: task->transform.rotation); |
234 | rotator_dst_set_buf(rot, buf: &task->dst); |
235 | rotator_start(rot); |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static const struct exynos_drm_ipp_funcs ipp_funcs = { |
241 | .commit = rotator_commit, |
242 | }; |
243 | |
244 | static int rotator_bind(struct device *dev, struct device *master, void *data) |
245 | { |
246 | struct rot_context *rot = dev_get_drvdata(dev); |
247 | struct drm_device *drm_dev = data; |
248 | struct exynos_drm_ipp *ipp = &rot->ipp; |
249 | |
250 | rot->drm_dev = drm_dev; |
251 | ipp->drm_dev = drm_dev; |
252 | exynos_drm_register_dma(drm: drm_dev, dev, dma_priv: &rot->dma_priv); |
253 | |
254 | exynos_drm_ipp_register(dev, ipp, funcs: &ipp_funcs, |
255 | caps: DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE, |
256 | formats: rot->formats, num_formats: rot->num_formats, name: "rotator" ); |
257 | |
258 | dev_info(dev, "The exynos rotator has been probed successfully\n" ); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | static void rotator_unbind(struct device *dev, struct device *master, |
264 | void *data) |
265 | { |
266 | struct rot_context *rot = dev_get_drvdata(dev); |
267 | struct exynos_drm_ipp *ipp = &rot->ipp; |
268 | |
269 | exynos_drm_ipp_unregister(dev, ipp); |
270 | exynos_drm_unregister_dma(drm: rot->drm_dev, dev: rot->dev, dma_priv: &rot->dma_priv); |
271 | } |
272 | |
273 | static const struct component_ops rotator_component_ops = { |
274 | .bind = rotator_bind, |
275 | .unbind = rotator_unbind, |
276 | }; |
277 | |
278 | static int rotator_probe(struct platform_device *pdev) |
279 | { |
280 | struct device *dev = &pdev->dev; |
281 | struct rot_context *rot; |
282 | const struct rot_variant *variant; |
283 | int irq; |
284 | int ret; |
285 | |
286 | rot = devm_kzalloc(dev, size: sizeof(*rot), GFP_KERNEL); |
287 | if (!rot) |
288 | return -ENOMEM; |
289 | |
290 | variant = of_device_get_match_data(dev); |
291 | rot->formats = variant->formats; |
292 | rot->num_formats = variant->num_formats; |
293 | rot->dev = dev; |
294 | rot->regs = devm_platform_ioremap_resource(pdev, index: 0); |
295 | if (IS_ERR(ptr: rot->regs)) |
296 | return PTR_ERR(ptr: rot->regs); |
297 | |
298 | irq = platform_get_irq(pdev, 0); |
299 | if (irq < 0) |
300 | return irq; |
301 | |
302 | ret = devm_request_irq(dev, irq, handler: rotator_irq_handler, irqflags: 0, devname: dev_name(dev), |
303 | dev_id: rot); |
304 | if (ret < 0) { |
305 | dev_err(dev, "failed to request irq\n" ); |
306 | return ret; |
307 | } |
308 | |
309 | rot->clock = devm_clk_get(dev, id: "rotator" ); |
310 | if (IS_ERR(ptr: rot->clock)) { |
311 | dev_err(dev, "failed to get clock\n" ); |
312 | return PTR_ERR(ptr: rot->clock); |
313 | } |
314 | |
315 | pm_runtime_use_autosuspend(dev); |
316 | pm_runtime_set_autosuspend_delay(dev, ROTATOR_AUTOSUSPEND_DELAY); |
317 | pm_runtime_enable(dev); |
318 | platform_set_drvdata(pdev, data: rot); |
319 | |
320 | ret = component_add(dev, &rotator_component_ops); |
321 | if (ret) |
322 | goto err_component; |
323 | |
324 | return 0; |
325 | |
326 | err_component: |
327 | pm_runtime_dont_use_autosuspend(dev); |
328 | pm_runtime_disable(dev); |
329 | return ret; |
330 | } |
331 | |
332 | static void rotator_remove(struct platform_device *pdev) |
333 | { |
334 | struct device *dev = &pdev->dev; |
335 | |
336 | component_del(dev, &rotator_component_ops); |
337 | pm_runtime_dont_use_autosuspend(dev); |
338 | pm_runtime_disable(dev); |
339 | } |
340 | |
341 | static int rotator_runtime_suspend(struct device *dev) |
342 | { |
343 | struct rot_context *rot = dev_get_drvdata(dev); |
344 | |
345 | clk_disable_unprepare(clk: rot->clock); |
346 | return 0; |
347 | } |
348 | |
349 | static int rotator_runtime_resume(struct device *dev) |
350 | { |
351 | struct rot_context *rot = dev_get_drvdata(dev); |
352 | |
353 | return clk_prepare_enable(clk: rot->clock); |
354 | } |
355 | |
356 | static const struct drm_exynos_ipp_limit rotator_s5pv210_rbg888_limits[] = { |
357 | { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) }, |
358 | { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) }, |
359 | }; |
360 | |
361 | static const struct drm_exynos_ipp_limit rotator_4210_rbg888_limits[] = { |
362 | { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) }, |
363 | { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) }, |
364 | }; |
365 | |
366 | static const struct drm_exynos_ipp_limit rotator_4412_rbg888_limits[] = { |
367 | { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) }, |
368 | { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) }, |
369 | }; |
370 | |
371 | static const struct drm_exynos_ipp_limit rotator_5250_rbg888_limits[] = { |
372 | { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) }, |
373 | { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) }, |
374 | }; |
375 | |
376 | static const struct drm_exynos_ipp_limit rotator_s5pv210_yuv_limits[] = { |
377 | { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) }, |
378 | { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) }, |
379 | }; |
380 | |
381 | static const struct drm_exynos_ipp_limit rotator_4210_yuv_limits[] = { |
382 | { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) }, |
383 | { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) }, |
384 | }; |
385 | |
386 | static const struct drm_exynos_ipp_limit rotator_4412_yuv_limits[] = { |
387 | { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_32K }, .v = { 32, SZ_32K }) }, |
388 | { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) }, |
389 | }; |
390 | |
391 | static const struct exynos_drm_ipp_formats rotator_s5pv210_formats[] = { |
392 | { IPP_SRCDST_FORMAT(XRGB8888, rotator_s5pv210_rbg888_limits) }, |
393 | { IPP_SRCDST_FORMAT(NV12, rotator_s5pv210_yuv_limits) }, |
394 | }; |
395 | |
396 | static const struct exynos_drm_ipp_formats rotator_4210_formats[] = { |
397 | { IPP_SRCDST_FORMAT(XRGB8888, rotator_4210_rbg888_limits) }, |
398 | { IPP_SRCDST_FORMAT(NV12, rotator_4210_yuv_limits) }, |
399 | }; |
400 | |
401 | static const struct exynos_drm_ipp_formats rotator_4412_formats[] = { |
402 | { IPP_SRCDST_FORMAT(XRGB8888, rotator_4412_rbg888_limits) }, |
403 | { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) }, |
404 | }; |
405 | |
406 | static const struct exynos_drm_ipp_formats rotator_5250_formats[] = { |
407 | { IPP_SRCDST_FORMAT(XRGB8888, rotator_5250_rbg888_limits) }, |
408 | { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) }, |
409 | }; |
410 | |
411 | static const struct rot_variant rotator_s5pv210_data = { |
412 | .formats = rotator_s5pv210_formats, |
413 | .num_formats = ARRAY_SIZE(rotator_s5pv210_formats), |
414 | }; |
415 | |
416 | static const struct rot_variant rotator_4210_data = { |
417 | .formats = rotator_4210_formats, |
418 | .num_formats = ARRAY_SIZE(rotator_4210_formats), |
419 | }; |
420 | |
421 | static const struct rot_variant rotator_4412_data = { |
422 | .formats = rotator_4412_formats, |
423 | .num_formats = ARRAY_SIZE(rotator_4412_formats), |
424 | }; |
425 | |
426 | static const struct rot_variant rotator_5250_data = { |
427 | .formats = rotator_5250_formats, |
428 | .num_formats = ARRAY_SIZE(rotator_5250_formats), |
429 | }; |
430 | |
431 | static const struct of_device_id exynos_rotator_match[] = { |
432 | { |
433 | .compatible = "samsung,s5pv210-rotator" , |
434 | .data = &rotator_s5pv210_data, |
435 | }, { |
436 | .compatible = "samsung,exynos4210-rotator" , |
437 | .data = &rotator_4210_data, |
438 | }, { |
439 | .compatible = "samsung,exynos4212-rotator" , |
440 | .data = &rotator_4412_data, |
441 | }, { |
442 | .compatible = "samsung,exynos5250-rotator" , |
443 | .data = &rotator_5250_data, |
444 | }, { |
445 | }, |
446 | }; |
447 | MODULE_DEVICE_TABLE(of, exynos_rotator_match); |
448 | |
449 | static DEFINE_RUNTIME_DEV_PM_OPS(rotator_pm_ops, rotator_runtime_suspend, |
450 | rotator_runtime_resume, NULL); |
451 | |
452 | struct platform_driver rotator_driver = { |
453 | .probe = rotator_probe, |
454 | .remove_new = rotator_remove, |
455 | .driver = { |
456 | .name = "exynos-rotator" , |
457 | .owner = THIS_MODULE, |
458 | .pm = pm_ptr(&rotator_pm_ops), |
459 | .of_match_table = exynos_rotator_match, |
460 | }, |
461 | }; |
462 | |