diff mbox series

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

Message ID 1554297284-14009-2-git-send-email-VenkataRajesh.Kalakodima@in.bosch.com (mailing list archive)
State New
Delegated to: Kieran Bingham
Headers show
Series v4.19.0 Added Color Management Module | expand

Commit Message

Kalakodima Venkata Rajesh (RBEI/ECF3) April 3, 2019, 1:14 p.m. UTC
From: kalakodima venkata rajesh <venkatarajesh.kalakodima@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@renesas.com>
Signed-off-by: Tsutomu Muroya <muroya@ksk.co.jp>
Signed-off-by: Steve Longerbeam <steve_longerbeam@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@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

Comments

Laurent Pinchart April 4, 2019, 10:09 a.m. UTC | #1
Hi Kalakodima,

Thank you for the patch.

On Wed, Apr 03, 2019 at 06:44:37PM +0530, VenkataRajesh.Kalakodima@in.bosch.com wrote:
> From: kalakodima venkata rajesh <venkatarajesh.kalakodima@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@renesas.com>
> Signed-off-by: Tsutomu Muroya <muroya@ksk.co.jp>
> Signed-off-by: Steve Longerbeam <steve_longerbeam@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

What we're interested in from a mainline point of view is a commit
message that explains what the patch does and why, not a changelog
compared to an out-of-tree BSP. Please reword the commit messages in
this series accordingly. You can briefly mention where the code came
from in the first place, but that's secondary.

> Signed-off-by: kalakodima venkata rajesh <venkatarajesh.kalakodima@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 DU driver is licensed under the terms of the GPL. Adding files with
a dual license will make license compliance more complex. Please use an
SPDX license header, remove all the boilerplate text below, and use the
GPL only.

> + *
> + * 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>

drmP.h is deprecated, please include the DRM headers you need directly.

> +#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>

Please sort headers alphabetically, with the linux/ headers first, then
drm/, then the local headers.

> +
> +/* #define DEBUG_PROCE_TIME 1 */

Please remove all commented-out code.

> +
> +#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;

Please don't blindly save and restore registers. The device should be
reconfigured at resume time in an ordered maner, similar to what is done
at runtime. Restoring registers blindly usually leads to disasters as
the order of the register writes matter.

> +	bool active;
> +	bool dbuf;
> +	bool clu_dbuf;
> +	bool init;
> +	bool direct;
> +	bool vsync;
> +	bool authority;
> +	pid_t pid;

I don't know what this is for, but a pid_t field here is most probably a
sign that something is wrong.

> +	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);

No global variables please.

> +
> +static inline void event_prev_cancel_locked(struct cmm_module_t *module);

And no forward declarations for functions when possible.

> +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;

CRTCs now have a hardware and a software index mechanism that should be
used instead of this hack.

I'll stop reviewing the implementation in details for now as I think
there are major issues in the series that will require large
refactoring, so I'll start with that, and review the code in details for
the next version.

> +}
> +
> +#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);

The fact that the SoC has a CMM doesn't mean it should be used
unconditionally. When the CMM features are not needed by userspace the
CMM should be disabled.

> +
>  		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;

I think you need to figure out all the places where you pulled in BSP
code completely unrelated to the series :-)

> +
> +	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)

The last define is not used here, and clearly unrelated to this patch
series. I think the series needs major cleanup.

>  
>  /*
>   * 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;

There's already a way in the driver to count CRTCs, no need to duplicate
this in the rcar_du_device_info structure.

>  	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

Why do you need an alias here (and for CM2_CTL1 below) ?

> +#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)

No need for parentheses. Hex constants in the DU code base use
lowercase.

> +#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

The CMM is a separate IP core, I think it would be best supported in a
separate platform_driver like the LVDS encoder, with the registers split
to a separate header.

> +
>  #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.
> +	 */

Do not use means do not use :-)

As commented on the cover letter, changes to the DRM core and DRM API
are fine, but need to be split to patches of their own, with
corresponding documentation and a clear explanation of what they do and
why they're needed.

> +	DRM_CONTROL_ALLOW	= BIT(3),
> +	/**
>  	 * @DRM_UNLOCKED:
>  	 *
>  	 * Whether &drm_ioctl_desc.func should be called with the DRM BKL held
diff mbox series

Patch

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