1 /* 2 * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd 3 * Author:Mark Yao <mark.yao@rock-chips.com> 4 * 5 * This software is licensed under the terms of the GNU General Public 6 * License version 2, as published by the Free Software Foundation, and 7 * may be copied, distributed, and modified under those terms. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15 #include <linux/kernel.h> 16 #include <drm/drm.h> 17 #include <drm/drmP.h> 18 #include <drm/drm_atomic.h> 19 #include <drm/drm_fb_helper.h> 20 #include <drm/drm_crtc_helper.h> 21 22 #include "rockchip_drm_drv.h" 23 #include "rockchip_drm_gem.h" 24 25 #define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb) 26 27 struct rockchip_drm_fb { 28 struct drm_framebuffer fb; 29 struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER]; 30 }; 31 32 struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb, 33 unsigned int plane) 34 { 35 struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb); 36 37 if (plane >= ROCKCHIP_MAX_FB_BUFFER) 38 return NULL; 39 40 return rk_fb->obj[plane]; 41 } 42 43 static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb) 44 { 45 struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb); 46 struct drm_gem_object *obj; 47 int i; 48 49 for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) { 50 obj = rockchip_fb->obj[i]; 51 if (obj) 52 drm_gem_object_unreference_unlocked(obj); 53 } 54 55 drm_framebuffer_cleanup(fb); 56 kfree(rockchip_fb); 57 } 58 59 static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, 60 struct drm_file *file_priv, 61 unsigned int *handle) 62 { 63 struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb); 64 65 return drm_gem_handle_create(file_priv, 66 rockchip_fb->obj[0], handle); 67 } 68 69 static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { 70 .destroy = rockchip_drm_fb_destroy, 71 .create_handle = rockchip_drm_fb_create_handle, 72 }; 73 74 static struct rockchip_drm_fb * 75 rockchip_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd, 76 struct drm_gem_object **obj, unsigned int num_planes) 77 { 78 struct rockchip_drm_fb *rockchip_fb; 79 int ret; 80 int i; 81 82 rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL); 83 if (!rockchip_fb) 84 return ERR_PTR(-ENOMEM); 85 86 drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd); 87 88 for (i = 0; i < num_planes; i++) 89 rockchip_fb->obj[i] = obj[i]; 90 91 ret = drm_framebuffer_init(dev, &rockchip_fb->fb, 92 &rockchip_drm_fb_funcs); 93 if (ret) { 94 dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", 95 ret); 96 kfree(rockchip_fb); 97 return ERR_PTR(ret); 98 } 99 100 return rockchip_fb; 101 } 102 103 static struct drm_framebuffer * 104 rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, 105 const struct drm_mode_fb_cmd2 *mode_cmd) 106 { 107 struct rockchip_drm_fb *rockchip_fb; 108 struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER]; 109 struct drm_gem_object *obj; 110 unsigned int hsub; 111 unsigned int vsub; 112 int num_planes; 113 int ret; 114 int i; 115 116 hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format); 117 vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format); 118 num_planes = min(drm_format_num_planes(mode_cmd->pixel_format), 119 ROCKCHIP_MAX_FB_BUFFER); 120 121 for (i = 0; i < num_planes; i++) { 122 unsigned int width = mode_cmd->width / (i ? hsub : 1); 123 unsigned int height = mode_cmd->height / (i ? vsub : 1); 124 unsigned int min_size; 125 126 obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); 127 if (!obj) { 128 dev_err(dev->dev, "Failed to lookup GEM object\n"); 129 ret = -ENXIO; 130 goto err_gem_object_unreference; 131 } 132 133 min_size = (height - 1) * mode_cmd->pitches[i] + 134 mode_cmd->offsets[i] + 135 width * drm_format_plane_cpp(mode_cmd->pixel_format, i); 136 137 if (obj->size < min_size) { 138 drm_gem_object_unreference_unlocked(obj); 139 ret = -EINVAL; 140 goto err_gem_object_unreference; 141 } 142 objs[i] = obj; 143 } 144 145 rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i); 146 if (IS_ERR(rockchip_fb)) { 147 ret = PTR_ERR(rockchip_fb); 148 goto err_gem_object_unreference; 149 } 150 151 return &rockchip_fb->fb; 152 153 err_gem_object_unreference: 154 for (i--; i >= 0; i--) 155 drm_gem_object_unreference_unlocked(objs[i]); 156 return ERR_PTR(ret); 157 } 158 159 static void rockchip_drm_output_poll_changed(struct drm_device *dev) 160 { 161 struct rockchip_drm_private *private = dev->dev_private; 162 struct drm_fb_helper *fb_helper = &private->fbdev_helper; 163 164 if (fb_helper) 165 drm_fb_helper_hotplug_event(fb_helper); 166 } 167 168 static void rockchip_crtc_wait_for_update(struct drm_crtc *crtc) 169 { 170 struct rockchip_drm_private *priv = crtc->dev->dev_private; 171 int pipe = drm_crtc_index(crtc); 172 const struct rockchip_crtc_funcs *crtc_funcs = priv->crtc_funcs[pipe]; 173 174 if (crtc_funcs && crtc_funcs->wait_for_update) 175 crtc_funcs->wait_for_update(crtc); 176 } 177 178 /* 179 * We can't use drm_atomic_helper_wait_for_vblanks() because rk3288 and rk3066 180 * have hardware counters for neither vblanks nor scanlines, which results in 181 * a race where: 182 * | <-- HW vsync irq and reg take effect 183 * plane_commit --> | 184 * get_vblank and wait --> | 185 * | <-- handle_vblank, vblank->count + 1 186 * cleanup_fb --> | 187 * iommu crash --> | 188 * | <-- HW vsync irq and reg take effect 189 * 190 * This function is equivalent but uses rockchip_crtc_wait_for_update() instead 191 * of waiting for vblank_count to change. 192 */ 193 static void 194 rockchip_atomic_wait_for_complete(struct drm_device *dev, struct drm_atomic_state *old_state) 195 { 196 struct drm_crtc_state *old_crtc_state; 197 struct drm_crtc *crtc; 198 int i, ret; 199 200 for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { 201 /* No one cares about the old state, so abuse it for tracking 202 * and store whether we hold a vblank reference (and should do a 203 * vblank wait) in the ->enable boolean. 204 */ 205 old_crtc_state->enable = false; 206 207 if (!crtc->state->active) 208 continue; 209 210 if (!drm_atomic_helper_framebuffer_changed(dev, 211 old_state, crtc)) 212 continue; 213 214 ret = drm_crtc_vblank_get(crtc); 215 if (ret != 0) 216 continue; 217 218 old_crtc_state->enable = true; 219 } 220 221 for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { 222 if (!old_crtc_state->enable) 223 continue; 224 225 rockchip_crtc_wait_for_update(crtc); 226 drm_crtc_vblank_put(crtc); 227 } 228 } 229 230 static void 231 rockchip_atomic_commit_complete(struct rockchip_atomic_commit *commit) 232 { 233 struct drm_atomic_state *state = commit->state; 234 struct drm_device *dev = commit->dev; 235 236 /* 237 * TODO: do fence wait here. 238 */ 239 240 /* 241 * Rockchip crtc support runtime PM, can't update display planes 242 * when crtc is disabled. 243 * 244 * drm_atomic_helper_commit comments detail that: 245 * For drivers supporting runtime PM the recommended sequence is 246 * 247 * drm_atomic_helper_commit_modeset_disables(dev, state); 248 * 249 * drm_atomic_helper_commit_modeset_enables(dev, state); 250 * 251 * drm_atomic_helper_commit_planes(dev, state, true); 252 * 253 * See the kerneldoc entries for these three functions for more details. 254 */ 255 drm_atomic_helper_commit_modeset_disables(dev, state); 256 257 drm_atomic_helper_commit_modeset_enables(dev, state); 258 259 drm_atomic_helper_commit_planes(dev, state, true); 260 261 rockchip_atomic_wait_for_complete(dev, state); 262 263 drm_atomic_helper_cleanup_planes(dev, state); 264 265 drm_atomic_state_free(state); 266 } 267 268 void rockchip_drm_atomic_work(struct work_struct *work) 269 { 270 struct rockchip_atomic_commit *commit = container_of(work, 271 struct rockchip_atomic_commit, work); 272 273 rockchip_atomic_commit_complete(commit); 274 } 275 276 int rockchip_drm_atomic_commit(struct drm_device *dev, 277 struct drm_atomic_state *state, 278 bool nonblock) 279 { 280 struct rockchip_drm_private *private = dev->dev_private; 281 struct rockchip_atomic_commit *commit = &private->commit; 282 int ret; 283 284 ret = drm_atomic_helper_prepare_planes(dev, state); 285 if (ret) 286 return ret; 287 288 /* serialize outstanding nonblocking commits */ 289 mutex_lock(&commit->lock); 290 flush_work(&commit->work); 291 292 drm_atomic_helper_swap_state(dev, state); 293 294 commit->dev = dev; 295 commit->state = state; 296 297 if (nonblock) 298 schedule_work(&commit->work); 299 else 300 rockchip_atomic_commit_complete(commit); 301 302 mutex_unlock(&commit->lock); 303 304 return 0; 305 } 306 307 static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { 308 .fb_create = rockchip_user_fb_create, 309 .output_poll_changed = rockchip_drm_output_poll_changed, 310 .atomic_check = drm_atomic_helper_check, 311 .atomic_commit = rockchip_drm_atomic_commit, 312 }; 313 314 struct drm_framebuffer * 315 rockchip_drm_framebuffer_init(struct drm_device *dev, 316 const struct drm_mode_fb_cmd2 *mode_cmd, 317 struct drm_gem_object *obj) 318 { 319 struct rockchip_drm_fb *rockchip_fb; 320 321 rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1); 322 if (IS_ERR(rockchip_fb)) 323 return NULL; 324 325 return &rockchip_fb->fb; 326 } 327 328 void rockchip_drm_mode_config_init(struct drm_device *dev) 329 { 330 dev->mode_config.min_width = 0; 331 dev->mode_config.min_height = 0; 332 333 /* 334 * set max width and height as default value(4096x4096). 335 * this value would be used to check framebuffer size limitation 336 * at drm_mode_addfb(). 337 */ 338 dev->mode_config.max_width = 4096; 339 dev->mode_config.max_height = 4096; 340 341 dev->mode_config.funcs = &rockchip_drm_mode_config_funcs; 342 } 343