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

From: VenkataRajesh.Kalakodima
Date: Wed Apr 03 2019 - 09:25:59 EST


From: kalakodima venkata rajesh <venkatarajesh.kalakodima@xxxxxxxxxxxx>

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@xxxxxxxxxxx>
Signed-off-by: Tsutomu Muroya <muroya@xxxxxxxxx>
Signed-off-by: Steve Longerbeam <steve_longerbeam@xxxxxxxxxx>

- 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@xxxxxxxxxxxx>
---
drivers/gpu/drm/rcar-du/Makefile | 2 +
drivers/gpu/drm/rcar-du/rcar_du_cmm.c | 1200 +++++++++++++++++++++++++++++++
drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 24 +
drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 16 +
drivers/gpu/drm/rcar-du/rcar_du_drv.c | 43 +-
drivers/gpu/drm/rcar-du/rcar_du_drv.h | 16 +-
drivers/gpu/drm/rcar-du/rcar_du_group.c | 5 +
drivers/gpu/drm/rcar-du/rcar_du_regs.h | 92 +++
include/drm/drm_ioctl.h | 7 +
9 files changed, 1398 insertions(+), 7 deletions(-)
create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_cmm.c

diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 2a3b8d7..595e719 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -6,12 +6,14 @@ rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_kms.o \
rcar_du_plane.o

+rcar-du-drm-y += rcar_du_cmm.o
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
rcar_du_of_lvds_r8a7790.dtb.o \
rcar_du_of_lvds_r8a7791.dtb.o \
rcar_du_of_lvds_r8a7793.dtb.o \
rcar_du_of_lvds_r8a7795.dtb.o \
rcar_du_of_lvds_r8a7796.dtb.o
+
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o

obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_cmm.c b/drivers/gpu/drm/rcar-du/rcar_du_cmm.c
new file mode 100644
index 0000000..ac613a6e
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_cmm.c
@@ -0,0 +1,1200 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*************************************************************************/ /*
+ * DU CMM
+ *
+ * Copyright (C) 2018 Renesas Electronics Corporation
+ *
+ * License Dual MIT/GPLv2
+ *
+ * The contents of this file are subject to the MIT license as set out below.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 ("GPL") in which case the provisions
+ * of GPL are applicable instead of those above.
+ *
+ * If you wish to allow use of your version of this file only under the terms of
+ * GPL, and not to allow others to use your version of this file under the terms
+ * of the MIT license, indicate your decision by deleting the provisions above
+ * and replace them with the notice and other provisions required by GPL as set
+ * out in the file called "GPL-COPYING" included in this distribution. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under the terms of either the MIT license or GPL.
+ *
+ * This License is also included in this distribution in the file called
+ * "MIT-COPYING".
+ *
+ * EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
+ * PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS
+ * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+ * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * GPLv2:
+ * If you wish to use this file under the terms of GPL, following terms are
+ * effective.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */ /*************************************************************************/
+#include <linux/syscalls.h>
+#include <linux/workqueue.h>
+
+#include <linux/reset.h>
+#include <linux/sys_soc.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_plane.h"
+#include "rcar_du_regs.h"
+#include <linux/clk.h>
+
+/* #define DEBUG_PROCE_TIME 1 */
+
+#define CMM_LUT_NUM 256
+#define CMM_CLU_NUM (17 * 17 * 17)
+#define CMM_HGO_NUM 64
+/* rcar_du_drm.h Include */
+#define LUT_DOUBLE_BUFFER_AUTO 0
+#define LUT_DOUBLE_BUFFER_A 1
+#define LUT_DOUBLE_BUFFER_B 2
+/* DRM_RCAR_DU_CMM_WAIT_EVENT: DU-CMM done event */
+#define CMM_EVENT_CLU_DONE BIT(0)
+#define CMM_EVENT_HGO_DONE BIT(1)
+#define CMM_EVENT_LUT_DONE BIT(2)
+
+#define CLU_DOUBLE_BUFFER_AUTO 0
+#define CLU_DOUBLE_BUFFER_A 1
+#define CLU_DOUBLE_BUFFER_B 2
+enum {
+ QUE_STAT_PENDING,
+ QUE_STAT_ACTIVE,
+ QUE_STAT_DONE,
+};
+
+static const struct soc_device_attribute rcar_du_cmm_r8a7795_es1[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.*" },
+ { /* sentinel */ }
+};
+
+struct rcar_du_cmm;
+struct rcar_du_cmm_file_priv;
+
+struct rcar_du_cmm_pending_event {
+ struct list_head link;
+ struct list_head fpriv_link;
+ unsigned int event;
+ unsigned int stat;
+ unsigned long callback_data;
+ struct drm_gem_object *gem_obj;
+ struct rcar_du_cmm *du_cmm;
+ struct rcar_du_cmm_file_priv *fpriv;
+};
+
+struct cmm_module_t {
+ struct list_head list;
+ union {
+ struct {
+ struct rcar_du_cmm_pending_event *p;
+ int buf_mode;
+ bool one_side;
+ };
+ int reset;
+ };
+};
+
+struct cmm_reg_save {
+#ifdef CONFIG_PM_SLEEP
+ wait_queue_head_t wait;
+
+ u32 *lut_table;
+ u32 *clu_table;
+#endif /* CONFIG_PM_SLEEP */
+
+ u32 cm2_ctl0; /* CM2_CTL0 */
+ u32 hgo_offset; /* CMM_HGO_OFFSET */
+ u32 hgo_size; /* CMM_HGO_SIZE */
+ u32 hgo_mode; /* CMM_HGO_MODE */
+};
+
+struct rcar_du_cmm {
+ struct rcar_du_crtc *rcrtc;
+
+ /* CMM base address */
+ void __iomem *cmm_base;
+ struct clk *clock;
+
+ struct cmm_module_t lut;
+ struct cmm_module_t clu;
+ struct cmm_module_t hgo;
+
+ struct mutex lock; /* lock for register setting */
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+
+ struct cmm_reg_save reg_save;
+ bool active;
+ bool dbuf;
+ bool clu_dbuf;
+ bool init;
+ bool direct;
+ bool vsync;
+ bool authority;
+ pid_t pid;
+ bool soc_support;
+};
+
+struct rcar_du_cmm_file_priv {
+ wait_queue_head_t event_wait;
+ struct list_head list;
+ struct list_head active_list;
+ struct list_head *done_list;
+};
+
+static DEFINE_MUTEX(cmm_event_lock);
+static DEFINE_SPINLOCK(cmm_direct_lock);
+
+static inline void event_prev_cancel_locked(struct cmm_module_t *module);
+
+static inline u32 cmm_index(struct rcar_du_cmm *_cmm)
+{
+ struct rcar_du_device *rcdu = _cmm->rcrtc->group->dev;
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_R8A77965_REGS)) {
+ if ((_cmm)->rcrtc->index == 3)
+ return 2;
+ }
+ return (_cmm)->rcrtc->index;
+}
+
+#define cmm_done_list(_cmm, _fpriv) \
+ (&((_fpriv)->done_list[cmm_index(_cmm)]))
+
+static inline u32 rcar_du_cmm_read(struct rcar_du_cmm *du_cmm, u32 reg)
+{
+ return ioread32(du_cmm->cmm_base + reg);
+}
+
+static inline void rcar_du_cmm_write(struct rcar_du_cmm *du_cmm,
+ u32 reg, u32 data)
+{
+ iowrite32(data, du_cmm->cmm_base + reg);
+}
+
+/* create default CLU table data */
+static inline u32 index_to_clu_data(int index)
+{
+ int r, g, b;
+
+ r = index % 17;
+ index /= 17;
+ g = index % 17;
+ index /= 17;
+ b = index % 17;
+
+ r = (r << 20);
+ if (r > (255 << 16))
+ r = (255 << 16);
+ g = (g << 12);
+ if (g > (255 << 8))
+ g = (255 << 8);
+ b = (b << 4);
+ if (b > (255 << 0))
+ b = (255 << 0);
+
+ return r | g | b;
+}
+
+#ifdef DEBUG_PROCE_TIME
+static long long diff_timevals(struct timeval *start, struct timeval *end)
+{
+ return (end->tv_sec * 1000000LL + end->tv_usec) -
+ (start->tv_sec * 1000000LL + start->tv_usec);
+}
+#endif
+
+static void du_cmm_clk(struct rcar_du_cmm *du_cmm, bool on)
+{
+ if (on)
+ clk_prepare_enable(du_cmm->clock);
+ else
+ clk_disable_unprepare(du_cmm->clock);
+}
+
+int rcar_du_cmm_start_stop(struct rcar_du_crtc *rcrtc, bool on)
+{
+ struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+ int i;
+ u32 table_data;
+ const struct drm_display_mode *mode;
+ int w, h, x, y;
+
+ if (!du_cmm)
+ return -EINVAL;
+
+ mutex_lock(&du_cmm->lock);
+
+ if (!on) {
+ du_cmm->active = false;
+
+ rcar_du_cmm_write(du_cmm, CMM_LUT_CTRL, 0x00000000);
+ rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL, 0x00000000);
+
+ du_cmm_clk(du_cmm, false);
+
+ goto end;
+ }
+
+ du_cmm_clk(du_cmm, true);
+
+ if (du_cmm->init)
+ goto init_done;
+
+ du_cmm->init = true;
+
+ mode = &du_cmm->rcrtc->crtc.mode;
+
+ x = (du_cmm->reg_save.hgo_offset >> 16) & 0xFFFF;
+ y = (du_cmm->reg_save.hgo_offset >> 0) & 0xFFFF;
+ w = (du_cmm->reg_save.hgo_size >> 16) & 0xFFFF;
+ h = (du_cmm->reg_save.hgo_size >> 0) & 0xFFFF;
+ if ((mode->hdisplay < (w + x)) || w == 0) {
+ x = 0;
+ w = mode->hdisplay;
+ }
+ if ((mode->vdisplay < (h + y)) || h == 0) {
+ y = 0;
+ h = mode->vdisplay;
+ }
+ du_cmm->reg_save.hgo_offset = (x << 16) | y;
+ du_cmm->reg_save.hgo_size = (w << 16) | h;
+
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_VPOL;
+ else
+ du_cmm->reg_save.cm2_ctl0 &= ~CMM_CTL0_VPOL;
+
+ rcar_du_cmm_write(du_cmm, CM2_CTL0, du_cmm->reg_save.cm2_ctl0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_OFFSET, du_cmm->reg_save.hgo_offset);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_SIZE, du_cmm->reg_save.hgo_size);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_MODE, du_cmm->reg_save.hgo_mode);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB_TH, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB0_H, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB0_V, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB1_H, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB1_V, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB2_H, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB2_V, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB3_H, 0);
+ rcar_du_cmm_write(du_cmm, CMM_HGO_LB3_V, 0);
+
+ /* init color table */
+ for (i = 0; i < CMM_LUT_NUM; i++) {
+ #ifdef CONFIG_PM_SLEEP
+ table_data = du_cmm->reg_save.lut_table[i];
+ #else
+ table_data = ((i << 16) | (i << 8) | (i << 0));
+ #endif /* CONFIG_PM_SLEEP */
+ rcar_du_cmm_write(du_cmm, CMM_LUT_TBLA(i), table_data);
+
+ if (du_cmm->dbuf)
+ rcar_du_cmm_write(du_cmm, CMM_LUT_TBLB(i),
+ table_data);
+ }
+
+ rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL,
+ CMM_CLU_CTRL_AAI | CMM_CLU_CTRL_MVS);
+
+ rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR, 0);
+ if (du_cmm->clu_dbuf)
+ rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR2, 0);
+
+ for (i = 0; i < CMM_CLU_NUM; i++) {
+ #ifdef CONFIG_PM_SLEEP
+ table_data = du_cmm->reg_save.clu_table[i];
+ #else
+ table_data = index_to_clu_data(i);
+ #endif /* CONFIG_PM_SLEEP */
+ rcar_du_cmm_write(du_cmm, CMM_CLU_DATA, table_data);
+
+ if (du_cmm->dbuf)
+ rcar_du_cmm_write(du_cmm, CMM_CLU_DATA2,
+ table_data);
+ }
+
+init_done:
+ /* enable color table */
+ rcar_du_cmm_write(du_cmm, CMM_LUT_CTRL, CMM_LUT_CTRL_EN);
+ rcar_du_cmm_write(du_cmm, CMM_CLU_CTRL, CMM_CLU_CTRL_AAI |
+ CMM_CLU_CTRL_MVS | CMM_CLU_CTRL_EN);
+
+ du_cmm->active = true;
+end:
+ mutex_unlock(&du_cmm->lock);
+
+ return 0;
+}
+
+#define gem_to_vaddr(gem_obj) \
+ (container_of((gem_obj), struct drm_gem_cma_object, base)->vaddr)
+
+static inline void cmm_vblank_put(struct rcar_du_cmm_pending_event *p)
+{
+ if (p->du_cmm)
+ drm_crtc_vblank_put(&p->du_cmm->rcrtc->crtc);
+}
+
+static inline void
+cmm_gem_object_unreference(struct rcar_du_cmm_pending_event *p)
+{
+ if (p->gem_obj)
+ drm_gem_object_unreference_unlocked(p->gem_obj);
+}
+
+static inline void _event_done_locked(struct rcar_du_cmm_pending_event *p)
+{
+ cmm_gem_object_unreference(p);
+
+ if (p->fpriv) {
+ p->stat = QUE_STAT_DONE;
+ list_del(&p->link); /* delete from p->fpriv->active_list */
+ list_add_tail(&p->link, cmm_done_list(p->du_cmm, p->fpriv));
+ wake_up_interruptible(&p->fpriv->event_wait);
+ } else {
+ /* link deleted by rcar_du_cmm_postclose */
+ kfree(p);
+ }
+}
+
+/* cancel from active_list (case of LUT/CLU double buffer mode) */
+static inline void event_prev_cancel_locked(struct cmm_module_t *module)
+{
+ struct rcar_du_cmm_pending_event *p = module->p;
+
+ if (!p)
+ return;
+
+ module->p = NULL;
+
+ _event_done_locked(p);
+}
+
+static inline void event_done(struct rcar_du_cmm_pending_event *p)
+{
+ /* vblank is put */
+
+ mutex_lock(&cmm_event_lock);
+
+ _event_done_locked(p);
+
+ mutex_unlock(&cmm_event_lock);
+}
+
+static inline void lc_event_done(struct cmm_module_t *module,
+ struct rcar_du_cmm_pending_event *p,
+ bool done)
+{
+ /* vblank is put */
+
+ mutex_lock(&cmm_event_lock);
+
+ if (!done && list_empty(&module->list))
+ module->p = p;
+ else
+ _event_done_locked(p);
+
+ mutex_unlock(&cmm_event_lock);
+}
+
+static inline struct rcar_du_cmm_pending_event *
+event_pop_locked(struct cmm_module_t *module)
+{
+ struct rcar_du_cmm_pending_event *p =
+ list_first_entry(&module->list,
+ struct rcar_du_cmm_pending_event,
+ link);
+
+ p->stat = QUE_STAT_ACTIVE;
+ list_del(&p->link); /* delete from du_cmm->[lut|clu|hgo].list */
+ list_add_tail(&p->link, &p->fpriv->active_list);
+ cmm_vblank_put(p);
+
+ return p;
+}
+
+struct rcar_du_cmm_work_stat {
+ union {
+ struct {
+ struct rcar_du_cmm_pending_event *p;
+ bool done;
+ bool table_copy;
+ };
+ struct {
+ struct rcar_du_cmm_pending_event *p2;
+ bool reset;
+ };
+ };
+};
+
+static inline void one_side(struct rcar_du_cmm *du_cmm,
+ struct cmm_module_t *module,
+ bool on)
+{
+ if (on && !module->one_side) {
+ module->one_side = true;
+ drm_crtc_vblank_get(&du_cmm->rcrtc->crtc);
+ } else if (!on && module->one_side) {
+ module->one_side = false;
+ drm_crtc_vblank_put(&du_cmm->rcrtc->crtc);
+ }
+}
+
+/* pop LUT que */
+static int lut_pop_locked(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ bool is_one_side = false;
+
+ stat->done = true;
+ stat->table_copy = false;
+
+ if (!list_empty(&du_cmm->lut.list)) {
+ stat->p = event_pop_locked(&du_cmm->lut);
+
+ /* prev lut table */
+ event_prev_cancel_locked(&du_cmm->lut);
+
+ if (du_cmm->lut.buf_mode == LUT_DOUBLE_BUFFER_AUTO) {
+ is_one_side = true;
+ if (list_empty(&du_cmm->lut.list))
+ stat->done = false;
+ }
+
+ } else if (du_cmm->lut.p) {
+ /* prev lut table */
+ stat->p = du_cmm->lut.p;
+ du_cmm->lut.p = NULL;
+ } else {
+ stat->done = false;
+ stat->p = NULL;
+ stat->table_copy = du_cmm->lut.one_side;
+ }
+
+ one_side(du_cmm, &du_cmm->lut, is_one_side);
+
+ return 0;
+}
+
+static int lut_table_copy(struct rcar_du_cmm *du_cmm)
+{
+ int i;
+ u32 src, dst;
+
+ if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+ dst = CMM_LUT_TBLA(0);
+ src = CMM_LUT_TBLB(0);
+ } else {
+ dst = CMM_LUT_TBLB(0);
+ src = CMM_LUT_TBLA(0);
+ }
+
+ for (i = 0; i < CMM_LUT_NUM; i++) {
+ rcar_du_cmm_write(du_cmm, dst, rcar_du_cmm_read(du_cmm, src));
+ dst += 4;
+ src += 4;
+ }
+
+ return 0;
+}
+
+/* set 1D look up table */
+static int lut_set(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ int i;
+ u32 lut_base;
+ u32 *lut_buf;
+
+ if (!stat->p) {
+ if (stat->table_copy)
+ lut_table_copy(du_cmm);
+ return 0; /* skip */
+ }
+
+ /* set LUT */
+ switch (du_cmm->lut.buf_mode) {
+ case LUT_DOUBLE_BUFFER_A:
+ lut_base = CMM_LUT_TBLA(0);
+ break;
+
+ case LUT_DOUBLE_BUFFER_AUTO:
+ if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+ lut_base = CMM_LUT_TBLA(0);
+ break;
+ }
+ lut_base = CMM_LUT_TBLB(0);
+ break;
+ case LUT_DOUBLE_BUFFER_B:
+ lut_base = CMM_LUT_TBLB(0);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ lut_buf = gem_to_vaddr(stat->p->gem_obj);
+ for (i = 0; i < CMM_LUT_NUM; i++)
+ rcar_du_cmm_write(du_cmm, lut_base + i * 4, lut_buf[i]);
+
+ lc_event_done(&du_cmm->lut, stat->p, stat->done);
+
+ return 0;
+}
+
+/* pop CLU que */
+static int clu_pop_locked(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ bool is_one_side = false;
+
+ stat->done = true;
+ stat->table_copy = false;
+
+ if (!list_empty(&du_cmm->clu.list)) {
+ stat->p = event_pop_locked(&du_cmm->clu);
+
+ /* prev clu table */
+ event_prev_cancel_locked(&du_cmm->clu);
+
+ if (du_cmm->clu.buf_mode == CLU_DOUBLE_BUFFER_AUTO) {
+ is_one_side = true;
+ if (list_empty(&du_cmm->clu.list))
+ stat->done = false;
+ }
+
+ } else if (du_cmm->clu.p) {
+ /* prev clu table */
+ stat->p = du_cmm->clu.p;
+ du_cmm->clu.p = NULL;
+ } else {
+ stat->done = false;
+ stat->p = NULL;
+ stat->table_copy = du_cmm->clu.one_side;
+ }
+
+ one_side(du_cmm, &du_cmm->clu, is_one_side);
+
+ return 0;
+}
+
+static int clu_table_copy(struct rcar_du_cmm *du_cmm)
+{
+ int i, j, k;
+ u32 src_addr, src_data, dst_addr, dst_data;
+
+ if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+ dst_addr = CMM_CLU_ADDR;
+ dst_data = CMM_CLU_DATA;
+ src_addr = CMM_CLU_ADDR2;
+ src_data = CMM_CLU_DATA2;
+ } else {
+ dst_addr = CMM_CLU_ADDR2;
+ dst_data = CMM_CLU_DATA2;
+ src_addr = CMM_CLU_ADDR;
+ src_data = CMM_CLU_DATA;
+ }
+
+ rcar_du_cmm_write(du_cmm, dst_addr, 0);
+ for (i = 0; i < 17; i++) {
+ for (j = 0; j < 17; j++) {
+ for (k = 0; k < 17; k++) {
+ rcar_du_cmm_write(du_cmm, src_addr,
+ (k << 16) | (j << 8) |
+ (i << 0));
+ rcar_du_cmm_write(du_cmm, dst_data,
+ rcar_du_cmm_read(du_cmm,
+ src_data));
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* set 3D look up table */
+static int clu_set(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ int i;
+ u32 addr_reg, data_reg;
+ u32 *clu_buf;
+
+ if (!stat->p) {
+ if (stat->table_copy)
+ clu_table_copy(du_cmm);
+ return 0; /* skip */
+ }
+
+ /* set CLU */
+ switch (du_cmm->clu.buf_mode) {
+ case CLU_DOUBLE_BUFFER_A:
+ addr_reg = CMM_CLU_ADDR;
+ data_reg = CMM_CLU_DATA;
+ break;
+
+ case CLU_DOUBLE_BUFFER_AUTO:
+ if (rcar_du_cmm_read(du_cmm, CM2_CTL1) & CMM_CTL1_BFS) {
+ addr_reg = CMM_CLU_ADDR;
+ data_reg = CMM_CLU_DATA;
+ break;
+ }
+ addr_reg = CMM_CLU_ADDR2;
+ data_reg = CMM_CLU_DATA2;
+ break;
+ case CLU_DOUBLE_BUFFER_B:
+ addr_reg = CMM_CLU_ADDR2;
+ data_reg = CMM_CLU_DATA2;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ clu_buf = gem_to_vaddr(stat->p->gem_obj);
+ rcar_du_cmm_write(du_cmm, addr_reg, 0);
+ for (i = 0; i < CMM_CLU_NUM; i++)
+ rcar_du_cmm_write(du_cmm, data_reg, clu_buf[i]);
+
+ lc_event_done(&du_cmm->clu, stat->p, stat->done);
+
+ return 0;
+}
+
+/* pop HGO que */
+static int hgo_pop_locked(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ struct rcar_du_cmm_pending_event *_p = NULL;
+
+ if (!list_empty(&du_cmm->hgo.list))
+ _p = event_pop_locked(&du_cmm->hgo);
+
+ if (du_cmm->hgo.reset) {
+ drm_crtc_vblank_put(&du_cmm->rcrtc->crtc);
+ du_cmm->hgo.reset = 0;
+ stat->reset = true;
+ } else {
+ stat->reset = false;
+ }
+
+ stat->p2 = _p;
+
+ return 0;
+}
+
+/* get histogram */
+static int hgo_get(struct rcar_du_cmm *du_cmm,
+ struct rcar_du_cmm_work_stat *stat)
+{
+ int i, j;
+ const u32 histo_offset[3] = {
+ CMM_HGO_R_HISTO(0),
+ CMM_HGO_G_HISTO(0),
+ CMM_HGO_B_HISTO(0),
+ };
+ void *vaddr;
+
+ if (!stat->p2) {
+ if (stat->reset)
+ goto hgo_reset;
+
+ return 0; /* skip */
+ }
+
+ vaddr = gem_to_vaddr(stat->p2->gem_obj);
+ for (i = 0; i < 3; i++) {
+ u32 *hgo_buf = vaddr + CMM_HGO_NUM * 4 * i;
+
+ for (j = 0; j < CMM_HGO_NUM; j++)
+ hgo_buf[j] = rcar_du_cmm_read(du_cmm,
+ histo_offset[i] + j * 4);
+ }
+
+ event_done(stat->p2);
+
+hgo_reset:
+ rcar_du_cmm_write(du_cmm, CMM_HGO_REGRST, CMM_HGO_REGRST_RCLEA);
+
+ return 0;
+}
+
+static bool du_cmm_vsync_get(struct rcar_du_cmm *du_cmm)
+{
+ unsigned long flags;
+ bool vsync;
+
+ spin_lock_irqsave(&cmm_direct_lock, flags);
+ vsync = du_cmm->vsync;
+ du_cmm->vsync = false;
+ spin_unlock_irqrestore(&cmm_direct_lock, flags);
+
+ return vsync;
+}
+
+static void du_cmm_vsync_set(struct rcar_du_cmm *du_cmm, bool vsync)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cmm_direct_lock, flags);
+ du_cmm->vsync = vsync;
+ spin_unlock_irqrestore(&cmm_direct_lock, flags);
+}
+
+static void du_cmm_work(struct work_struct *work)
+{
+ struct rcar_du_cmm *du_cmm =
+ container_of(work, struct rcar_du_cmm, work);
+ struct rcar_du_cmm_work_stat s_lut;
+ struct rcar_du_cmm_work_stat s_clu;
+ struct rcar_du_cmm_work_stat s_hgo;
+#ifdef DEBUG_PROCE_TIME
+ struct timeval start_time, end_time;
+ unsigned long lut_time, clu_time, hgo_time;
+#endif
+ bool vsync_status = false;
+
+ memset(&s_lut, 0, sizeof(struct rcar_du_cmm_work_stat));
+ memset(&s_clu, 0, sizeof(struct rcar_du_cmm_work_stat));
+ memset(&s_hgo, 0, sizeof(struct rcar_du_cmm_work_stat));
+
+ vsync_status = du_cmm_vsync_get(du_cmm);
+
+ mutex_lock(&cmm_event_lock);
+
+ lut_pop_locked(du_cmm, &s_lut);
+ clu_pop_locked(du_cmm, &s_clu);
+ if (vsync_status)
+ hgo_pop_locked(du_cmm, &s_hgo);
+
+ mutex_unlock(&cmm_event_lock);
+
+ /* set LUT */
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&start_time);
+#endif
+ lut_set(du_cmm, &s_lut);
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&end_time);
+ lut_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+ /* set CLU */
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&start_time);
+#endif
+ clu_set(du_cmm, &s_clu);
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&end_time);
+ clu_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+ /* get HGO */
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&start_time);
+#endif
+ if (vsync_status)
+ hgo_get(du_cmm, &s_hgo);
+#ifdef DEBUG_PROCE_TIME
+ do_gettimeofday(&end_time);
+ hgo_time = (long)diff_timevals(&start_time, &end_time);
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+ wake_up_interruptible(&du_cmm->reg_save.wait);
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef DEBUG_PROCE_TIME
+ {
+ struct rcar_du_device *rcdu = du_cmm->rcrtc->group->dev;
+
+ if (s_lut.p)
+ dev_info(rcdu->dev, "LUT %ld usec.\n", lut_time);
+ if (s_clu.p)
+ dev_info(rcdu->dev, "LUT %ld usec.\n", clu_time);
+ if (s_hgo.p2)
+ dev_info(rcdu->dev, "HGO %ld usec.\n", hgo_time);
+ }
+#endif
+}
+
+static int du_cmm_que_empty(struct rcar_du_cmm *du_cmm)
+{
+ if (list_empty(&du_cmm->lut.list) && !du_cmm->lut.p &&
+ !du_cmm->lut.one_side &&
+ list_empty(&du_cmm->clu.list) && !du_cmm->clu.p &&
+ !du_cmm->clu.one_side &&
+ list_empty(&du_cmm->hgo.list) && !du_cmm->hgo.reset)
+ return 1;
+
+ return 0;
+}
+
+void rcar_du_cmm_kick(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+
+ if (!du_cmm)
+ return;
+
+ if (!du_cmm->active)
+ return;
+
+ if (!du_cmm_que_empty(du_cmm)) {
+ du_cmm_vsync_set(du_cmm, true);
+ queue_work(du_cmm->workqueue, &du_cmm->work);
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+int rcar_du_cmm_pm_suspend(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_cmm *du_cmm = rcrtc->cmm_handle;
+ struct rcar_du_device *rcdu = rcrtc->group->dev;
+ int i, j, k, index;
+ int ret;
+
+ if (!du_cmm)
+ return 0;
+
+ ret = wait_event_timeout(du_cmm->reg_save.wait,
+ du_cmm_que_empty(du_cmm),
+ msecs_to_jiffies(500));
+ if (ret == 0)
+ dev_err(rcdu->dev, "rcar-du cmm suspend : timeout\n");
+
+ if (!du_cmm->init)
+ return 0;
+
+ du_cmm->init = false;
+
+ if (!du_cmm->active)
+ du_cmm_clk(du_cmm, true);
+
+ /* table save */
+ for (i = 0; i < CMM_LUT_NUM; i++) {
+ du_cmm->reg_save.lut_table[i] =
+ rcar_du_cmm_read(du_cmm, CMM_LUT_TBLA(i));
+ }
+
+ index = 0;
+ for (i = 0; i < 17; i++) {
+ for (j = 0; j < 17; j++) {
+ for (k = 0; k < 17; k++) {
+ rcar_du_cmm_write(du_cmm, CMM_CLU_ADDR,
+ (k << 16) | (j << 8) |
+ (i << 0));
+ du_cmm->reg_save.clu_table[index++] =
+ rcar_du_cmm_read(du_cmm, CMM_CLU_DATA);
+ }
+ }
+ }
+
+ if (!du_cmm->active)
+ du_cmm_clk(du_cmm, false);
+
+ return 0;
+}
+
+int rcar_du_cmm_pm_resume(struct rcar_du_crtc *rcrtc)
+{
+ /* none */
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+int rcar_du_cmm_driver_open(struct drm_device *dev, struct drm_file *file_priv)
+{
+ struct rcar_du_device *rcdu = dev->dev_private;
+ struct rcar_du_cmm_file_priv *fpriv;
+ int i;
+
+ if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+ return 0;
+
+ file_priv->driver_priv = NULL;
+
+ fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);
+ if (unlikely(!fpriv))
+ return -ENOMEM;
+
+ fpriv->done_list = kcalloc(rcdu->info->num_crtcs,
+ sizeof(*fpriv->done_list),
+ GFP_KERNEL);
+ if (unlikely(!fpriv->done_list)) {
+ kfree(fpriv);
+ return -ENOMEM;
+ }
+
+ init_waitqueue_head(&fpriv->event_wait);
+ INIT_LIST_HEAD(&fpriv->list);
+ INIT_LIST_HEAD(&fpriv->active_list);
+ for (i = 0; i < rcdu->info->num_crtcs; i++)
+ INIT_LIST_HEAD(&fpriv->done_list[i]);
+
+ file_priv->driver_priv = fpriv;
+
+ return 0;
+}
+
+void rcar_du_cmm_postclose(struct drm_device *dev, struct drm_file *file_priv)
+{
+ struct rcar_du_device *rcdu = dev->dev_private;
+ struct rcar_du_cmm_file_priv *fpriv = file_priv->driver_priv;
+ struct rcar_du_cmm_pending_event *p, *pt;
+ struct rcar_du_crtc *rcrtc;
+ struct rcar_du_cmm *du_cmm;
+ int i, crtcs_cnt, ret;
+ u32 table_data;
+
+ if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+ return;
+
+ mutex_lock(&cmm_event_lock);
+
+ /* Unlink file priv events */
+ list_for_each_entry_safe(p, pt, &fpriv->list, fpriv_link) {
+ list_del(&p->fpriv_link);
+ list_del(&p->link);
+ switch (p->stat) {
+ case QUE_STAT_PENDING:
+ cmm_vblank_put(p);
+ cmm_gem_object_unreference(p);
+ kfree(p);
+ break;
+ case QUE_STAT_DONE:
+ kfree(p);
+ break;
+ case QUE_STAT_ACTIVE:
+ p->fpriv = NULL;
+ break;
+ }
+ }
+
+ mutex_unlock(&cmm_event_lock);
+
+ kfree(fpriv->done_list);
+ kfree(fpriv);
+ file_priv->driver_priv = NULL;
+
+ for (crtcs_cnt = 0; crtcs_cnt < rcdu->num_crtcs; crtcs_cnt++) {
+ rcrtc = &rcdu->crtcs[crtcs_cnt];
+ du_cmm = rcrtc->cmm_handle;
+ if (du_cmm->authority && du_cmm->pid == task_pid_nr(current)) {
+ du_cmm->authority = false;
+ du_cmm->pid = 0;
+ ret = wait_event_timeout(du_cmm->reg_save.wait,
+ du_cmm_que_empty(du_cmm),
+ msecs_to_jiffies(500));
+ if (ret == 0)
+ dev_err(rcdu->dev, "rcar-du cmm close : timeout\n");
+
+ for (i = 0; i < CMM_LUT_NUM; i++)
+ du_cmm->reg_save.lut_table[i] = (i << 16) |
+ (i << 8) |
+ (i << 0);
+
+ for (i = 0; i < CMM_CLU_NUM; i++) {
+ du_cmm->reg_save.clu_table[i] =
+ index_to_clu_data(i);
+ }
+
+ for (i = 0; i < CMM_LUT_NUM; i++) {
+#ifdef CONFIG_PM_SLEEP
+ table_data = du_cmm->reg_save.lut_table[i];
+#else
+ table_data = ((i << 16) | (i << 8) | (i << 0));
+#endif /* CONFIG_PM_SLEEP */
+ rcar_du_cmm_write(du_cmm, CMM_LUT_TBLA(i),
+ table_data);
+ if (du_cmm->dbuf) {
+ rcar_du_cmm_write(du_cmm,
+ CMM_LUT_TBLB(i),
+ table_data);
+ }
+ }
+
+ for (i = 0; i < CMM_CLU_NUM; i++) {
+#ifdef CONFIG_PM_SLEEP
+ table_data = du_cmm->reg_save.clu_table[i];
+#else
+ table_data = index_to_clu_data(i);
+#endif /* CONFIG_PM_SLEEP */
+ rcar_du_cmm_write(du_cmm, CMM_CLU_DATA,
+ table_data);
+
+ if (du_cmm->dbuf) {
+ rcar_du_cmm_write(du_cmm, CMM_CLU_DATA2,
+ table_data);
+ }
+ }
+ }
+ }
+}
+
+int rcar_du_cmm_init(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_cmm *du_cmm;
+ int ret;
+ int i;
+ struct rcar_du_device *rcdu = rcrtc->group->dev;
+ char name[64];
+ struct resource *mem;
+
+ if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM))
+ return 0;
+
+ du_cmm = devm_kzalloc(rcdu->dev, sizeof(*du_cmm), GFP_KERNEL);
+ if (!du_cmm) {
+ ret = -ENOMEM;
+ goto error_alloc;
+ }
+
+ /* DU-CMM mapping */
+ sprintf(name, "cmm.%u", rcrtc->index);
+ mem = platform_get_resource_byname(to_platform_device(rcdu->dev),
+ IORESOURCE_MEM, name);
+ if (!mem) {
+ dev_err(rcdu->dev, "rcar-du cmm init : failed to get memory resource\n");
+ ret = -EINVAL;
+ goto error_mapping_cmm;
+ }
+ du_cmm->cmm_base = devm_ioremap_nocache(rcdu->dev, mem->start,
+ resource_size(mem));
+ if (!du_cmm->cmm_base) {
+ dev_err(rcdu->dev, "rcar-du cmm init : failed to map iomem\n");
+ ret = -EINVAL;
+ goto error_mapping_cmm;
+ }
+ du_cmm->clock = devm_clk_get(rcdu->dev, name);
+ if (IS_ERR(du_cmm->clock)) {
+ dev_err(rcdu->dev, "failed to get clock\n");
+ ret = PTR_ERR(du_cmm->clock);
+ goto error_clock_cmm;
+ }
+
+ du_cmm->rcrtc = rcrtc;
+
+ du_cmm->reg_save.cm2_ctl0 = 0;
+ du_cmm->reg_save.hgo_offset = 0;
+ du_cmm->reg_save.hgo_size = 0;
+ du_cmm->reg_save.hgo_mode = 0;
+
+ du_cmm->dbuf = rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM_LUT_DBUF);
+ if (du_cmm->dbuf) {
+ du_cmm->lut.buf_mode = LUT_DOUBLE_BUFFER_AUTO;
+ du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_DBUF;
+ } else {
+ dev_err(rcdu->dev, "single buffer is not supported.\n");
+ du_cmm->dbuf = true;
+ du_cmm->lut.buf_mode = LUT_DOUBLE_BUFFER_AUTO;
+ du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_DBUF;
+ }
+
+ du_cmm->clu_dbuf = rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM_CLU_DBUF);
+ if (du_cmm->clu_dbuf) {
+ du_cmm->clu.buf_mode = CLU_DOUBLE_BUFFER_AUTO;
+ du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_CLUDB;
+ } else {
+ dev_err(rcdu->dev, "single buffer is not supported.\n");
+ du_cmm->clu_dbuf = true;
+ du_cmm->clu.buf_mode = CLU_DOUBLE_BUFFER_AUTO;
+ du_cmm->reg_save.cm2_ctl0 |= CMM_CTL0_CLUDB;
+ }
+
+#ifdef CONFIG_PM_SLEEP
+ du_cmm->reg_save.lut_table =
+ devm_kzalloc(rcdu->dev, CMM_LUT_NUM * 4, GFP_KERNEL);
+ if (!du_cmm->reg_save.lut_table) {
+ ret = -ENOMEM;
+ goto error_lut_reg_save_buf;
+ }
+ for (i = 0; i < CMM_LUT_NUM; i++)
+ du_cmm->reg_save.lut_table[i] = (i << 16) | (i << 8) | (i << 0);
+
+ du_cmm->reg_save.clu_table =
+ devm_kzalloc(rcdu->dev, CMM_CLU_NUM * 4, GFP_KERNEL);
+ if (!du_cmm->reg_save.clu_table) {
+ ret = -ENOMEM;
+ goto error_clu_reg_save_buf;
+ }
+ for (i = 0; i < CMM_CLU_NUM; i++)
+ du_cmm->reg_save.clu_table[i] = index_to_clu_data(i);
+
+ init_waitqueue_head(&du_cmm->reg_save.wait);
+#endif /* CONFIG_PM_SLEEP */
+ if (soc_device_match(rcar_du_cmm_r8a7795_es1))
+ du_cmm->soc_support = false;
+ else
+ du_cmm->soc_support = true;
+
+ du_cmm->active = false;
+ du_cmm->init = false;
+ du_cmm->direct = true;
+
+ mutex_init(&du_cmm->lock);
+ INIT_LIST_HEAD(&du_cmm->lut.list);
+ du_cmm->lut.p = NULL;
+ du_cmm->lut.one_side = false;
+ INIT_LIST_HEAD(&du_cmm->clu.list);
+ du_cmm->clu.p = NULL;
+ du_cmm->clu.one_side = false;
+ INIT_LIST_HEAD(&du_cmm->hgo.list);
+ du_cmm->hgo.reset = 0;
+
+ sprintf(name, "du-cmm%d", rcrtc->index);
+ du_cmm->workqueue = create_singlethread_workqueue(name);
+ INIT_WORK(&du_cmm->work, du_cmm_work);
+
+ rcrtc->cmm_handle = du_cmm;
+
+ dev_info(rcdu->dev, "DU%d use CMM(%s buffer)\n",
+ rcrtc->index, du_cmm->dbuf ? "Double" : "Single");
+
+ return 0;
+
+#ifdef CONFIG_PM_SLEEP
+error_clu_reg_save_buf:
+error_lut_reg_save_buf:
+#endif /* CONFIG_PM_SLEEP */
+error_clock_cmm:
+ devm_iounmap(rcdu->dev, du_cmm->cmm_base);
+error_mapping_cmm:
+ devm_kfree(rcdu->dev, du_cmm);
+error_alloc:
+ return ret;
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 15dc9ca..864fb94 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -296,6 +296,19 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19);
rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start +
mode->hdisplay - 19);
+ if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM)) {
+ rcar_du_crtc_write(rcrtc, HDSR, mode->htotal -
+ mode->hsync_start - 19 - 25);
+ rcar_du_crtc_write(rcrtc, HDER, mode->htotal -
+ mode->hsync_start +
+ mode->hdisplay - 19 - 25);
+ } else {
+ rcar_du_crtc_write(rcrtc, HDSR, mode->htotal -
+ mode->hsync_start - 19);
+ rcar_du_crtc_write(rcrtc, HDER, mode->htotal -
+ mode->hsync_start +
+ mode->hdisplay - 19);
+ }
rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end -
mode->hsync_start - 1);
rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1);
@@ -530,6 +543,9 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
DSYSR_TVM_MASTER);

rcar_du_group_start_stop(rcrtc->group, true);
+
+ if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+ rcar_du_cmm_start_stop(rcrtc, true);
}

static void rcar_du_crtc_disable_planes(struct rcar_du_crtc *rcrtc)
@@ -565,6 +581,9 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;

+ if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+ rcar_du_cmm_start_stop(rcrtc, false);
+
/*
* Disable all planes and wait for the change to take effect. This is
* required as the plane enable registers are updated on vblank, and no
@@ -899,6 +918,9 @@ static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
rcar_du_crtc_finish_page_flip(rcrtc);
}

+ if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_CMM))
+ rcar_du_cmm_kick(rcrtc);
+
ret = IRQ_HANDLED;
}

@@ -999,5 +1021,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
return ret;
}

+ rcar_du_cmm_init(rcrtc);
+
return 0;
}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 7680cb2..74e0a22 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -67,6 +67,10 @@ struct rcar_du_crtc {
struct rcar_du_group *group;
struct rcar_du_vsp *vsp;
unsigned int vsp_pipe;
+ int lvds_ch;
+
+ void *cmm_handle;
+
};

#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
@@ -104,4 +108,16 @@ void rcar_du_crtc_route_output(struct drm_crtc *crtc,
enum rcar_du_output output);
void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc);

+/* DU-CMM functions */
+int rcar_du_cmm_init(struct rcar_du_crtc *rcrtc);
+int rcar_du_cmm_driver_open(struct drm_device *dev, struct drm_file *file_priv);
+void rcar_du_cmm_postclose(struct drm_device *dev, struct drm_file *file_priv);
+int rcar_du_cmm_start_stop(struct rcar_du_crtc *rcrtc, bool on);
+void rcar_du_cmm_kick(struct rcar_du_crtc *rcrtc);
+
+#ifdef CONFIG_PM_SLEEP
+int rcar_du_cmm_pm_suspend(struct rcar_du_crtc *rcrtc);
+int rcar_du_cmm_pm_resume(struct rcar_du_crtc *rcrtc);
+#endif /* CONFIG_PM_SLEEP */
+
#endif /* __RCAR_DU_CRTC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 02aee6c..838b7c9 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -26,8 +26,8 @@
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
-
#include "rcar_du_drv.h"
+#include "rcar_du_encoder.h"
#include "rcar_du_kms.h"
#include "rcar_du_of.h"
#include "rcar_du_regs.h"
@@ -128,7 +128,9 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
static const struct rcar_du_device_info rcar_du_r8a7791_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
- | RCAR_DU_FEATURE_EXT_CTRL_REGS,
+ | RCAR_DU_FEATURE_EXT_CTRL_REGS
+ | RCAR_DU_FEATURE_CMM,
+ .num_crtcs = 2,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
@@ -190,7 +192,10 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS
- | RCAR_DU_FEATURE_VSP1_SOURCE,
+ | RCAR_DU_FEATURE_VSP1_SOURCE
+ | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+ | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+ .num_crtcs = 4,
.channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
@@ -222,7 +227,10 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS
- | RCAR_DU_FEATURE_VSP1_SOURCE,
+ | RCAR_DU_FEATURE_VSP1_SOURCE
+ | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+ | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+ .num_crtcs = 3,
.channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
@@ -250,7 +258,11 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS
- | RCAR_DU_FEATURE_VSP1_SOURCE,
+ | RCAR_DU_FEATURE_VSP1_SOURCE
+ | RCAR_DU_FEATURE_R8A77965_REGS
+ | RCAR_DU_FEATURE_CMM | RCAR_DU_FEATURE_CMM_LUT_DBUF
+ | RCAR_DU_FEATURE_CMM_CLU_DBUF,
+ .num_crtcs = 3,
.channels_mask = BIT(3) | BIT(1) | BIT(0),
.routes = {
/*
@@ -328,6 +340,8 @@ DEFINE_DRM_GEM_CMA_FOPS(rcar_du_fops);
static struct drm_driver rcar_du_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
| DRIVER_ATOMIC,
+ .open = rcar_du_cmm_driver_open,
+ .postclose = rcar_du_cmm_postclose,
.lastclose = rcar_du_lastclose,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
@@ -358,6 +372,12 @@ static int rcar_du_pm_suspend(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
struct drm_atomic_state *state;
+ int i;
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM)) {
+ for (i = 0; i < rcdu->num_crtcs; ++i)
+ rcar_du_cmm_pm_suspend(&rcdu->crtcs[i]);
+ }

drm_kms_helper_poll_disable(rcdu->ddev);
drm_fbdev_cma_set_suspend_unlocked(rcdu->fbdev, true);
@@ -377,7 +397,20 @@ static int rcar_du_pm_suspend(struct device *dev)
static int rcar_du_pm_resume(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+#if IS_ENABLED(CONFIG_DRM_RCAR_DW_HDMI)
+ struct drm_encoder *encoder;
+ int i;
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CMM)) {
+ for (i = 0; (i < rcdu->num_crtcs); ++i)
+ rcar_du_cmm_pm_resume(&rcdu->crtcs[i]);
+ }

+ list_for_each_entry(encoder, &rcdu->ddev->mode_config.encoder_list,
+ head) {
+ to_rcar_encoder(encoder);
+ }
+#endif
drm_atomic_helper_resume(rcdu->ddev, rcdu->suspend_state);
drm_fbdev_cma_set_suspend_unlocked(rcdu->fbdev, false);
drm_kms_helper_poll_enable(rcdu->ddev);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index b3a25e8..f2afe36 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -30,8 +30,19 @@ struct rcar_du_device;
#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */
#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */
#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */
-
-#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */
+/* Use R8A77965 registers */
+#define RCAR_DU_FEATURE_R8A77965_REGS BIT(3)
+
+/* Has DEF7R register & CMM */
+#define RCAR_DU_FEATURE_CMM BIT(10)
+/* Has CMM LUT Double buffer */
+#define RCAR_DU_FEATURE_CMM_LUT_DBUF BIT(11)
+/* Has CMM CLU Double buffer */
+#define RCAR_DU_FEATURE_CMM_CLU_DBUF BIT(12)
+/* Align pitches to 128 bytes */
+#define RCAR_DU_QUIRK_ALIGN_128B BIT(0)
+/* LVDS lanes 1 and 3 inverted */
+#define RCAR_DU_QUIRK_LVDS_LANES BIT(1)

/*
* struct rcar_du_output_routing - Output routing specification
@@ -61,6 +72,7 @@ struct rcar_du_device_info {
unsigned int features;
unsigned int quirks;
unsigned int channels_mask;
+ unsigned int num_crtcs;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds;
unsigned int dpll_ch;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c
index d539cb2..83a2836 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c
@@ -130,6 +130,11 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
if (rcdu->info->gen >= 3)
rcar_du_group_write(rgrp, DEFR10, DEFR10_CODE | DEFR10_DEFE10);

+ if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_CMM)) {
+ rcar_du_group_write(rgrp, DEF7R, DEF7R_CODE |
+ DEF7R_CMME1 | DEF7R_CMME0);
+ }
+
/*
* Use DS1PR and DS2PR to configure planes priorities and connects the
* superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
index 9dfd220..b20e783 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -200,6 +200,11 @@
#define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE1)

+#define DEF7R 0x000ec
+#define DEF7R_CODE (0x7779 << 16)
+#define DEF7R_CMME1 BIT(6)
+#define DEF7R_CMME0 BIT(4)
+
/* -----------------------------------------------------------------------------
* R8A7790-only Control Registers
*/
@@ -552,4 +557,91 @@
#define GCBCR 0x11098
#define BCBCR 0x1109c

+/* -----------------------------------------------------------------------------
+ * DU Color Management Module Registers
+ */
+
+#define CMM_LUT_CTRL 0x0000
+#define CMM_LUT_CTRL_EN BIT(0)
+
+#define CMM_CLU_CTRL 0x0100
+#define CMM_CLU_CTRL_EN BIT(0)
+#define CMM_CLU_CTRL_MVS BIT(24)
+#define CMM_CLU_CTRL_AAI BIT(28)
+
+#define CMM_CTL0 0x0180
+#define CM2_CTL0 CMM_CTL0
+#define CMM_CTL0_CLUDB BIT(24)
+#define CMM_CTL0_HISTS BIT(20)
+#define CMM_CTL0_TM1_MASK (3 << 16)
+#define CMM_CTL0_TM1_BT601_YC240 (0 << 16)
+#define CMM_CTL0_TM1_BT601_YC255 BIT(16)
+#define CMM_CTL0_TM1_BT709_RG255 (2 << 16)
+#define CMM_CTL0_TM1_BT709_RG235 (3 << 16)
+#define CMM_CTL0_TM0_MASK (3 << 12)
+#define CMM_CTL0_TM0_BT601_YC240 (0 << 12)
+#define CMM_CTL0_TM0_BT601_YC255 BIT(12)
+#define CMM_CTL0_TM0_BT709_RG255 (2 << 12)
+#define CMM_CTL0_TM0_BT709_RG235 (3 << 12)
+#define CMM_CTL0_TM_BT601_YC240 (CMM_CTL0_TM1_BT601_YC240 |\
+ CMM_CTL0_TM0_BT601_YC240)
+#define CMM_CTL0_TM_BT601_YC255 (CMM_CTL0_TM1_BT601_YC255 |\
+ CMM_CTL0_TM0_BT601_YC255)
+#define CMM_CTL0_TM_BT709_RG255 (CMM_CTL0_TM1_BT709_RG255 |\
+ CMM_CTL0_TM0_BT709_RG255)
+#define CMM_CTL0_TM_BT709_RG235 (CMM_CTL0_TM1_BT709_RG235 |\
+ CMM_CTL0_TM0_BT709_RG235)
+#define CMM_CTL0_YC BIT(8)
+#define CMM_CTL0_VPOL BIT(4)
+#define CMM_CTL0_DBUF BIT(0)
+
+#define CMM_CTL1 0x0184
+#define CM2_CTL1 CMM_CTL1
+#define CMM_CTL1_BFS BIT(0)
+
+#define CMM_CTL2 0x0188
+#define CMM_HGO_OFFSET 0x0200
+#define CMM_HGO_SIZE 0x0204
+#define CMM_HGO_MODE 0x0208
+#define CMM_HGO_MODE_MASK (0xFF)
+#define CMM_HGO_MODE_MAXRGB BIT(7)
+#define CMM_HGO_MODE_OFSB_R BIT(6)
+#define CMM_HGO_MODE_OFSB_G BIT(5)
+#define CMM_HGO_MODE_OFSB_B BIT(4)
+#define CMM_HGO_MODE_HRATIO_NO_SKIPP (0 << 2)
+#define CMM_HGO_MODE_HRATIO_HALF_SKIPP BIT(2)
+#define CMM_HGO_MODE_HRATIO_QUARTER_SKIPP (2 << 2)
+#define CMM_HGO_MODE_VRATIO_NO_SKIPP (0 << 0)
+#define CMM_HGO_MODE_VRATIO_HALF_SKIPP BIT(0)
+#define CMM_HGO_MODE_VRATIO_QUARTER_SKIPP (2 << 0)
+#define CMM_HGO_LB_TH 0x020C
+#define CMM_HGO_LB0_H 0x0210
+#define CMM_HGO_LB0_V 0x0214
+#define CMM_HGO_LB1_H 0x0218
+#define CMM_HGO_LB1_V 0x021C
+#define CMM_HGO_LB2_H 0x0220
+#define CMM_HGO_LB2_V 0x0224
+#define CMM_HGO_LB3_H 0x0228
+#define CMM_HGO_LB3_V 0x022C
+#define CMM_HGO_R_HISTO(n) (0x0230 + ((n) * 4))
+#define CMM_HGO_R_MAXMIN 0x0330
+#define CMM_HGO_R_SUM 0x0334
+#define CMM_HGO_R_LB_DET 0x0338
+#define CMM_HGO_G_HISTO(n) (0x0340 + ((n) * 4))
+#define CMM_HGO_G_MAXMIN 0x0440
+#define CMM_HGO_G_SUM 0x0444
+#define CMM_HGO_G_LB_DET 0x0448
+#define CMM_HGO_B_HISTO(n) (0x0450 + ((n) * 4))
+#define CMM_HGO_B_MAXMIN 0x0550
+#define CMM_HGO_B_SUM 0x0554
+#define CMM_HGO_B_LB_DET 0x0558
+#define CMM_HGO_REGRST 0x05FC
+#define CMM_HGO_REGRST_RCLEA BIT(0)
+#define CMM_LUT_TBLA(n) (0x0600 + ((n) * 4))
+#define CMM_CLU_ADDR 0x0A00
+#define CMM_CLU_DATA 0x0A04
+#define CMM_LUT_TBLB(n) (0x0B00 + ((n) * 4))
+#define CMM_CLU_ADDR2 0x0F00
+#define CMM_CLU_DATA2 0x0F04
+
#endif /* __RCAR_DU_REGS_H__ */
diff --git a/include/drm/drm_ioctl.h b/include/drm/drm_ioctl.h
index fafb6f5..add4280 100644
--- a/include/drm/drm_ioctl.h
+++ b/include/drm/drm_ioctl.h
@@ -109,6 +109,13 @@ enum drm_ioctl_flags {
*/
DRM_ROOT_ONLY = BIT(2),
/**
+ * @DRM_CONTROL_ALLOW:
+ *
+ * Deprecated, do not use. Control nodes are in the process of getting
+ * removed.
+ */
+ DRM_CONTROL_ALLOW = BIT(3),
+ /**
* @DRM_UNLOCKED:
*
* Whether &drm_ioctl_desc.func should be called with the DRM BKL held
--
2.7.4