drm/nvd0/disp: initial crtc object implementation
authorBen Skeggs <bskeggs@redhat.com>
Tue, 5 Jul 2011 06:48:06 +0000 (16:48 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Tue, 20 Sep 2011 06:06:30 +0000 (16:06 +1000)
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nvd0_display.c

index 65e48f953d4c74fa77992e8be911330149e6f74b..cf294886a696454e746f3f3db8178917456aa6c1 100644 (file)
@@ -31,6 +31,7 @@
 #include "nouveau_connector.h"
 #include "nouveau_encoder.h"
 #include "nouveau_crtc.h"
+#include "nouveau_fb.h"
 
 #define MEM_SYNC 0xe0000001
 #define MEM_VRAM 0xe0010000
@@ -100,6 +101,449 @@ nvd0_display_crtc_get(struct drm_encoder *encoder)
        return nouveau_encoder(encoder)->crtc;
 }
 
+/******************************************************************************
+ * CRTC
+ *****************************************************************************/
+static int
+nvd0_crtc_set_dither(struct nouveau_crtc *nv_crtc, bool on, bool update)
+{
+       struct drm_device *dev = nv_crtc->base.dev;
+       u32 *push, mode;
+
+       mode = 0x00000000;
+       if (on) {
+               /* 0x11: 6bpc dynamic 2x2
+                * 0x13: 8bpc dynamic 2x2
+                * 0x19: 6bpc static 2x2
+                * 0x1b: 8bpc static 2x2
+                * 0x21: 6bpc temporal
+                * 0x23: 8bpc temporal
+                */
+               mode = 0x00000011;
+       }
+
+       push = evo_wait(dev, 0, 4);
+       if (push) {
+               evo_mthd(push, 0x0490 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, mode);
+               if (update) {
+                       evo_mthd(push, 0x0080, 1);
+                       evo_data(push, 0x00000000);
+               }
+               evo_kick(push, dev, 0);
+       }
+
+       return 0;
+}
+
+static int
+nvd0_crtc_set_scale(struct nouveau_crtc *nv_crtc, int type, bool update)
+{
+       struct drm_display_mode *mode = &nv_crtc->base.mode;
+       struct drm_device *dev = nv_crtc->base.dev;
+       u32 *push;
+
+       /*XXX: actually handle scaling */
+
+       push = evo_wait(dev, 0, 16);
+       if (push) {
+               evo_mthd(push, 0x04c0 + (nv_crtc->index * 0x300), 3);
+               evo_data(push, (mode->vdisplay << 16) | mode->hdisplay);
+               evo_data(push, (mode->vdisplay << 16) | mode->hdisplay);
+               evo_data(push, (mode->vdisplay << 16) | mode->hdisplay);
+               evo_mthd(push, 0x0494 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x00000000);
+               evo_mthd(push, 0x04b0 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x00000000);
+               evo_mthd(push, 0x04b8 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, (mode->vdisplay << 16) | mode->hdisplay);
+               if (update) {
+                       evo_mthd(push, 0x0080, 1);
+                       evo_data(push, 0x00000000);
+               }
+               evo_kick(push, dev, 0);
+       }
+
+       return 0;
+}
+
+static int
+nvd0_crtc_set_image(struct nouveau_crtc *nv_crtc, struct drm_framebuffer *fb,
+                   int x, int y, bool update)
+{
+       struct nouveau_framebuffer *nvfb = nouveau_framebuffer(fb);
+       u32 *push;
+
+       /*XXX*/
+       nv_crtc->fb.tile_flags = MEM_VRAM;
+
+       push = evo_wait(fb->dev, 0, 16);
+       if (push) {
+               evo_mthd(push, 0x0460 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, nvfb->nvbo->bo.offset >> 8);
+               evo_mthd(push, 0x0468 + (nv_crtc->index * 0x300), 4);
+               evo_data(push, (fb->height << 16) | fb->width);
+               evo_data(push, nvfb->r_pitch);
+               evo_data(push, nvfb->r_format);
+               evo_data(push, nv_crtc->fb.tile_flags);
+               evo_kick(push, fb->dev, 0);
+       }
+
+       return 0;
+}
+
+static void
+nvd0_crtc_cursor_show(struct nouveau_crtc *nv_crtc, bool show, bool update)
+{
+       struct drm_device *dev = nv_crtc->base.dev;
+       u32 *push = evo_wait(dev, 0, 16);
+       if (push) {
+               if (show) {
+                       evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 2);
+                       evo_data(push, 0x85000000);
+                       evo_data(push, nv_crtc->cursor.nvbo->bo.offset >> 8);
+                       evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1);
+                       evo_data(push, MEM_VRAM);
+               } else {
+                       evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 1);
+                       evo_data(push, 0x05000000);
+                       evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1);
+                       evo_data(push, 0x00000000);
+               }
+
+               if (update) {
+                       evo_mthd(push, 0x0080, 1);
+                       evo_data(push, 0x00000000);
+               }
+
+               evo_kick(push, dev, 0);
+       }
+}
+
+static void
+nvd0_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+}
+
+static void
+nvd0_crtc_prepare(struct drm_crtc *crtc)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       u32 *push;
+
+       push = evo_wait(crtc->dev, 0, 2);
+       if (push) {
+               evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x00000000);
+               evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x03000000);
+               evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x00000000);
+               evo_kick(push, crtc->dev, 0);
+       }
+
+       nvd0_crtc_cursor_show(nv_crtc, false, false);
+}
+
+static void
+nvd0_crtc_commit(struct drm_crtc *crtc)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       u32 *push;
+
+       push = evo_wait(crtc->dev, 0, 32);
+       if (push) {
+               evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, nv_crtc->fb.tile_flags);
+               evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 4);
+               evo_data(push, 0x83000000);
+               evo_data(push, nv_crtc->lut.nvbo->bo.offset >> 8);
+               evo_data(push, 0x00000000);
+               evo_data(push, 0x00000000);
+               evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1);
+               evo_data(push, MEM_VRAM);
+               evo_kick(push, crtc->dev, 0);
+       }
+
+       nvd0_crtc_cursor_show(nv_crtc, nv_crtc->cursor.visible, true);
+}
+
+static bool
+nvd0_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *mode,
+                    struct drm_display_mode *adjusted_mode)
+{
+       return true;
+}
+
+static int
+nvd0_crtc_swap_fbs(struct drm_crtc *crtc, struct drm_framebuffer *old_fb)
+{
+       struct nouveau_framebuffer *nvfb = nouveau_framebuffer(crtc->fb);
+       int ret;
+
+       ret = nouveau_bo_pin(nvfb->nvbo, TTM_PL_FLAG_VRAM);
+       if (ret)
+               return ret;
+
+       if (old_fb) {
+               nvfb = nouveau_framebuffer(old_fb);
+               nouveau_bo_unpin(nvfb->nvbo);
+       }
+
+       return 0;
+}
+
+static int
+nvd0_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *umode,
+                  struct drm_display_mode *mode, int x, int y,
+                  struct drm_framebuffer *old_fb)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       struct nouveau_connector *nv_connector;
+       u32 htotal = mode->htotal;
+       u32 vtotal = mode->vtotal;
+       u32 hsyncw = mode->hsync_end - mode->hsync_start - 1;
+       u32 vsyncw = mode->vsync_end - mode->vsync_start - 1;
+       u32 hfrntp = mode->hsync_start - mode->hdisplay;
+       u32 vfrntp = mode->vsync_start - mode->vdisplay;
+       u32 hbackp = mode->htotal - mode->hsync_end;
+       u32 vbackp = mode->vtotal - mode->vsync_end;
+       u32 hss2be = hsyncw + hbackp;
+       u32 vss2be = vsyncw + vbackp;
+       u32 hss2de = htotal - hfrntp;
+       u32 vss2de = vtotal - vfrntp;
+       u32 hstart = 0;
+       u32 vstart = 0;
+       u32 *push;
+       int ret;
+
+       ret = nvd0_crtc_swap_fbs(crtc, old_fb);
+       if (ret)
+               return ret;
+
+       push = evo_wait(crtc->dev, 0, 64);
+       if (push) {
+               evo_mthd(push, 0x0410 + (nv_crtc->index * 0x300), 5);
+               evo_data(push, (vstart << 16) | hstart);
+               evo_data(push, (vtotal << 16) | htotal);
+               evo_data(push, (vsyncw << 16) | hsyncw);
+               evo_data(push, (vss2be << 16) | hss2be);
+               evo_data(push, (vss2de << 16) | hss2de);
+               evo_mthd(push, 0x042c + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x00000000); /* ??? */
+               evo_mthd(push, 0x0450 + (nv_crtc->index * 0x300), 3);
+               evo_data(push, mode->clock * 1000);
+               evo_data(push, 0x00200000); /* ??? */
+               evo_data(push, mode->clock * 1000);
+               evo_mthd(push, 0x0408 + (nv_crtc->index * 0x300), 1);
+               evo_data(push, 0x31ec6000); /* ??? */
+               evo_kick(push, crtc->dev, 0);
+       }
+
+       nv_connector = nouveau_crtc_connector_get(nv_crtc);
+       nvd0_crtc_set_dither(nv_crtc, nv_connector->use_dithering, false);
+       nvd0_crtc_set_scale(nv_crtc, nv_connector->scaling_mode, false);
+       nvd0_crtc_set_image(nv_crtc, crtc->fb, x, y, false);
+       return 0;
+}
+
+static int
+nvd0_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+                       struct drm_framebuffer *old_fb)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       int ret;
+
+       ret = nvd0_crtc_swap_fbs(crtc, old_fb);
+       if (ret)
+               return ret;
+
+       nvd0_crtc_set_image(nv_crtc, crtc->fb, x, y, true);
+       return 0;
+}
+
+static int
+nvd0_crtc_mode_set_base_atomic(struct drm_crtc *crtc,
+                              struct drm_framebuffer *fb, int x, int y,
+                              enum mode_set_atomic state)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       nvd0_crtc_set_image(nv_crtc, fb, x, y, true);
+       return 0;
+}
+
+static void
+nvd0_crtc_lut_load(struct drm_crtc *crtc)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       void __iomem *lut = nvbo_kmap_obj_iovirtual(nv_crtc->lut.nvbo);
+       int i;
+
+       for (i = 0; i < 256; i++) {
+               writew(nv_crtc->lut.r[i] >> 2, lut + 8*i + 0);
+               writew(nv_crtc->lut.g[i] >> 2, lut + 8*i + 2);
+               writew(nv_crtc->lut.b[i] >> 2, lut + 8*i + 4);
+       }
+}
+
+static int
+nvd0_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
+                    uint32_t handle, uint32_t width, uint32_t height)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       struct drm_device *dev = crtc->dev;
+       struct drm_gem_object *gem;
+       struct nouveau_bo *nvbo;
+       bool visible = (handle != 0);
+       int i, ret = 0;
+
+       if (visible) {
+               if (width != 64 || height != 64)
+                       return -EINVAL;
+
+               gem = drm_gem_object_lookup(dev, file_priv, handle);
+               if (unlikely(!gem))
+                       return -ENOENT;
+               nvbo = nouveau_gem_object(gem);
+
+               ret = nouveau_bo_map(nvbo);
+               if (ret == 0) {
+                       for (i = 0; i < 64 * 64; i++) {
+                               u32 v = nouveau_bo_rd32(nvbo, i);
+                               nouveau_bo_wr32(nv_crtc->cursor.nvbo, i, v);
+                       }
+                       nouveau_bo_unmap(nvbo);
+               }
+
+               drm_gem_object_unreference_unlocked(gem);
+       }
+
+       if (visible != nv_crtc->cursor.visible) {
+               nvd0_crtc_cursor_show(nv_crtc, visible, true);
+               nv_crtc->cursor.visible = visible;
+       }
+
+       return ret;
+}
+
+static int
+nvd0_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       const u32 data = (y << 16) | x;
+
+       nv_wr32(crtc->dev, 0x64d084 + (nv_crtc->index * 0x1000), data);
+       nv_wr32(crtc->dev, 0x64d080 + (nv_crtc->index * 0x1000), 0x00000000);
+       return 0;
+}
+
+static void
+nvd0_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b,
+                   uint32_t start, uint32_t size)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       u32 end = max(start + size, (u32)256);
+       u32 i;
+
+       for (i = start; i < end; i++) {
+               nv_crtc->lut.r[i] = r[i];
+               nv_crtc->lut.g[i] = g[i];
+               nv_crtc->lut.b[i] = b[i];
+       }
+
+       nvd0_crtc_lut_load(crtc);
+}
+
+static void
+nvd0_crtc_destroy(struct drm_crtc *crtc)
+{
+       struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+       nouveau_bo_unmap(nv_crtc->cursor.nvbo);
+       nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo);
+       nouveau_bo_unmap(nv_crtc->lut.nvbo);
+       nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo);
+       drm_crtc_cleanup(crtc);
+       kfree(crtc);
+}
+
+static const struct drm_crtc_helper_funcs nvd0_crtc_hfunc = {
+       .dpms = nvd0_crtc_dpms,
+       .prepare = nvd0_crtc_prepare,
+       .commit = nvd0_crtc_commit,
+       .mode_fixup = nvd0_crtc_mode_fixup,
+       .mode_set = nvd0_crtc_mode_set,
+       .mode_set_base = nvd0_crtc_mode_set_base,
+       .mode_set_base_atomic = nvd0_crtc_mode_set_base_atomic,
+       .load_lut = nvd0_crtc_lut_load,
+};
+
+static const struct drm_crtc_funcs nvd0_crtc_func = {
+       .cursor_set = nvd0_crtc_cursor_set,
+       .cursor_move = nvd0_crtc_cursor_move,
+       .gamma_set = nvd0_crtc_gamma_set,
+       .set_config = drm_crtc_helper_set_config,
+       .destroy = nvd0_crtc_destroy,
+};
+
+static int
+nvd0_crtc_create(struct drm_device *dev, int index)
+{
+       struct nouveau_crtc *nv_crtc;
+       struct drm_crtc *crtc;
+       int ret, i;
+
+       nv_crtc = kzalloc(sizeof(*nv_crtc), GFP_KERNEL);
+       if (!nv_crtc)
+               return -ENOMEM;
+
+       nv_crtc->index = index;
+       nv_crtc->set_dither = nvd0_crtc_set_dither;
+       nv_crtc->set_scale = nvd0_crtc_set_scale;
+       for (i = 0; i < 256; i++) {
+               nv_crtc->lut.r[i] = i << 8;
+               nv_crtc->lut.g[i] = i << 8;
+               nv_crtc->lut.b[i] = i << 8;
+       }
+
+       crtc = &nv_crtc->base;
+       drm_crtc_init(dev, crtc, &nvd0_crtc_func);
+       drm_crtc_helper_add(crtc, &nvd0_crtc_hfunc);
+       drm_mode_crtc_set_gamma_size(crtc, 256);
+
+       ret = nouveau_bo_new(dev, 64 * 64 * 4, 0x100, TTM_PL_FLAG_VRAM,
+                            0, 0x0000, &nv_crtc->cursor.nvbo);
+       if (!ret) {
+               ret = nouveau_bo_pin(nv_crtc->cursor.nvbo, TTM_PL_FLAG_VRAM);
+               if (!ret)
+                       ret = nouveau_bo_map(nv_crtc->cursor.nvbo);
+               if (ret)
+                       nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo);
+       }
+
+       if (ret)
+               goto out;
+
+       ret = nouveau_bo_new(dev, 4096, 0x100, TTM_PL_FLAG_VRAM,
+                            0, 0x0000, &nv_crtc->lut.nvbo);
+       if (!ret) {
+               ret = nouveau_bo_pin(nv_crtc->lut.nvbo, TTM_PL_FLAG_VRAM);
+               if (!ret)
+                       ret = nouveau_bo_map(nv_crtc->lut.nvbo);
+               if (ret)
+                       nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo);
+       }
+
+       if (ret)
+               goto out;
+
+       nvd0_crtc_lut_load(crtc);
+
+out:
+       if (ret)
+               nvd0_crtc_destroy(crtc);
+       return ret;
+}
+
 /******************************************************************************
  * DAC
  *****************************************************************************/
@@ -194,6 +638,7 @@ nvd0_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode,
        if (push) {
                evo_mthd(push, 0x0200 + (nv_encoder->or * 0x20), 1);
                evo_data(push, mode_ctrl);
+               evo_kick(push, encoder->dev, 0);
        }
 
        nv_encoder->crtc = encoder->crtc;
@@ -204,9 +649,12 @@ nvd0_sor_disconnect(struct drm_encoder *encoder)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct drm_device *dev = encoder->dev;
+       u32 *push;
 
        if (nv_encoder->crtc) {
-               u32 *push = evo_wait(dev, 0, 4);
+               nvd0_crtc_prepare(nv_encoder->crtc);
+
+               push = evo_wait(dev, 0, 4);
                if (push) {
                        evo_mthd(push, 0x0200 + (nv_encoder->or * 0x20), 1);
                        evo_data(push, 0x00000000);
@@ -493,6 +941,13 @@ nvd0_display_create(struct drm_device *dev)
                return -ENOMEM;
        dev_priv->engine.display.priv = disp;
 
+       /* create crtc objects to represent the hw heads */
+       for (i = 0; i < 2; i++) {
+               ret = nvd0_crtc_create(dev, i);
+               if (ret)
+                       goto out;
+       }
+
        /* create encoder/connector objects based on VBIOS DCB table */
        for (i = 0, dcbe = &dcb->entry[0]; i < dcb->entries; i++, dcbe++) {
                connector = nouveau_connector_create(dev, dcbe->connector);