+static int vmw_fb_compute_depth(struct fb_var_screeninfo *var,
+ int *depth)
+{
+ switch (var->bits_per_pixel) {
+ case 32:
+ *depth = (var->transp.length > 0) ? 32 : 24;
+ break;
+ default:
+ DRM_ERROR("Bad bpp %u.\n", var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vmw_fb_kms_detach(struct vmw_fb_par *par,
+ bool detach_bo,
+ bool unref_bo)
+{
+ struct drm_framebuffer *cur_fb = par->set_fb;
+ int ret;
+
+ /* Detach the KMS framebuffer from crtcs */
+ if (par->set_mode) {
+ struct drm_mode_set set;
+
+ set.crtc = par->crtc;
+ set.x = 0;
+ set.y = 0;
+ set.mode = NULL;
+ set.fb = NULL;
+ set.num_connectors = 1;
+ set.connectors = &par->con;
+ ret = drm_mode_set_config_internal(&set);
+ if (ret) {
+ DRM_ERROR("Could not unset a mode.\n");
+ return ret;
+ }
+ drm_mode_destroy(par->vmw_priv->dev, par->set_mode);
+ par->set_mode = NULL;
+ }
+
+ if (cur_fb) {
+ drm_framebuffer_unreference(cur_fb);
+ par->set_fb = NULL;
+ }
+
+ if (par->vmw_bo && detach_bo) {
+ if (par->bo_ptr) {
+ ttm_bo_kunmap(&par->map);
+ par->bo_ptr = NULL;
+ }
+ if (unref_bo)
+ vmw_dmabuf_unreference(&par->vmw_bo);
+ else
+ vmw_dmabuf_unpin(par->vmw_priv, par->vmw_bo, false);
+ }
+
+ return 0;
+}
+
+static int vmw_fb_kms_framebuffer(struct fb_info *info)
+{
+ struct drm_mode_fb_cmd mode_cmd;
+ struct vmw_fb_par *par = info->par;
+ struct fb_var_screeninfo *var = &info->var;
+ struct drm_framebuffer *cur_fb;
+ struct vmw_framebuffer *vfb;
+ int ret = 0;
+ size_t new_bo_size;
+
+ ret = vmw_fb_compute_depth(var, &mode_cmd.depth);
+ if (ret)
+ return ret;
+
+ mode_cmd.width = var->xres;
+ mode_cmd.height = var->yres;
+ mode_cmd.bpp = var->bits_per_pixel;
+ mode_cmd.pitch = ((mode_cmd.bpp + 7) / 8) * mode_cmd.width;
+
+ cur_fb = par->set_fb;
+ if (cur_fb && cur_fb->width == mode_cmd.width &&
+ cur_fb->height == mode_cmd.height &&
+ cur_fb->bits_per_pixel == mode_cmd.bpp &&
+ cur_fb->depth == mode_cmd.depth &&
+ cur_fb->pitches[0] == mode_cmd.pitch)
+ return 0;
+
+ /* Need new buffer object ? */
+ new_bo_size = (size_t) mode_cmd.pitch * (size_t) mode_cmd.height;
+ ret = vmw_fb_kms_detach(par,
+ par->bo_size < new_bo_size ||
+ par->bo_size > 2*new_bo_size,
+ true);
+ if (ret)
+ return ret;
+
+ if (!par->vmw_bo) {
+ ret = vmw_fb_create_bo(par->vmw_priv, new_bo_size,
+ &par->vmw_bo);
+ if (ret) {
+ DRM_ERROR("Failed creating a buffer object for "
+ "fbdev.\n");
+ return ret;
+ }
+ par->bo_size = new_bo_size;
+ }
+
+ vfb = vmw_kms_new_framebuffer(par->vmw_priv, par->vmw_bo, NULL,
+ true, &mode_cmd);
+ if (IS_ERR(vfb))
+ return PTR_ERR(vfb);
+
+ par->set_fb = &vfb->base;
+
+ if (!par->bo_ptr) {
+ /*
+ * Pin before mapping. Since we don't know in what placement
+ * to pin, call into KMS to do it for us.
+ */
+ ret = vfb->pin(vfb);
+ if (ret) {
+ DRM_ERROR("Could not pin the fbdev framebuffer.\n");
+ return ret;
+ }
+
+ ret = ttm_bo_kmap(&par->vmw_bo->base, 0,
+ par->vmw_bo->base.num_pages, &par->map);
+ if (ret) {
+ vfb->unpin(vfb);
+ DRM_ERROR("Could not map the fbdev framebuffer.\n");
+ return ret;
+ }
+
+ par->bo_ptr = ttm_kmap_obj_virtual(&par->map, &par->bo_iowrite);
+ }
+
+ return 0;
+}
+
+static int vmw_fb_set_par(struct fb_info *info)
+{
+ struct vmw_fb_par *par = info->par;
+ struct vmw_private *vmw_priv = par->vmw_priv;
+ struct drm_mode_set set;
+ struct fb_var_screeninfo *var = &info->var;
+ struct drm_display_mode new_mode = { DRM_MODE("fb_mode",
+ DRM_MODE_TYPE_DRIVER,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC)
+ };
+ struct drm_display_mode *old_mode;
+ struct drm_display_mode *mode;
+ int ret;
+
+ old_mode = par->set_mode;
+ mode = drm_mode_duplicate(vmw_priv->dev, &new_mode);
+ if (!mode) {
+ DRM_ERROR("Could not create new fb mode.\n");
+ return -ENOMEM;
+ }
+
+ mode->hdisplay = var->xres;
+ mode->vdisplay = var->yres;
+ vmw_guess_mode_timing(mode);
+
+ if (old_mode && drm_mode_equal(old_mode, mode)) {
+ drm_mode_destroy(vmw_priv->dev, mode);
+ mode = old_mode;
+ old_mode = NULL;
+ } else if (!vmw_kms_validate_mode_vram(vmw_priv,
+ mode->hdisplay *
+ (var->bits_per_pixel + 7) / 8,
+ mode->vdisplay)) {
+ drm_mode_destroy(vmw_priv->dev, mode);
+ return -EINVAL;
+ }
+
+ mutex_lock(&par->bo_mutex);
+ drm_modeset_lock_all(vmw_priv->dev);
+ ret = vmw_fb_kms_framebuffer(info);
+ if (ret)
+ goto out_unlock;
+
+ par->fb_x = var->xoffset;
+ par->fb_y = var->yoffset;
+
+ set.crtc = par->crtc;
+ set.x = 0;
+ set.y = 0;
+ set.mode = mode;
+ set.fb = par->set_fb;
+ set.num_connectors = 1;
+ set.connectors = &par->con;
+
+ ret = drm_mode_set_config_internal(&set);
+ if (ret)
+ goto out_unlock;
+
+ vmw_fb_dirty_mark(par, par->fb_x, par->fb_y,
+ par->set_fb->width, par->set_fb->height);
+
+ /* If there already was stuff dirty we wont
+ * schedule a new work, so lets do it now */
+
+#if (defined(VMWGFX_STANDALONE) && defined(VMWGFX_FB_DEFERRED))
+ schedule_delayed_work(&par->def_par.deferred_work, 0);
+#else
+ schedule_delayed_work(&info->deferred_work, 0);
+#endif
+
+out_unlock:
+ if (old_mode)
+ drm_mode_destroy(vmw_priv->dev, old_mode);
+ par->set_mode = mode;
+
+ drm_modeset_unlock_all(vmw_priv->dev);
+ mutex_unlock(&par->bo_mutex);
+
+ return ret;
+}
+
+
+static struct fb_ops vmw_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = vmw_fb_check_var,
+ .fb_set_par = vmw_fb_set_par,
+ .fb_setcolreg = vmw_fb_setcolreg,
+ .fb_fillrect = vmw_fb_fillrect,
+ .fb_copyarea = vmw_fb_copyarea,
+ .fb_imageblit = vmw_fb_imageblit,
+ .fb_pan_display = vmw_fb_pan_display,
+ .fb_blank = vmw_fb_blank,
+};
+