[PATCH 1/8] drm: Add DU CMM support functions

VenkataRajesh.Kalakodima at in.bosch.com VenkataRajesh.Kalakodima at in.bosch.com
Wed Apr 3 13:14:37 UTC 2019


From: kalakodima venkata rajesh <venkatarajesh.kalakodima at in.bosch.com>

This is the out-of-tree patch for DU CMM driver support from
Yocto release v3.4.0.

Link: https://github.com/renesas-rcar/du_cmm/commit/2d8ea2b667ad4616aa639c54ecc11f7c4b58959d.patch

Following is from the patch description:

    du_cmm: Release for Yocto v3.4.0

    This patch made the following correspondence.

      - Corresponds to kernel v 4.14.
      - Double buffer only is supported.
      - Fix CLU / LUT update timing.
      - Add CMM Channel occupation mode.
      - Fix Close process.

Signed-off-by: Koji Matsuoka <koji.matsuoka.xm at renesas.com>
Signed-off-by: Tsutomu Muroya <muroya at ksk.co.jp>
Signed-off-by: Steve Longerbeam <steve_longerbeam at mentor.com>

      - Removal of rcar specific ioctals
      - Resolved checkpatch errors
      - Resolved merge conflicts according to latest version
      - Included CMM drivers and included files from base patch
      - Removed rcar_du_drm.h include file

Signed-off-by: kalakodima venkata rajesh <venkatarajesh.kalakodima at in.bosch.com>
---
 drivers/gpu/drm/rcar-du/Makefile        |    2 +
 drivers/gpu/drm/rcar-du/rcar_du_cmm.c   | 1200 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_crtc.c  |   24 +
 drivers/gpu/drm/rcar-du/rcar_du_crtc.h  |   16 +
 drivers/gpu/drm/rcar-du/rcar_du_drv.c   |   43 +-
 drivers/gpu/drm/rcar-du/rcar_du_drv.h   |   16 +-
 drivers/gpu/drm/rcar-du/rcar_du_group.c |    5 +
 drivers/gpu/drm/rcar-du/rcar_du_regs.h  |   92 +++
 include/drm/drm_ioctl.h                 |    7 +
 9 files changed, 1398 insertions(+), 7 deletions(-)
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_cmm.c

diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 2a3b8d7..595e719 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -6,12 +6,14 @@ rcar-du-drm-y := rcar_du_crtc.o \
 		 rcar_du_kms.o \
 		 rcar_du_plane.o
 
+rcar-du-drm-y += rcar_du_cmm.o
 rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_of.o \
 					   rcar_du_of_lvds_r8a7790.dtb.o \
 					   rcar_du_of_lvds_r8a7791.dtb.o \
 					   rcar_du_of_lvds_r8a7793.dtb.o \
 					   rcar_du_of_lvds_r8a7795.dtb.o \
 					   rcar_du_of_lvds_r8a7796.dtb.o
+
 rcar-du-drm-$(CONFIG_DRM_RCAR_VSP)	+= rcar_du_vsp.o
 
 obj-$(CONFIG_DRM_RCAR_DU)		+= rcar-du-drm.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_cmm.c b/drivers/gpu/drm/rcar-du/rcar_du_cmm.c
new file mode 100644
index 0000000..ac613a6e
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_cmm.c
@@ -0,0 +1,1200 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*************************************************************************/ /*
+ * DU CMM
+ *
+ * Copyright (C) 2018 Renesas Electronics Corporation
+ *
+ * License        Dual MIT/GPLv2
+ *
+ * The contents of this file are subject to the MIT license as set out below.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 ("GPL") in which case the provisions
+ * of GPL are applicable instead of those above.
+ *
+ * If you wish to allow use of your version of this file only under the terms of
+ * GPL, and not to allow others to use your version of this file under the terms
+ * of the MIT license, indicate your decision by deleting the provisions above
+ * and replace them with the notice and other provisions required by GPL as set
+ * out in the file called "GPL-COPYING" included in this distribution. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under the terms of either the MIT license or GPL.
+ *
+ * This License is also included in this distribution in the file called
+ * "MIT-COPYING".
+ *
+ * EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
+ * PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS
+ * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+ * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * GPLv2:
+ * If you wish to use this file under the terms of GPL, following terms are
+ * effective.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */ /*************************************************************************/
+#include <linux/syscalls.h>
+#include <linux/workqueue.h>
+
+#include <linux/reset.h>
+#include <linux/sys_soc.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_plane.h"
+#include "rcar_du_regs.h"
+#include <linux/clk.h>
+
+/* #define DEBUG_PROCE_TIME 1 */
+
+#define CMM_LUT_NUM 256
+#define CMM_CLU_NUM (17 * 17 * 17)
+#define CMM_HGO_NUM 64
+/* rcar_du_drm.h Include */
+#define LUT_DOUBLE_BUFFER_AUTO		0
+#define LUT_DOUBLE_BUFFER_A		1
+#define LUT_DOUBLE_BUFFER_B		2
+/* DRM_RCAR_DU_CMM_WAIT_EVENT: DU-CMM done event */
+#define CMM_EVENT_CLU_DONE		BIT(0)
+#define CMM_EVENT_HGO_DONE		BIT(1)
+#define CMM_EVENT_LUT_DONE		BIT(2)
+
+#define CLU_DOUBLE_BUFFER_AUTO		0
+#define CLU_DOUBLE_BUFFER_A		1
+#define CLU_DOUBLE_BUFFER_B		2
+enum {
+	QUE_STAT_PENDING,
+	QUE_STAT_ACTIVE,
+	QUE_STAT_DONE,
+};
+
+static const struct soc_device_attribute rcar_du_cmm_r8a7795_es1[] = {
+	{ .soc_id = "r8a7795", .revision = "ES1.*" },
+	{ /* sentinel */ }
+};
+
+struct rcar_du_cmm;
+struct rcar_du_cmm_file_priv;
+
+struct rcar_du_cmm_pending_event {
+	struct list_head link;
+	struct list_head  fpriv_link;
+	unsigned int event;
+	unsigned int stat;
+	unsigned long callback_data;
+	struct drm_gem_object *gem_obj;
+	struct rcar_du_cmm *du_cmm;
+	struct rcar_du_cmm_file_priv *fpriv;
+};
+
+struct cmm_module_t {
+	struct list_head list;
+	union {
+		struct {
+			struct rcar_du_cmm_pending_event *p;
+			int buf_mode;
+			bool one_side;
+		};
+		int reset;
+	};
+};
+
+struct cmm_reg_save {
+#ifdef CONFIG_PM_SLEEP
+	wait_queue_head_t wait;
+
+	u32 *lut_table;
+	u32 *clu_table;
+#endif /* CONFIG_PM_SLEEP */
+
+	u32 cm2_ctl0;	/* CM2_CTL0 */
+	u32 hgo_offset;	/* CMM_HGO_OFFSET */
+	u32 hgo_size;	/* CMM_HGO_SIZE */
+	u32 hgo_mode;	/* CMM_HGO_MODE */
+};
+
+struct rcar_du_cmm {
+	struct rcar_du_crtc *rcrtc;
+
+	/* CMM base address */
+	void __iomem *cmm_base;
+	struct clk *clock;
+
+	struct cmm_module_t lut;
+	struct cmm_module_t clu;
+	struct cmm_module_t hgo;
+
+	struct mutex lock;	/* lock for register setting */
+	struct workqueue_struct *workqueue;
+	struct work_struct work;
+
+	struct cmm_reg_save reg_save;
+	bool active;
+	bool dbuf;
+	bool clu_dbuf;
+	bool init;
+	bool direct;
+	bool vsync;
+	bool authority;
+	pid_t pid;
+	bool soc_support;
+};
+
+struct rcar_du_cmm_file_priv {
+	wait_queue_head_t event_wait;
+	struct list_head list;
+	struct list_head active_list;
+	struct list_head *done_list;
+};
+
+static DEFINE_MUTEX(cmm_event_lock);
+static DEFINE_SPINLOCK(cmm_direct_lock);
+
+static inline void event_prev_cancel_locked(struct cmm_module_t *module);
+
+static inline u32 cmm_index(struct rcar_du_cmm *_cmm)
+{
+	struct rcar_du_device *rcdu = _cmm->rcrtc->group->dev;
+
+	if (rcar_du_has(rcdu, RCAR_DU_FEATURE_R8A77965_REGS)) {
+		if ((_cmm)->rcrtc->index == 3)
+			return 2;
+	}
+	return (_cmm)->rcrtc->index;
+}
+
+#define cmm_done_list(_cmm, _fpriv) \
+	(&((_fpriv)->done_list[cmm_index(_cmm)]))
+
+static inline u32 rcar_du_cmm_read(struct rcar_du_cmm *du_cmm, u32 reg)
+{
+	return ioread32(du_cmm->cmm_base + reg);
+}
+
+static inline void rcar_du_cmm_write(struct rcar_du_cmm *du_cmm,
+				     u32 reg, u32 data)
+{
+	iowrite32(data, du_cmm->cmm_base + reg);
+}
+
+/* create default CLU table data */
+static inline u32 index_to_clu_data(int index)
+{
+	int r, g, b;
+
+	r = index % 17;
+	index /= 17;
+	g = index % 17;
+	index /= 17;
+	b = index % 17;
+
+	r = (r << 20);
+	if (r > (255 << 16))
+		r = (255 << 16);
+	g = (g << 12);
+	if (g > (255 << 8))
+		g = (255 << 8);
+	b = (b << 4);
+	if (b > (255 << 0))
+		b = (255 << 0);
+
+	return r | g | b;
+}
+
+#ifdef DEBUG_PROCE_TIME
+static long long diff_timevals(struct timeval *start, struct timeval *end)
+{
+	return (end->tv_sec * 1000000LL + end->tv_usec) -
+		(start->tv_sec * 1000000LL + start->tv_usec);
+}
+#endif
+
+static void du_cmm_clk(struct rcar_du_cmm *du_cmm, bool on)
+{
+	if (on)
+		clk_prepare_enable(du_cmm->clock);
+	else
+		clk_disable_unprepare(du_cmm->clock);
+}
+
+int rcar_du_cmm_start_stop(struct rcar_du_crtc *rcrtc, bool on)
+{
+	struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+	int i;
+	u32 table_data;
+	const struct drm_display_mode *mode;
+	int w, h, x, y;
+
+	if (!du_cmm)
+		return -EINVAL;
+
+	mutex_lock(&du_cmm->lock);
+
+	if (!on) {
+		du_cmm->active = false;
+
+		rcar_du_cmm_write(du_cmm, CMM_LUT_CTRL, 0x00000000);
+		rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL, 0x00000000);
+
+		du_cmm_clk(du_cmm, false);
+
+		goto end;
+	}
+
+	du_cmm_clk(du_cmm, true);
+
+	if (du_cmm->init)
+		goto init_done;
+
+	du_cmm->init = true;
+
+	mode = &du_cmm->rcrtc->crtc.mode;
+
+	x = (du_cmm->reg_save.hgo_offset >> 16) & 0xFFFF;
+	y = (du_cmm->reg_save.hgo_offset >> 0)  & 0xFFFF;
+	w = (du_cmm->reg_save.hgo_size >> 16) & 0xFFFF;
+	h = (du_cmm->reg_save.hgo_size >> 0)  & 0xFFFF;
+	if ((mode->hdisplay < (w + x)) || w == 0) {
+		x = 0;
+		w = mode->hdisplay;
+	}
+	if ((mode->vdisplay < (h + y)) || h == 0) {
+		y = 0;
+		h = mode->vdisplay;
+	}
+	du_cmm->reg_save.hgo_offset = (x << 16) | y;
+	du_cmm->reg_save.hgo_size = (w << 16) | h;
+
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_VPOL;
+	else
+		du_cmm->reg_save.cm2_ctl0 &= ~CMM_CTL0_VPOL;
+
+	rcar_du_cmm_write(du_cmm, CM2_CTL0, du_cmm->reg_save.cm2_ctl0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_OFFSET, du_cmm->reg_save.hgo_offset);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_SIZE, du_cmm->reg_save.hgo_size);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_MODE, du_cmm->reg_save.hgo_mode);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB_TH, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB0_H, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB0_V, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB1_H, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB1_V, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB2_H, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB2_V, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB3_H, 0);
+	rcar_du_cmm_write(du_cmm, CMM_HGO_LB3_V, 0);
+
+	/* init color table */
+	for (i = 0; i < CMM_LUT_NUM; i++) {
+	#ifdef CONFIG_PM_SLEEP
+		table_data = du_cmm->reg_save.lut_table[i];
+	#else
+		table_data = ((i << 16) | (i << 8) | (i << 0));
+	#endif /* CONFIG_PM_SLEEP */
+		rcar_du_cmm_write(du_cmm, CMM_LUT_TBLA(i), table_data);
+
+		if (du_cmm->dbuf)
+			rcar_du_cmm_write(du_cmm, CMM_LUT_TBLB(i),
+					  table_data);
+	}
+
+	rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL,
+			  CMM_CLU_CTRL_AAI | CMM_CLU_CTRL_MVS);
+
+	rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR, 0);
+	if (du_cmm->clu_dbuf)
+		rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR2, 0);
+
+	for (i = 0; i < CMM_CLU_NUM; i++) {
+	#ifdef CONFIG_PM_SLEEP
+		table_data = du_cmm->reg_save.clu_table[i];
+	#else
+		table_data = index_to_clu_data(i);
+	#endif /* CONFIG_PM_SLEEP */
+		rcar_du_cmm_write(du_cmm, CMM_CLU_DATA, table_data);
+
+		if (du_cmm->dbuf)
+			rcar_du_cmm_write(du_cmm, CMM_CLU_DATA2,
+					  table_data);
+	}
+
+init_done:
+	/* enable color table */
+	rcar_du_cmm_write(du_cmm, CMM_LUT_CTRL, CMM_LUT_CTRL_EN);
+	rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL, CMM_CLU_CTRL_AAI |
+			  CMM_CLU_CTRL_MVS | CMM_CLU_CTRL_EN);
+
+	du_cmm->active = true;
+end:
+	mutex_unlock(&du_cmm->lock);
+
+	return 0;
+}
+
+#define gem_to_vaddr(gem_obj) \
+	(container_of((gem_obj), struct drm_gem_cma_object, base)->vaddr)
+
+static inline void cmm_vblank_put(struct rcar_du_cmm_pending_event *p)
+{
+	if (p->du_cmm)
+		drm_crtc_vblank_put(&p->du_cmm->rcrtc->crtc);
+}
+
+static inline void
+cmm_gem_object_unreference(struct rcar_du_cmm_pending_event *p)
+{
+	if (p->gem_obj)
+		drm_gem_object_unreference_unlocked(p->gem_obj);
+}
+
+static inline void _event_done_locked(struct rcar_du_cmm_pending_event *p)
+{
+	cmm_gem_object_unreference(p);
+
+	if (p->fpriv) {
+		p->stat = QUE_STAT_DONE;
+		list_del(&p->link); /* delete from p->fpriv->active_list */
+		list_add_tail(&p->link, cmm_done_list(p->du_cmm, p->fpriv));
+		wake_up_interruptible(&p->fpriv->event_wait);
+	} else {
+		/* link deleted by rcar_du_cmm_postclose */
+		kfree(p);
+	}
+}
+
+/* cancel from active_list (case of LUT/CLU double buffer mode) */
+static inline void event_prev_cancel_locked(struct cmm_module_t *module)
+{
+	struct rcar_du_cmm_pending_event *p = module->p;
+
+	if (!p)
+		return;
+
+	module->p = NULL;
+
+	_event_done_locked(p);
+}
+
+static inline void event_done(struct rcar_du_cmm_pending_event *p)
+{
+	/* vblank is put */
+
+	mutex_lock(&cmm_event_lock);
+
+	_event_done_locked(p);
+
+	mutex_unlock(&cmm_event_lock);
+}
+
+static inline void lc_event_done(struct cmm_module_t *module,
+				 struct rcar_du_cmm_pending_event *p,
+				 bool done)
+{
+	/* vblank is put */
+
+	mutex_lock(&cmm_event_lock);
+
+	if (!done && list_empty(&module->list))
+		module->p = p;
+	else
+		_event_done_locked(p);
+
+	mutex_unlock(&cmm_event_lock);
+}
+
+static inline struct rcar_du_cmm_pending_event *
+event_pop_locked(struct cmm_module_t *module)
+{
+	struct rcar_du_cmm_pending_event *p =
+		list_first_entry(&module->list,
+				 struct rcar_du_cmm_pending_event,
+				 link);
+
+	p->stat = QUE_STAT_ACTIVE;
+	list_del(&p->link); /* delete from du_cmm->[lut|clu|hgo].list */
+	list_add_tail(&p->link, &p->fpriv->active_list);
+	cmm_vblank_put(p);
+
+	return p;
+}
+
+struct rcar_du_cmm_work_stat {
+	union {
+		struct {
+			struct rcar_du_cmm_pending_event *p;
+			bool done;
+			bool table_copy;
+		};
+		struct {
+			struct rcar_du_cmm_pending_event *p2;
+			bool reset;
+		};
+	};
+};
+
+static inline void one_side(struct rcar_du_cmm *du_cmm,
+			    struct cmm_module_t *module,
+			    bool on)
+{
+	if (on && !module->one_side) {
+		module->one_side = true;
+		drm_crtc_vblank_get(&du_cmm->rcrtc->crtc);
+	} else if (!on && module->one_side) {
+		module->one_side = false;
+		drm_crtc_vblank_put(&du_cmm->rcrtc->crtc);
+	}
+}
+
+/* pop LUT que */
+static int lut_pop_locked(struct rcar_du_cmm *du_cmm,
+			  struct rcar_du_cmm_work_stat *stat)
+{
+	bool is_one_side = false;
+
+	stat->done = true;
+	stat->table_copy = false;
+
+	if (!list_empty(&du_cmm->lut.list)) {
+		stat->p = event_pop_locked(&du_cmm->lut);
+
+		/* prev lut table */
+		event_prev_cancel_locked(&du_cmm->lut);
+
+		if (du_cmm->lut.buf_mode == LUT_DOUBLE_BUFFER_AUTO) {
+			is_one_side = true;
+			if (list_empty(&du_cmm->lut.list))
+				stat->done = false;
+		}
+
+	} else if (du_cmm->lut.p) {
+		/* prev lut table */
+		stat->p = du_cmm->lut.p;
+		du_cmm->lut.p = NULL;
+	} else {
+		stat->done = false;
+		stat->p = NULL;
+		stat->table_copy = du_cmm->lut.one_side;
+	}
+
+	one_side(du_cmm, &du_cmm->lut, is_one_side);
+
+	return 0;
+}
+
+static int lut_table_copy(struct rcar_du_cmm *du_cmm)
+{
+	int i;
+	u32 src, dst;
+
+	if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+		dst = CMM_LUT_TBLA(0);
+		src = CMM_LUT_TBLB(0);
+	} else {
+		dst = CMM_LUT_TBLB(0);
+		src = CMM_LUT_TBLA(0);
+	}
+
+	for (i = 0; i < CMM_LUT_NUM; i++) {
+		rcar_du_cmm_write(du_cmm, dst, rcar_du_cmm_read(du_cmm, src));
+		dst += 4;
+		src += 4;
+	}
+
+	return 0;
+}
+
+/* set 1D look up table */
+static int lut_set(struct rcar_du_cmm *du_cmm,
+		   struct rcar_du_cmm_work_stat *stat)
+{
+	int i;
+	u32 lut_base;
+	u32 *lut_buf;
+
+	if (!stat->p) {
+		if (stat->table_copy)
+			lut_table_copy(du_cmm);
+		return 0; /* skip */
+	}
+
+	/* set LUT */
+	switch (du_cmm->lut.buf_mode) {
+	case LUT_DOUBLE_BUFFER_A:
+		lut_base = CMM_LUT_TBLA(0);
+		break;
+
+	case LUT_DOUBLE_BUFFER_AUTO:
+		if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+			lut_base = CMM_LUT_TBLA(0);
+			break;
+		}
+		lut_base = CMM_LUT_TBLB(0);
+		break;
+	case LUT_DOUBLE_BUFFER_B:
+		lut_base = CMM_LUT_TBLB(0);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	lut_buf = gem_to_vaddr(stat->p->gem_obj);
+	for (i = 0; i < CMM_LUT_NUM; i++)
+		rcar_du_cmm_write(du_cmm, lut_base + i * 4, lut_buf[i]);
+
+	lc_event_done(&du_cmm->lut, stat->p, stat->done);
+
+	return 0;
+}
+
+/* pop CLU que */
+static int clu_pop_locked(struct rcar_du_cmm *du_cmm,
+			  struct rcar_du_cmm_work_stat *stat)
+{
+	bool is_one_side = false;
+
+	stat->done = true;
+	stat->table_copy = false;
+
+	if (!list_empty(&du_cmm->clu.list)) {
+		stat->p = event_pop_locked(&du_cmm->clu);
+
+		/* prev clu table */
+		event_prev_cancel_locked(&du_cmm->clu);
+
+		if (du_cmm->clu.buf_mode == CLU_DOUBLE_BUFFER_AUTO) {
+			is_one_side = true;
+			if (list_empty(&du_cmm->clu.list))
+				stat->done = false;
+		}
+
+	} else if (du_cmm->clu.p) {
+		/* prev clu table */
+		stat->p = du_cmm->clu.p;
+		du_cmm->clu.p = NULL;
+	} else {
+		stat->done = false;
+		stat->p = NULL;
+		stat->table_copy = du_cmm->clu.one_side;
+	}
+
+	one_side(du_cmm, &du_cmm->clu, is_one_side);
+
+	return 0;
+}
+
+static int clu_table_copy(struct rcar_du_cmm *du_cmm)
+{
+	int i, j, k;
+	u32 src_addr, src_data, dst_addr, dst_data;
+
+	if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+		dst_addr = CMM_CLU_ADDR;
+		dst_data = CMM_CLU_DATA;
+		src_addr = CMM_CLU_ADDR2;
+		src_data = CMM_CLU_DATA2;
+	} else {
+		dst_addr = CMM_CLU_ADDR2;
+		dst_data = CMM_CLU_DATA2;
+		src_addr = CMM_CLU_ADDR;
+		src_data = CMM_CLU_DATA;
+	}
+
+	rcar_du_cmm_write(du_cmm, dst_addr, 0);
+	for (i = 0; i < 17; i++) {
+		for (j = 0; j < 17; j++) {
+			for (k = 0; k < 17; k++) {
+				rcar_du_cmm_write(du_cmm, src_addr,
+						  (k << 16) | (j << 8) |
+						  (i << 0));
+				rcar_du_cmm_write(du_cmm, dst_data,
+						  rcar_du_cmm_read(du_cmm,
+								   src_data));
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* set 3D look up table */
+static int clu_set(struct rcar_du_cmm *du_cmm,
+		   struct rcar_du_cmm_work_stat *stat)
+{
+	int i;
+	u32 addr_reg, data_reg;
+	u32 *clu_buf;
+
+	if (!stat->p) {
+		if (stat->table_copy)
+			clu_table_copy(du_cmm);
+		return 0; /* skip */
+	}
+
+	/* set CLU */
+	switch (du_cmm->clu.buf_mode) {
+	case CLU_DOUBLE_BUFFER_A:
+		addr_reg = CMM_CLU_ADDR;
+		data_reg = CMM_CLU_DATA;
+		break;
+
+	case CLU_DOUBLE_BUFFER_AUTO:
+		if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+			addr_reg = CMM_CLU_ADDR;
+			data_reg = CMM_CLU_DATA;
+			break;
+		}
+		addr_reg = CMM_CLU_ADDR2;
+		data_reg = CMM_CLU_DATA2;
+		break;
+	case CLU_DOUBLE_BUFFER_B:
+		addr_reg = CMM_CLU_ADDR2;
+		data_reg = CMM_CLU_DATA2;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	clu_buf = gem_to_vaddr(stat->p->gem_obj);
+	rcar_du_cmm_write(du_cmm, addr_reg, 0);
+	for (i = 0; i < CMM_CLU_NUM; i++)
+		rcar_du_cmm_write(du_cmm, data_reg, clu_buf[i]);
+
+	lc_event_done(&du_cmm->clu, stat->p, stat->done);
+
+	return 0;
+}
+
+/* pop HGO que */
+static int hgo_pop_locked(struct rcar_du_cmm *du_cmm,
+			  struct rcar_du_cmm_work_stat *stat)
+{
+	struct rcar_du_cmm_pending_event *_p = NULL;
+
+	if (!list_empty(&du_cmm->hgo.list))
+		_p = event_pop_locked(&du_cmm->hgo);
+
+	if (du_cmm->hgo.reset) {
+		drm_crtc_vblank_put(&du_cmm->rcrtc->crtc);
+		du_cmm->hgo.reset = 0;
+		stat->reset = true;
+	} else {
+		stat->reset = false;
+	}
+
+	stat->p2 = _p;
+
+	return 0;
+}
+
+/* get histogram */
+static int hgo_get(struct rcar_du_cmm *du_cmm,
+		   struct rcar_du_cmm_work_stat *stat)
+{
+	int i, j;
+	const u32 histo_offset[3] = {
+		CMM_HGO_R_HISTO(0),
+		CMM_HGO_G_HISTO(0),
+		CMM_HGO_B_HISTO(0),
+	};
+	void *vaddr;
+
+	if (!stat->p2) {
+		if (stat->reset)
+			goto hgo_reset;
+
+		return 0; /* skip */
+	}
+
+	vaddr = gem_to_vaddr(stat->p2->gem_obj);
+	for (i = 0; i < 3; i++) {
+		u32 *hgo_buf = vaddr + CMM_HGO_NUM * 4 * i;
+
+		for (j = 0; j < CMM_HGO_NUM; j++)
+			hgo_buf[j] = rcar_du_cmm_read(du_cmm,
+						      histo_offset[i] + j * 4);
+	}
+
+	event_done(stat->p2);
+
+hgo_reset:
+	rcar_du_cmm_write(du_cmm, CMM_HGO_REGRST, CMM_HGO_REGRST_RCLEA);
+
+	return 0;
+}
+
+static bool du_cmm_vsync_get(struct rcar_du_cmm *du_cmm)
+{
+	unsigned long flags;
+	bool vsync;
+
+	spin_lock_irqsave(&cmm_direct_lock, flags);
+	vsync = du_cmm->vsync;
+	du_cmm->vsync = false;
+	spin_unlock_irqrestore(&cmm_direct_lock, flags);
+
+	return vsync;
+}
+
+static void du_cmm_vsync_set(struct rcar_du_cmm *du_cmm, bool vsync)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&cmm_direct_lock, flags);
+	du_cmm->vsync = vsync;
+	spin_unlock_irqrestore(&cmm_direct_lock, flags);
+}
+
+static void du_cmm_work(struct work_struct *work)
+{
+	struct rcar_du_cmm *du_cmm =
+			container_of(work, struct rcar_du_cmm, work);
+	struct rcar_du_cmm_work_stat s_lut;
+	struct rcar_du_cmm_work_stat s_clu;
+	struct rcar_du_cmm_work_stat s_hgo;
+#ifdef DEBUG_PROCE_TIME
+	struct timeval start_time, end_time;
+	unsigned long lut_time, clu_time, hgo_time;
+#endif
+	bool vsync_status = false;
+
+	memset(&s_lut, 0, sizeof(struct rcar_du_cmm_work_stat));
+	memset(&s_clu, 0, sizeof(struct rcar_du_cmm_work_stat));
+	memset(&s_hgo, 0, sizeof(struct rcar_du_cmm_work_stat));
+
+	vsync_status = du_cmm_vsync_get(du_cmm);
+
+	mutex_lock(&cmm_event_lock);
+
+	lut_pop_locked(du_cmm, &s_lut);
+	clu_pop_locked(du_cmm, &s_clu);
+	if (vsync_status)
+		hgo_pop_locked(du_cmm, &s_hgo);
+
+	mutex_unlock(&cmm_event_lock);
+
+	/* set LUT */
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&start_time);
+#endif
+	lut_set(du_cmm, &s_lut);
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&end_time);
+	lut_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+	/* set CLU */
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&start_time);
+#endif
+	clu_set(du_cmm, &s_clu);
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&end_time);
+	clu_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+	/* get HGO */
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&start_time);
+#endif
+	if (vsync_status)
+		hgo_get(du_cmm, &s_hgo);
+#ifdef DEBUG_PROCE_TIME
+	do_gettimeofday(&end_time);
+	hgo_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+	wake_up_interruptible(&du_cmm->reg_save.wait);
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef DEBUG_PROCE_TIME
+	{
+		struct rcar_du_device *rcdu = du_cmm->rcrtc->group->dev;
+
+		if (s_lut.p)
+			dev_info(rcdu->dev, "LUT %ld usec.\n", lut_time);
+		if (s_clu.p)
+			dev_info(rcdu->dev, "LUT %ld usec.\n", clu_time);
+		if (s_hgo.p2)
+			dev_info(rcdu->dev, "HGO %ld usec.\n", hgo_time);
+	}
+#endif
+}
+
+static int du_cmm_que_empty(struct rcar_du_cmm *du_cmm)
+{
+	if (list_empty(&du_cmm->lut.list) && !du_cmm->lut.p &&
+	    !du_cmm->lut.one_side &&
+	    list_empty(&du_cmm->clu.list) && !du_cmm->clu.p &&
+	    !du_cmm->clu.one_side &&
+	    list_empty(&du_cmm->hgo.list) && !du_cmm->hgo.reset)
+		return 1;
+
+	return 0;
+}
+
+void rcar_du_cmm_kick(struct rcar_du_crtc *rcrtc)
+{
+	struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+
+	if (!du_cmm)
+		return;
+
+	if (!du_cmm->active)
+		return;
+
+	if (!du_cmm_que_empty(du_cmm)) {
+		du_cmm_vsync_set(du_cmm, true);
+		queue_work(du_cmm->workqueue, &du_cmm->work);
+	}
+}
+
+#ifdef CONFIG_PM_SLEEP
+int rcar_du_cmm_pm_suspend(struct rcar_du_crtc *rcrtc)
+{
+	struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+	struct rcar_du_device *rcdu = rcrtc->group->dev;
+	int i, j, k, index;
+	int ret;
+
+	if (!du_cmm)
+		return 0;
+
+	ret = wait_event_timeout(du_cmm->reg_save.wait,
+				 du_cmm_que_empty(du_cmm),
+				 msecs_to_jiffies(500));
+	if (ret == 0)
+		dev_err(rcdu->dev, "rcar-du cmm suspend : timeout\n");
+
+	if (!du_cmm->init)
+		return 0;
+
+	du_cmm->init = false;
+
+	if (!du_cmm->active)
+		du_cmm_clk(du_cmm, true);
+
+	/* table save */
+	for (i = 0; i < CMM_LUT_NUM; i++) {
+		du_cmm->reg_save.lut_table[i] =
+			rcar_du_cmm_read(du_cmm, CMM_LUT_TBLA(i));
+	}
+
+	index = 0;
+	for (i = 0; i < 17; i++) {
+		for (j = 0; j < 17; j++) {
+			for (k = 0; k < 17; k++) {
+				rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR,
+						  (k << 16) | (j << 8) |
+						  (i << 0));
+				du_cmm->reg_save.clu_table[index++] =
+					rcar_du_cmm_read(du_cmm, CMM_CLU_DATA);
+			}
+		}
+	}
+
+	if (!du_cmm->active)
+		du_cmm_clk(du_cmm, false);
+
+	return 0;
+}
+
+int rcar_du_cmm_pm_resume(struct rcar_du_crtc *rcrtc)
+{
+	/* none */
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+int rcar_du_cmm_driver_open(struct drm_device *dev, struct drm_file *file_priv)
+{
+	struct rcar_du_device *rcdu = dev->dev_private;
+	struct rcar_du_cmm_file_priv *fpriv;
+	int i;
+
+	if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+		return 0;
+
+	file_priv->driver_priv = NULL;
+
+	fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);
+	if (unlikely(!fpriv))
+		return -ENOMEM;
+
+	fpriv->done_list = kcalloc(rcdu->info->num_crtcs,
+				   sizeof(*fpriv->done_list),
+				   GFP_KERNEL);
+	if (unlikely(!fpriv->done_list)) {
+		kfree(fpriv);
+		return -ENOMEM;
+	}
+
+	init_waitqueue_head(&fpriv->event_wait);
+	INIT_LIST_HEAD(&fpriv->list);
+	INIT_LIST_HEAD(&fpriv->active_list);
+	for (i = 0; i < rcdu->info->num_crtcs; i++)
+		INIT_LIST_HEAD(&fpriv->done_list[i]);
+
+	file_priv->driver_priv = fpriv;
+
+	return 0;
+}
+
+void rcar_du_cmm_postclose(struct drm_device *dev, struct drm_file *file_priv)
+{
+	struct rcar_du_device *rcdu = dev->dev_private;
+	struct rcar_du_cmm_file_priv *fpriv = file_priv->driver_priv;
+	struct rcar_du_cmm_pending_event *p, *pt;
+	struct rcar_du_crtc *rcrtc;
+	struct rcar_du_cmm *du_cmm;
+	int i, crtcs_cnt, ret;
+	u32 table_data;
+
+	if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+		return;
+
+	mutex_lock(&cmm_event_lock);
+
+	/* Unlink file priv events */
+	list_for_each_entry_safe(p, pt, &fpriv->list, fpriv_link) {
+		list_del(&p->fpriv_link);
+		list_del(&p->link);
+		switch (p->stat) {
+		case QUE_STAT_PENDING:
+			cmm_vblank_put(p);
+			cmm_gem_object_unreference(p);
+			kfree(p);
+			break;
+		case QUE_STAT_DONE:
+			kfree(p);
+			break;
+		case QUE_STAT_ACTIVE:
+			p->fpriv = NULL;
+			break;
+		}
+	}
+
+	mutex_unlock(&cmm_event_lock);
+
+	kfree(fpriv->done_list);
+	kfree(fpriv);
+	file_priv->driver_priv = NULL;
+
+	for (crtcs_cnt = 0; crtcs_cnt < rcdu->num_crtcs; crtcs_cnt++) {
+		rcrtc = &rcdu->crtcs[crtcs_cnt];
+		du_cmm = rcrtc->cmm_handle;
+		if (du_cmm->authority && du_cmm->pid == task_pid_nr(current)) {
+			du_cmm->authority = false;
+			du_cmm->pid = 0;
+			ret = wait_event_timeout(du_cmm->reg_save.wait,
+						 du_cmm_que_empty(du_cmm),
+						 msecs_to_jiffies(500));
+			if (ret == 0)
+				dev_err(rcdu->dev, "rcar-du cmm close : timeout\n");
+
+			for (i = 0; i < CMM_LUT_NUM; i++)
+				du_cmm->reg_save.lut_table[i] = (i << 16) |
+								(i << 8) |
+								(i << 0);
+
+			for (i = 0; i < CMM_CLU_NUM; i++) {
+				du_cmm->reg_save.clu_table[i] =
+							index_to_clu_data(i);
+			}
+
+			for (i = 0; i < CMM_LUT_NUM; i++) {
+#ifdef CONFIG_PM_SLEEP
+				table_data = du_cmm->reg_save.lut_table[i];
+#else
+				table_data = ((i << 16) | (i << 8) | (i << 0));
+#endif /* CONFIG_PM_SLEEP */
+				rcar_du_cmm_write(du_cmm, CMM_LUT_TBLA(i),
+						  table_data);
+				if (du_cmm->dbuf) {
+					rcar_du_cmm_write(du_cmm,
+							  CMM_LUT_TBLB(i),
+							  table_data);
+				}
+			}
+
+			for (i = 0; i < CMM_CLU_NUM; i++) {
+#ifdef CONFIG_PM_SLEEP
+				table_data = du_cmm->reg_save.clu_table[i];
+#else
+				table_data = index_to_clu_data(i);
+#endif /* CONFIG_PM_SLEEP */
+				rcar_du_cmm_write(du_cmm, CMM_CLU_DATA,
+						  table_data);
+
+				if (du_cmm->dbuf) {
+					rcar_du_cmm_write(du_cmm, CMM_CLU_DATA2,
+							  table_data);
+				}
+			}
+		}
+	}
+}
+
+int rcar_du_cmm_init(struct rcar_du_crtc *rcrtc)
+{
+	struct rcar_du_cmm *du_cmm;
+	int ret;
+	int i;
+	struct rcar_du_device *rcdu = rcrtc->group->dev;
+	char name[64];
+	struct resource *mem;
+
+	if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+		return 0;
+
+	du_cmm = devm_kzalloc(rcdu->dev, sizeof(*du_cmm), GFP_KERNEL);
+	if (!du_cmm) {
+		ret = -ENOMEM;
+		goto error_alloc;
+	}
+
+	/* DU-CMM mapping */
+	sprintf(name, "cmm.%u", rcrtc->index);
+	mem = platform_get_resource_byname(to_platform_device(rcdu->dev),
+					   IORESOURCE_MEM, name);
+	if (!mem) {
+		dev_err(rcdu->dev, "rcar-du cmm init : failed to get memory resource\n");
+		ret = -EINVAL;
+		goto error_mapping_cmm;
+	}
+	du_cmm->cmm_base = devm_ioremap_nocache(rcdu->dev, mem->start,
+						resource_size(mem));
+	if (!du_cmm->cmm_base) {
+		dev_err(rcdu->dev, "rcar-du cmm init : failed to map iomem\n");
+		ret = -EINVAL;
+		goto error_mapping_cmm;
+	}
+	du_cmm->clock = devm_clk_get(rcdu->dev, name);
+	if (IS_ERR(du_cmm->clock)) {
+		dev_err(rcdu->dev, "failed to get clock\n");
+		ret = PTR_ERR(du_cmm->clock);
+		goto error_clock_cmm;
+	}
+
+	du_cmm->rcrtc = rcrtc;
+
+	du_cmm->reg_save.cm2_ctl0 = 0;
+	du_cmm->reg_save.hgo_offset = 0;
+	du_cmm->reg_save.hgo_size = 0;
+	du_cmm->reg_save.hgo_mode = 0;
+
+	du_cmm->dbuf = rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM_LUT_DBUF);
+	if (du_cmm->dbuf) {
+		du_cmm->lut.buf_mode = LUT_DOUBLE_BUFFER_AUTO;
+		du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_DBUF;
+	} else {
+		dev_err(rcdu->dev, "single buffer is not supported.\n");
+		du_cmm->dbuf = true;
+		du_cmm->lut.buf_mode = LUT_DOUBLE_BUFFER_AUTO;
+		du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_DBUF;
+	}
+
+	du_cmm->clu_dbuf = rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM_CLU_DBUF);
+	if (du_cmm->clu_dbuf) {
+		du_cmm->clu.buf_mode = CLU_DOUBLE_BUFFER_AUTO;
+		du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_CLUDB;
+	} else {
+		dev_err(rcdu->dev, "single buffer is not supported.\n");
+		du_cmm->clu_dbuf = true;
+		du_cmm->clu.buf_mode = CLU_DOUBLE_BUFFER_AUTO;
+		du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_CLUDB;
+	}
+
+#ifdef CONFIG_PM_SLEEP
+	du_cmm->reg_save.lut_table =
+		devm_kzalloc(rcdu->dev, CMM_LUT_NUM * 4, GFP_KERNEL);
+	if (!du_cmm->reg_save.lut_table) {
+		ret = -ENOMEM;
+		goto error_lut_reg_save_buf;
+	}
+	for (i = 0; i < CMM_LUT_NUM; i++)
+		du_cmm->reg_save.lut_table[i] = (i << 16) | (i << 8) | (i << 0);
+
+	du_cmm->reg_save.clu_table =
+		devm_kzalloc(rcdu->dev, CMM_CLU_NUM * 4, GFP_KERNEL);
+	if (!du_cmm->reg_save.clu_table) {
+		ret = -ENOMEM;
+		goto error_clu_reg_save_buf;
+	}
+	for (i = 0; i < CMM_CLU_NUM; i++)
+		du_cmm->reg_save.clu_table[i] = index_to_clu_data(i);
+
+	init_waitqueue_head(&du_cmm->reg_save.wait);
+#endif /* CONFIG_PM_SLEEP */
+	if (soc_device_match(rcar_du_cmm_r8a7795_es1))
+		du_cmm->soc_support = false;
+	else
+		du_cmm->soc_support = true;
+
+	du_cmm->active = false;
+	du_cmm->init = false;
+	du_cmm->direct = true;
+
+	mutex_init(&du_cmm->lock);
+	INIT_LIST_HEAD(&du_cmm->lut.list);
+	du_cmm->lut.p = NULL;
+	du_cmm->lut.one_side = false;
+	INIT_LIST_HEAD(&du_cmm->clu.list);
+	du_cmm->clu.p = NULL;
+	du_cmm->clu.one_side = false;
+	INIT_LIST_HEAD(&du_cmm->hgo.list);
+	du_cmm->hgo.reset = 0;
+
+	sprintf(name, "du-cmm%d", rcrtc->index);
+	du_cmm->workqueue = create_singlethread_workqueue(name);
+	INIT_WORK(&du_cmm->work, du_cmm_work);
+
+	rcrtc->cmm_handle = du_cmm;
+
+	dev_info(rcdu->dev, "DU%d use CMM(%s buffer)\n",
+		 rcrtc->index, du_cmm->dbuf ? "Double" : "Single");
+
+	return 0;
+
+#ifdef CONFIG_PM_SLEEP
+error_clu_reg_save_buf:
+error_lut_reg_save_buf:
+#endif /* CONFIG_PM_SLEEP */
+error_clock_cmm:
+	devm_iounmap(rcdu->dev, du_cmm->cmm_base);
+error_mapping_cmm:
+	devm_kfree(rcdu->dev, du_cmm);
+error_alloc:
+	return ret;
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 15dc9ca..864fb94 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -296,6 +296,19 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
 	rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19);
 	rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start +
 					mode->hdisplay - 19);
+	if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM)) {
+		rcar_du_crtc_write(rcrtc, HDSR, mode->htotal -
+						mode->hsync_start - 19 - 25);
+		rcar_du_crtc_write(rcrtc, HDER, mode->htotal -
+						mode->hsync_start +
+						mode->hdisplay - 19 - 25);
+	} else {
+		rcar_du_crtc_write(rcrtc, HDSR, mode->htotal -
+						mode->hsync_start - 19);
+		rcar_du_crtc_write(rcrtc, HDER, mode->htotal -
+						mode->hsync_start +
+						mode->hdisplay - 19);
+	}
 	rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end -
 					mode->hsync_start - 1);
 	rcar_du_crtc_write(rcrtc, HCR,  mode->htotal - 1);
@@ -530,6 +543,9 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
 			     DSYSR_TVM_MASTER);
 
 	rcar_du_group_start_stop(rcrtc->group, true);
+
+	if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+		rcar_du_cmm_start_stop(rcrtc, true);
 }
 
 static void rcar_du_crtc_disable_planes(struct rcar_du_crtc *rcrtc)
@@ -565,6 +581,9 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
 {
 	struct drm_crtc *crtc = &rcrtc->crtc;
 
+	if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+		rcar_du_cmm_start_stop(rcrtc, false);
+
 	/*
 	 * Disable all planes and wait for the change to take effect. This is
 	 * required as the plane enable registers are updated on vblank, and no
@@ -899,6 +918,9 @@ static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
 			rcar_du_crtc_finish_page_flip(rcrtc);
 		}
 
+		if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+			rcar_du_cmm_kick(rcrtc);
+
 		ret = IRQ_HANDLED;
 	}
 
@@ -999,5 +1021,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
 		return ret;
 	}
 
+	rcar_du_cmm_init(rcrtc);
+
 	return 0;
 }
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 7680cb2..74e0a22 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -67,6 +67,10 @@ struct rcar_du_crtc {
 	struct rcar_du_group *group;
 	struct rcar_du_vsp *vsp;
 	unsigned int vsp_pipe;
+	int lvds_ch;
+
+	void *cmm_handle;
+
 };
 
 #define to_rcar_crtc(c)	container_of(c, struct rcar_du_crtc, crtc)
@@ -104,4 +108,16 @@ void rcar_du_crtc_route_output(struct drm_crtc *crtc,
 			       enum rcar_du_output output);
 void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc);
 
+/* DU-CMM functions */
+int rcar_du_cmm_init(struct rcar_du_crtc *rcrtc);
+int rcar_du_cmm_driver_open(struct drm_device *dev, struct drm_file *file_priv);
+void rcar_du_cmm_postclose(struct drm_device *dev, struct drm_file *file_priv);
+int rcar_du_cmm_start_stop(struct rcar_du_crtc *rcrtc, bool on);
+void rcar_du_cmm_kick(struct rcar_du_crtc *rcrtc);
+
+#ifdef CONFIG_PM_SLEEP
+int rcar_du_cmm_pm_suspend(struct rcar_du_crtc *rcrtc);
+int rcar_du_cmm_pm_resume(struct rcar_du_crtc *rcrtc);
+#endif /* CONFIG_PM_SLEEP */
+
 #endif /* __RCAR_DU_CRTC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 02aee6c..838b7c9 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -26,8 +26,8 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
-
 #include "rcar_du_drv.h"
+#include "rcar_du_encoder.h"
 #include "rcar_du_kms.h"
 #include "rcar_du_of.h"
 #include "rcar_du_regs.h"
@@ -128,7 +128,9 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
 static const struct rcar_du_device_info rcar_du_r8a7791_info = {
 	.gen = 2,
 	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
-		  | RCAR_DU_FEATURE_EXT_CTRL_REGS,
+		  | RCAR_DU_FEATURE_EXT_CTRL_REGS
+		  | RCAR_DU_FEATURE_CMM,
+	.num_crtcs = 2,
 	.channels_mask = BIT(1) | BIT(0),
 	.routes = {
 		/*
@@ -190,7 +192,10 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
 	.gen = 3,
 	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
 		  | RCAR_DU_FEATURE_EXT_CTRL_REGS
-		  | RCAR_DU_FEATURE_VSP1_SOURCE,
+		  | RCAR_DU_FEATURE_VSP1_SOURCE
+		  | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+		  | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+	.num_crtcs = 4,
 	.channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
 	.routes = {
 		/*
@@ -222,7 +227,10 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
 	.gen = 3,
 	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
 		  | RCAR_DU_FEATURE_EXT_CTRL_REGS
-		  | RCAR_DU_FEATURE_VSP1_SOURCE,
+		  | RCAR_DU_FEATURE_VSP1_SOURCE
+		  | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+		  | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+	.num_crtcs = 3,
 	.channels_mask = BIT(2) | BIT(1) | BIT(0),
 	.routes = {
 		/*
@@ -250,7 +258,11 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
 	.gen = 3,
 	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
 		  | RCAR_DU_FEATURE_EXT_CTRL_REGS
-		  | RCAR_DU_FEATURE_VSP1_SOURCE,
+		  | RCAR_DU_FEATURE_VSP1_SOURCE
+		  | RCAR_DU_FEATURE_R8A77965_REGS
+		  | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+		  | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+	.num_crtcs = 3,
 	.channels_mask = BIT(3) | BIT(1) | BIT(0),
 	.routes = {
 		/*
@@ -328,6 +340,8 @@ DEFINE_DRM_GEM_CMA_FOPS(rcar_du_fops);
 static struct drm_driver rcar_du_driver = {
 	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
 				| DRIVER_ATOMIC,
+	.open			= rcar_du_cmm_driver_open,
+	.postclose		= rcar_du_cmm_postclose,
 	.lastclose		= rcar_du_lastclose,
 	.gem_free_object_unlocked = drm_gem_cma_free_object,
 	.gem_vm_ops		= &drm_gem_cma_vm_ops,
@@ -358,6 +372,12 @@ static int rcar_du_pm_suspend(struct device *dev)
 {
 	struct rcar_du_device *rcdu = dev_get_drvdata(dev);
 	struct drm_atomic_state *state;
+	int i;
+
+	if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM)) {
+		for (i = 0; i < rcdu->num_crtcs; ++i)
+			rcar_du_cmm_pm_suspend(&rcdu->crtcs[i]);
+	}
 
 	drm_kms_helper_poll_disable(rcdu->ddev);
 	drm_fbdev_cma_set_suspend_unlocked(rcdu->fbdev, true);
@@ -377,7 +397,20 @@ static int rcar_du_pm_suspend(struct device *dev)
 static int rcar_du_pm_resume(struct device *dev)
 {
 	struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+#if IS_ENABLED(CONFIG_DRM_RCAR_DW_HDMI)
+	struct drm_encoder *encoder;
+	int i;
+
+	if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM)) {
+		for (i = 0; (i < rcdu->num_crtcs); ++i)
+			rcar_du_cmm_pm_resume(&rcdu->crtcs[i]);
+	}
 
+	list_for_each_entry(encoder, &rcdu->ddev->mode_config.encoder_list,
+			    head) {
+		to_rcar_encoder(encoder);
+	}
+#endif
 	drm_atomic_helper_resume(rcdu->ddev, rcdu->suspend_state);
 	drm_fbdev_cma_set_suspend_unlocked(rcdu->fbdev, false);
 	drm_kms_helper_poll_enable(rcdu->ddev);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index b3a25e8..f2afe36 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -30,8 +30,19 @@ struct rcar_du_device;
 #define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK	(1 << 0)	/* Per-CRTC IRQ and clock */
 #define RCAR_DU_FEATURE_EXT_CTRL_REGS	(1 << 1)	/* Has extended control registers */
 #define RCAR_DU_FEATURE_VSP1_SOURCE	(1 << 2)	/* Has inputs from VSP1 */
-
-#define RCAR_DU_QUIRK_ALIGN_128B	(1 << 0)	/* Align pitches to 128 bytes */
+/* Use R8A77965 registers */
+#define RCAR_DU_FEATURE_R8A77965_REGS	BIT(3)
+
+/* Has DEF7R register & CMM */
+#define RCAR_DU_FEATURE_CMM		BIT(10)
+/* Has CMM LUT Double buffer */
+#define RCAR_DU_FEATURE_CMM_LUT_DBUF	BIT(11)
+/* Has CMM CLU Double buffer */
+#define RCAR_DU_FEATURE_CMM_CLU_DBUF	BIT(12)
+/* Align pitches to 128 bytes */
+#define RCAR_DU_QUIRK_ALIGN_128B	BIT(0)
+/* LVDS lanes 1 and 3 inverted */
+#define RCAR_DU_QUIRK_LVDS_LANES	BIT(1)
 
 /*
  * struct rcar_du_output_routing - Output routing specification
@@ -61,6 +72,7 @@ struct rcar_du_device_info {
 	unsigned int features;
 	unsigned int quirks;
 	unsigned int channels_mask;
+	unsigned int num_crtcs;
 	struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
 	unsigned int num_lvds;
 	unsigned int dpll_ch;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c
index d539cb2..83a2836 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c
@@ -130,6 +130,11 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
 	if (rcdu->info->gen >= 3)
 		rcar_du_group_write(rgrp, DEFR10, DEFR10_CODE | DEFR10_DEFE10);
 
+	if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_CMM)) {
+		rcar_du_group_write(rgrp, DEF7R, DEF7R_CODE |
+				    DEF7R_CMME1 | DEF7R_CMME0);
+	}
+
 	/*
 	 * Use DS1PR and DS2PR to configure planes priorities and connects the
 	 * superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
index 9dfd220..b20e783 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -200,6 +200,11 @@
 #define DEFR6_MLOS1		(1 << 2)
 #define DEFR6_DEFAULT		(DEFR6_CODE | DEFR6_TCNE1)
 
+#define DEF7R			0x000ec
+#define DEF7R_CODE		(0x7779 << 16)
+#define DEF7R_CMME1		BIT(6)
+#define DEF7R_CMME0		BIT(4)
+
 /* -----------------------------------------------------------------------------
  * R8A7790-only Control Registers
  */
@@ -552,4 +557,91 @@
 #define GCBCR			0x11098
 #define BCBCR			0x1109c
 
+/* -----------------------------------------------------------------------------
+ * DU Color Management Module Registers
+ */
+
+#define CMM_LUT_CTRL			0x0000
+#define CMM_LUT_CTRL_EN			BIT(0)
+
+#define CMM_CLU_CTRL			0x0100
+#define CMM_CLU_CTRL_EN			BIT(0)
+#define CMM_CLU_CTRL_MVS		BIT(24)
+#define CMM_CLU_CTRL_AAI		BIT(28)
+
+#define CMM_CTL0			0x0180
+#define CM2_CTL0			CMM_CTL0
+#define CMM_CTL0_CLUDB			BIT(24)
+#define CMM_CTL0_HISTS			BIT(20)
+#define CMM_CTL0_TM1_MASK		(3 << 16)
+#define CMM_CTL0_TM1_BT601_YC240	(0 << 16)
+#define CMM_CTL0_TM1_BT601_YC255	BIT(16)
+#define CMM_CTL0_TM1_BT709_RG255	(2 << 16)
+#define CMM_CTL0_TM1_BT709_RG235	(3 << 16)
+#define CMM_CTL0_TM0_MASK		(3 << 12)
+#define CMM_CTL0_TM0_BT601_YC240	(0 << 12)
+#define CMM_CTL0_TM0_BT601_YC255	BIT(12)
+#define CMM_CTL0_TM0_BT709_RG255	(2 << 12)
+#define CMM_CTL0_TM0_BT709_RG235	(3 << 12)
+#define CMM_CTL0_TM_BT601_YC240		(CMM_CTL0_TM1_BT601_YC240 |\
+					 CMM_CTL0_TM0_BT601_YC240)
+#define CMM_CTL0_TM_BT601_YC255		(CMM_CTL0_TM1_BT601_YC255 |\
+					 CMM_CTL0_TM0_BT601_YC255)
+#define CMM_CTL0_TM_BT709_RG255		(CMM_CTL0_TM1_BT709_RG255 |\
+					 CMM_CTL0_TM0_BT709_RG255)
+#define CMM_CTL0_TM_BT709_RG235		(CMM_CTL0_TM1_BT709_RG235 |\
+					 CMM_CTL0_TM0_BT709_RG235)
+#define CMM_CTL0_YC			BIT(8)
+#define CMM_CTL0_VPOL			BIT(4)
+#define CMM_CTL0_DBUF			BIT(0)
+
+#define CMM_CTL1			0x0184
+#define CM2_CTL1			CMM_CTL1
+#define CMM_CTL1_BFS			BIT(0)
+
+#define CMM_CTL2			0x0188
+#define CMM_HGO_OFFSET			0x0200
+#define CMM_HGO_SIZE			0x0204
+#define CMM_HGO_MODE			0x0208
+#define CMM_HGO_MODE_MASK		(0xFF)
+#define CMM_HGO_MODE_MAXRGB		BIT(7)
+#define CMM_HGO_MODE_OFSB_R		BIT(6)
+#define CMM_HGO_MODE_OFSB_G		BIT(5)
+#define CMM_HGO_MODE_OFSB_B		BIT(4)
+#define CMM_HGO_MODE_HRATIO_NO_SKIPP		(0 << 2)
+#define CMM_HGO_MODE_HRATIO_HALF_SKIPP		BIT(2)
+#define CMM_HGO_MODE_HRATIO_QUARTER_SKIPP	(2 << 2)
+#define CMM_HGO_MODE_VRATIO_NO_SKIPP		(0 << 0)
+#define CMM_HGO_MODE_VRATIO_HALF_SKIPP		BIT(0)
+#define CMM_HGO_MODE_VRATIO_QUARTER_SKIPP	(2 << 0)
+#define CMM_HGO_LB_TH			0x020C
+#define CMM_HGO_LB0_H			0x0210
+#define CMM_HGO_LB0_V			0x0214
+#define CMM_HGO_LB1_H			0x0218
+#define CMM_HGO_LB1_V			0x021C
+#define CMM_HGO_LB2_H			0x0220
+#define CMM_HGO_LB2_V			0x0224
+#define CMM_HGO_LB3_H			0x0228
+#define CMM_HGO_LB3_V			0x022C
+#define CMM_HGO_R_HISTO(n)		(0x0230 + ((n) * 4))
+#define CMM_HGO_R_MAXMIN		0x0330
+#define CMM_HGO_R_SUM			0x0334
+#define CMM_HGO_R_LB_DET		0x0338
+#define CMM_HGO_G_HISTO(n)		(0x0340 + ((n) * 4))
+#define CMM_HGO_G_MAXMIN		0x0440
+#define CMM_HGO_G_SUM			0x0444
+#define CMM_HGO_G_LB_DET		0x0448
+#define CMM_HGO_B_HISTO(n)		(0x0450 + ((n) * 4))
+#define CMM_HGO_B_MAXMIN		0x0550
+#define CMM_HGO_B_SUM			0x0554
+#define CMM_HGO_B_LB_DET		0x0558
+#define CMM_HGO_REGRST			0x05FC
+#define CMM_HGO_REGRST_RCLEA		BIT(0)
+#define CMM_LUT_TBLA(n)			(0x0600 + ((n) * 4))
+#define CMM_CLU_ADDR			0x0A00
+#define CMM_CLU_DATA			0x0A04
+#define CMM_LUT_TBLB(n)			(0x0B00 + ((n) * 4))
+#define CMM_CLU_ADDR2			0x0F00
+#define CMM_CLU_DATA2			0x0F04
+
 #endif /* __RCAR_DU_REGS_H__ */
diff --git a/include/drm/drm_ioctl.h b/include/drm/drm_ioctl.h
index fafb6f5..add4280 100644
--- a/include/drm/drm_ioctl.h
+++ b/include/drm/drm_ioctl.h
@@ -109,6 +109,13 @@ enum drm_ioctl_flags {
 	 */
 	DRM_ROOT_ONLY		= BIT(2),
 	/**
+	 * @DRM_CONTROL_ALLOW:
+	 *
+	 * Deprecated, do not use. Control nodes are in the process of getting
+	 * removed.
+	 */
+	DRM_CONTROL_ALLOW	= BIT(3),
+	/**
 	 * @DRM_UNLOCKED:
 	 *
 	 * Whether &drm_ioctl_desc.func should be called with the DRM BKL held
-- 
2.7.4



More information about the dri-devel mailing list