CHROMIUM: drm/rockchip: add support for gamma table

Introduce support for gamma table, 10 bit per component, 1024 entries.

Gamma table has to be uploaded when the LUT is disabled which only takes
effect at the end of a frame, therefore actual hardware updates is done
from a worker and can take more than one frame.

BUG=chromium:484831
TEST=emerge and deploy drm-tests; stop ui and kill frecon, run gamma_test

Change-Id: I662b5e5df9529fbcf39bbac6d0cf141e97d2d851
Signed-off-by: Dominik Behr <dbehr@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/272209
Reviewed-by: Daniel Kurtz <djkurtz@chromium.org>
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
index 0967802..fc68111 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
@@ -20,6 +20,7 @@
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_gem.h"
 #include "rockchip_drm_fb.h"
+#include "rockchip_drm_fbdev.h"
 
 #define PREFERRED_BPP		32
 #define to_drm_private(x) \
@@ -134,6 +135,8 @@
 }
 
 static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
+	.gamma_set = rockchip_vop_crtc_fb_gamma_set,
+	.gamma_get = rockchip_vop_crtc_fb_gamma_get,
 	.fb_probe = rockchip_drm_fbdev_create,
 };
 
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
index 5edcf6a..5ec94809 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
@@ -17,4 +17,10 @@
 
 int rockchip_drm_fbdev_init(struct drm_device *dev);
 
+void rockchip_vop_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
+				    u16 blue, int regno);
+
+void rockchip_vop_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
+				    u16 *blue, int regno);
+
 #endif /* _ROCKCHIP_DRM_FBDEV_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 5e86ed4..8b80626 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -24,12 +24,14 @@
 #include <linux/delay.h>
 #include <linux/dma-buf.h>
 #include <linux/fence.h>
+#include <linux/iopoll.h>
 #include <linux/kernel.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/reset.h>
+#include <linux/workqueue.h>
 
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_fb.h"
@@ -38,6 +40,8 @@
 
 #include <soc/rockchip/dmc-sync.h>
 
+#define VOP_GAMMA_LUT_SIZE 1024
+
 #define VOP_REG(off, _mask, s) \
 		{.offset = off, \
 		 .mask = _mask, \
@@ -58,6 +62,8 @@
 
 #define VOP_WIN_GET(x, win, name) \
 		vop_read_reg(x, win->base, &win->phy->name)
+#define VOP_CTRL_GET(x, name) \
+		vop_read_reg(x, 0, &(x)->data->ctrl->name)
 
 #define VOP_WIN_GET_YRGBADDR(vop, win) \
 		vop_readl(vop, win->base + win->phy->yrgb_mst.offset)
@@ -117,6 +123,12 @@
 	/* physical map length of vop register */
 	uint32_t len;
 
+	void __iomem *lut_regs;
+	uint32_t lut_len;
+	uint32_t lut[VOP_GAMMA_LUT_SIZE];
+	struct work_struct load_lut_work;
+	bool lut_active;
+
 	/* one time only one process allowed to config the register */
 	spinlock_t reg_lock;
 	/* lock vop irq reg */
@@ -180,6 +192,7 @@
 	struct vop_reg out_mode;
 	struct vop_reg dither_down;
 	struct vop_reg dither_up;
+	struct vop_reg dsp_lut_en;
 	struct vop_reg pin_pol;
 
 	struct vop_reg htotal_pw;
@@ -301,6 +314,7 @@
 	.mipi_en = VOP_REG(SYS_CTRL, 0x1, 15),
 	.dither_down = VOP_REG(DSP_CTRL1, 0xf, 1),
 	.dither_up = VOP_REG(DSP_CTRL1, 0x1, 6),
+	.dsp_lut_en = VOP_REG(DSP_CTRL1, 0x1, 0),
 	.data_blank = VOP_REG(DSP_CTRL0, 0x1, 19),
 	.out_mode = VOP_REG(DSP_CTRL0, 0xf, 0),
 	.pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4),
@@ -398,6 +412,16 @@
 	}
 }
 
+static inline void vop_write_lut(struct vop *vop, uint32_t offset, uint32_t v)
+{
+	writel(v, vop->lut_regs + offset);
+}
+
+static inline uint32_t vop_read_lut(struct vop *vop, uint32_t offset)
+{
+	return readl(vop->lut_regs + offset);
+}
+
 static bool has_rb_swapped(uint32_t format)
 {
 	switch (format) {
@@ -543,6 +567,44 @@
 	spin_unlock_irqrestore(&vop->irq_lock, flags);
 }
 
+static void vop_crtc_do_load_lut(struct vop *vop)
+{
+	int i, dle;
+
+	spin_lock(&vop->reg_lock);
+	VOP_CTRL_SET(vop, dsp_lut_en, 0);
+	vop_cfg_done(vop);
+	spin_unlock(&vop->reg_lock);
+
+#define CTRL_GET(name) VOP_CTRL_GET(vop, name)
+	readx_poll_timeout(CTRL_GET, dsp_lut_en,
+			   dle, !dle, 5, 33333);
+#undef CTRL_GET
+
+	spin_lock(&vop->reg_lock);
+	for (i = 0; i < VOP_GAMMA_LUT_SIZE; i++)
+		vop_write_lut(vop, i << 2, vop->lut[i]);
+
+	VOP_CTRL_SET(vop, dsp_lut_en, 1);
+	vop_cfg_done(vop);
+	spin_unlock(&vop->reg_lock);
+}
+
+static void vop_crtc_load_lut_worker(struct work_struct *work)
+{
+	struct vop *vop = container_of(work, struct vop, load_lut_work);
+	if (!vop->lut_active)
+		return;
+	vop_crtc_do_load_lut(vop);
+}
+
+static void vop_crtc_load_lut(struct drm_crtc *crtc)
+{
+	struct vop *vop = to_vop(crtc);
+
+	schedule_work(&vop->load_lut_work);
+}
+
 static void vop_enable(struct drm_crtc *crtc)
 {
 	struct vop *vop = to_vop(crtc);
@@ -597,6 +659,8 @@
 	VOP_CTRL_SET(vop, standby, 0);
 
 	spin_unlock(&vop->reg_lock);
+	vop->lut_active = true;
+	vop_crtc_do_load_lut(vop);
 
 	enable_irq(vop->irq);
 
@@ -630,6 +694,9 @@
 		complete(&vop_win->completion);
 	}
 
+	vop->lut_active = false;
+	cancel_work_sync(&vop->load_lut_work);
+
 	rockchip_dmc_put(&vop->dmc_nb);
 	drm_vblank_off(crtc->dev, vop->pipe);
 
@@ -1317,6 +1384,7 @@
 	.mode_fixup = vop_crtc_mode_fixup,
 	.mode_set = vop_crtc_mode_set,
 	.mode_set_base = vop_crtc_mode_set_base,
+	.load_lut = vop_crtc_load_lut,
 	.commit = vop_crtc_commit,
 	.disable = vop_crtc_disable,
 };
@@ -1350,13 +1418,55 @@
 	drm_crtc_cleanup(crtc);
 }
 
+static void vop_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
+			       u16 *blue, uint32_t start, uint32_t size)
+{
+	struct vop *vop = to_vop(crtc);
+	int end = min_t(u32, start + size, VOP_GAMMA_LUT_SIZE);
+	int i;
+
+	flush_work(&vop->load_lut_work);
+	for (i = start; i < end; i++) {
+		vop->lut[i] = (((uint32_t)red[i] & 0xFFC0) << 14) |
+			      (((uint32_t)green[i] & 0xFFC0) << 4) |
+			      ((uint32_t)blue[i] >> 6);
+	}
+	vop_crtc_load_lut(crtc);
+}
+
 static const struct drm_crtc_funcs vop_crtc_funcs = {
+	.gamma_set = vop_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
 	.page_flip = vop_crtc_page_flip,
 	.destroy = vop_crtc_destroy,
 	.set_property = drm_atomic_crtc_set_property
 };
 
+void rockchip_vop_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
+				    u16 blue, int regno)
+{
+	struct vop *vop = to_vop(crtc);
+
+	if (regno >= VOP_GAMMA_LUT_SIZE)
+		return;
+	flush_work(&vop->load_lut_work);
+	vop->lut[regno] = (((uint32_t)red & 0xFFC0) << 14) |
+			  (((uint32_t)green & 0xFFC0) << 4) |
+			  ((uint32_t)blue >> 6);
+}
+
+void rockchip_vop_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
+				    u16 *blue, int regno)
+{
+	struct vop *vop = to_vop(crtc);
+
+	if (regno >= VOP_GAMMA_LUT_SIZE)
+		return;
+	*red = (u16)((vop->lut[regno] >> 14) & 0xFFC0);
+	*green = (u16)((vop->lut[regno] >> 4) & 0xFFC0);
+	*blue = (u16)((vop->lut[regno] << 6) & 0xFFC0);
+}
+
 static bool vop_win_pending_is_complete_fb(struct vop_win *vop_win)
 {
 	dma_addr_t yrgb_mst;
@@ -1579,10 +1689,15 @@
 	vop->dmc_nb.notifier_call = dmc_notify;
 	init_completion(&vop->dmc_completion);
 	init_completion(&vop->dsp_hold_completion);
+	INIT_WORK(&vop->load_lut_work, vop_crtc_load_lut_worker);
 
 	crtc->port = port;
 	vop->pipe = crtc->index;
 
+	drm_mode_crtc_set_gamma_size(crtc, VOP_GAMMA_LUT_SIZE);
+	for (i = 0; i < VOP_GAMMA_LUT_SIZE; i++)
+		vop->lut[i] = (i << 20) | (i << 10) | i;
+
 	return 0;
 
 err_cleanup_crtc:
@@ -1788,6 +1903,12 @@
 	if (!vop->regsbak)
 		return -ENOMEM;
 
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	vop->lut_len = resource_size(res);
+	vop->lut_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vop->lut_regs))
+		return PTR_ERR(vop->lut_regs);
+
 	ret = vop_initial(vop);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "cannot initial vop dev - err %d\n", ret);