]>
Commit | Line | Data |
---|---|---|
5bb5f5da JS |
1 | /* |
2 | * Copyright (C) Icenowy Zheng <icenowy@aosc.io> | |
3 | * | |
4 | * Based on sun4i_layer.h, which is: | |
5 | * Copyright (C) 2015 Free Electrons | |
6 | * Copyright (C) 2015 NextThing Co | |
7 | * | |
8 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU General Public License as | |
12 | * published by the Free Software Foundation; either version 2 of | |
13 | * the License, or (at your option) any later version. | |
14 | */ | |
15 | ||
16 | #include <drm/drm_atomic.h> | |
17 | #include <drm/drm_atomic_helper.h> | |
18 | #include <drm/drm_crtc.h> | |
5bb5f5da JS |
19 | #include <drm/drm_fb_cma_helper.h> |
20 | #include <drm/drm_gem_cma_helper.h> | |
7b24eec7 | 21 | #include <drm/drm_gem_framebuffer_helper.h> |
5bb5f5da | 22 | #include <drm/drm_plane_helper.h> |
fcd70cd3 | 23 | #include <drm/drm_probe_helper.h> |
5bb5f5da JS |
24 | #include <drm/drmP.h> |
25 | ||
26 | #include "sun8i_ui_layer.h" | |
27 | #include "sun8i_mixer.h" | |
b862a648 | 28 | #include "sun8i_ui_scaler.h" |
5bb5f5da JS |
29 | |
30 | static void sun8i_ui_layer_enable(struct sun8i_mixer *mixer, int channel, | |
d8b3f454 PK |
31 | int overlay, bool enable, unsigned int zpos, |
32 | unsigned int old_zpos) | |
5bb5f5da | 33 | { |
4b09c073 JS |
34 | u32 val, bld_base, ch_base; |
35 | ||
36 | bld_base = sun8i_blender_base(mixer); | |
37 | ch_base = sun8i_channel_base(mixer, channel); | |
5bb5f5da JS |
38 | |
39 | DRM_DEBUG_DRIVER("%sabling channel %d overlay %d\n", | |
40 | enable ? "En" : "Dis", channel, overlay); | |
41 | ||
42 | if (enable) | |
43 | val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; | |
44 | else | |
45 | val = 0; | |
46 | ||
47 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 48 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), |
5bb5f5da JS |
49 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN, val); |
50 | ||
d8b3f454 PK |
51 | if (!enable || zpos != old_zpos) { |
52 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 53 | SUN8I_MIXER_BLEND_PIPE_CTL(bld_base), |
d8b3f454 PK |
54 | SUN8I_MIXER_BLEND_PIPE_CTL_EN(old_zpos), |
55 | 0); | |
56 | ||
57 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 58 | SUN8I_MIXER_BLEND_ROUTE(bld_base), |
d8b3f454 PK |
59 | SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(old_zpos), |
60 | 0); | |
61 | } | |
62 | ||
f88c5ee7 JS |
63 | if (enable) { |
64 | val = SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos); | |
5bb5f5da | 65 | |
f88c5ee7 | 66 | regmap_update_bits(mixer->engine.regs, |
4b09c073 JS |
67 | SUN8I_MIXER_BLEND_PIPE_CTL(bld_base), |
68 | val, val); | |
f88c5ee7 JS |
69 | |
70 | val = channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); | |
71 | ||
72 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 73 | SUN8I_MIXER_BLEND_ROUTE(bld_base), |
f88c5ee7 JS |
74 | SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(zpos), |
75 | val); | |
76 | } | |
5bb5f5da JS |
77 | } |
78 | ||
79 | static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, | |
f88c5ee7 JS |
80 | int overlay, struct drm_plane *plane, |
81 | unsigned int zpos) | |
5bb5f5da JS |
82 | { |
83 | struct drm_plane_state *state = plane->state; | |
b862a648 | 84 | u32 src_w, src_h, dst_w, dst_h; |
4b09c073 | 85 | u32 bld_base, ch_base; |
b862a648 JS |
86 | u32 outsize, insize; |
87 | u32 hphase, vphase; | |
5bb5f5da | 88 | |
b862a648 JS |
89 | DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n", |
90 | channel, overlay); | |
5bb5f5da | 91 | |
4b09c073 JS |
92 | bld_base = sun8i_blender_base(mixer); |
93 | ch_base = sun8i_channel_base(mixer, channel); | |
94 | ||
b862a648 JS |
95 | src_w = drm_rect_width(&state->src) >> 16; |
96 | src_h = drm_rect_height(&state->src) >> 16; | |
97 | dst_w = drm_rect_width(&state->dst); | |
98 | dst_h = drm_rect_height(&state->dst); | |
99 | ||
100 | hphase = state->src.x1 & 0xffff; | |
101 | vphase = state->src.y1 & 0xffff; | |
102 | ||
103 | insize = SUN8I_MIXER_SIZE(src_w, src_h); | |
104 | outsize = SUN8I_MIXER_SIZE(dst_w, dst_h); | |
5bb5f5da JS |
105 | |
106 | if (plane->type == DRM_PLANE_TYPE_PRIMARY) { | |
107 | bool interlaced = false; | |
108 | u32 val; | |
109 | ||
110 | DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", | |
b862a648 | 111 | dst_w, dst_h); |
5bb5f5da JS |
112 | regmap_write(mixer->engine.regs, |
113 | SUN8I_MIXER_GLOBAL_SIZE, | |
b862a648 | 114 | outsize); |
4b09c073 JS |
115 | regmap_write(mixer->engine.regs, |
116 | SUN8I_MIXER_BLEND_OUTSIZE(bld_base), outsize); | |
5bb5f5da JS |
117 | |
118 | if (state->crtc) | |
119 | interlaced = state->crtc->state->adjusted_mode.flags | |
120 | & DRM_MODE_FLAG_INTERLACE; | |
121 | ||
122 | if (interlaced) | |
123 | val = SUN8I_MIXER_BLEND_OUTCTL_INTERLACED; | |
124 | else | |
125 | val = 0; | |
126 | ||
127 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 128 | SUN8I_MIXER_BLEND_OUTCTL(bld_base), |
5bb5f5da JS |
129 | SUN8I_MIXER_BLEND_OUTCTL_INTERLACED, |
130 | val); | |
131 | ||
132 | DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n", | |
133 | interlaced ? "on" : "off"); | |
134 | } | |
135 | ||
136 | /* Set height and width */ | |
b862a648 JS |
137 | DRM_DEBUG_DRIVER("Layer source offset X: %d Y: %d\n", |
138 | state->src.x1 >> 16, state->src.y1 >> 16); | |
139 | DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); | |
5bb5f5da | 140 | regmap_write(mixer->engine.regs, |
4b09c073 | 141 | SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, overlay), |
b862a648 | 142 | insize); |
5bb5f5da | 143 | regmap_write(mixer->engine.regs, |
4b09c073 | 144 | SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch_base), |
b862a648 JS |
145 | insize); |
146 | ||
147 | if (insize != outsize || hphase || vphase) { | |
148 | u32 hscale, vscale; | |
149 | ||
150 | DRM_DEBUG_DRIVER("HW scaling is enabled\n"); | |
151 | ||
152 | hscale = state->src_w / state->crtc_w; | |
153 | vscale = state->src_h / state->crtc_h; | |
154 | ||
155 | sun8i_ui_scaler_setup(mixer, channel, src_w, src_h, dst_w, | |
156 | dst_h, hscale, vscale, hphase, vphase); | |
157 | sun8i_ui_scaler_enable(mixer, channel, true); | |
158 | } else { | |
159 | DRM_DEBUG_DRIVER("HW scaling is not needed\n"); | |
160 | sun8i_ui_scaler_enable(mixer, channel, false); | |
161 | } | |
5bb5f5da JS |
162 | |
163 | /* Set base coordinates */ | |
b862a648 | 164 | DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n", |
5bb5f5da | 165 | state->dst.x1, state->dst.y1); |
b862a648 | 166 | DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h); |
5bb5f5da | 167 | regmap_write(mixer->engine.regs, |
4b09c073 | 168 | SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos), |
5bb5f5da JS |
169 | SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1)); |
170 | regmap_write(mixer->engine.regs, | |
4b09c073 | 171 | SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), |
b862a648 | 172 | outsize); |
5bb5f5da JS |
173 | |
174 | return 0; | |
175 | } | |
176 | ||
177 | static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, | |
178 | int overlay, struct drm_plane *plane) | |
179 | { | |
180 | struct drm_plane_state *state = plane->state; | |
181 | const struct de2_fmt_info *fmt_info; | |
4b09c073 JS |
182 | u32 val, ch_base; |
183 | ||
184 | ch_base = sun8i_channel_base(mixer, channel); | |
5bb5f5da JS |
185 | |
186 | fmt_info = sun8i_mixer_format_info(state->fb->format->format); | |
60a3dcf9 | 187 | if (!fmt_info || !fmt_info->rgb) { |
5bb5f5da JS |
188 | DRM_DEBUG_DRIVER("Invalid format\n"); |
189 | return -EINVAL; | |
190 | } | |
191 | ||
192 | val = fmt_info->de2_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; | |
193 | regmap_update_bits(mixer->engine.regs, | |
4b09c073 | 194 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), |
5bb5f5da JS |
195 | SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, | |
201 | int overlay, struct drm_plane *plane) | |
202 | { | |
203 | struct drm_plane_state *state = plane->state; | |
204 | struct drm_framebuffer *fb = state->fb; | |
205 | struct drm_gem_cma_object *gem; | |
206 | dma_addr_t paddr; | |
4b09c073 | 207 | u32 ch_base; |
5bb5f5da JS |
208 | int bpp; |
209 | ||
4b09c073 JS |
210 | ch_base = sun8i_channel_base(mixer, channel); |
211 | ||
5bb5f5da JS |
212 | /* Get the physical address of the buffer in memory */ |
213 | gem = drm_fb_cma_get_gem_obj(fb, 0); | |
214 | ||
215 | DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); | |
216 | ||
217 | /* Compute the start of the displayed memory */ | |
218 | bpp = fb->format->cpp[0]; | |
219 | paddr = gem->paddr + fb->offsets[0]; | |
220 | ||
221 | /* Fixup framebuffer address for src coordinates */ | |
222 | paddr += (state->src.x1 >> 16) * bpp; | |
223 | paddr += (state->src.y1 >> 16) * fb->pitches[0]; | |
224 | ||
225 | /* Set the line width */ | |
226 | DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); | |
227 | regmap_write(mixer->engine.regs, | |
4b09c073 | 228 | SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, overlay), |
5bb5f5da JS |
229 | fb->pitches[0]); |
230 | ||
231 | DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); | |
232 | ||
233 | regmap_write(mixer->engine.regs, | |
4b09c073 | 234 | SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, overlay), |
5bb5f5da JS |
235 | lower_32_bits(paddr)); |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, | |
241 | struct drm_plane_state *state) | |
242 | { | |
b862a648 | 243 | struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); |
5bb5f5da JS |
244 | struct drm_crtc *crtc = state->crtc; |
245 | struct drm_crtc_state *crtc_state; | |
b862a648 | 246 | int min_scale, max_scale; |
5bb5f5da JS |
247 | |
248 | if (!crtc) | |
249 | return 0; | |
250 | ||
251 | crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); | |
252 | if (WARN_ON(!crtc_state)) | |
253 | return -EINVAL; | |
254 | ||
b862a648 JS |
255 | min_scale = DRM_PLANE_HELPER_NO_SCALING; |
256 | max_scale = DRM_PLANE_HELPER_NO_SCALING; | |
257 | ||
258 | if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) { | |
259 | min_scale = SUN8I_UI_SCALER_SCALE_MIN; | |
260 | max_scale = SUN8I_UI_SCALER_SCALE_MAX; | |
261 | } | |
262 | ||
81af63a4 | 263 | return drm_atomic_helper_check_plane_state(state, crtc_state, |
b862a648 | 264 | min_scale, max_scale, |
5bb5f5da JS |
265 | true, true); |
266 | } | |
267 | ||
268 | static void sun8i_ui_layer_atomic_disable(struct drm_plane *plane, | |
269 | struct drm_plane_state *old_state) | |
270 | { | |
271 | struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); | |
d8b3f454 | 272 | unsigned int old_zpos = old_state->normalized_zpos; |
5bb5f5da JS |
273 | struct sun8i_mixer *mixer = layer->mixer; |
274 | ||
d8b3f454 PK |
275 | sun8i_ui_layer_enable(mixer, layer->channel, layer->overlay, false, 0, |
276 | old_zpos); | |
5bb5f5da JS |
277 | } |
278 | ||
279 | static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, | |
280 | struct drm_plane_state *old_state) | |
281 | { | |
282 | struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); | |
f88c5ee7 | 283 | unsigned int zpos = plane->state->normalized_zpos; |
d8b3f454 | 284 | unsigned int old_zpos = old_state->normalized_zpos; |
5bb5f5da JS |
285 | struct sun8i_mixer *mixer = layer->mixer; |
286 | ||
287 | if (!plane->state->visible) { | |
288 | sun8i_ui_layer_enable(mixer, layer->channel, | |
d8b3f454 | 289 | layer->overlay, false, 0, old_zpos); |
5bb5f5da JS |
290 | return; |
291 | } | |
292 | ||
293 | sun8i_ui_layer_update_coord(mixer, layer->channel, | |
f88c5ee7 | 294 | layer->overlay, plane, zpos); |
5bb5f5da JS |
295 | sun8i_ui_layer_update_formats(mixer, layer->channel, |
296 | layer->overlay, plane); | |
297 | sun8i_ui_layer_update_buffer(mixer, layer->channel, | |
298 | layer->overlay, plane); | |
f88c5ee7 | 299 | sun8i_ui_layer_enable(mixer, layer->channel, layer->overlay, |
d8b3f454 | 300 | true, zpos, old_zpos); |
5bb5f5da JS |
301 | } |
302 | ||
303 | static struct drm_plane_helper_funcs sun8i_ui_layer_helper_funcs = { | |
7b24eec7 | 304 | .prepare_fb = drm_gem_fb_prepare_fb, |
5bb5f5da JS |
305 | .atomic_check = sun8i_ui_layer_atomic_check, |
306 | .atomic_disable = sun8i_ui_layer_atomic_disable, | |
307 | .atomic_update = sun8i_ui_layer_atomic_update, | |
308 | }; | |
309 | ||
310 | static const struct drm_plane_funcs sun8i_ui_layer_funcs = { | |
311 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, | |
312 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, | |
313 | .destroy = drm_plane_cleanup, | |
314 | .disable_plane = drm_atomic_helper_disable_plane, | |
315 | .reset = drm_atomic_helper_plane_reset, | |
316 | .update_plane = drm_atomic_helper_update_plane, | |
317 | }; | |
318 | ||
319 | static const u32 sun8i_ui_layer_formats[] = { | |
320 | DRM_FORMAT_ABGR1555, | |
321 | DRM_FORMAT_ABGR4444, | |
322 | DRM_FORMAT_ABGR8888, | |
323 | DRM_FORMAT_ARGB1555, | |
324 | DRM_FORMAT_ARGB4444, | |
325 | DRM_FORMAT_ARGB8888, | |
326 | DRM_FORMAT_BGR565, | |
327 | DRM_FORMAT_BGR888, | |
328 | DRM_FORMAT_BGRA5551, | |
329 | DRM_FORMAT_BGRA4444, | |
330 | DRM_FORMAT_BGRA8888, | |
331 | DRM_FORMAT_BGRX8888, | |
332 | DRM_FORMAT_RGB565, | |
333 | DRM_FORMAT_RGB888, | |
334 | DRM_FORMAT_RGBA4444, | |
335 | DRM_FORMAT_RGBA5551, | |
336 | DRM_FORMAT_RGBA8888, | |
337 | DRM_FORMAT_RGBX8888, | |
338 | DRM_FORMAT_XBGR8888, | |
339 | DRM_FORMAT_XRGB8888, | |
340 | }; | |
341 | ||
342 | struct sun8i_ui_layer *sun8i_ui_layer_init_one(struct drm_device *drm, | |
343 | struct sun8i_mixer *mixer, | |
344 | int index) | |
345 | { | |
346 | enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; | |
347 | int channel = mixer->cfg->vi_num + index; | |
348 | struct sun8i_ui_layer *layer; | |
f88c5ee7 | 349 | unsigned int plane_cnt; |
5bb5f5da JS |
350 | int ret; |
351 | ||
352 | layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); | |
353 | if (!layer) | |
354 | return ERR_PTR(-ENOMEM); | |
355 | ||
356 | if (index == 0) | |
357 | type = DRM_PLANE_TYPE_PRIMARY; | |
358 | ||
359 | /* possible crtcs are set later */ | |
360 | ret = drm_universal_plane_init(drm, &layer->plane, 0, | |
361 | &sun8i_ui_layer_funcs, | |
362 | sun8i_ui_layer_formats, | |
363 | ARRAY_SIZE(sun8i_ui_layer_formats), | |
364 | NULL, type, NULL); | |
365 | if (ret) { | |
366 | dev_err(drm->dev, "Couldn't initialize layer\n"); | |
367 | return ERR_PTR(ret); | |
368 | } | |
369 | ||
f88c5ee7 JS |
370 | plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; |
371 | ||
372 | ret = drm_plane_create_zpos_property(&layer->plane, channel, | |
373 | 0, plane_cnt - 1); | |
5bb5f5da JS |
374 | if (ret) { |
375 | dev_err(drm->dev, "Couldn't add zpos property\n"); | |
376 | return ERR_PTR(ret); | |
377 | } | |
378 | ||
379 | drm_plane_helper_add(&layer->plane, &sun8i_ui_layer_helper_funcs); | |
380 | layer->mixer = mixer; | |
381 | layer->channel = channel; | |
382 | layer->overlay = 0; | |
383 | ||
384 | return layer; | |
385 | } |