1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Ingenic JZ47xx IPU driver |
4 | // |
5 | // Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> |
6 | // Copyright (C) 2020, Daniel Silsby <dansilsby@gmail.com> |
7 | |
8 | #include "ingenic-drm.h" |
9 | #include "ingenic-ipu.h" |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/component.h> |
13 | #include <linux/gcd.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/regmap.h> |
19 | #include <linux/time.h> |
20 | |
21 | #include <drm/drm_atomic.h> |
22 | #include <drm/drm_atomic_helper.h> |
23 | #include <drm/drm_damage_helper.h> |
24 | #include <drm/drm_drv.h> |
25 | #include <drm/drm_fb_dma_helper.h> |
26 | #include <drm/drm_fourcc.h> |
27 | #include <drm/drm_framebuffer.h> |
28 | #include <drm/drm_gem_atomic_helper.h> |
29 | #include <drm/drm_gem_dma_helper.h> |
30 | #include <drm/drm_gem_framebuffer_helper.h> |
31 | #include <drm/drm_plane.h> |
32 | #include <drm/drm_property.h> |
33 | #include <drm/drm_vblank.h> |
34 | |
35 | struct ingenic_ipu; |
36 | |
37 | struct soc_info { |
38 | const u32 *formats; |
39 | size_t num_formats; |
40 | bool has_bicubic; |
41 | bool manual_restart; |
42 | |
43 | void (*set_coefs)(struct ingenic_ipu *ipu, unsigned int reg, |
44 | unsigned int sharpness, bool downscale, |
45 | unsigned int weight, unsigned int offset); |
46 | }; |
47 | |
48 | struct ingenic_ipu_private_state { |
49 | struct drm_private_state base; |
50 | |
51 | unsigned int num_w, num_h, denom_w, denom_h; |
52 | }; |
53 | |
54 | struct ingenic_ipu { |
55 | struct drm_plane plane; |
56 | struct drm_device *drm; |
57 | struct device *dev, *master; |
58 | struct regmap *map; |
59 | struct clk *clk; |
60 | const struct soc_info *soc_info; |
61 | bool clk_enabled; |
62 | |
63 | dma_addr_t addr_y, addr_u, addr_v; |
64 | |
65 | struct drm_property *sharpness_prop; |
66 | unsigned int sharpness; |
67 | |
68 | struct drm_private_obj private_obj; |
69 | }; |
70 | |
71 | /* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */ |
72 | #define I2F(i) ((s32)(i) * 65536) |
73 | #define F2I(f) ((f) / 65536) |
74 | #define FMUL(fa, fb) ((s32)(((s64)(fa) * (s64)(fb)) / 65536)) |
75 | #define SHARPNESS_INCR (I2F(-1) / 8) |
76 | |
77 | static inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane) |
78 | { |
79 | return container_of(plane, struct ingenic_ipu, plane); |
80 | } |
81 | |
82 | static inline struct ingenic_ipu_private_state * |
83 | to_ingenic_ipu_priv_state(struct drm_private_state *state) |
84 | { |
85 | return container_of(state, struct ingenic_ipu_private_state, base); |
86 | } |
87 | |
88 | static struct ingenic_ipu_private_state * |
89 | ingenic_ipu_get_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state) |
90 | { |
91 | struct drm_private_state *priv_state; |
92 | |
93 | priv_state = drm_atomic_get_private_obj_state(state, obj: &priv->private_obj); |
94 | if (IS_ERR(ptr: priv_state)) |
95 | return ERR_CAST(ptr: priv_state); |
96 | |
97 | return to_ingenic_ipu_priv_state(state: priv_state); |
98 | } |
99 | |
100 | static struct ingenic_ipu_private_state * |
101 | ingenic_ipu_get_new_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state) |
102 | { |
103 | struct drm_private_state *priv_state; |
104 | |
105 | priv_state = drm_atomic_get_new_private_obj_state(state, obj: &priv->private_obj); |
106 | if (!priv_state) |
107 | return NULL; |
108 | |
109 | return to_ingenic_ipu_priv_state(state: priv_state); |
110 | } |
111 | |
112 | /* |
113 | * Apply conventional cubic convolution kernel. Both parameters |
114 | * and return value are 15.16 signed fixed-point. |
115 | * |
116 | * @f_a: Sharpness factor, typically in range [-4.0, -0.25]. |
117 | * A larger magnitude increases perceived sharpness, but going past |
118 | * -2.0 might cause ringing artifacts to outweigh any improvement. |
119 | * Nice values on a 320x240 LCD are between -0.75 and -2.0. |
120 | * |
121 | * @f_x: Absolute distance in pixels from 'pixel 0' sample position |
122 | * along horizontal (or vertical) source axis. Range is [0, +2.0]. |
123 | * |
124 | * returns: Weight of this pixel within 4-pixel sample group. Range is |
125 | * [-2.0, +2.0]. For moderate (i.e. > -3.0) sharpness factors, |
126 | * range is within [-1.0, +1.0]. |
127 | */ |
128 | static inline s32 cubic_conv(s32 f_a, s32 f_x) |
129 | { |
130 | const s32 f_1 = I2F(1); |
131 | const s32 f_2 = I2F(2); |
132 | const s32 f_3 = I2F(3); |
133 | const s32 f_4 = I2F(4); |
134 | const s32 f_x2 = FMUL(f_x, f_x); |
135 | const s32 f_x3 = FMUL(f_x, f_x2); |
136 | |
137 | if (f_x <= f_1) |
138 | return FMUL((f_a + f_2), f_x3) - FMUL((f_a + f_3), f_x2) + f_1; |
139 | else if (f_x <= f_2) |
140 | return FMUL(f_a, (f_x3 - 5 * f_x2 + 8 * f_x - f_4)); |
141 | else |
142 | return 0; |
143 | } |
144 | |
145 | /* |
146 | * On entry, "weight" is a coefficient suitable for bilinear mode, |
147 | * which is converted to a set of four suitable for bicubic mode. |
148 | * |
149 | * "weight 512" means all of pixel 0; |
150 | * "weight 256" means half of pixel 0 and half of pixel 1; |
151 | * "weight 0" means all of pixel 1; |
152 | * |
153 | * "offset" is increment to next source pixel sample location. |
154 | */ |
155 | static void jz4760_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, |
156 | unsigned int sharpness, bool downscale, |
157 | unsigned int weight, unsigned int offset) |
158 | { |
159 | u32 val; |
160 | s32 w0, w1, w2, w3; /* Pixel weights at X (or Y) offsets -1,0,1,2 */ |
161 | |
162 | weight = clamp_val(weight, 0, 512); |
163 | |
164 | if (sharpness < 2) { |
165 | /* |
166 | * When sharpness setting is 0, emulate nearest-neighbor. |
167 | * When sharpness setting is 1, emulate bilinear. |
168 | */ |
169 | |
170 | if (sharpness == 0) |
171 | weight = weight >= 256 ? 512 : 0; |
172 | w0 = 0; |
173 | w1 = weight; |
174 | w2 = 512 - weight; |
175 | w3 = 0; |
176 | } else { |
177 | const s32 f_a = SHARPNESS_INCR * sharpness; |
178 | const s32 f_h = I2F(1) / 2; /* Round up 0.5 */ |
179 | |
180 | /* |
181 | * Note that always rounding towards +infinity here is intended. |
182 | * The resulting coefficients match a round-to-nearest-int |
183 | * double floating-point implementation. |
184 | */ |
185 | |
186 | weight = 512 - weight; |
187 | w0 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 + weight) / 512)); |
188 | w1 = F2I(f_h + 512 * cubic_conv(f_a, I2F(0 + weight) / 512)); |
189 | w2 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 - weight) / 512)); |
190 | w3 = F2I(f_h + 512 * cubic_conv(f_a, I2F(1024 - weight) / 512)); |
191 | w0 = clamp_val(w0, -1024, 1023); |
192 | w1 = clamp_val(w1, -1024, 1023); |
193 | w2 = clamp_val(w2, -1024, 1023); |
194 | w3 = clamp_val(w3, -1024, 1023); |
195 | } |
196 | |
197 | val = ((w1 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) | |
198 | ((w0 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB); |
199 | regmap_write(map: ipu->map, reg, val); |
200 | |
201 | val = ((w3 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) | |
202 | ((w2 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB) | |
203 | ((offset & JZ4760_IPU_RSZ_OFFSET_MASK) << JZ4760_IPU_RSZ_OFFSET_LSB); |
204 | regmap_write(map: ipu->map, reg, val); |
205 | } |
206 | |
207 | static void jz4725b_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, |
208 | unsigned int sharpness, bool downscale, |
209 | unsigned int weight, unsigned int offset) |
210 | { |
211 | u32 val = JZ4725B_IPU_RSZ_LUT_OUT_EN; |
212 | unsigned int i; |
213 | |
214 | weight = clamp_val(weight, 0, 512); |
215 | |
216 | if (sharpness == 0) |
217 | weight = weight >= 256 ? 512 : 0; |
218 | |
219 | val |= (weight & JZ4725B_IPU_RSZ_LUT_COEF_MASK) << JZ4725B_IPU_RSZ_LUT_COEF_LSB; |
220 | if (downscale || !!offset) |
221 | val |= JZ4725B_IPU_RSZ_LUT_IN_EN; |
222 | |
223 | regmap_write(map: ipu->map, reg, val); |
224 | |
225 | if (downscale) { |
226 | for (i = 1; i < offset; i++) |
227 | regmap_write(map: ipu->map, reg, JZ4725B_IPU_RSZ_LUT_IN_EN); |
228 | } |
229 | } |
230 | |
231 | static void ingenic_ipu_set_downscale_coefs(struct ingenic_ipu *ipu, |
232 | unsigned int reg, |
233 | unsigned int num, |
234 | unsigned int denom) |
235 | { |
236 | unsigned int i, offset, weight, weight_num = denom; |
237 | |
238 | for (i = 0; i < num; i++) { |
239 | weight_num = num + (weight_num - num) % (num * 2); |
240 | weight = 512 - 512 * (weight_num - num) / (num * 2); |
241 | weight_num += denom * 2; |
242 | offset = (weight_num - num) / (num * 2); |
243 | |
244 | ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness, |
245 | true, weight, offset); |
246 | } |
247 | } |
248 | |
249 | static void ingenic_ipu_set_integer_upscale_coefs(struct ingenic_ipu *ipu, |
250 | unsigned int reg, |
251 | unsigned int num) |
252 | { |
253 | /* |
254 | * Force nearest-neighbor scaling and use simple math when upscaling |
255 | * by an integer ratio. It looks better, and fixes a few problem cases. |
256 | */ |
257 | unsigned int i; |
258 | |
259 | for (i = 0; i < num; i++) |
260 | ipu->soc_info->set_coefs(ipu, reg, 0, false, 512, i == num - 1); |
261 | } |
262 | |
263 | static void ingenic_ipu_set_upscale_coefs(struct ingenic_ipu *ipu, |
264 | unsigned int reg, |
265 | unsigned int num, |
266 | unsigned int denom) |
267 | { |
268 | unsigned int i, offset, weight, weight_num = 0; |
269 | |
270 | for (i = 0; i < num; i++) { |
271 | weight = 512 - 512 * weight_num / num; |
272 | weight_num += denom; |
273 | offset = weight_num >= num; |
274 | |
275 | if (offset) |
276 | weight_num -= num; |
277 | |
278 | ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness, |
279 | false, weight, offset); |
280 | } |
281 | } |
282 | |
283 | static void ingenic_ipu_set_coefs(struct ingenic_ipu *ipu, unsigned int reg, |
284 | unsigned int num, unsigned int denom) |
285 | { |
286 | /* Begin programming the LUT */ |
287 | regmap_write(map: ipu->map, reg, val: -1); |
288 | |
289 | if (denom > num) |
290 | ingenic_ipu_set_downscale_coefs(ipu, reg, num, denom); |
291 | else if (denom == 1) |
292 | ingenic_ipu_set_integer_upscale_coefs(ipu, reg, num); |
293 | else |
294 | ingenic_ipu_set_upscale_coefs(ipu, reg, num, denom); |
295 | } |
296 | |
297 | static int reduce_fraction(unsigned int *num, unsigned int *denom) |
298 | { |
299 | unsigned long d = gcd(a: *num, b: *denom); |
300 | |
301 | /* The scaling table has only 31 entries */ |
302 | if (*num > 31 * d) |
303 | return -EINVAL; |
304 | |
305 | *num /= d; |
306 | *denom /= d; |
307 | return 0; |
308 | } |
309 | |
310 | static inline bool osd_changed(struct drm_plane_state *state, |
311 | struct drm_plane_state *oldstate) |
312 | { |
313 | return state->src_x != oldstate->src_x || |
314 | state->src_y != oldstate->src_y || |
315 | state->src_w != oldstate->src_w || |
316 | state->src_h != oldstate->src_h || |
317 | state->crtc_x != oldstate->crtc_x || |
318 | state->crtc_y != oldstate->crtc_y || |
319 | state->crtc_w != oldstate->crtc_w || |
320 | state->crtc_h != oldstate->crtc_h; |
321 | } |
322 | |
323 | static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane, |
324 | struct drm_atomic_state *state) |
325 | { |
326 | struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); |
327 | struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state, plane); |
328 | struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, plane); |
329 | const struct drm_format_info *finfo; |
330 | u32 ctrl, stride = 0, coef_index = 0, format = 0; |
331 | bool needs_modeset, upscaling_w, upscaling_h; |
332 | struct ingenic_ipu_private_state *ipu_state; |
333 | int err; |
334 | |
335 | if (!newstate || !newstate->fb) |
336 | return; |
337 | |
338 | ipu_state = ingenic_ipu_get_new_priv_state(priv: ipu, state); |
339 | if (WARN_ON(!ipu_state)) |
340 | return; |
341 | |
342 | finfo = drm_format_info(format: newstate->fb->format->format); |
343 | |
344 | if (!ipu->clk_enabled) { |
345 | err = clk_enable(clk: ipu->clk); |
346 | if (err) { |
347 | dev_err(ipu->dev, "Unable to enable clock: %d\n" , err); |
348 | return; |
349 | } |
350 | |
351 | ipu->clk_enabled = true; |
352 | } |
353 | |
354 | /* Reset all the registers if needed */ |
355 | needs_modeset = drm_atomic_crtc_needs_modeset(state: newstate->crtc->state); |
356 | if (needs_modeset) { |
357 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RST); |
358 | |
359 | /* Enable the chip */ |
360 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, |
361 | JZ_IPU_CTRL_CHIP_EN | JZ_IPU_CTRL_LCDC_SEL); |
362 | } |
363 | |
364 | if (ingenic_drm_map_noncoherent(dev: ipu->master)) |
365 | drm_fb_dma_sync_non_coherent(drm: ipu->drm, old_state: oldstate, state: newstate); |
366 | |
367 | /* New addresses will be committed in vblank handler... */ |
368 | ipu->addr_y = drm_fb_dma_get_gem_addr(fb: newstate->fb, state: newstate, plane: 0); |
369 | if (finfo->num_planes > 1) |
370 | ipu->addr_u = drm_fb_dma_get_gem_addr(fb: newstate->fb, state: newstate, |
371 | plane: 1); |
372 | if (finfo->num_planes > 2) |
373 | ipu->addr_v = drm_fb_dma_get_gem_addr(fb: newstate->fb, state: newstate, |
374 | plane: 2); |
375 | |
376 | if (!needs_modeset) |
377 | return; |
378 | |
379 | /* Or right here if we're doing a full modeset. */ |
380 | regmap_write(map: ipu->map, JZ_REG_IPU_Y_ADDR, val: ipu->addr_y); |
381 | regmap_write(map: ipu->map, JZ_REG_IPU_U_ADDR, val: ipu->addr_u); |
382 | regmap_write(map: ipu->map, JZ_REG_IPU_V_ADDR, val: ipu->addr_v); |
383 | |
384 | if (finfo->num_planes == 1) |
385 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_SPKG_SEL); |
386 | |
387 | ingenic_drm_plane_config(dev: ipu->master, plane, DRM_FORMAT_XRGB8888); |
388 | |
389 | /* Set the input height/width/strides */ |
390 | if (finfo->num_planes > 2) |
391 | stride = ((newstate->src_w >> 16) * finfo->cpp[2] / finfo->hsub) |
392 | << JZ_IPU_UV_STRIDE_V_LSB; |
393 | |
394 | if (finfo->num_planes > 1) |
395 | stride |= ((newstate->src_w >> 16) * finfo->cpp[1] / finfo->hsub) |
396 | << JZ_IPU_UV_STRIDE_U_LSB; |
397 | |
398 | regmap_write(map: ipu->map, JZ_REG_IPU_UV_STRIDE, val: stride); |
399 | |
400 | stride = ((newstate->src_w >> 16) * finfo->cpp[0]) << JZ_IPU_Y_STRIDE_Y_LSB; |
401 | regmap_write(map: ipu->map, JZ_REG_IPU_Y_STRIDE, val: stride); |
402 | |
403 | regmap_write(map: ipu->map, JZ_REG_IPU_IN_GS, |
404 | val: (stride << JZ_IPU_IN_GS_W_LSB) | |
405 | ((newstate->src_h >> 16) << JZ_IPU_IN_GS_H_LSB)); |
406 | |
407 | switch (finfo->format) { |
408 | case DRM_FORMAT_XRGB1555: |
409 | format = JZ_IPU_D_FMT_IN_FMT_RGB555 | |
410 | JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; |
411 | break; |
412 | case DRM_FORMAT_XBGR1555: |
413 | format = JZ_IPU_D_FMT_IN_FMT_RGB555 | |
414 | JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; |
415 | break; |
416 | case DRM_FORMAT_RGB565: |
417 | format = JZ_IPU_D_FMT_IN_FMT_RGB565 | |
418 | JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; |
419 | break; |
420 | case DRM_FORMAT_BGR565: |
421 | format = JZ_IPU_D_FMT_IN_FMT_RGB565 | |
422 | JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; |
423 | break; |
424 | case DRM_FORMAT_XRGB8888: |
425 | case DRM_FORMAT_XYUV8888: |
426 | format = JZ_IPU_D_FMT_IN_FMT_RGB888 | |
427 | JZ_IPU_D_FMT_RGB_OUT_OFT_RGB; |
428 | break; |
429 | case DRM_FORMAT_XBGR8888: |
430 | format = JZ_IPU_D_FMT_IN_FMT_RGB888 | |
431 | JZ_IPU_D_FMT_RGB_OUT_OFT_BGR; |
432 | break; |
433 | case DRM_FORMAT_YUYV: |
434 | format = JZ_IPU_D_FMT_IN_FMT_YUV422 | |
435 | JZ_IPU_D_FMT_YUV_VY1UY0; |
436 | break; |
437 | case DRM_FORMAT_YVYU: |
438 | format = JZ_IPU_D_FMT_IN_FMT_YUV422 | |
439 | JZ_IPU_D_FMT_YUV_UY1VY0; |
440 | break; |
441 | case DRM_FORMAT_UYVY: |
442 | format = JZ_IPU_D_FMT_IN_FMT_YUV422 | |
443 | JZ_IPU_D_FMT_YUV_Y1VY0U; |
444 | break; |
445 | case DRM_FORMAT_VYUY: |
446 | format = JZ_IPU_D_FMT_IN_FMT_YUV422 | |
447 | JZ_IPU_D_FMT_YUV_Y1UY0V; |
448 | break; |
449 | case DRM_FORMAT_YUV411: |
450 | format = JZ_IPU_D_FMT_IN_FMT_YUV411; |
451 | break; |
452 | case DRM_FORMAT_YUV420: |
453 | format = JZ_IPU_D_FMT_IN_FMT_YUV420; |
454 | break; |
455 | case DRM_FORMAT_YUV422: |
456 | format = JZ_IPU_D_FMT_IN_FMT_YUV422; |
457 | break; |
458 | case DRM_FORMAT_YUV444: |
459 | format = JZ_IPU_D_FMT_IN_FMT_YUV444; |
460 | break; |
461 | default: |
462 | WARN_ONCE(1, "Unsupported format" ); |
463 | break; |
464 | } |
465 | |
466 | /* Fix output to RGB888 */ |
467 | format |= JZ_IPU_D_FMT_OUT_FMT_RGB888; |
468 | |
469 | /* Set pixel format */ |
470 | regmap_write(map: ipu->map, JZ_REG_IPU_D_FMT, val: format); |
471 | |
472 | /* Set the output height/width/stride */ |
473 | regmap_write(map: ipu->map, JZ_REG_IPU_OUT_GS, |
474 | val: ((newstate->crtc_w * 4) << JZ_IPU_OUT_GS_W_LSB) |
475 | | newstate->crtc_h << JZ_IPU_OUT_GS_H_LSB); |
476 | regmap_write(map: ipu->map, JZ_REG_IPU_OUT_STRIDE, val: newstate->crtc_w * 4); |
477 | |
478 | if (finfo->is_yuv) { |
479 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CSC_EN); |
480 | |
481 | /* |
482 | * Offsets for Chroma/Luma. |
483 | * y = source Y - LUMA, |
484 | * u = source Cb - CHROMA, |
485 | * v = source Cr - CHROMA |
486 | */ |
487 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_OFFSET, |
488 | val: 128 << JZ_IPU_CSC_OFFSET_CHROMA_LSB | |
489 | 0 << JZ_IPU_CSC_OFFSET_LUMA_LSB); |
490 | |
491 | /* |
492 | * YUV422 to RGB conversion table. |
493 | * R = C0 / 0x400 * y + C1 / 0x400 * v |
494 | * G = C0 / 0x400 * y - C2 / 0x400 * u - C3 / 0x400 * v |
495 | * B = C0 / 0x400 * y + C4 / 0x400 * u |
496 | */ |
497 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_C0_COEF, val: 0x4a8); |
498 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_C1_COEF, val: 0x662); |
499 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_C2_COEF, val: 0x191); |
500 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_C3_COEF, val: 0x341); |
501 | regmap_write(map: ipu->map, JZ_REG_IPU_CSC_C4_COEF, val: 0x811); |
502 | } |
503 | |
504 | ctrl = 0; |
505 | |
506 | /* |
507 | * Must set ZOOM_SEL before programming bicubic LUTs. |
508 | * If the IPU supports bicubic, we enable it unconditionally, since it |
509 | * can do anything bilinear can and more. |
510 | */ |
511 | if (ipu->soc_info->has_bicubic) |
512 | ctrl |= JZ_IPU_CTRL_ZOOM_SEL; |
513 | |
514 | upscaling_w = ipu_state->num_w > ipu_state->denom_w; |
515 | if (upscaling_w) |
516 | ctrl |= JZ_IPU_CTRL_HSCALE; |
517 | |
518 | if (ipu_state->num_w != 1 || ipu_state->denom_w != 1) { |
519 | if (!ipu->soc_info->has_bicubic && !upscaling_w) |
520 | coef_index |= (ipu_state->denom_w - 1) << 16; |
521 | else |
522 | coef_index |= (ipu_state->num_w - 1) << 16; |
523 | ctrl |= JZ_IPU_CTRL_HRSZ_EN; |
524 | } |
525 | |
526 | upscaling_h = ipu_state->num_h > ipu_state->denom_h; |
527 | if (upscaling_h) |
528 | ctrl |= JZ_IPU_CTRL_VSCALE; |
529 | |
530 | if (ipu_state->num_h != 1 || ipu_state->denom_h != 1) { |
531 | if (!ipu->soc_info->has_bicubic && !upscaling_h) |
532 | coef_index |= ipu_state->denom_h - 1; |
533 | else |
534 | coef_index |= ipu_state->num_h - 1; |
535 | ctrl |= JZ_IPU_CTRL_VRSZ_EN; |
536 | } |
537 | |
538 | regmap_update_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_ZOOM_SEL | |
539 | JZ_IPU_CTRL_HRSZ_EN | JZ_IPU_CTRL_VRSZ_EN | |
540 | JZ_IPU_CTRL_HSCALE | JZ_IPU_CTRL_VSCALE, val: ctrl); |
541 | |
542 | /* Set the LUT index register */ |
543 | regmap_write(map: ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, val: coef_index); |
544 | |
545 | if (ipu_state->num_w != 1 || ipu_state->denom_w != 1) |
546 | ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT, |
547 | num: ipu_state->num_w, denom: ipu_state->denom_w); |
548 | |
549 | if (ipu_state->num_h != 1 || ipu_state->denom_h != 1) |
550 | ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT, |
551 | num: ipu_state->num_h, denom: ipu_state->denom_h); |
552 | |
553 | /* Clear STATUS register */ |
554 | regmap_write(map: ipu->map, JZ_REG_IPU_STATUS, val: 0); |
555 | |
556 | /* Start IPU */ |
557 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, |
558 | JZ_IPU_CTRL_RUN | JZ_IPU_CTRL_FM_IRQ_EN); |
559 | |
560 | dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n" , |
561 | newstate->src_w >> 16, newstate->src_h >> 16, |
562 | newstate->crtc_w, newstate->crtc_h, |
563 | ipu_state->num_w, ipu_state->denom_w, |
564 | ipu_state->num_h, ipu_state->denom_h); |
565 | } |
566 | |
567 | static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane, |
568 | struct drm_atomic_state *state) |
569 | { |
570 | struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, |
571 | plane); |
572 | struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, |
573 | plane); |
574 | unsigned int num_w, denom_w, num_h, denom_h, xres, yres, max_w, max_h; |
575 | struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); |
576 | struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc; |
577 | struct drm_crtc_state *crtc_state; |
578 | struct ingenic_ipu_private_state *ipu_state; |
579 | |
580 | if (!crtc) |
581 | return 0; |
582 | |
583 | crtc_state = drm_atomic_get_existing_crtc_state(state, crtc); |
584 | if (WARN_ON(!crtc_state)) |
585 | return -EINVAL; |
586 | |
587 | ipu_state = ingenic_ipu_get_priv_state(priv: ipu, state); |
588 | if (IS_ERR(ptr: ipu_state)) |
589 | return PTR_ERR(ptr: ipu_state); |
590 | |
591 | /* Request a full modeset if we are enabling or disabling the IPU. */ |
592 | if (!old_plane_state->crtc ^ !new_plane_state->crtc) |
593 | crtc_state->mode_changed = true; |
594 | |
595 | if (!new_plane_state->crtc || |
596 | !crtc_state->mode.hdisplay || !crtc_state->mode.vdisplay) |
597 | goto out_check_damage; |
598 | |
599 | /* Plane must be fully visible */ |
600 | if (new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 || |
601 | new_plane_state->crtc_x + new_plane_state->crtc_w > crtc_state->mode.hdisplay || |
602 | new_plane_state->crtc_y + new_plane_state->crtc_h > crtc_state->mode.vdisplay) |
603 | return -EINVAL; |
604 | |
605 | /* Minimum size is 4x4 */ |
606 | if ((new_plane_state->src_w >> 16) < 4 || (new_plane_state->src_h >> 16) < 4) |
607 | return -EINVAL; |
608 | |
609 | /* Input and output lines must have an even number of pixels. */ |
610 | if (((new_plane_state->src_w >> 16) & 1) || (new_plane_state->crtc_w & 1)) |
611 | return -EINVAL; |
612 | |
613 | if (!osd_changed(state: new_plane_state, oldstate: old_plane_state)) |
614 | goto out_check_damage; |
615 | |
616 | crtc_state->mode_changed = true; |
617 | |
618 | xres = new_plane_state->src_w >> 16; |
619 | yres = new_plane_state->src_h >> 16; |
620 | |
621 | /* |
622 | * Increase the scaled image's theorical width/height until we find a |
623 | * configuration that has valid scaling coefficients, up to 102% of the |
624 | * screen's resolution. This makes sure that we can scale from almost |
625 | * every resolution possible at the cost of a very small distorsion. |
626 | * The CRTC_W / CRTC_H are not modified. |
627 | */ |
628 | max_w = crtc_state->mode.hdisplay * 102 / 100; |
629 | max_h = crtc_state->mode.vdisplay * 102 / 100; |
630 | |
631 | for (denom_w = xres, num_w = new_plane_state->crtc_w; num_w <= max_w; num_w++) |
632 | if (!reduce_fraction(num: &num_w, denom: &denom_w)) |
633 | break; |
634 | if (num_w > max_w) |
635 | return -EINVAL; |
636 | |
637 | for (denom_h = yres, num_h = new_plane_state->crtc_h; num_h <= max_h; num_h++) |
638 | if (!reduce_fraction(num: &num_h, denom: &denom_h)) |
639 | break; |
640 | if (num_h > max_h) |
641 | return -EINVAL; |
642 | |
643 | ipu_state->num_w = num_w; |
644 | ipu_state->num_h = num_h; |
645 | ipu_state->denom_w = denom_w; |
646 | ipu_state->denom_h = denom_h; |
647 | |
648 | out_check_damage: |
649 | if (ingenic_drm_map_noncoherent(dev: ipu->master)) |
650 | drm_atomic_helper_check_plane_damage(state, plane_state: new_plane_state); |
651 | |
652 | return 0; |
653 | } |
654 | |
655 | static void ingenic_ipu_plane_atomic_disable(struct drm_plane *plane, |
656 | struct drm_atomic_state *state) |
657 | { |
658 | struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); |
659 | |
660 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_STOP); |
661 | regmap_clear_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN); |
662 | |
663 | ingenic_drm_plane_disable(dev: ipu->master, plane); |
664 | |
665 | if (ipu->clk_enabled) { |
666 | clk_disable(clk: ipu->clk); |
667 | ipu->clk_enabled = false; |
668 | } |
669 | } |
670 | |
671 | static const struct drm_plane_helper_funcs ingenic_ipu_plane_helper_funcs = { |
672 | .atomic_update = ingenic_ipu_plane_atomic_update, |
673 | .atomic_check = ingenic_ipu_plane_atomic_check, |
674 | .atomic_disable = ingenic_ipu_plane_atomic_disable, |
675 | }; |
676 | |
677 | static int |
678 | ingenic_ipu_plane_atomic_get_property(struct drm_plane *plane, |
679 | const struct drm_plane_state *state, |
680 | struct drm_property *property, u64 *val) |
681 | { |
682 | struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); |
683 | |
684 | if (property != ipu->sharpness_prop) |
685 | return -EINVAL; |
686 | |
687 | *val = ipu->sharpness; |
688 | |
689 | return 0; |
690 | } |
691 | |
692 | static int |
693 | ingenic_ipu_plane_atomic_set_property(struct drm_plane *plane, |
694 | struct drm_plane_state *state, |
695 | struct drm_property *property, u64 val) |
696 | { |
697 | struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane); |
698 | struct drm_crtc_state *crtc_state; |
699 | bool mode_changed; |
700 | |
701 | if (property != ipu->sharpness_prop) |
702 | return -EINVAL; |
703 | |
704 | mode_changed = val != ipu->sharpness; |
705 | ipu->sharpness = val; |
706 | |
707 | if (state->crtc) { |
708 | crtc_state = drm_atomic_get_existing_crtc_state(state: state->state, crtc: state->crtc); |
709 | if (WARN_ON(!crtc_state)) |
710 | return -EINVAL; |
711 | |
712 | crtc_state->mode_changed |= mode_changed; |
713 | } |
714 | |
715 | return 0; |
716 | } |
717 | |
718 | static const struct drm_plane_funcs ingenic_ipu_plane_funcs = { |
719 | .update_plane = drm_atomic_helper_update_plane, |
720 | .disable_plane = drm_atomic_helper_disable_plane, |
721 | .reset = drm_atomic_helper_plane_reset, |
722 | .destroy = drm_plane_cleanup, |
723 | |
724 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, |
725 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, |
726 | |
727 | .atomic_get_property = ingenic_ipu_plane_atomic_get_property, |
728 | .atomic_set_property = ingenic_ipu_plane_atomic_set_property, |
729 | }; |
730 | |
731 | static struct drm_private_state * |
732 | ingenic_ipu_duplicate_state(struct drm_private_obj *obj) |
733 | { |
734 | struct ingenic_ipu_private_state *state = to_ingenic_ipu_priv_state(state: obj->state); |
735 | |
736 | state = kmemdup(p: state, size: sizeof(*state), GFP_KERNEL); |
737 | if (!state) |
738 | return NULL; |
739 | |
740 | __drm_atomic_helper_private_obj_duplicate_state(obj, state: &state->base); |
741 | |
742 | return &state->base; |
743 | } |
744 | |
745 | static void ingenic_ipu_destroy_state(struct drm_private_obj *obj, |
746 | struct drm_private_state *state) |
747 | { |
748 | struct ingenic_ipu_private_state *priv_state = to_ingenic_ipu_priv_state(state); |
749 | |
750 | kfree(objp: priv_state); |
751 | } |
752 | |
753 | static const struct drm_private_state_funcs ingenic_ipu_private_state_funcs = { |
754 | .atomic_duplicate_state = ingenic_ipu_duplicate_state, |
755 | .atomic_destroy_state = ingenic_ipu_destroy_state, |
756 | }; |
757 | |
758 | static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg) |
759 | { |
760 | struct ingenic_ipu *ipu = arg; |
761 | struct drm_crtc *crtc = drm_crtc_from_index(dev: ipu->drm, idx: 0); |
762 | unsigned int dummy; |
763 | |
764 | /* dummy read allows CPU to reconfigure IPU */ |
765 | if (ipu->soc_info->manual_restart) |
766 | regmap_read(map: ipu->map, JZ_REG_IPU_STATUS, val: &dummy); |
767 | |
768 | /* ACK interrupt */ |
769 | regmap_write(map: ipu->map, JZ_REG_IPU_STATUS, val: 0); |
770 | |
771 | /* Set previously cached addresses */ |
772 | regmap_write(map: ipu->map, JZ_REG_IPU_Y_ADDR, val: ipu->addr_y); |
773 | regmap_write(map: ipu->map, JZ_REG_IPU_U_ADDR, val: ipu->addr_u); |
774 | regmap_write(map: ipu->map, JZ_REG_IPU_V_ADDR, val: ipu->addr_v); |
775 | |
776 | /* Run IPU for the new frame */ |
777 | if (ipu->soc_info->manual_restart) |
778 | regmap_set_bits(map: ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RUN); |
779 | |
780 | drm_crtc_handle_vblank(crtc); |
781 | |
782 | return IRQ_HANDLED; |
783 | } |
784 | |
785 | static const struct regmap_config ingenic_ipu_regmap_config = { |
786 | .reg_bits = 32, |
787 | .val_bits = 32, |
788 | .reg_stride = 4, |
789 | |
790 | .max_register = JZ_REG_IPU_OUT_PHY_T_ADDR, |
791 | }; |
792 | |
793 | static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d) |
794 | { |
795 | struct platform_device *pdev = to_platform_device(dev); |
796 | struct ingenic_ipu_private_state *private_state; |
797 | const struct soc_info *soc_info; |
798 | struct drm_device *drm = d; |
799 | struct drm_plane *plane; |
800 | struct ingenic_ipu *ipu; |
801 | void __iomem *base; |
802 | unsigned int sharpness_max; |
803 | int err, irq; |
804 | |
805 | ipu = devm_kzalloc(dev, size: sizeof(*ipu), GFP_KERNEL); |
806 | if (!ipu) |
807 | return -ENOMEM; |
808 | |
809 | soc_info = of_device_get_match_data(dev); |
810 | if (!soc_info) { |
811 | dev_err(dev, "Missing platform data\n" ); |
812 | return -EINVAL; |
813 | } |
814 | |
815 | ipu->dev = dev; |
816 | ipu->drm = drm; |
817 | ipu->master = master; |
818 | ipu->soc_info = soc_info; |
819 | |
820 | base = devm_platform_ioremap_resource(pdev, index: 0); |
821 | if (IS_ERR(ptr: base)) { |
822 | dev_err(dev, "Failed to get memory resource\n" ); |
823 | return PTR_ERR(ptr: base); |
824 | } |
825 | |
826 | ipu->map = devm_regmap_init_mmio(dev, base, &ingenic_ipu_regmap_config); |
827 | if (IS_ERR(ptr: ipu->map)) { |
828 | dev_err(dev, "Failed to create regmap\n" ); |
829 | return PTR_ERR(ptr: ipu->map); |
830 | } |
831 | |
832 | irq = platform_get_irq(pdev, 0); |
833 | if (irq < 0) |
834 | return irq; |
835 | |
836 | ipu->clk = devm_clk_get(dev, id: "ipu" ); |
837 | if (IS_ERR(ptr: ipu->clk)) { |
838 | dev_err(dev, "Failed to get pixel clock\n" ); |
839 | return PTR_ERR(ptr: ipu->clk); |
840 | } |
841 | |
842 | err = devm_request_irq(dev, irq, handler: ingenic_ipu_irq_handler, irqflags: 0, |
843 | devname: dev_name(dev), dev_id: ipu); |
844 | if (err) { |
845 | dev_err(dev, "Unable to request IRQ\n" ); |
846 | return err; |
847 | } |
848 | |
849 | plane = &ipu->plane; |
850 | dev_set_drvdata(dev, data: plane); |
851 | |
852 | drm_plane_helper_add(plane, funcs: &ingenic_ipu_plane_helper_funcs); |
853 | |
854 | err = drm_universal_plane_init(dev: drm, plane, possible_crtcs: 1, funcs: &ingenic_ipu_plane_funcs, |
855 | formats: soc_info->formats, format_count: soc_info->num_formats, |
856 | NULL, type: DRM_PLANE_TYPE_OVERLAY, NULL); |
857 | if (err) { |
858 | dev_err(dev, "Failed to init plane: %i\n" , err); |
859 | return err; |
860 | } |
861 | |
862 | if (ingenic_drm_map_noncoherent(dev: master)) |
863 | drm_plane_enable_fb_damage_clips(plane); |
864 | |
865 | /* |
866 | * Sharpness settings range is [0,32] |
867 | * 0 : nearest-neighbor |
868 | * 1 : bilinear |
869 | * 2 .. 32 : bicubic (translated to sharpness factor -0.25 .. -4.0) |
870 | */ |
871 | sharpness_max = soc_info->has_bicubic ? 32 : 1; |
872 | ipu->sharpness_prop = drm_property_create_range(dev: drm, flags: 0, name: "sharpness" , |
873 | min: 0, max: sharpness_max); |
874 | if (!ipu->sharpness_prop) { |
875 | dev_err(dev, "Unable to create sharpness property\n" ); |
876 | return -ENOMEM; |
877 | } |
878 | |
879 | /* Default sharpness factor: -0.125 * 8 = -1.0 */ |
880 | ipu->sharpness = soc_info->has_bicubic ? 8 : 1; |
881 | drm_object_attach_property(obj: &plane->base, property: ipu->sharpness_prop, |
882 | init_val: ipu->sharpness); |
883 | |
884 | err = clk_prepare(clk: ipu->clk); |
885 | if (err) { |
886 | dev_err(dev, "Unable to prepare clock\n" ); |
887 | return err; |
888 | } |
889 | |
890 | private_state = kzalloc(size: sizeof(*private_state), GFP_KERNEL); |
891 | if (!private_state) { |
892 | err = -ENOMEM; |
893 | goto err_clk_unprepare; |
894 | } |
895 | |
896 | drm_atomic_private_obj_init(dev: drm, obj: &ipu->private_obj, state: &private_state->base, |
897 | funcs: &ingenic_ipu_private_state_funcs); |
898 | |
899 | return 0; |
900 | |
901 | err_clk_unprepare: |
902 | clk_unprepare(clk: ipu->clk); |
903 | return err; |
904 | } |
905 | |
906 | static void ingenic_ipu_unbind(struct device *dev, |
907 | struct device *master, void *d) |
908 | { |
909 | struct ingenic_ipu *ipu = dev_get_drvdata(dev); |
910 | |
911 | drm_atomic_private_obj_fini(obj: &ipu->private_obj); |
912 | clk_unprepare(clk: ipu->clk); |
913 | } |
914 | |
915 | static const struct component_ops ingenic_ipu_ops = { |
916 | .bind = ingenic_ipu_bind, |
917 | .unbind = ingenic_ipu_unbind, |
918 | }; |
919 | |
920 | static int ingenic_ipu_probe(struct platform_device *pdev) |
921 | { |
922 | return component_add(&pdev->dev, &ingenic_ipu_ops); |
923 | } |
924 | |
925 | static void ingenic_ipu_remove(struct platform_device *pdev) |
926 | { |
927 | component_del(&pdev->dev, &ingenic_ipu_ops); |
928 | } |
929 | |
930 | static const u32 jz4725b_ipu_formats[] = { |
931 | /* |
932 | * While officially supported, packed YUV 4:2:2 formats can cause |
933 | * random hardware crashes on JZ4725B under certain circumstances. |
934 | * It seems to happen with some specific resize ratios. |
935 | * Until a proper workaround or fix is found, disable these formats. |
936 | DRM_FORMAT_YUYV, |
937 | DRM_FORMAT_YVYU, |
938 | DRM_FORMAT_UYVY, |
939 | DRM_FORMAT_VYUY, |
940 | */ |
941 | DRM_FORMAT_YUV411, |
942 | DRM_FORMAT_YUV420, |
943 | DRM_FORMAT_YUV422, |
944 | DRM_FORMAT_YUV444, |
945 | }; |
946 | |
947 | static const struct soc_info jz4725b_soc_info = { |
948 | .formats = jz4725b_ipu_formats, |
949 | .num_formats = ARRAY_SIZE(jz4725b_ipu_formats), |
950 | .has_bicubic = false, |
951 | .manual_restart = true, |
952 | .set_coefs = jz4725b_set_coefs, |
953 | }; |
954 | |
955 | static const u32 jz4760_ipu_formats[] = { |
956 | DRM_FORMAT_XRGB1555, |
957 | DRM_FORMAT_XBGR1555, |
958 | DRM_FORMAT_RGB565, |
959 | DRM_FORMAT_BGR565, |
960 | DRM_FORMAT_XRGB8888, |
961 | DRM_FORMAT_XBGR8888, |
962 | DRM_FORMAT_YUYV, |
963 | DRM_FORMAT_YVYU, |
964 | DRM_FORMAT_UYVY, |
965 | DRM_FORMAT_VYUY, |
966 | DRM_FORMAT_YUV411, |
967 | DRM_FORMAT_YUV420, |
968 | DRM_FORMAT_YUV422, |
969 | DRM_FORMAT_YUV444, |
970 | DRM_FORMAT_XYUV8888, |
971 | }; |
972 | |
973 | static const struct soc_info jz4760_soc_info = { |
974 | .formats = jz4760_ipu_formats, |
975 | .num_formats = ARRAY_SIZE(jz4760_ipu_formats), |
976 | .has_bicubic = true, |
977 | .manual_restart = false, |
978 | .set_coefs = jz4760_set_coefs, |
979 | }; |
980 | |
981 | static const struct of_device_id ingenic_ipu_of_match[] = { |
982 | { .compatible = "ingenic,jz4725b-ipu" , .data = &jz4725b_soc_info }, |
983 | { .compatible = "ingenic,jz4760-ipu" , .data = &jz4760_soc_info }, |
984 | { /* sentinel */ }, |
985 | }; |
986 | MODULE_DEVICE_TABLE(of, ingenic_ipu_of_match); |
987 | |
988 | static struct platform_driver ingenic_ipu_driver = { |
989 | .driver = { |
990 | .name = "ingenic-ipu" , |
991 | .of_match_table = ingenic_ipu_of_match, |
992 | }, |
993 | .probe = ingenic_ipu_probe, |
994 | .remove_new = ingenic_ipu_remove, |
995 | }; |
996 | |
997 | struct platform_driver *ingenic_ipu_driver_ptr = &ingenic_ipu_driver; |
998 | |