1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. |
4 | * Author: Brian Starkey <brian.starkey@arm.com> |
5 | * |
6 | * ARM Mali DP Writeback connector implementation |
7 | */ |
8 | |
9 | #include <drm/drm_atomic.h> |
10 | #include <drm/drm_atomic_helper.h> |
11 | #include <drm/drm_crtc.h> |
12 | #include <drm/drm_edid.h> |
13 | #include <drm/drm_fb_dma_helper.h> |
14 | #include <drm/drm_fourcc.h> |
15 | #include <drm/drm_framebuffer.h> |
16 | #include <drm/drm_gem_dma_helper.h> |
17 | #include <drm/drm_probe_helper.h> |
18 | #include <drm/drm_writeback.h> |
19 | |
20 | #include "malidp_drv.h" |
21 | #include "malidp_hw.h" |
22 | #include "malidp_mw.h" |
23 | |
24 | #define to_mw_state(_state) (struct malidp_mw_connector_state *)(_state) |
25 | |
26 | struct malidp_mw_connector_state { |
27 | struct drm_connector_state base; |
28 | dma_addr_t addrs[2]; |
29 | s32 pitches[2]; |
30 | u8 format; |
31 | u8 n_planes; |
32 | bool rgb2yuv_initialized; |
33 | const s16 *rgb2yuv_coeffs; |
34 | }; |
35 | |
36 | static int malidp_mw_connector_get_modes(struct drm_connector *connector) |
37 | { |
38 | struct drm_device *dev = connector->dev; |
39 | |
40 | return drm_add_modes_noedid(connector, hdisplay: dev->mode_config.max_width, |
41 | vdisplay: dev->mode_config.max_height); |
42 | } |
43 | |
44 | static enum drm_mode_status |
45 | malidp_mw_connector_mode_valid(struct drm_connector *connector, |
46 | struct drm_display_mode *mode) |
47 | { |
48 | struct drm_device *dev = connector->dev; |
49 | struct drm_mode_config *mode_config = &dev->mode_config; |
50 | int w = mode->hdisplay, h = mode->vdisplay; |
51 | |
52 | if ((w < mode_config->min_width) || (w > mode_config->max_width)) |
53 | return MODE_BAD_HVALUE; |
54 | |
55 | if ((h < mode_config->min_height) || (h > mode_config->max_height)) |
56 | return MODE_BAD_VVALUE; |
57 | |
58 | return MODE_OK; |
59 | } |
60 | |
61 | static const struct drm_connector_helper_funcs malidp_mw_connector_helper_funcs = { |
62 | .get_modes = malidp_mw_connector_get_modes, |
63 | .mode_valid = malidp_mw_connector_mode_valid, |
64 | }; |
65 | |
66 | static void malidp_mw_connector_reset(struct drm_connector *connector) |
67 | { |
68 | struct malidp_mw_connector_state *mw_state = |
69 | kzalloc(size: sizeof(*mw_state), GFP_KERNEL); |
70 | |
71 | if (connector->state) |
72 | __drm_atomic_helper_connector_destroy_state(state: connector->state); |
73 | |
74 | kfree(objp: connector->state); |
75 | __drm_atomic_helper_connector_reset(connector, conn_state: &mw_state->base); |
76 | } |
77 | |
78 | static enum drm_connector_status |
79 | malidp_mw_connector_detect(struct drm_connector *connector, bool force) |
80 | { |
81 | return connector_status_connected; |
82 | } |
83 | |
84 | static void malidp_mw_connector_destroy(struct drm_connector *connector) |
85 | { |
86 | drm_connector_cleanup(connector); |
87 | } |
88 | |
89 | static struct drm_connector_state * |
90 | malidp_mw_connector_duplicate_state(struct drm_connector *connector) |
91 | { |
92 | struct malidp_mw_connector_state *mw_state, *mw_current_state; |
93 | |
94 | if (WARN_ON(!connector->state)) |
95 | return NULL; |
96 | |
97 | mw_state = kzalloc(size: sizeof(*mw_state), GFP_KERNEL); |
98 | if (!mw_state) |
99 | return NULL; |
100 | |
101 | mw_current_state = to_mw_state(connector->state); |
102 | mw_state->rgb2yuv_coeffs = mw_current_state->rgb2yuv_coeffs; |
103 | mw_state->rgb2yuv_initialized = mw_current_state->rgb2yuv_initialized; |
104 | |
105 | __drm_atomic_helper_connector_duplicate_state(connector, state: &mw_state->base); |
106 | |
107 | return &mw_state->base; |
108 | } |
109 | |
110 | static const struct drm_connector_funcs malidp_mw_connector_funcs = { |
111 | .reset = malidp_mw_connector_reset, |
112 | .detect = malidp_mw_connector_detect, |
113 | .fill_modes = drm_helper_probe_single_connector_modes, |
114 | .destroy = malidp_mw_connector_destroy, |
115 | .atomic_duplicate_state = malidp_mw_connector_duplicate_state, |
116 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
117 | }; |
118 | |
119 | static const s16 rgb2yuv_coeffs_bt709_limited[MALIDP_COLORADJ_NUM_COEFFS] = { |
120 | 47, 157, 16, |
121 | -26, -87, 112, |
122 | 112, -102, -10, |
123 | 16, 128, 128 |
124 | }; |
125 | |
126 | static int |
127 | malidp_mw_encoder_atomic_check(struct drm_encoder *encoder, |
128 | struct drm_crtc_state *crtc_state, |
129 | struct drm_connector_state *conn_state) |
130 | { |
131 | struct malidp_mw_connector_state *mw_state = to_mw_state(conn_state); |
132 | struct malidp_drm *malidp = drm_to_malidp(encoder->dev); |
133 | struct drm_framebuffer *fb; |
134 | int i, n_planes; |
135 | |
136 | if (!conn_state->writeback_job) |
137 | return 0; |
138 | |
139 | fb = conn_state->writeback_job->fb; |
140 | if ((fb->width != crtc_state->mode.hdisplay) || |
141 | (fb->height != crtc_state->mode.vdisplay)) { |
142 | DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n" , |
143 | fb->width, fb->height); |
144 | return -EINVAL; |
145 | } |
146 | |
147 | if (fb->modifier) { |
148 | DRM_DEBUG_KMS("Writeback framebuffer does not support modifiers\n" ); |
149 | return -EINVAL; |
150 | } |
151 | |
152 | mw_state->format = |
153 | malidp_hw_get_format_id(map: &malidp->dev->hw->map, layer_id: SE_MEMWRITE, |
154 | format: fb->format->format, has_modifier: !!fb->modifier); |
155 | if (mw_state->format == MALIDP_INVALID_FORMAT_ID) { |
156 | DRM_DEBUG_KMS("Invalid pixel format %p4cc\n" , |
157 | &fb->format->format); |
158 | return -EINVAL; |
159 | } |
160 | |
161 | n_planes = fb->format->num_planes; |
162 | for (i = 0; i < n_planes; i++) { |
163 | struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, plane: i); |
164 | /* memory write buffers are never rotated */ |
165 | u8 alignment = malidp_hw_get_pitch_align(hwdev: malidp->dev, rotated: 0); |
166 | |
167 | if (fb->pitches[i] & (alignment - 1)) { |
168 | DRM_DEBUG_KMS("Invalid pitch %u for plane %d\n" , |
169 | fb->pitches[i], i); |
170 | return -EINVAL; |
171 | } |
172 | mw_state->pitches[i] = fb->pitches[i]; |
173 | mw_state->addrs[i] = obj->dma_addr + fb->offsets[i]; |
174 | } |
175 | mw_state->n_planes = n_planes; |
176 | |
177 | if (fb->format->is_yuv) |
178 | mw_state->rgb2yuv_coeffs = rgb2yuv_coeffs_bt709_limited; |
179 | |
180 | return 0; |
181 | } |
182 | |
183 | static const struct drm_encoder_helper_funcs malidp_mw_encoder_helper_funcs = { |
184 | .atomic_check = malidp_mw_encoder_atomic_check, |
185 | }; |
186 | |
187 | static u32 *get_writeback_formats(struct malidp_drm *malidp, int *n_formats) |
188 | { |
189 | const struct malidp_hw_regmap *map = &malidp->dev->hw->map; |
190 | u32 *formats; |
191 | int n, i; |
192 | |
193 | formats = kcalloc(n: map->n_pixel_formats, size: sizeof(*formats), |
194 | GFP_KERNEL); |
195 | if (!formats) |
196 | return NULL; |
197 | |
198 | for (n = 0, i = 0; i < map->n_pixel_formats; i++) { |
199 | if (map->pixel_formats[i].layer & SE_MEMWRITE) |
200 | formats[n++] = map->pixel_formats[i].format; |
201 | } |
202 | |
203 | *n_formats = n; |
204 | |
205 | return formats; |
206 | } |
207 | |
208 | int malidp_mw_connector_init(struct drm_device *drm) |
209 | { |
210 | struct malidp_drm *malidp = drm_to_malidp(drm); |
211 | u32 *formats; |
212 | int ret, n_formats; |
213 | |
214 | if (!malidp->dev->hw->enable_memwrite) |
215 | return 0; |
216 | |
217 | drm_connector_helper_add(connector: &malidp->mw_connector.base, |
218 | funcs: &malidp_mw_connector_helper_funcs); |
219 | |
220 | formats = get_writeback_formats(malidp, n_formats: &n_formats); |
221 | if (!formats) |
222 | return -ENOMEM; |
223 | |
224 | ret = drm_writeback_connector_init(dev: drm, wb_connector: &malidp->mw_connector, |
225 | con_funcs: &malidp_mw_connector_funcs, |
226 | enc_helper_funcs: &malidp_mw_encoder_helper_funcs, |
227 | formats, n_formats, |
228 | possible_crtcs: 1 << drm_crtc_index(crtc: &malidp->crtc)); |
229 | kfree(objp: formats); |
230 | if (ret) |
231 | return ret; |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | void malidp_mw_atomic_commit(struct drm_device *drm, |
237 | struct drm_atomic_state *old_state) |
238 | { |
239 | struct malidp_drm *malidp = drm_to_malidp(drm); |
240 | struct drm_writeback_connector *mw_conn = &malidp->mw_connector; |
241 | struct drm_connector_state *conn_state = mw_conn->base.state; |
242 | struct malidp_hw_device *hwdev = malidp->dev; |
243 | struct malidp_mw_connector_state *mw_state; |
244 | |
245 | if (!conn_state) |
246 | return; |
247 | |
248 | mw_state = to_mw_state(conn_state); |
249 | |
250 | if (conn_state->writeback_job) { |
251 | struct drm_framebuffer *fb = conn_state->writeback_job->fb; |
252 | |
253 | DRM_DEV_DEBUG_DRIVER(drm->dev, |
254 | "Enable memwrite %ux%u:%d %pad fmt: %u\n" , |
255 | fb->width, fb->height, |
256 | mw_state->pitches[0], |
257 | &mw_state->addrs[0], |
258 | mw_state->format); |
259 | |
260 | drm_writeback_queue_job(wb_connector: mw_conn, conn_state); |
261 | hwdev->hw->enable_memwrite(hwdev, mw_state->addrs, |
262 | mw_state->pitches, mw_state->n_planes, |
263 | fb->width, fb->height, mw_state->format, |
264 | !mw_state->rgb2yuv_initialized ? |
265 | mw_state->rgb2yuv_coeffs : NULL); |
266 | mw_state->rgb2yuv_initialized = !!mw_state->rgb2yuv_coeffs; |
267 | } else { |
268 | DRM_DEV_DEBUG_DRIVER(drm->dev, "Disable memwrite\n" ); |
269 | hwdev->hw->disable_memwrite(hwdev); |
270 | } |
271 | } |
272 | |